Repository: github/gh-ost Branch: master Commit: b9652c336746 Files: 2299 Total size: 21.9 MB Directory structure: gitextract_0igtfzyc/ ├── .github/ │ ├── CODEOWNERS │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yml │ └── workflows/ │ ├── ci.yml │ ├── codeql.yml │ ├── golangci-lint.yml │ └── replica-tests.yml ├── .gitignore ├── .golangci.yml ├── Dockerfile.packaging ├── Dockerfile.test ├── LICENSE ├── README.md ├── build.sh ├── doc/ │ ├── azure.md │ ├── cheatsheet.md │ ├── coding-ghost.md │ ├── command-line-flags.md │ ├── cut-over.md │ ├── hooks.md │ ├── interactive-commands.md │ ├── local-tests.md │ ├── migrating-with-sbr.md │ ├── perks.md │ ├── questions.md │ ├── rds.md │ ├── requirements-and-limitations.md │ ├── resume.md │ ├── revert.md │ ├── shared-key.md │ ├── subsecond-lag.md │ ├── testing-on-replica.md │ ├── the-fine-print.md │ ├── throttle.md │ ├── triggerless-design.md │ ├── understanding-output.md │ ├── what-if.md │ └── why-triggerless.md ├── docker-compose.yml ├── go/ │ ├── base/ │ │ ├── context.go │ │ ├── context_test.go │ │ ├── default_logger.go │ │ ├── load_map.go │ │ ├── load_map_test.go │ │ ├── utils.go │ │ └── utils_test.go │ ├── binlog/ │ │ ├── binlog_dml_event.go │ │ ├── binlog_entry.go │ │ ├── binlog_reader.go │ │ ├── gomysql_reader.go │ │ └── testdata/ │ │ ├── mysql-bin.000066 │ │ ├── mysql-bin.000070 │ │ ├── rbr-sample-0.txt │ │ ├── rbr-sample-1.txt │ │ └── rbr-sample-2.txt │ ├── cmd/ │ │ └── gh-ost/ │ │ └── main.go │ ├── logic/ │ │ ├── applier.go │ │ ├── applier_test.go │ │ ├── checkpoint.go │ │ ├── hooks.go │ │ ├── hooks_test.go │ │ ├── inspect.go │ │ ├── inspect_test.go │ │ ├── migrator.go │ │ ├── migrator_test.go │ │ ├── my.cnf.test │ │ ├── server.go │ │ ├── server_test.go │ │ ├── streamer.go │ │ ├── streamer_test.go │ │ ├── test_utils.go │ │ └── throttler.go │ ├── mysql/ │ │ ├── binlog.go │ │ ├── binlog_file.go │ │ ├── binlog_file_test.go │ │ ├── binlog_gtid.go │ │ ├── connection.go │ │ ├── connection_test.go │ │ ├── instance_key.go │ │ ├── instance_key_map.go │ │ ├── instance_key_test.go │ │ ├── replica_terminology_map.go │ │ └── utils.go │ └── sql/ │ ├── builder.go │ ├── builder_test.go │ ├── encoding.go │ ├── parser.go │ ├── parser_test.go │ ├── types.go │ └── types_test.go ├── go.mod ├── go.sum ├── localtests/ │ ├── alter-charset/ │ │ ├── create.sql │ │ └── extra_args │ ├── alter-charset-all-dml/ │ │ ├── create.sql │ │ └── extra_args │ ├── attempt-instant-ddl/ │ │ ├── create.sql │ │ └── extra_args │ ├── autoinc-copy-deletes/ │ │ ├── create.sql │ │ └── expect_table_structure │ ├── autoinc-copy-deletes-user-defined/ │ │ ├── create.sql │ │ ├── expect_table_structure │ │ └── extra_args │ ├── autoinc-copy-simple/ │ │ ├── create.sql │ │ └── expect_table_structure │ ├── autoinc-zero-value/ │ │ └── create.sql │ ├── bigint-change-nullable/ │ │ ├── create.sql │ │ └── extra_args │ ├── binary-to-varbinary/ │ │ ├── create.sql │ │ └── extra_args │ ├── bit-add/ │ │ ├── create.sql │ │ ├── extra_args │ │ ├── ghost_columns │ │ └── orig_columns │ ├── bit-dml/ │ │ ├── create.sql │ │ └── extra_args │ ├── compound-pk/ │ │ └── create.sql │ ├── compound-pk-ts/ │ │ └── create.sql │ ├── convert-utf8mb4/ │ │ ├── create.sql │ │ └── extra_args │ ├── copy-retries-exhausted/ │ │ ├── after.sql │ │ ├── before.sql │ │ ├── create.sql │ │ ├── expect_failure │ │ └── extra_args │ ├── datetime/ │ │ └── create.sql │ ├── datetime-1970/ │ │ ├── create.sql │ │ ├── extra_args │ │ ├── ghost_columns │ │ ├── orig_columns │ │ └── sql_mode │ ├── datetime-submillis/ │ │ └── create.sql │ ├── datetime-submillis-zeroleading/ │ │ └── create.sql │ ├── datetime-to-timestamp/ │ │ ├── create.sql │ │ └── extra_args │ ├── datetime-to-timestamp-pk-fail/ │ │ ├── create.sql │ │ ├── expect_failure │ │ └── extra_args │ ├── datetime-with-zero/ │ │ ├── create.sql │ │ └── extra_args │ ├── decimal/ │ │ └── create.sql │ ├── discard-fk/ │ │ ├── create.sql │ │ ├── extra_args │ │ └── ignore_versions │ ├── docker-compose.yml │ ├── drop-null-add-not-null/ │ │ ├── create.sql │ │ ├── extra_args │ │ ├── ghost_columns │ │ └── orig_columns │ ├── enum/ │ │ ├── create.sql │ │ └── extra_args │ ├── enum-pk/ │ │ └── create.sql │ ├── enum-to-varchar/ │ │ ├── create.sql │ │ └── extra_args │ ├── existing-datetime-with-zero/ │ │ ├── create.sql │ │ └── extra_args │ ├── fail-datetime-with-zero/ │ │ ├── create.sql │ │ ├── expect_failure │ │ └── extra_args │ ├── fail-drop-pk/ │ │ ├── create.sql │ │ ├── expect_failure │ │ └── extra_args │ ├── fail-existing-datetime-with-zero/ │ │ ├── create.sql │ │ ├── expect_failure │ │ └── extra_args │ ├── fail-fk/ │ │ ├── create.sql │ │ ├── expect_failure │ │ └── ignore_versions │ ├── fail-fk-parent/ │ │ ├── create.sql │ │ ├── destroy.sql │ │ ├── expect_failure │ │ ├── extra_args │ │ └── ignore_versions │ ├── fail-float-unique-key/ │ │ ├── create.sql │ │ ├── expect_failure │ │ └── extra_args │ ├── fail-no-shared-uk/ │ │ ├── create.sql │ │ ├── expect_failure │ │ └── extra_args │ ├── fail-no-unique-key/ │ │ ├── create.sql │ │ ├── expect_failure │ │ └── extra_args │ ├── fail-rename-table/ │ │ ├── create.sql │ │ ├── expect_failure │ │ └── extra_args │ ├── fail-update-pk-column/ │ │ └── create.sql │ ├── gbk-charset/ │ │ ├── create.sql │ │ └── extra_args │ ├── generated-columns/ │ │ ├── create.sql │ │ └── ignore_versions │ ├── generated-columns-add/ │ │ ├── create.sql │ │ ├── extra_args │ │ ├── ghost_columns │ │ ├── ignore_versions │ │ ├── order_by │ │ └── orig_columns │ ├── generated-columns-rename/ │ │ ├── create.sql │ │ ├── extra_args │ │ └── ignore_versions │ ├── generated-columns-unique/ │ │ ├── create.sql │ │ └── ignore_versions │ ├── geometry/ │ │ ├── create.sql │ │ └── ignore_versions │ ├── gtid/ │ │ ├── create.sql │ │ ├── extra_args │ │ ├── gtid_mode │ │ └── ignore_versions │ ├── json/ │ │ └── create.sql │ ├── json-dml/ │ │ └── create.sql │ ├── keyword-column/ │ │ ├── create.sql │ │ ├── extra_args │ │ ├── ghost_columns │ │ └── orig_columns │ ├── latin1/ │ │ └── create.sql │ ├── latin1text/ │ │ └── create.sql │ ├── mixed-charset/ │ │ └── create.sql │ ├── modify-change-case/ │ │ ├── create.sql │ │ └── extra_args │ ├── modify-change-case-pk/ │ │ ├── create.sql │ │ ├── expect_failure │ │ └── extra_args │ ├── panic-on-warnings-duplicate-unique-values-on-column-type-change/ │ │ ├── create.sql │ │ ├── expect_failure │ │ └── extra_args │ ├── panic-on-warnings-duplicate-values-for-unique-index/ │ │ ├── create.sql │ │ ├── expect_failure │ │ └── extra_args │ ├── panic-on-warnings-update-pk-with-duplicate-on-new-unique-index/ │ │ ├── create.sql │ │ ├── expect_failure │ │ ├── extra_args │ │ └── test.sh │ ├── rename/ │ │ ├── create.sql │ │ └── extra_args │ ├── rename-inserts-only/ │ │ ├── create.sql │ │ └── extra_args │ ├── rename-none-column/ │ │ ├── create.sql │ │ └── extra_args │ ├── rename-none-comment/ │ │ ├── create.sql │ │ └── extra_args │ ├── rename-reorder-column/ │ │ ├── create.sql │ │ ├── extra_args │ │ ├── ghost_columns │ │ └── orig_columns │ ├── rename-reorder-columns/ │ │ ├── create.sql │ │ ├── extra_args │ │ ├── ghost_columns │ │ └── orig_columns │ ├── reorder-columns/ │ │ ├── create.sql │ │ ├── extra_args │ │ ├── ghost_columns │ │ └── orig_columns │ ├── shared-uk/ │ │ ├── create.sql │ │ └── extra_args │ ├── spatial/ │ │ ├── create.sql │ │ └── ignore_versions │ ├── swap-pk-uk/ │ │ ├── create.sql │ │ ├── extra_args │ │ └── order_by │ ├── swap-uk/ │ │ ├── create.sql │ │ └── extra_args │ ├── swap-uk-uk/ │ │ ├── create.sql │ │ ├── extra_args │ │ └── order_by │ ├── sysbench/ │ │ └── create.sql │ ├── test.sh │ ├── timestamp/ │ │ └── create.sql │ ├── timestamp-datetime/ │ │ └── create.sql │ ├── timestamp-to-datetime/ │ │ ├── create.sql │ │ └── extra_args │ ├── trigger-advanced-features/ │ │ ├── create.sql │ │ └── extra_args │ ├── trigger-ghost-name-conflict/ │ │ ├── create.sql │ │ ├── destroy.sql │ │ ├── expect_failure │ │ └── extra_args │ ├── trigger-long-name-validation/ │ │ ├── create.sql │ │ └── extra_args │ ├── trivial/ │ │ ├── create.sql │ │ └── extra_args │ ├── tz/ │ │ └── create.sql │ ├── tz-datetime/ │ │ └── create.sql │ ├── tz-datetime-ts/ │ │ ├── create.sql │ │ └── extra_args │ ├── unsigned/ │ │ └── create.sql │ ├── unsigned-modify/ │ │ └── create.sql │ ├── unsigned-rename/ │ │ ├── create.sql │ │ ├── extra_args │ │ ├── ghost_columns │ │ └── orig_columns │ ├── unsigned-reorder/ │ │ ├── create.sql │ │ ├── extra_args │ │ ├── ghost_columns │ │ └── orig_columns │ ├── utf8/ │ │ └── create.sql │ ├── utf8mb4/ │ │ └── create.sql │ └── varbinary/ │ └── create.sql ├── resources/ │ └── hooks-sample/ │ ├── gh-ost-on-before-cut-over-hook │ ├── gh-ost-on-before-row-copy-hook │ ├── gh-ost-on-begin-postponed-hook │ ├── gh-ost-on-failure-hook │ ├── gh-ost-on-interactive-command-hook │ ├── gh-ost-on-row-copy-complete-hook │ ├── gh-ost-on-rowcount-complete-hook │ ├── gh-ost-on-start-replication-hook │ ├── gh-ost-on-startup-hook │ ├── gh-ost-on-status-hook │ ├── gh-ost-on-stop-replication-hook │ ├── gh-ost-on-success-hook │ ├── gh-ost-on-success-hook-2 │ └── gh-ost-on-validated-hook ├── script/ │ ├── bootstrap │ ├── build │ ├── build-deploy-tarball │ ├── cibuild │ ├── cibuild-gh-ost-build-deploy-tarball │ ├── dock │ ├── docker-gh-ost-replica-tests │ ├── ensure-go-installed │ ├── ensure-golangci-lint-installed │ ├── gh-ost-test-mysql-master │ ├── gh-ost-test-mysql-replica │ ├── go │ ├── lint │ └── test ├── test.sh ├── tmp/ │ └── .gitkeep └── vendor/ ├── dario.cat/ │ └── mergo/ │ ├── .deepsource.toml │ ├── .gitignore │ ├── .travis.yml │ ├── CODE_OF_CONDUCT.md │ ├── CONTRIBUTING.md │ ├── LICENSE │ ├── README.md │ ├── SECURITY.md │ ├── doc.go │ ├── map.go │ ├── merge.go │ └── mergo.go ├── filippo.io/ │ └── edwards25519/ │ ├── LICENSE │ ├── README.md │ ├── doc.go │ ├── edwards25519.go │ ├── extra.go │ ├── field/ │ │ ├── fe.go │ │ ├── fe_amd64.go │ │ ├── fe_amd64.s │ │ ├── fe_amd64_noasm.go │ │ ├── fe_arm64.go │ │ ├── fe_arm64.s │ │ ├── fe_arm64_noasm.go │ │ ├── fe_extra.go │ │ └── fe_generic.go │ ├── scalar.go │ ├── scalar_fiat.go │ ├── scalarmult.go │ └── tables.go ├── github.com/ │ ├── Azure/ │ │ └── go-ansiterm/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── constants.go │ │ ├── context.go │ │ ├── csi_entry_state.go │ │ ├── csi_param_state.go │ │ ├── escape_intermediate_state.go │ │ ├── escape_state.go │ │ ├── event_handler.go │ │ ├── ground_state.go │ │ ├── osc_string_state.go │ │ ├── parser.go │ │ ├── parser_action_helpers.go │ │ ├── parser_actions.go │ │ ├── states.go │ │ ├── utilities.go │ │ └── winterm/ │ │ ├── ansi.go │ │ ├── api.go │ │ ├── attr_translation.go │ │ ├── cursor_helpers.go │ │ ├── erase_helpers.go │ │ ├── scroll_helper.go │ │ ├── utilities.go │ │ └── win_event_handler.go │ ├── Masterminds/ │ │ └── semver/ │ │ ├── .travis.yml │ │ ├── CHANGELOG.md │ │ ├── LICENSE.txt │ │ ├── Makefile │ │ ├── README.md │ │ ├── appveyor.yml │ │ ├── collection.go │ │ ├── constraints.go │ │ ├── doc.go │ │ ├── version.go │ │ └── version_fuzz.go │ ├── Microsoft/ │ │ └── go-winio/ │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── .golangci.yml │ │ ├── CODEOWNERS │ │ ├── LICENSE │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── backup.go │ │ ├── doc.go │ │ ├── ea.go │ │ ├── file.go │ │ ├── fileinfo.go │ │ ├── hvsock.go │ │ ├── internal/ │ │ │ ├── fs/ │ │ │ │ ├── doc.go │ │ │ │ ├── fs.go │ │ │ │ ├── security.go │ │ │ │ └── zsyscall_windows.go │ │ │ ├── socket/ │ │ │ │ ├── rawaddr.go │ │ │ │ ├── socket.go │ │ │ │ └── zsyscall_windows.go │ │ │ └── stringbuffer/ │ │ │ └── wstring.go │ │ ├── pipe.go │ │ ├── pkg/ │ │ │ └── guid/ │ │ │ ├── guid.go │ │ │ ├── guid_nonwindows.go │ │ │ ├── guid_windows.go │ │ │ └── variant_string.go │ │ ├── privilege.go │ │ ├── reparse.go │ │ ├── sd.go │ │ ├── syscall.go │ │ └── zsyscall_windows.go │ ├── cenkalti/ │ │ └── backoff/ │ │ └── v4/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── backoff.go │ │ ├── context.go │ │ ├── exponential.go │ │ ├── retry.go │ │ ├── ticker.go │ │ ├── timer.go │ │ └── tries.go │ ├── containerd/ │ │ ├── log/ │ │ │ ├── .golangci.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── context.go │ │ └── platforms/ │ │ ├── .gitattributes │ │ ├── .golangci.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── compare.go │ │ ├── cpuinfo.go │ │ ├── cpuinfo_linux.go │ │ ├── cpuinfo_other.go │ │ ├── database.go │ │ ├── defaults.go │ │ ├── defaults_darwin.go │ │ ├── defaults_freebsd.go │ │ ├── defaults_unix.go │ │ ├── defaults_windows.go │ │ ├── errors.go │ │ ├── platform_compat_windows.go │ │ ├── platforms.go │ │ ├── platforms_other.go │ │ └── platforms_windows.go │ ├── cpuguy83/ │ │ └── dockercfg/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── auth.go │ │ ├── config.go │ │ └── load.go │ ├── davecgh/ │ │ └── go-spew/ │ │ ├── LICENSE │ │ └── spew/ │ │ ├── bypass.go │ │ ├── bypasssafe.go │ │ ├── common.go │ │ ├── config.go │ │ ├── doc.go │ │ ├── dump.go │ │ ├── format.go │ │ └── spew.go │ ├── distribution/ │ │ └── reference/ │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── .golangci.yml │ │ ├── CODE-OF-CONDUCT.md │ │ ├── CONTRIBUTING.md │ │ ├── GOVERNANCE.md │ │ ├── LICENSE │ │ ├── MAINTAINERS │ │ ├── Makefile │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── helpers.go │ │ ├── normalize.go │ │ ├── reference.go │ │ ├── regexp.go │ │ └── sort.go │ ├── docker/ │ │ ├── docker/ │ │ │ ├── AUTHORS │ │ │ ├── LICENSE │ │ │ ├── NOTICE │ │ │ ├── api/ │ │ │ │ ├── README.md │ │ │ │ ├── common.go │ │ │ │ ├── swagger-gen.yaml │ │ │ │ ├── swagger.yaml │ │ │ │ └── types/ │ │ │ │ ├── blkiodev/ │ │ │ │ │ └── blkio.go │ │ │ │ ├── checkpoint/ │ │ │ │ │ ├── list.go │ │ │ │ │ └── options.go │ │ │ │ ├── client.go │ │ │ │ ├── common/ │ │ │ │ │ └── id_response.go │ │ │ │ ├── container/ │ │ │ │ │ ├── change_type.go │ │ │ │ │ ├── change_types.go │ │ │ │ │ ├── commit.go │ │ │ │ │ ├── config.go │ │ │ │ │ ├── container.go │ │ │ │ │ ├── create_request.go │ │ │ │ │ ├── create_response.go │ │ │ │ │ ├── errors.go │ │ │ │ │ ├── exec.go │ │ │ │ │ ├── filesystem_change.go │ │ │ │ │ ├── health.go │ │ │ │ │ ├── hostconfig.go │ │ │ │ │ ├── hostconfig_unix.go │ │ │ │ │ ├── hostconfig_windows.go │ │ │ │ │ ├── network_settings.go │ │ │ │ │ ├── options.go │ │ │ │ │ ├── port.go │ │ │ │ │ ├── stats.go │ │ │ │ │ ├── top_response.go │ │ │ │ │ ├── update_response.go │ │ │ │ │ ├── wait_exit_error.go │ │ │ │ │ ├── wait_response.go │ │ │ │ │ └── waitcondition.go │ │ │ │ ├── error_response.go │ │ │ │ ├── error_response_ext.go │ │ │ │ ├── events/ │ │ │ │ │ └── events.go │ │ │ │ ├── filters/ │ │ │ │ │ ├── errors.go │ │ │ │ │ └── parse.go │ │ │ │ ├── image/ │ │ │ │ │ ├── delete_response.go │ │ │ │ │ ├── image.go │ │ │ │ │ ├── image_history.go │ │ │ │ │ ├── image_inspect.go │ │ │ │ │ ├── manifest.go │ │ │ │ │ ├── opts.go │ │ │ │ │ └── summary.go │ │ │ │ ├── mount/ │ │ │ │ │ └── mount.go │ │ │ │ ├── network/ │ │ │ │ │ ├── create_response.go │ │ │ │ │ ├── endpoint.go │ │ │ │ │ ├── ipam.go │ │ │ │ │ └── network.go │ │ │ │ ├── plugin.go │ │ │ │ ├── plugin_device.go │ │ │ │ ├── plugin_env.go │ │ │ │ ├── plugin_interface_type.go │ │ │ │ ├── plugin_mount.go │ │ │ │ ├── plugin_responses.go │ │ │ │ ├── registry/ │ │ │ │ │ ├── authconfig.go │ │ │ │ │ ├── authenticate.go │ │ │ │ │ ├── registry.go │ │ │ │ │ └── search.go │ │ │ │ ├── storage/ │ │ │ │ │ └── driver_data.go │ │ │ │ ├── strslice/ │ │ │ │ │ └── strslice.go │ │ │ │ ├── swarm/ │ │ │ │ │ ├── common.go │ │ │ │ │ ├── config.go │ │ │ │ │ ├── container.go │ │ │ │ │ ├── network.go │ │ │ │ │ ├── node.go │ │ │ │ │ ├── runtime/ │ │ │ │ │ │ ├── gen.go │ │ │ │ │ │ ├── plugin.pb.go │ │ │ │ │ │ └── plugin.proto │ │ │ │ │ ├── runtime.go │ │ │ │ │ ├── secret.go │ │ │ │ │ ├── service.go │ │ │ │ │ ├── service_create_response.go │ │ │ │ │ ├── service_update_response.go │ │ │ │ │ ├── swarm.go │ │ │ │ │ └── task.go │ │ │ │ ├── system/ │ │ │ │ │ ├── info.go │ │ │ │ │ ├── runtime.go │ │ │ │ │ └── security_opts.go │ │ │ │ ├── time/ │ │ │ │ │ └── timestamp.go │ │ │ │ ├── types.go │ │ │ │ ├── types_deprecated.go │ │ │ │ ├── versions/ │ │ │ │ │ └── compare.go │ │ │ │ └── volume/ │ │ │ │ ├── cluster_volume.go │ │ │ │ ├── create_options.go │ │ │ │ ├── list_response.go │ │ │ │ ├── options.go │ │ │ │ ├── volume.go │ │ │ │ └── volume_update.go │ │ │ ├── client/ │ │ │ │ ├── README.md │ │ │ │ ├── build_cancel.go │ │ │ │ ├── build_prune.go │ │ │ │ ├── checkpoint.go │ │ │ │ ├── checkpoint_create.go │ │ │ │ ├── checkpoint_delete.go │ │ │ │ ├── checkpoint_list.go │ │ │ │ ├── client.go │ │ │ │ ├── client_deprecated.go │ │ │ │ ├── client_interfaces.go │ │ │ │ ├── client_unix.go │ │ │ │ ├── client_windows.go │ │ │ │ ├── config_create.go │ │ │ │ ├── config_inspect.go │ │ │ │ ├── config_list.go │ │ │ │ ├── config_remove.go │ │ │ │ ├── config_update.go │ │ │ │ ├── container_attach.go │ │ │ │ ├── container_commit.go │ │ │ │ ├── container_copy.go │ │ │ │ ├── container_create.go │ │ │ │ ├── container_diff.go │ │ │ │ ├── container_exec.go │ │ │ │ ├── container_export.go │ │ │ │ ├── container_inspect.go │ │ │ │ ├── container_kill.go │ │ │ │ ├── container_list.go │ │ │ │ ├── container_logs.go │ │ │ │ ├── container_pause.go │ │ │ │ ├── container_prune.go │ │ │ │ ├── container_remove.go │ │ │ │ ├── container_rename.go │ │ │ │ ├── container_resize.go │ │ │ │ ├── container_restart.go │ │ │ │ ├── container_start.go │ │ │ │ ├── container_stats.go │ │ │ │ ├── container_stop.go │ │ │ │ ├── container_top.go │ │ │ │ ├── container_unpause.go │ │ │ │ ├── container_update.go │ │ │ │ ├── container_wait.go │ │ │ │ ├── disk_usage.go │ │ │ │ ├── distribution_inspect.go │ │ │ │ ├── envvars.go │ │ │ │ ├── errors.go │ │ │ │ ├── events.go │ │ │ │ ├── hijack.go │ │ │ │ ├── image_build.go │ │ │ │ ├── image_create.go │ │ │ │ ├── image_history.go │ │ │ │ ├── image_history_opts.go │ │ │ │ ├── image_import.go │ │ │ │ ├── image_inspect.go │ │ │ │ ├── image_inspect_opts.go │ │ │ │ ├── image_list.go │ │ │ │ ├── image_load.go │ │ │ │ ├── image_load_opts.go │ │ │ │ ├── image_prune.go │ │ │ │ ├── image_pull.go │ │ │ │ ├── image_push.go │ │ │ │ ├── image_remove.go │ │ │ │ ├── image_save.go │ │ │ │ ├── image_save_opts.go │ │ │ │ ├── image_search.go │ │ │ │ ├── image_tag.go │ │ │ │ ├── info.go │ │ │ │ ├── login.go │ │ │ │ ├── network_connect.go │ │ │ │ ├── network_create.go │ │ │ │ ├── network_disconnect.go │ │ │ │ ├── network_inspect.go │ │ │ │ ├── network_list.go │ │ │ │ ├── network_prune.go │ │ │ │ ├── network_remove.go │ │ │ │ ├── node_inspect.go │ │ │ │ ├── node_list.go │ │ │ │ ├── node_remove.go │ │ │ │ ├── node_update.go │ │ │ │ ├── options.go │ │ │ │ ├── ping.go │ │ │ │ ├── plugin_create.go │ │ │ │ ├── plugin_disable.go │ │ │ │ ├── plugin_enable.go │ │ │ │ ├── plugin_inspect.go │ │ │ │ ├── plugin_install.go │ │ │ │ ├── plugin_list.go │ │ │ │ ├── plugin_push.go │ │ │ │ ├── plugin_remove.go │ │ │ │ ├── plugin_set.go │ │ │ │ ├── plugin_upgrade.go │ │ │ │ ├── request.go │ │ │ │ ├── secret_create.go │ │ │ │ ├── secret_inspect.go │ │ │ │ ├── secret_list.go │ │ │ │ ├── secret_remove.go │ │ │ │ ├── secret_update.go │ │ │ │ ├── service_create.go │ │ │ │ ├── service_inspect.go │ │ │ │ ├── service_list.go │ │ │ │ ├── service_logs.go │ │ │ │ ├── service_remove.go │ │ │ │ ├── service_update.go │ │ │ │ ├── swarm_get_unlock_key.go │ │ │ │ ├── swarm_init.go │ │ │ │ ├── swarm_inspect.go │ │ │ │ ├── swarm_join.go │ │ │ │ ├── swarm_leave.go │ │ │ │ ├── swarm_unlock.go │ │ │ │ ├── swarm_update.go │ │ │ │ ├── task_inspect.go │ │ │ │ ├── task_list.go │ │ │ │ ├── task_logs.go │ │ │ │ ├── utils.go │ │ │ │ ├── version.go │ │ │ │ ├── volume_create.go │ │ │ │ ├── volume_inspect.go │ │ │ │ ├── volume_list.go │ │ │ │ ├── volume_prune.go │ │ │ │ ├── volume_remove.go │ │ │ │ └── volume_update.go │ │ │ ├── errdefs/ │ │ │ │ ├── defs.go │ │ │ │ ├── doc.go │ │ │ │ ├── helpers.go │ │ │ │ ├── http_helpers.go │ │ │ │ └── is.go │ │ │ ├── internal/ │ │ │ │ ├── lazyregexp/ │ │ │ │ │ └── lazyregexp.go │ │ │ │ └── multierror/ │ │ │ │ └── multierror.go │ │ │ └── pkg/ │ │ │ ├── archive/ │ │ │ │ ├── archive.go │ │ │ │ ├── archive_linux.go │ │ │ │ ├── archive_other.go │ │ │ │ ├── archive_unix.go │ │ │ │ ├── archive_windows.go │ │ │ │ ├── changes.go │ │ │ │ ├── changes_linux.go │ │ │ │ ├── changes_other.go │ │ │ │ ├── changes_unix.go │ │ │ │ ├── changes_windows.go │ │ │ │ ├── copy.go │ │ │ │ ├── copy_unix.go │ │ │ │ ├── copy_windows.go │ │ │ │ ├── dev_freebsd.go │ │ │ │ ├── dev_unix.go │ │ │ │ ├── diff.go │ │ │ │ ├── diff_unix.go │ │ │ │ ├── diff_windows.go │ │ │ │ ├── path.go │ │ │ │ ├── path_unix.go │ │ │ │ ├── path_windows.go │ │ │ │ ├── time.go │ │ │ │ ├── time_nonwindows.go │ │ │ │ ├── time_windows.go │ │ │ │ ├── whiteouts.go │ │ │ │ ├── wrap.go │ │ │ │ ├── xattr_supported.go │ │ │ │ ├── xattr_supported_linux.go │ │ │ │ ├── xattr_supported_unix.go │ │ │ │ └── xattr_unsupported.go │ │ │ ├── idtools/ │ │ │ │ ├── idtools.go │ │ │ │ ├── idtools_unix.go │ │ │ │ └── idtools_windows.go │ │ │ ├── jsonmessage/ │ │ │ │ └── jsonmessage.go │ │ │ └── stdcopy/ │ │ │ └── stdcopy.go │ │ ├── go-connections/ │ │ │ ├── LICENSE │ │ │ ├── nat/ │ │ │ │ ├── nat.go │ │ │ │ ├── parse.go │ │ │ │ └── sort.go │ │ │ ├── sockets/ │ │ │ │ ├── README.md │ │ │ │ ├── inmem_socket.go │ │ │ │ ├── proxy.go │ │ │ │ ├── sockets.go │ │ │ │ ├── sockets_unix.go │ │ │ │ ├── sockets_windows.go │ │ │ │ ├── tcp_socket.go │ │ │ │ └── unix_socket.go │ │ │ └── tlsconfig/ │ │ │ ├── certpool.go │ │ │ ├── config.go │ │ │ └── config_client_ciphers.go │ │ └── go-units/ │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── MAINTAINERS │ │ ├── README.md │ │ ├── circle.yml │ │ ├── duration.go │ │ ├── size.go │ │ └── ulimit.go │ ├── ebitengine/ │ │ └── purego/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── abi_amd64.h │ │ ├── abi_arm64.h │ │ ├── cgo.go │ │ ├── dlerror.go │ │ ├── dlfcn.go │ │ ├── dlfcn_android.go │ │ ├── dlfcn_darwin.go │ │ ├── dlfcn_freebsd.go │ │ ├── dlfcn_linux.go │ │ ├── dlfcn_nocgo_freebsd.go │ │ ├── dlfcn_nocgo_linux.go │ │ ├── dlfcn_playground.go │ │ ├── dlfcn_stubs.s │ │ ├── func.go │ │ ├── go_runtime.go │ │ ├── internal/ │ │ │ ├── cgo/ │ │ │ │ ├── dlfcn_cgo_unix.go │ │ │ │ ├── empty.go │ │ │ │ └── syscall_cgo_unix.go │ │ │ ├── fakecgo/ │ │ │ │ ├── abi_amd64.h │ │ │ │ ├── abi_arm64.h │ │ │ │ ├── asm_amd64.s │ │ │ │ ├── asm_arm64.s │ │ │ │ ├── callbacks.go │ │ │ │ ├── doc.go │ │ │ │ ├── freebsd.go │ │ │ │ ├── go_darwin_amd64.go │ │ │ │ ├── go_darwin_arm64.go │ │ │ │ ├── go_freebsd_amd64.go │ │ │ │ ├── go_freebsd_arm64.go │ │ │ │ ├── go_libinit.go │ │ │ │ ├── go_linux_amd64.go │ │ │ │ ├── go_linux_arm64.go │ │ │ │ ├── go_setenv.go │ │ │ │ ├── go_util.go │ │ │ │ ├── iscgo.go │ │ │ │ ├── libcgo.go │ │ │ │ ├── libcgo_darwin.go │ │ │ │ ├── libcgo_freebsd.go │ │ │ │ ├── libcgo_linux.go │ │ │ │ ├── setenv.go │ │ │ │ ├── symbols.go │ │ │ │ ├── symbols_darwin.go │ │ │ │ ├── symbols_freebsd.go │ │ │ │ ├── symbols_linux.go │ │ │ │ ├── trampolines_amd64.s │ │ │ │ ├── trampolines_arm64.s │ │ │ │ └── trampolines_stubs.s │ │ │ └── strings/ │ │ │ └── strings.go │ │ ├── is_ios.go │ │ ├── nocgo.go │ │ ├── struct_amd64.go │ │ ├── struct_arm64.go │ │ ├── struct_other.go │ │ ├── sys_amd64.s │ │ ├── sys_arm64.s │ │ ├── sys_unix_arm64.s │ │ ├── syscall.go │ │ ├── syscall_cgo_linux.go │ │ ├── syscall_sysv.go │ │ ├── syscall_windows.go │ │ ├── zcallback_amd64.s │ │ └── zcallback_arm64.s │ ├── felixge/ │ │ └── httpsnoop/ │ │ ├── .gitignore │ │ ├── LICENSE.txt │ │ ├── Makefile │ │ ├── README.md │ │ ├── capture_metrics.go │ │ ├── docs.go │ │ ├── wrap_generated_gteq_1.8.go │ │ └── wrap_generated_lt_1.8.go │ ├── go-ini/ │ │ └── ini/ │ │ ├── .editorconfig │ │ ├── .gitignore │ │ ├── .golangci.yml │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── README.md │ │ ├── codecov.yml │ │ ├── data_source.go │ │ ├── deprecated.go │ │ ├── error.go │ │ ├── file.go │ │ ├── helper.go │ │ ├── ini.go │ │ ├── key.go │ │ ├── parser.go │ │ ├── section.go │ │ └── struct.go │ ├── go-logr/ │ │ ├── logr/ │ │ │ ├── .golangci.yaml │ │ │ ├── CHANGELOG.md │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── SECURITY.md │ │ │ ├── context.go │ │ │ ├── context_noslog.go │ │ │ ├── context_slog.go │ │ │ ├── discard.go │ │ │ ├── funcr/ │ │ │ │ ├── funcr.go │ │ │ │ └── slogsink.go │ │ │ ├── logr.go │ │ │ ├── sloghandler.go │ │ │ ├── slogr.go │ │ │ └── slogsink.go │ │ └── stdr/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── stdr.go │ ├── go-mysql-org/ │ │ └── go-mysql/ │ │ ├── LICENSE │ │ ├── client/ │ │ │ ├── auth.go │ │ │ ├── conn.go │ │ │ ├── pool.go │ │ │ ├── pool_options.go │ │ │ ├── req.go │ │ │ ├── resp.go │ │ │ ├── stmt.go │ │ │ └── tls.go │ │ ├── compress/ │ │ │ └── zlib.go │ │ ├── mysql/ │ │ │ ├── const.go │ │ │ ├── errcode.go │ │ │ ├── errname.go │ │ │ ├── error.go │ │ │ ├── field.go │ │ │ ├── gtid.go │ │ │ ├── mariadb_gtid.go │ │ │ ├── mysql_gtid.go │ │ │ ├── parse_binary.go │ │ │ ├── position.go │ │ │ ├── result.go │ │ │ ├── resultset.go │ │ │ ├── resultset_helper.go │ │ │ ├── rowdata.go │ │ │ ├── state.go │ │ │ ├── util.go │ │ │ └── validate.go │ │ ├── packet/ │ │ │ └── conn.go │ │ ├── replication/ │ │ │ ├── backup.go │ │ │ ├── binlogstreamer.go │ │ │ ├── binlogsyncer.go │ │ │ ├── const.go │ │ │ ├── doc.go │ │ │ ├── event.go │ │ │ ├── generic_event.go │ │ │ ├── json_binary.go │ │ │ ├── parser.go │ │ │ ├── row_event.go │ │ │ ├── time.go │ │ │ └── transaction_payload_event.go │ │ └── utils/ │ │ ├── byte_slice_pool.go │ │ ├── bytes_buffer_pool.go │ │ ├── now.go │ │ ├── now_unix.go │ │ └── zeroalloc.go │ ├── go-ole/ │ │ └── go-ole/ │ │ ├── .travis.yml │ │ ├── ChangeLog.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── appveyor.yml │ │ ├── com.go │ │ ├── com_func.go │ │ ├── connect.go │ │ ├── constants.go │ │ ├── error.go │ │ ├── error_func.go │ │ ├── error_windows.go │ │ ├── guid.go │ │ ├── iconnectionpoint.go │ │ ├── iconnectionpoint_func.go │ │ ├── iconnectionpoint_windows.go │ │ ├── iconnectionpointcontainer.go │ │ ├── iconnectionpointcontainer_func.go │ │ ├── iconnectionpointcontainer_windows.go │ │ ├── idispatch.go │ │ ├── idispatch_func.go │ │ ├── idispatch_windows.go │ │ ├── ienumvariant.go │ │ ├── ienumvariant_func.go │ │ ├── ienumvariant_windows.go │ │ ├── iinspectable.go │ │ ├── iinspectable_func.go │ │ ├── iinspectable_windows.go │ │ ├── iprovideclassinfo.go │ │ ├── iprovideclassinfo_func.go │ │ ├── iprovideclassinfo_windows.go │ │ ├── itypeinfo.go │ │ ├── itypeinfo_func.go │ │ ├── itypeinfo_windows.go │ │ ├── iunknown.go │ │ ├── iunknown_func.go │ │ ├── iunknown_windows.go │ │ ├── ole.go │ │ ├── oleutil/ │ │ │ ├── connection.go │ │ │ ├── connection_func.go │ │ │ ├── connection_windows.go │ │ │ ├── go-get.go │ │ │ └── oleutil.go │ │ ├── safearray.go │ │ ├── safearray_func.go │ │ ├── safearray_windows.go │ │ ├── safearrayconversion.go │ │ ├── safearrayslices.go │ │ ├── utility.go │ │ ├── variables.go │ │ ├── variant.go │ │ ├── variant_386.go │ │ ├── variant_amd64.go │ │ ├── variant_arm.go │ │ ├── variant_arm64.go │ │ ├── variant_date_386.go │ │ ├── variant_date_amd64.go │ │ ├── variant_date_arm.go │ │ ├── variant_date_arm64.go │ │ ├── variant_ppc64le.go │ │ ├── variant_s390x.go │ │ ├── vt_string.go │ │ ├── winrt.go │ │ └── winrt_doc.go │ ├── go-sql-driver/ │ │ └── mysql/ │ │ ├── .gitignore │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── atomic_bool.go │ │ ├── atomic_bool_go118.go │ │ ├── auth.go │ │ ├── buffer.go │ │ ├── collations.go │ │ ├── conncheck.go │ │ ├── conncheck_dummy.go │ │ ├── connection.go │ │ ├── connector.go │ │ ├── const.go │ │ ├── driver.go │ │ ├── dsn.go │ │ ├── errors.go │ │ ├── fields.go │ │ ├── infile.go │ │ ├── nulltime.go │ │ ├── packets.go │ │ ├── result.go │ │ ├── rows.go │ │ ├── statement.go │ │ ├── transaction.go │ │ └── utils.go │ ├── goccy/ │ │ └── go-json/ │ │ ├── .codecov.yml │ │ ├── .gitignore │ │ ├── .golangci.yml │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── README.md │ │ ├── color.go │ │ ├── decode.go │ │ ├── docker-compose.yml │ │ ├── encode.go │ │ ├── error.go │ │ ├── internal/ │ │ │ ├── decoder/ │ │ │ │ ├── anonymous_field.go │ │ │ │ ├── array.go │ │ │ │ ├── assign.go │ │ │ │ ├── bool.go │ │ │ │ ├── bytes.go │ │ │ │ ├── compile.go │ │ │ │ ├── compile_norace.go │ │ │ │ ├── compile_race.go │ │ │ │ ├── context.go │ │ │ │ ├── float.go │ │ │ │ ├── func.go │ │ │ │ ├── int.go │ │ │ │ ├── interface.go │ │ │ │ ├── invalid.go │ │ │ │ ├── map.go │ │ │ │ ├── number.go │ │ │ │ ├── option.go │ │ │ │ ├── path.go │ │ │ │ ├── ptr.go │ │ │ │ ├── slice.go │ │ │ │ ├── stream.go │ │ │ │ ├── string.go │ │ │ │ ├── struct.go │ │ │ │ ├── type.go │ │ │ │ ├── uint.go │ │ │ │ ├── unmarshal_json.go │ │ │ │ ├── unmarshal_text.go │ │ │ │ └── wrapped_string.go │ │ │ ├── encoder/ │ │ │ │ ├── code.go │ │ │ │ ├── compact.go │ │ │ │ ├── compiler.go │ │ │ │ ├── compiler_norace.go │ │ │ │ ├── compiler_race.go │ │ │ │ ├── context.go │ │ │ │ ├── decode_rune.go │ │ │ │ ├── encoder.go │ │ │ │ ├── indent.go │ │ │ │ ├── int.go │ │ │ │ ├── map112.go │ │ │ │ ├── map113.go │ │ │ │ ├── opcode.go │ │ │ │ ├── option.go │ │ │ │ ├── optype.go │ │ │ │ ├── query.go │ │ │ │ ├── string.go │ │ │ │ ├── string_table.go │ │ │ │ ├── vm/ │ │ │ │ │ ├── debug_vm.go │ │ │ │ │ ├── hack.go │ │ │ │ │ ├── util.go │ │ │ │ │ └── vm.go │ │ │ │ ├── vm_color/ │ │ │ │ │ ├── debug_vm.go │ │ │ │ │ ├── hack.go │ │ │ │ │ ├── util.go │ │ │ │ │ └── vm.go │ │ │ │ ├── vm_color_indent/ │ │ │ │ │ ├── debug_vm.go │ │ │ │ │ ├── util.go │ │ │ │ │ └── vm.go │ │ │ │ └── vm_indent/ │ │ │ │ ├── debug_vm.go │ │ │ │ ├── hack.go │ │ │ │ ├── util.go │ │ │ │ └── vm.go │ │ │ ├── errors/ │ │ │ │ └── error.go │ │ │ └── runtime/ │ │ │ ├── rtype.go │ │ │ ├── struct_field.go │ │ │ └── type.go │ │ ├── json.go │ │ ├── option.go │ │ ├── path.go │ │ └── query.go │ ├── gogo/ │ │ └── protobuf/ │ │ ├── AUTHORS │ │ ├── CONTRIBUTORS │ │ ├── LICENSE │ │ └── proto/ │ │ ├── Makefile │ │ ├── clone.go │ │ ├── custom_gogo.go │ │ ├── decode.go │ │ ├── deprecated.go │ │ ├── discard.go │ │ ├── duration.go │ │ ├── duration_gogo.go │ │ ├── encode.go │ │ ├── encode_gogo.go │ │ ├── equal.go │ │ ├── extensions.go │ │ ├── extensions_gogo.go │ │ ├── lib.go │ │ ├── lib_gogo.go │ │ ├── message_set.go │ │ ├── pointer_reflect.go │ │ ├── pointer_reflect_gogo.go │ │ ├── pointer_unsafe.go │ │ ├── pointer_unsafe_gogo.go │ │ ├── properties.go │ │ ├── properties_gogo.go │ │ ├── skip_gogo.go │ │ ├── table_marshal.go │ │ ├── table_marshal_gogo.go │ │ ├── table_merge.go │ │ ├── table_unmarshal.go │ │ ├── table_unmarshal_gogo.go │ │ ├── text.go │ │ ├── text_gogo.go │ │ ├── text_parser.go │ │ ├── timestamp.go │ │ ├── timestamp_gogo.go │ │ ├── wrappers.go │ │ └── wrappers_gogo.go │ ├── google/ │ │ └── uuid/ │ │ ├── CHANGELOG.md │ │ ├── CONTRIBUTING.md │ │ ├── CONTRIBUTORS │ │ ├── LICENSE │ │ ├── README.md │ │ ├── dce.go │ │ ├── doc.go │ │ ├── hash.go │ │ ├── marshal.go │ │ ├── node.go │ │ ├── node_js.go │ │ ├── node_net.go │ │ ├── null.go │ │ ├── sql.go │ │ ├── time.go │ │ ├── util.go │ │ ├── uuid.go │ │ ├── version1.go │ │ ├── version4.go │ │ ├── version6.go │ │ └── version7.go │ ├── hashicorp/ │ │ └── go-version/ │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── constraint.go │ │ ├── version.go │ │ └── version_collection.go │ ├── klauspost/ │ │ └── compress/ │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── .goreleaser.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── compressible.go │ │ ├── flate/ │ │ │ ├── deflate.go │ │ │ ├── dict_decoder.go │ │ │ ├── fast_encoder.go │ │ │ ├── huffman_bit_writer.go │ │ │ ├── huffman_code.go │ │ │ ├── huffman_sortByFreq.go │ │ │ ├── huffman_sortByLiteral.go │ │ │ ├── inflate.go │ │ │ ├── inflate_gen.go │ │ │ ├── level1.go │ │ │ ├── level2.go │ │ │ ├── level3.go │ │ │ ├── level4.go │ │ │ ├── level5.go │ │ │ ├── level6.go │ │ │ ├── matchlen_amd64.go │ │ │ ├── matchlen_amd64.s │ │ │ ├── matchlen_generic.go │ │ │ ├── regmask_amd64.go │ │ │ ├── regmask_other.go │ │ │ ├── stateless.go │ │ │ └── token.go │ │ ├── fse/ │ │ │ ├── README.md │ │ │ ├── bitreader.go │ │ │ ├── bitwriter.go │ │ │ ├── bytereader.go │ │ │ ├── compress.go │ │ │ ├── decompress.go │ │ │ └── fse.go │ │ ├── gen.sh │ │ ├── huff0/ │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── bitreader.go │ │ │ ├── bitwriter.go │ │ │ ├── compress.go │ │ │ ├── decompress.go │ │ │ ├── decompress_amd64.go │ │ │ ├── decompress_amd64.s │ │ │ ├── decompress_generic.go │ │ │ └── huff0.go │ │ ├── internal/ │ │ │ ├── cpuinfo/ │ │ │ │ ├── cpuinfo.go │ │ │ │ ├── cpuinfo_amd64.go │ │ │ │ └── cpuinfo_amd64.s │ │ │ └── snapref/ │ │ │ ├── LICENSE │ │ │ ├── decode.go │ │ │ ├── decode_other.go │ │ │ ├── encode.go │ │ │ ├── encode_other.go │ │ │ └── snappy.go │ │ ├── s2sx.mod │ │ ├── s2sx.sum │ │ ├── zlib/ │ │ │ ├── reader.go │ │ │ └── writer.go │ │ └── zstd/ │ │ ├── README.md │ │ ├── bitreader.go │ │ ├── bitwriter.go │ │ ├── blockdec.go │ │ ├── blockenc.go │ │ ├── blocktype_string.go │ │ ├── bytebuf.go │ │ ├── bytereader.go │ │ ├── decodeheader.go │ │ ├── decoder.go │ │ ├── decoder_options.go │ │ ├── dict.go │ │ ├── enc_base.go │ │ ├── enc_best.go │ │ ├── enc_better.go │ │ ├── enc_dfast.go │ │ ├── enc_fast.go │ │ ├── encoder.go │ │ ├── encoder_options.go │ │ ├── framedec.go │ │ ├── frameenc.go │ │ ├── fse_decoder.go │ │ ├── fse_decoder_amd64.go │ │ ├── fse_decoder_amd64.s │ │ ├── fse_decoder_generic.go │ │ ├── fse_encoder.go │ │ ├── fse_predefined.go │ │ ├── hash.go │ │ ├── history.go │ │ ├── internal/ │ │ │ └── xxhash/ │ │ │ ├── LICENSE.txt │ │ │ ├── README.md │ │ │ ├── xxhash.go │ │ │ ├── xxhash_amd64.s │ │ │ ├── xxhash_arm64.s │ │ │ ├── xxhash_asm.go │ │ │ ├── xxhash_other.go │ │ │ └── xxhash_safe.go │ │ ├── matchlen_amd64.go │ │ ├── matchlen_amd64.s │ │ ├── matchlen_generic.go │ │ ├── seqdec.go │ │ ├── seqdec_amd64.go │ │ ├── seqdec_amd64.s │ │ ├── seqdec_generic.go │ │ ├── seqenc.go │ │ ├── snappy.go │ │ ├── zip.go │ │ └── zstd.go │ ├── lufia/ │ │ └── plan9stats/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── cpu.go │ │ ├── doc.go │ │ ├── host.go │ │ ├── int.go │ │ ├── opts.go │ │ └── stats.go │ ├── magiconair/ │ │ └── properties/ │ │ ├── .gitignore │ │ ├── LICENSE.md │ │ ├── README.md │ │ ├── decode.go │ │ ├── doc.go │ │ ├── integrate.go │ │ ├── lex.go │ │ ├── load.go │ │ ├── parser.go │ │ ├── properties.go │ │ └── rangecheck.go │ ├── moby/ │ │ ├── docker-image-spec/ │ │ │ ├── LICENSE │ │ │ └── specs-go/ │ │ │ └── v1/ │ │ │ └── image.go │ │ ├── patternmatcher/ │ │ │ ├── LICENSE │ │ │ ├── NOTICE │ │ │ ├── ignorefile/ │ │ │ │ └── ignorefile.go │ │ │ └── patternmatcher.go │ │ └── sys/ │ │ ├── sequential/ │ │ │ ├── LICENSE │ │ │ ├── doc.go │ │ │ ├── sequential_unix.go │ │ │ └── sequential_windows.go │ │ ├── user/ │ │ │ ├── LICENSE │ │ │ ├── lookup_unix.go │ │ │ ├── user.go │ │ │ └── user_fuzzer.go │ │ └── userns/ │ │ ├── LICENSE │ │ ├── userns.go │ │ ├── userns_linux.go │ │ ├── userns_linux_fuzzer.go │ │ └── userns_unsupported.go │ ├── morikuni/ │ │ └── aec/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── aec.go │ │ ├── ansi.go │ │ ├── builder.go │ │ └── sgr.go │ ├── openark/ │ │ └── golib/ │ │ ├── LICENSE │ │ ├── log/ │ │ │ └── log.go │ │ └── sqlutils/ │ │ ├── dialect.go │ │ ├── sqlite_dialect.go │ │ └── sqlutils.go │ ├── opencontainers/ │ │ ├── go-digest/ │ │ │ ├── .mailmap │ │ │ ├── .pullapprove.yml │ │ │ ├── .travis.yml │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE │ │ │ ├── LICENSE.docs │ │ │ ├── MAINTAINERS │ │ │ ├── README.md │ │ │ ├── algorithm.go │ │ │ ├── digest.go │ │ │ ├── digester.go │ │ │ ├── doc.go │ │ │ └── verifiers.go │ │ └── image-spec/ │ │ ├── LICENSE │ │ └── specs-go/ │ │ ├── v1/ │ │ │ ├── annotations.go │ │ │ ├── config.go │ │ │ ├── descriptor.go │ │ │ ├── index.go │ │ │ ├── layout.go │ │ │ ├── manifest.go │ │ │ └── mediatype.go │ │ ├── version.go │ │ └── versioned.go │ ├── pingcap/ │ │ ├── errors/ │ │ │ ├── .gitignore │ │ │ ├── .travis.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── compatible_shim.go │ │ │ ├── errors.go │ │ │ ├── group.go │ │ │ ├── juju_adaptor.go │ │ │ ├── normalize.go │ │ │ └── stack.go │ │ └── tidb/ │ │ └── pkg/ │ │ └── parser/ │ │ ├── LICENSE │ │ ├── charset/ │ │ │ ├── BUILD.bazel │ │ │ ├── charset.go │ │ │ ├── encoding.go │ │ │ ├── encoding_ascii.go │ │ │ ├── encoding_base.go │ │ │ ├── encoding_bin.go │ │ │ ├── encoding_gbk.go │ │ │ ├── encoding_latin1.go │ │ │ ├── encoding_table.go │ │ │ └── encoding_utf8.go │ │ ├── format/ │ │ │ ├── BUILD.bazel │ │ │ └── format.go │ │ ├── mysql/ │ │ │ ├── BUILD.bazel │ │ │ ├── charset.go │ │ │ ├── const.go │ │ │ ├── errcode.go │ │ │ ├── errname.go │ │ │ ├── error.go │ │ │ ├── locale_format.go │ │ │ ├── privs.go │ │ │ ├── state.go │ │ │ ├── type.go │ │ │ └── util.go │ │ └── terror/ │ │ ├── BUILD.bazel │ │ └── terror.go │ ├── pkg/ │ │ └── errors/ │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── README.md │ │ ├── appveyor.yml │ │ ├── errors.go │ │ ├── go113.go │ │ └── stack.go │ ├── pmezard/ │ │ └── go-difflib/ │ │ ├── LICENSE │ │ └── difflib/ │ │ └── difflib.go │ ├── power-devops/ │ │ └── perfstat/ │ │ ├── LICENSE │ │ ├── c_helpers.c │ │ ├── c_helpers.h │ │ ├── config.go │ │ ├── cpustat.go │ │ ├── diskstat.go │ │ ├── doc.go │ │ ├── fsstat.go │ │ ├── helpers.go │ │ ├── lparstat.go │ │ ├── lvmstat.go │ │ ├── memstat.go │ │ ├── netstat.go │ │ ├── procstat.go │ │ ├── sysconf.go │ │ ├── systemcfg.go │ │ ├── types_cpu.go │ │ ├── types_disk.go │ │ ├── types_fs.go │ │ ├── types_lpar.go │ │ ├── types_lvm.go │ │ ├── types_memory.go │ │ ├── types_network.go │ │ ├── types_process.go │ │ └── uptime.go │ ├── shirou/ │ │ └── gopsutil/ │ │ └── v4/ │ │ ├── LICENSE │ │ ├── common/ │ │ │ └── env.go │ │ ├── cpu/ │ │ │ ├── cpu.go │ │ │ ├── cpu_aix.go │ │ │ ├── cpu_aix_cgo.go │ │ │ ├── cpu_aix_nocgo.go │ │ │ ├── cpu_darwin.go │ │ │ ├── cpu_darwin_arm64.go │ │ │ ├── cpu_darwin_fallback.go │ │ │ ├── cpu_dragonfly.go │ │ │ ├── cpu_dragonfly_amd64.go │ │ │ ├── cpu_fallback.go │ │ │ ├── cpu_freebsd.go │ │ │ ├── cpu_freebsd_386.go │ │ │ ├── cpu_freebsd_amd64.go │ │ │ ├── cpu_freebsd_arm.go │ │ │ ├── cpu_freebsd_arm64.go │ │ │ ├── cpu_linux.go │ │ │ ├── cpu_netbsd.go │ │ │ ├── cpu_netbsd_amd64.go │ │ │ ├── cpu_netbsd_arm.go │ │ │ ├── cpu_netbsd_arm64.go │ │ │ ├── cpu_openbsd.go │ │ │ ├── cpu_openbsd_386.go │ │ │ ├── cpu_openbsd_amd64.go │ │ │ ├── cpu_openbsd_arm.go │ │ │ ├── cpu_openbsd_arm64.go │ │ │ ├── cpu_openbsd_riscv64.go │ │ │ ├── cpu_plan9.go │ │ │ ├── cpu_solaris.go │ │ │ └── cpu_windows.go │ │ ├── internal/ │ │ │ └── common/ │ │ │ ├── binary.go │ │ │ ├── common.go │ │ │ ├── common_darwin.go │ │ │ ├── common_freebsd.go │ │ │ ├── common_linux.go │ │ │ ├── common_netbsd.go │ │ │ ├── common_openbsd.go │ │ │ ├── common_unix.go │ │ │ ├── common_windows.go │ │ │ ├── endian.go │ │ │ ├── sleep.go │ │ │ └── warnings.go │ │ ├── mem/ │ │ │ ├── ex_linux.go │ │ │ ├── ex_windows.go │ │ │ ├── mem.go │ │ │ ├── mem_aix.go │ │ │ ├── mem_aix_cgo.go │ │ │ ├── mem_aix_nocgo.go │ │ │ ├── mem_bsd.go │ │ │ ├── mem_darwin.go │ │ │ ├── mem_fallback.go │ │ │ ├── mem_freebsd.go │ │ │ ├── mem_linux.go │ │ │ ├── mem_netbsd.go │ │ │ ├── mem_openbsd.go │ │ │ ├── mem_openbsd_386.go │ │ │ ├── mem_openbsd_amd64.go │ │ │ ├── mem_openbsd_arm.go │ │ │ ├── mem_openbsd_arm64.go │ │ │ ├── mem_openbsd_riscv64.go │ │ │ ├── mem_plan9.go │ │ │ ├── mem_solaris.go │ │ │ └── mem_windows.go │ │ ├── net/ │ │ │ ├── net.go │ │ │ ├── net_aix.go │ │ │ ├── net_aix_cgo.go │ │ │ ├── net_aix_nocgo.go │ │ │ ├── net_darwin.go │ │ │ ├── net_fallback.go │ │ │ ├── net_freebsd.go │ │ │ ├── net_linux.go │ │ │ ├── net_openbsd.go │ │ │ ├── net_solaris.go │ │ │ ├── net_unix.go │ │ │ └── net_windows.go │ │ └── process/ │ │ ├── process.go │ │ ├── process_bsd.go │ │ ├── process_darwin.go │ │ ├── process_darwin_amd64.go │ │ ├── process_darwin_arm64.go │ │ ├── process_fallback.go │ │ ├── process_freebsd.go │ │ ├── process_freebsd_386.go │ │ ├── process_freebsd_amd64.go │ │ ├── process_freebsd_arm.go │ │ ├── process_freebsd_arm64.go │ │ ├── process_linux.go │ │ ├── process_openbsd.go │ │ ├── process_openbsd_386.go │ │ ├── process_openbsd_amd64.go │ │ ├── process_openbsd_arm.go │ │ ├── process_openbsd_arm64.go │ │ ├── process_openbsd_riscv64.go │ │ ├── process_plan9.go │ │ ├── process_posix.go │ │ ├── process_solaris.go │ │ ├── process_windows.go │ │ ├── process_windows_32bit.go │ │ └── process_windows_64bit.go │ ├── shopspring/ │ │ └── decimal/ │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── decimal-go.go │ │ ├── decimal.go │ │ └── rounding.go │ ├── siddontang/ │ │ └── go-log/ │ │ ├── LICENSE │ │ ├── log/ │ │ │ ├── doc.go │ │ │ ├── filehandler.go │ │ │ ├── handler.go │ │ │ ├── log.go │ │ │ └── logger.go │ │ └── loggers/ │ │ └── loggers.go │ ├── stretchr/ │ │ └── testify/ │ │ ├── LICENSE │ │ ├── assert/ │ │ │ ├── assertion_compare.go │ │ │ ├── assertion_format.go │ │ │ ├── assertion_format.go.tmpl │ │ │ ├── assertion_forward.go │ │ │ ├── assertion_forward.go.tmpl │ │ │ ├── assertion_order.go │ │ │ ├── assertions.go │ │ │ ├── doc.go │ │ │ ├── errors.go │ │ │ ├── forward_assertions.go │ │ │ ├── http_assertions.go │ │ │ └── yaml/ │ │ │ ├── yaml_custom.go │ │ │ ├── yaml_default.go │ │ │ └── yaml_fail.go │ │ ├── require/ │ │ │ ├── doc.go │ │ │ ├── forward_requirements.go │ │ │ ├── require.go │ │ │ ├── require.go.tmpl │ │ │ ├── require_forward.go │ │ │ ├── require_forward.go.tmpl │ │ │ └── requirements.go │ │ └── suite/ │ │ ├── doc.go │ │ ├── interfaces.go │ │ ├── stats.go │ │ └── suite.go │ ├── tklauser/ │ │ ├── go-sysconf/ │ │ │ ├── .cirrus.yml │ │ │ ├── .gitignore │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── sysconf.go │ │ │ ├── sysconf_bsd.go │ │ │ ├── sysconf_darwin.go │ │ │ ├── sysconf_dragonfly.go │ │ │ ├── sysconf_freebsd.go │ │ │ ├── sysconf_generic.go │ │ │ ├── sysconf_linux.go │ │ │ ├── sysconf_netbsd.go │ │ │ ├── sysconf_openbsd.go │ │ │ ├── sysconf_posix.go │ │ │ ├── sysconf_solaris.go │ │ │ ├── sysconf_unsupported.go │ │ │ ├── zsysconf_defs_darwin.go │ │ │ ├── zsysconf_defs_dragonfly.go │ │ │ ├── zsysconf_defs_freebsd.go │ │ │ ├── zsysconf_defs_linux.go │ │ │ ├── zsysconf_defs_netbsd.go │ │ │ ├── zsysconf_defs_openbsd.go │ │ │ ├── zsysconf_defs_solaris.go │ │ │ ├── zsysconf_values_freebsd_386.go │ │ │ ├── zsysconf_values_freebsd_amd64.go │ │ │ ├── zsysconf_values_freebsd_arm.go │ │ │ ├── zsysconf_values_freebsd_arm64.go │ │ │ ├── zsysconf_values_freebsd_riscv64.go │ │ │ ├── zsysconf_values_linux_386.go │ │ │ ├── zsysconf_values_linux_amd64.go │ │ │ ├── zsysconf_values_linux_arm.go │ │ │ ├── zsysconf_values_linux_arm64.go │ │ │ ├── zsysconf_values_linux_loong64.go │ │ │ ├── zsysconf_values_linux_mips.go │ │ │ ├── zsysconf_values_linux_mips64.go │ │ │ ├── zsysconf_values_linux_mips64le.go │ │ │ ├── zsysconf_values_linux_mipsle.go │ │ │ ├── zsysconf_values_linux_ppc64.go │ │ │ ├── zsysconf_values_linux_ppc64le.go │ │ │ ├── zsysconf_values_linux_riscv64.go │ │ │ ├── zsysconf_values_linux_s390x.go │ │ │ ├── zsysconf_values_netbsd_386.go │ │ │ ├── zsysconf_values_netbsd_amd64.go │ │ │ ├── zsysconf_values_netbsd_arm.go │ │ │ └── zsysconf_values_netbsd_arm64.go │ │ └── numcpus/ │ │ ├── .cirrus.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── numcpus.go │ │ ├── numcpus_bsd.go │ │ ├── numcpus_linux.go │ │ ├── numcpus_solaris.go │ │ ├── numcpus_unsupported.go │ │ └── numcpus_windows.go │ └── yusufpapurcu/ │ └── wmi/ │ ├── LICENSE │ ├── README.md │ ├── swbemservices.go │ └── wmi.go ├── go.opentelemetry.io/ │ ├── auto/ │ │ └── sdk/ │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── VERSIONING.md │ │ ├── doc.go │ │ ├── internal/ │ │ │ └── telemetry/ │ │ │ ├── attr.go │ │ │ ├── doc.go │ │ │ ├── id.go │ │ │ ├── number.go │ │ │ ├── resource.go │ │ │ ├── scope.go │ │ │ ├── span.go │ │ │ ├── status.go │ │ │ ├── traces.go │ │ │ └── value.go │ │ ├── limit.go │ │ ├── span.go │ │ ├── tracer.go │ │ └── tracer_provider.go │ ├── contrib/ │ │ └── instrumentation/ │ │ └── net/ │ │ └── http/ │ │ └── otelhttp/ │ │ ├── LICENSE │ │ ├── client.go │ │ ├── common.go │ │ ├── config.go │ │ ├── doc.go │ │ ├── handler.go │ │ ├── internal/ │ │ │ └── semconvutil/ │ │ │ ├── gen.go │ │ │ ├── httpconv.go │ │ │ └── netconv.go │ │ ├── labeler.go │ │ ├── transport.go │ │ ├── version.go │ │ └── wrap.go │ └── otel/ │ ├── .codespellignore │ ├── .codespellrc │ ├── .gitattributes │ ├── .gitignore │ ├── .golangci.yml │ ├── .lycheeignore │ ├── .markdownlint.yaml │ ├── CHANGELOG.md │ ├── CODEOWNERS │ ├── CONTRIBUTING.md │ ├── LICENSE │ ├── Makefile │ ├── README.md │ ├── RELEASING.md │ ├── VERSIONING.md │ ├── attribute/ │ │ ├── README.md │ │ ├── doc.go │ │ ├── encoder.go │ │ ├── filter.go │ │ ├── iterator.go │ │ ├── key.go │ │ ├── kv.go │ │ ├── set.go │ │ ├── type_string.go │ │ └── value.go │ ├── baggage/ │ │ ├── README.md │ │ ├── baggage.go │ │ ├── context.go │ │ └── doc.go │ ├── codes/ │ │ ├── README.md │ │ ├── codes.go │ │ └── doc.go │ ├── dependencies.Dockerfile │ ├── doc.go │ ├── error_handler.go │ ├── get_main_pkgs.sh │ ├── handler.go │ ├── internal/ │ │ ├── attribute/ │ │ │ └── attribute.go │ │ ├── baggage/ │ │ │ ├── baggage.go │ │ │ └── context.go │ │ ├── gen.go │ │ ├── global/ │ │ │ ├── handler.go │ │ │ ├── instruments.go │ │ │ ├── internal_logging.go │ │ │ ├── meter.go │ │ │ ├── propagator.go │ │ │ ├── state.go │ │ │ └── trace.go │ │ └── rawhelpers.go │ ├── internal_logging.go │ ├── metric/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── asyncfloat64.go │ │ ├── asyncint64.go │ │ ├── config.go │ │ ├── doc.go │ │ ├── embedded/ │ │ │ ├── README.md │ │ │ └── embedded.go │ │ ├── instrument.go │ │ ├── meter.go │ │ ├── syncfloat64.go │ │ └── syncint64.go │ ├── metric.go │ ├── propagation/ │ │ ├── README.md │ │ ├── baggage.go │ │ ├── doc.go │ │ ├── propagation.go │ │ └── trace_context.go │ ├── propagation.go │ ├── renovate.json │ ├── requirements.txt │ ├── semconv/ │ │ ├── v1.20.0/ │ │ │ ├── README.md │ │ │ ├── attribute_group.go │ │ │ ├── doc.go │ │ │ ├── event.go │ │ │ ├── exception.go │ │ │ ├── http.go │ │ │ ├── resource.go │ │ │ ├── schema.go │ │ │ └── trace.go │ │ └── v1.26.0/ │ │ ├── README.md │ │ ├── attribute_group.go │ │ ├── doc.go │ │ ├── exception.go │ │ ├── metric.go │ │ └── schema.go │ ├── trace/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── auto.go │ │ ├── config.go │ │ ├── context.go │ │ ├── doc.go │ │ ├── embedded/ │ │ │ ├── README.md │ │ │ └── embedded.go │ │ ├── internal/ │ │ │ └── telemetry/ │ │ │ ├── attr.go │ │ │ ├── doc.go │ │ │ ├── id.go │ │ │ ├── number.go │ │ │ ├── resource.go │ │ │ ├── scope.go │ │ │ ├── span.go │ │ │ ├── status.go │ │ │ ├── traces.go │ │ │ └── value.go │ │ ├── nonrecording.go │ │ ├── noop/ │ │ │ ├── README.md │ │ │ └── noop.go │ │ ├── noop.go │ │ ├── provider.go │ │ ├── span.go │ │ ├── trace.go │ │ ├── tracer.go │ │ └── tracestate.go │ ├── trace.go │ ├── verify_readmes.sh │ ├── verify_released_changelog.sh │ ├── version.go │ └── versions.yaml ├── golang.org/ │ └── x/ │ ├── crypto/ │ │ ├── LICENSE │ │ ├── PATENTS │ │ ├── blowfish/ │ │ │ ├── block.go │ │ │ ├── cipher.go │ │ │ └── const.go │ │ ├── chacha20/ │ │ │ ├── chacha_arm64.go │ │ │ ├── chacha_arm64.s │ │ │ ├── chacha_generic.go │ │ │ ├── chacha_noasm.go │ │ │ ├── chacha_ppc64x.go │ │ │ ├── chacha_ppc64x.s │ │ │ ├── chacha_s390x.go │ │ │ ├── chacha_s390x.s │ │ │ └── xor.go │ │ ├── curve25519/ │ │ │ └── curve25519.go │ │ ├── internal/ │ │ │ ├── alias/ │ │ │ │ ├── alias.go │ │ │ │ └── alias_purego.go │ │ │ └── poly1305/ │ │ │ ├── mac_noasm.go │ │ │ ├── poly1305.go │ │ │ ├── sum_amd64.s │ │ │ ├── sum_asm.go │ │ │ ├── sum_generic.go │ │ │ ├── sum_loong64.s │ │ │ ├── sum_ppc64x.s │ │ │ ├── sum_s390x.go │ │ │ └── sum_s390x.s │ │ └── ssh/ │ │ ├── buffer.go │ │ ├── certs.go │ │ ├── channel.go │ │ ├── cipher.go │ │ ├── client.go │ │ ├── client_auth.go │ │ ├── common.go │ │ ├── connection.go │ │ ├── doc.go │ │ ├── handshake.go │ │ ├── internal/ │ │ │ └── bcrypt_pbkdf/ │ │ │ └── bcrypt_pbkdf.go │ │ ├── kex.go │ │ ├── keys.go │ │ ├── mac.go │ │ ├── messages.go │ │ ├── mux.go │ │ ├── server.go │ │ ├── session.go │ │ ├── ssh_gss.go │ │ ├── streamlocal.go │ │ ├── tcpip.go │ │ └── transport.go │ ├── net/ │ │ ├── LICENSE │ │ ├── PATENTS │ │ └── context/ │ │ └── context.go │ ├── sync/ │ │ ├── LICENSE │ │ ├── PATENTS │ │ └── errgroup/ │ │ └── errgroup.go │ ├── sys/ │ │ ├── LICENSE │ │ ├── PATENTS │ │ ├── cpu/ │ │ │ ├── asm_aix_ppc64.s │ │ │ ├── asm_darwin_x86_gc.s │ │ │ ├── byteorder.go │ │ │ ├── cpu.go │ │ │ ├── cpu_aix.go │ │ │ ├── cpu_arm.go │ │ │ ├── cpu_arm64.go │ │ │ ├── cpu_arm64.s │ │ │ ├── cpu_darwin_x86.go │ │ │ ├── cpu_gc_arm64.go │ │ │ ├── cpu_gc_s390x.go │ │ │ ├── cpu_gc_x86.go │ │ │ ├── cpu_gc_x86.s │ │ │ ├── cpu_gccgo_arm64.go │ │ │ ├── cpu_gccgo_s390x.go │ │ │ ├── cpu_gccgo_x86.c │ │ │ ├── cpu_gccgo_x86.go │ │ │ ├── cpu_linux.go │ │ │ ├── cpu_linux_arm.go │ │ │ ├── cpu_linux_arm64.go │ │ │ ├── cpu_linux_loong64.go │ │ │ ├── cpu_linux_mips64x.go │ │ │ ├── cpu_linux_noinit.go │ │ │ ├── cpu_linux_ppc64x.go │ │ │ ├── cpu_linux_riscv64.go │ │ │ ├── cpu_linux_s390x.go │ │ │ ├── cpu_loong64.go │ │ │ ├── cpu_loong64.s │ │ │ ├── cpu_mips64x.go │ │ │ ├── cpu_mipsx.go │ │ │ ├── cpu_netbsd_arm64.go │ │ │ ├── cpu_openbsd_arm64.go │ │ │ ├── cpu_openbsd_arm64.s │ │ │ ├── cpu_other_arm.go │ │ │ ├── cpu_other_arm64.go │ │ │ ├── cpu_other_mips64x.go │ │ │ ├── cpu_other_ppc64x.go │ │ │ ├── cpu_other_riscv64.go │ │ │ ├── cpu_other_x86.go │ │ │ ├── cpu_ppc64x.go │ │ │ ├── cpu_riscv64.go │ │ │ ├── cpu_s390x.go │ │ │ ├── cpu_s390x.s │ │ │ ├── cpu_wasm.go │ │ │ ├── cpu_x86.go │ │ │ ├── cpu_zos.go │ │ │ ├── cpu_zos_s390x.go │ │ │ ├── endian_big.go │ │ │ ├── endian_little.go │ │ │ ├── hwcap_linux.go │ │ │ ├── parse.go │ │ │ ├── proc_cpuinfo_linux.go │ │ │ ├── runtime_auxv.go │ │ │ ├── runtime_auxv_go121.go │ │ │ ├── syscall_aix_gccgo.go │ │ │ ├── syscall_aix_ppc64_gc.go │ │ │ └── syscall_darwin_x86_gc.go │ │ ├── plan9/ │ │ │ ├── asm.s │ │ │ ├── asm_plan9_386.s │ │ │ ├── asm_plan9_amd64.s │ │ │ ├── asm_plan9_arm.s │ │ │ ├── const_plan9.go │ │ │ ├── dir_plan9.go │ │ │ ├── env_plan9.go │ │ │ ├── errors_plan9.go │ │ │ ├── mkall.sh │ │ │ ├── mkerrors.sh │ │ │ ├── mksysnum_plan9.sh │ │ │ ├── pwd_go15_plan9.go │ │ │ ├── pwd_plan9.go │ │ │ ├── race.go │ │ │ ├── race0.go │ │ │ ├── str.go │ │ │ ├── syscall.go │ │ │ ├── syscall_plan9.go │ │ │ ├── zsyscall_plan9_386.go │ │ │ ├── zsyscall_plan9_amd64.go │ │ │ ├── zsyscall_plan9_arm.go │ │ │ └── zsysnum_plan9.go │ │ ├── unix/ │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── affinity_linux.go │ │ │ ├── aliases.go │ │ │ ├── asm_aix_ppc64.s │ │ │ ├── asm_bsd_386.s │ │ │ ├── asm_bsd_amd64.s │ │ │ ├── asm_bsd_arm.s │ │ │ ├── asm_bsd_arm64.s │ │ │ ├── asm_bsd_ppc64.s │ │ │ ├── asm_bsd_riscv64.s │ │ │ ├── asm_linux_386.s │ │ │ ├── asm_linux_amd64.s │ │ │ ├── asm_linux_arm.s │ │ │ ├── asm_linux_arm64.s │ │ │ ├── asm_linux_loong64.s │ │ │ ├── asm_linux_mips64x.s │ │ │ ├── asm_linux_mipsx.s │ │ │ ├── asm_linux_ppc64x.s │ │ │ ├── asm_linux_riscv64.s │ │ │ ├── asm_linux_s390x.s │ │ │ ├── asm_openbsd_mips64.s │ │ │ ├── asm_solaris_amd64.s │ │ │ ├── asm_zos_s390x.s │ │ │ ├── auxv.go │ │ │ ├── auxv_unsupported.go │ │ │ ├── bluetooth_linux.go │ │ │ ├── bpxsvc_zos.go │ │ │ ├── bpxsvc_zos.s │ │ │ ├── cap_freebsd.go │ │ │ ├── constants.go │ │ │ ├── dev_aix_ppc.go │ │ │ ├── dev_aix_ppc64.go │ │ │ ├── dev_darwin.go │ │ │ ├── dev_dragonfly.go │ │ │ ├── dev_freebsd.go │ │ │ ├── dev_linux.go │ │ │ ├── dev_netbsd.go │ │ │ ├── dev_openbsd.go │ │ │ ├── dev_zos.go │ │ │ ├── dirent.go │ │ │ ├── endian_big.go │ │ │ ├── endian_little.go │ │ │ ├── env_unix.go │ │ │ ├── fcntl.go │ │ │ ├── fcntl_darwin.go │ │ │ ├── fcntl_linux_32bit.go │ │ │ ├── fdset.go │ │ │ ├── gccgo.go │ │ │ ├── gccgo_c.c │ │ │ ├── gccgo_linux_amd64.go │ │ │ ├── ifreq_linux.go │ │ │ ├── ioctl_linux.go │ │ │ ├── ioctl_signed.go │ │ │ ├── ioctl_unsigned.go │ │ │ ├── ioctl_zos.go │ │ │ ├── mkall.sh │ │ │ ├── mkerrors.sh │ │ │ ├── mmap_nomremap.go │ │ │ ├── mremap.go │ │ │ ├── pagesize_unix.go │ │ │ ├── pledge_openbsd.go │ │ │ ├── ptrace_darwin.go │ │ │ ├── ptrace_ios.go │ │ │ ├── race.go │ │ │ ├── race0.go │ │ │ ├── readdirent_getdents.go │ │ │ ├── readdirent_getdirentries.go │ │ │ ├── sockcmsg_dragonfly.go │ │ │ ├── sockcmsg_linux.go │ │ │ ├── sockcmsg_unix.go │ │ │ ├── sockcmsg_unix_other.go │ │ │ ├── sockcmsg_zos.go │ │ │ ├── symaddr_zos_s390x.s │ │ │ ├── syscall.go │ │ │ ├── syscall_aix.go │ │ │ ├── syscall_aix_ppc.go │ │ │ ├── syscall_aix_ppc64.go │ │ │ ├── syscall_bsd.go │ │ │ ├── syscall_darwin.go │ │ │ ├── syscall_darwin_amd64.go │ │ │ ├── syscall_darwin_arm64.go │ │ │ ├── syscall_darwin_libSystem.go │ │ │ ├── syscall_dragonfly.go │ │ │ ├── syscall_dragonfly_amd64.go │ │ │ ├── syscall_freebsd.go │ │ │ ├── syscall_freebsd_386.go │ │ │ ├── syscall_freebsd_amd64.go │ │ │ ├── syscall_freebsd_arm.go │ │ │ ├── syscall_freebsd_arm64.go │ │ │ ├── syscall_freebsd_riscv64.go │ │ │ ├── syscall_hurd.go │ │ │ ├── syscall_hurd_386.go │ │ │ ├── syscall_illumos.go │ │ │ ├── syscall_linux.go │ │ │ ├── syscall_linux_386.go │ │ │ ├── syscall_linux_alarm.go │ │ │ ├── syscall_linux_amd64.go │ │ │ ├── syscall_linux_amd64_gc.go │ │ │ ├── syscall_linux_arm.go │ │ │ ├── syscall_linux_arm64.go │ │ │ ├── syscall_linux_gc.go │ │ │ ├── syscall_linux_gc_386.go │ │ │ ├── syscall_linux_gc_arm.go │ │ │ ├── syscall_linux_gccgo_386.go │ │ │ ├── syscall_linux_gccgo_arm.go │ │ │ ├── syscall_linux_loong64.go │ │ │ ├── syscall_linux_mips64x.go │ │ │ ├── syscall_linux_mipsx.go │ │ │ ├── syscall_linux_ppc.go │ │ │ ├── syscall_linux_ppc64x.go │ │ │ ├── syscall_linux_riscv64.go │ │ │ ├── syscall_linux_s390x.go │ │ │ ├── syscall_linux_sparc64.go │ │ │ ├── syscall_netbsd.go │ │ │ ├── syscall_netbsd_386.go │ │ │ ├── syscall_netbsd_amd64.go │ │ │ ├── syscall_netbsd_arm.go │ │ │ ├── syscall_netbsd_arm64.go │ │ │ ├── syscall_openbsd.go │ │ │ ├── syscall_openbsd_386.go │ │ │ ├── syscall_openbsd_amd64.go │ │ │ ├── syscall_openbsd_arm.go │ │ │ ├── syscall_openbsd_arm64.go │ │ │ ├── syscall_openbsd_libc.go │ │ │ ├── syscall_openbsd_mips64.go │ │ │ ├── syscall_openbsd_ppc64.go │ │ │ ├── syscall_openbsd_riscv64.go │ │ │ ├── syscall_solaris.go │ │ │ ├── syscall_solaris_amd64.go │ │ │ ├── syscall_unix.go │ │ │ ├── syscall_unix_gc.go │ │ │ ├── syscall_unix_gc_ppc64x.go │ │ │ ├── syscall_zos_s390x.go │ │ │ ├── sysvshm_linux.go │ │ │ ├── sysvshm_unix.go │ │ │ ├── sysvshm_unix_other.go │ │ │ ├── timestruct.go │ │ │ ├── unveil_openbsd.go │ │ │ ├── vgetrandom_linux.go │ │ │ ├── vgetrandom_unsupported.go │ │ │ ├── xattr_bsd.go │ │ │ ├── zerrors_aix_ppc.go │ │ │ ├── zerrors_aix_ppc64.go │ │ │ ├── zerrors_darwin_amd64.go │ │ │ ├── zerrors_darwin_arm64.go │ │ │ ├── zerrors_dragonfly_amd64.go │ │ │ ├── zerrors_freebsd_386.go │ │ │ ├── zerrors_freebsd_amd64.go │ │ │ ├── zerrors_freebsd_arm.go │ │ │ ├── zerrors_freebsd_arm64.go │ │ │ ├── zerrors_freebsd_riscv64.go │ │ │ ├── zerrors_linux.go │ │ │ ├── zerrors_linux_386.go │ │ │ ├── zerrors_linux_amd64.go │ │ │ ├── zerrors_linux_arm.go │ │ │ ├── zerrors_linux_arm64.go │ │ │ ├── zerrors_linux_loong64.go │ │ │ ├── zerrors_linux_mips.go │ │ │ ├── zerrors_linux_mips64.go │ │ │ ├── zerrors_linux_mips64le.go │ │ │ ├── zerrors_linux_mipsle.go │ │ │ ├── zerrors_linux_ppc.go │ │ │ ├── zerrors_linux_ppc64.go │ │ │ ├── zerrors_linux_ppc64le.go │ │ │ ├── zerrors_linux_riscv64.go │ │ │ ├── zerrors_linux_s390x.go │ │ │ ├── zerrors_linux_sparc64.go │ │ │ ├── zerrors_netbsd_386.go │ │ │ ├── zerrors_netbsd_amd64.go │ │ │ ├── zerrors_netbsd_arm.go │ │ │ ├── zerrors_netbsd_arm64.go │ │ │ ├── zerrors_openbsd_386.go │ │ │ ├── zerrors_openbsd_amd64.go │ │ │ ├── zerrors_openbsd_arm.go │ │ │ ├── zerrors_openbsd_arm64.go │ │ │ ├── zerrors_openbsd_mips64.go │ │ │ ├── zerrors_openbsd_ppc64.go │ │ │ ├── zerrors_openbsd_riscv64.go │ │ │ ├── zerrors_solaris_amd64.go │ │ │ ├── zerrors_zos_s390x.go │ │ │ ├── zptrace_armnn_linux.go │ │ │ ├── zptrace_linux_arm64.go │ │ │ ├── zptrace_mipsnn_linux.go │ │ │ ├── zptrace_mipsnnle_linux.go │ │ │ ├── zptrace_x86_linux.go │ │ │ ├── zsymaddr_zos_s390x.s │ │ │ ├── zsyscall_aix_ppc.go │ │ │ ├── zsyscall_aix_ppc64.go │ │ │ ├── zsyscall_aix_ppc64_gc.go │ │ │ ├── zsyscall_aix_ppc64_gccgo.go │ │ │ ├── zsyscall_darwin_amd64.go │ │ │ ├── zsyscall_darwin_amd64.s │ │ │ ├── zsyscall_darwin_arm64.go │ │ │ ├── zsyscall_darwin_arm64.s │ │ │ ├── zsyscall_dragonfly_amd64.go │ │ │ ├── zsyscall_freebsd_386.go │ │ │ ├── zsyscall_freebsd_amd64.go │ │ │ ├── zsyscall_freebsd_arm.go │ │ │ ├── zsyscall_freebsd_arm64.go │ │ │ ├── zsyscall_freebsd_riscv64.go │ │ │ ├── zsyscall_illumos_amd64.go │ │ │ ├── zsyscall_linux.go │ │ │ ├── zsyscall_linux_386.go │ │ │ ├── zsyscall_linux_amd64.go │ │ │ ├── zsyscall_linux_arm.go │ │ │ ├── zsyscall_linux_arm64.go │ │ │ ├── zsyscall_linux_loong64.go │ │ │ ├── zsyscall_linux_mips.go │ │ │ ├── zsyscall_linux_mips64.go │ │ │ ├── zsyscall_linux_mips64le.go │ │ │ ├── zsyscall_linux_mipsle.go │ │ │ ├── zsyscall_linux_ppc.go │ │ │ ├── zsyscall_linux_ppc64.go │ │ │ ├── zsyscall_linux_ppc64le.go │ │ │ ├── zsyscall_linux_riscv64.go │ │ │ ├── zsyscall_linux_s390x.go │ │ │ ├── zsyscall_linux_sparc64.go │ │ │ ├── zsyscall_netbsd_386.go │ │ │ ├── zsyscall_netbsd_amd64.go │ │ │ ├── zsyscall_netbsd_arm.go │ │ │ ├── zsyscall_netbsd_arm64.go │ │ │ ├── zsyscall_openbsd_386.go │ │ │ ├── zsyscall_openbsd_386.s │ │ │ ├── zsyscall_openbsd_amd64.go │ │ │ ├── zsyscall_openbsd_amd64.s │ │ │ ├── zsyscall_openbsd_arm.go │ │ │ ├── zsyscall_openbsd_arm.s │ │ │ ├── zsyscall_openbsd_arm64.go │ │ │ ├── zsyscall_openbsd_arm64.s │ │ │ ├── zsyscall_openbsd_mips64.go │ │ │ ├── zsyscall_openbsd_mips64.s │ │ │ ├── zsyscall_openbsd_ppc64.go │ │ │ ├── zsyscall_openbsd_ppc64.s │ │ │ ├── zsyscall_openbsd_riscv64.go │ │ │ ├── zsyscall_openbsd_riscv64.s │ │ │ ├── zsyscall_solaris_amd64.go │ │ │ ├── zsyscall_zos_s390x.go │ │ │ ├── zsysctl_openbsd_386.go │ │ │ ├── zsysctl_openbsd_amd64.go │ │ │ ├── zsysctl_openbsd_arm.go │ │ │ ├── zsysctl_openbsd_arm64.go │ │ │ ├── zsysctl_openbsd_mips64.go │ │ │ ├── zsysctl_openbsd_ppc64.go │ │ │ ├── zsysctl_openbsd_riscv64.go │ │ │ ├── zsysnum_darwin_amd64.go │ │ │ ├── zsysnum_darwin_arm64.go │ │ │ ├── zsysnum_dragonfly_amd64.go │ │ │ ├── zsysnum_freebsd_386.go │ │ │ ├── zsysnum_freebsd_amd64.go │ │ │ ├── zsysnum_freebsd_arm.go │ │ │ ├── zsysnum_freebsd_arm64.go │ │ │ ├── zsysnum_freebsd_riscv64.go │ │ │ ├── zsysnum_linux_386.go │ │ │ ├── zsysnum_linux_amd64.go │ │ │ ├── zsysnum_linux_arm.go │ │ │ ├── zsysnum_linux_arm64.go │ │ │ ├── zsysnum_linux_loong64.go │ │ │ ├── zsysnum_linux_mips.go │ │ │ ├── zsysnum_linux_mips64.go │ │ │ ├── zsysnum_linux_mips64le.go │ │ │ ├── zsysnum_linux_mipsle.go │ │ │ ├── zsysnum_linux_ppc.go │ │ │ ├── zsysnum_linux_ppc64.go │ │ │ ├── zsysnum_linux_ppc64le.go │ │ │ ├── zsysnum_linux_riscv64.go │ │ │ ├── zsysnum_linux_s390x.go │ │ │ ├── zsysnum_linux_sparc64.go │ │ │ ├── zsysnum_netbsd_386.go │ │ │ ├── zsysnum_netbsd_amd64.go │ │ │ ├── zsysnum_netbsd_arm.go │ │ │ ├── zsysnum_netbsd_arm64.go │ │ │ ├── zsysnum_openbsd_386.go │ │ │ ├── zsysnum_openbsd_amd64.go │ │ │ ├── zsysnum_openbsd_arm.go │ │ │ ├── zsysnum_openbsd_arm64.go │ │ │ ├── zsysnum_openbsd_mips64.go │ │ │ ├── zsysnum_openbsd_ppc64.go │ │ │ ├── zsysnum_openbsd_riscv64.go │ │ │ ├── zsysnum_zos_s390x.go │ │ │ ├── ztypes_aix_ppc.go │ │ │ ├── ztypes_aix_ppc64.go │ │ │ ├── ztypes_darwin_amd64.go │ │ │ ├── ztypes_darwin_arm64.go │ │ │ ├── ztypes_dragonfly_amd64.go │ │ │ ├── ztypes_freebsd_386.go │ │ │ ├── ztypes_freebsd_amd64.go │ │ │ ├── ztypes_freebsd_arm.go │ │ │ ├── ztypes_freebsd_arm64.go │ │ │ ├── ztypes_freebsd_riscv64.go │ │ │ ├── ztypes_linux.go │ │ │ ├── ztypes_linux_386.go │ │ │ ├── ztypes_linux_amd64.go │ │ │ ├── ztypes_linux_arm.go │ │ │ ├── ztypes_linux_arm64.go │ │ │ ├── ztypes_linux_loong64.go │ │ │ ├── ztypes_linux_mips.go │ │ │ ├── ztypes_linux_mips64.go │ │ │ ├── ztypes_linux_mips64le.go │ │ │ ├── ztypes_linux_mipsle.go │ │ │ ├── ztypes_linux_ppc.go │ │ │ ├── ztypes_linux_ppc64.go │ │ │ ├── ztypes_linux_ppc64le.go │ │ │ ├── ztypes_linux_riscv64.go │ │ │ ├── ztypes_linux_s390x.go │ │ │ ├── ztypes_linux_sparc64.go │ │ │ ├── ztypes_netbsd_386.go │ │ │ ├── ztypes_netbsd_amd64.go │ │ │ ├── ztypes_netbsd_arm.go │ │ │ ├── ztypes_netbsd_arm64.go │ │ │ ├── ztypes_openbsd_386.go │ │ │ ├── ztypes_openbsd_amd64.go │ │ │ ├── ztypes_openbsd_arm.go │ │ │ ├── ztypes_openbsd_arm64.go │ │ │ ├── ztypes_openbsd_mips64.go │ │ │ ├── ztypes_openbsd_ppc64.go │ │ │ ├── ztypes_openbsd_riscv64.go │ │ │ ├── ztypes_solaris_amd64.go │ │ │ └── ztypes_zos_s390x.go │ │ └── windows/ │ │ ├── aliases.go │ │ ├── dll_windows.go │ │ ├── env_windows.go │ │ ├── eventlog.go │ │ ├── exec_windows.go │ │ ├── memory_windows.go │ │ ├── mkerrors.bash │ │ ├── mkknownfolderids.bash │ │ ├── mksyscall.go │ │ ├── race.go │ │ ├── race0.go │ │ ├── security_windows.go │ │ ├── service.go │ │ ├── setupapi_windows.go │ │ ├── str.go │ │ ├── syscall.go │ │ ├── syscall_windows.go │ │ ├── types_windows.go │ │ ├── types_windows_386.go │ │ ├── types_windows_amd64.go │ │ ├── types_windows_arm.go │ │ ├── types_windows_arm64.go │ │ ├── zerrors_windows.go │ │ ├── zknownfolderids_windows.go │ │ └── zsyscall_windows.go │ ├── term/ │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── PATENTS │ │ ├── README.md │ │ ├── codereview.cfg │ │ ├── term.go │ │ ├── term_plan9.go │ │ ├── term_unix.go │ │ ├── term_unix_bsd.go │ │ ├── term_unix_other.go │ │ ├── term_unsupported.go │ │ ├── term_windows.go │ │ └── terminal.go │ └── text/ │ ├── LICENSE │ ├── PATENTS │ ├── encoding/ │ │ ├── charmap/ │ │ │ ├── charmap.go │ │ │ └── tables.go │ │ ├── encoding.go │ │ ├── internal/ │ │ │ ├── identifier/ │ │ │ │ ├── identifier.go │ │ │ │ └── mib.go │ │ │ └── internal.go │ │ ├── japanese/ │ │ │ ├── all.go │ │ │ ├── eucjp.go │ │ │ ├── iso2022jp.go │ │ │ ├── shiftjis.go │ │ │ └── tables.go │ │ ├── korean/ │ │ │ ├── euckr.go │ │ │ └── tables.go │ │ ├── simplifiedchinese/ │ │ │ ├── all.go │ │ │ ├── gbk.go │ │ │ ├── hzgb2312.go │ │ │ └── tables.go │ │ ├── traditionalchinese/ │ │ │ ├── big5.go │ │ │ └── tables.go │ │ └── unicode/ │ │ ├── override.go │ │ └── unicode.go │ ├── internal/ │ │ └── utf8internal/ │ │ └── utf8internal.go │ ├── runes/ │ │ ├── cond.go │ │ └── runes.go │ └── transform/ │ └── transform.go ├── gopkg.in/ │ ├── natefinch/ │ │ └── lumberjack.v2/ │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── chown.go │ │ ├── chown_linux.go │ │ └── lumberjack.go │ └── yaml.v3/ │ ├── LICENSE │ ├── NOTICE │ ├── README.md │ ├── apic.go │ ├── decode.go │ ├── emitterc.go │ ├── encode.go │ ├── parserc.go │ ├── readerc.go │ ├── resolve.go │ ├── scannerc.go │ ├── sorter.go │ ├── writerc.go │ ├── yaml.go │ ├── yamlh.go │ └── yamlprivateh.go └── modules.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/CODEOWNERS ================================================ * @rashiq @meiji163 @timvaillancourt ================================================ FILE: .github/CONTRIBUTING.md ================================================ ## Contributing Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. This project adheres to the [Open Code of Conduct](http://todogroup.org/opencodeofconduct/#gh-ost/opensource@github.com). By participating, you are expected to uphold this code. ## Submitting a pull request 0. [Fork](https://github.com/github/gh-ost/fork) and clone the repository 0. Create a new branch: `git checkout -b my-branch-name` 0. Make your change, add tests, and make sure the tests still pass 0. Push to your fork and [submit a pull request](https://github.com/github/gh-ost/compare) 0. Pat your self on the back and wait for your pull request to be reviewed and merged. Here are a few things you can do that will increase the likelihood of your pull request being accepted: - Follow the [style guide](https://golang.org/doc/effective_go.html#formatting). - Write tests. - Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). ## Development Guidelines ### Channel Safety When working with channels in goroutines, it's critical to prevent deadlocks that can occur when a channel receiver exits due to an error while senders are still trying to send values. Always use `base.SendWithContext` for channel sends to avoid deadlocks: ```go // ✅ CORRECT - Uses helper to prevent deadlock if err := base.SendWithContext(ctx, ch, value); err != nil { return err // context was cancelled } // ❌ WRONG - Can deadlock if receiver exits ch <- value ``` Even if the destination channel is buffered, deadlocks could still occur if the buffer fills up and the receiver exits, so it's important to use `SendWithContext` in those cases as well. ## Resources - [Contributing to Open Source on GitHub](https://guides.github.com/activities/contributing-to-open-source/) - [Using Pull Requests](https://help.github.com/articles/using-pull-requests/) - [GitHub Help](https://help.github.com) ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ > This is the place to report a bug, ask a question, or suggest an enhancement. > This is also the place to make a discussion before creating a PR. > If this is a bug report, please provide a test case (e.g., your table definition and gh-ost command) and the error output. > Please use markdown to format code or SQL: https://guides.github.com/features/mastering-markdown/ > Please label the issue on the right (bug, enhancement, question, etc.). > And please understand if this issue is not addressed immediately or in a timeframe you were expecting. > Thank you! ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## A Pull Request should be associated with an Issue. > We wish to have discussions in Issues. A single issue may be targeted by multiple PRs. > If you're offering a new feature or fixing anything, we'd like to know beforehand in Issues, > and potentially we'll be able to point development in a particular direction. Related issue: https://github.com/github/gh-ost/issues/0123456789 > Further notes in https://github.com/github/gh-ost/blob/master/.github/CONTRIBUTING.md > Thank you! We are open to PRs, but please understand if for technical reasons we are unable to accept each and any PR ### Description This PR [briefly explain what it does] > In case this PR introduced Go code changes: - [ ] contributed code is using same conventions as original code - [ ] `script/cibuild` returns with no formatting errors, build errors or unit test errors. ================================================ FILE: .github/dependabot.yml ================================================ --- version: 2 updates: - package-ecosystem: github-actions directory: "/" schedule: interval: daily ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI permissions: contents: read on: [pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v6 with: go-version-file: go.mod - name: Build run: script/cibuild - name: Upload gh-ost binary artifact uses: actions/upload-artifact@v4 with: name: gh-ost path: bin/gh-ost ================================================ FILE: .github/workflows/codeql.yml ================================================ name: "CodeQL analysis" on: push: branches: [ master ] pull_request: branches: [ master ] schedule: - cron: '25 22 * * 6' jobs: codeql: permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'actions', 'go' ] runs-on: ubuntu-latest # windows-latest and ubuntu-latest are supported. macos-latest is not supported at this time. steps: - name: Checkout repository uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 ================================================ FILE: .github/workflows/golangci-lint.yml ================================================ name: golangci-lint on: push: branches: - master pull_request: permissions: contents: read # Optional: allow read access to pull request. Use with `only-new-issues` option. # pull-requests: read jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v6 with: go-version-file: go.mod - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: version: v1.61.0 ================================================ FILE: .github/workflows/replica-tests.yml ================================================ name: migration tests permissions: contents: read on: [pull_request] jobs: docker-tests: runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: image: ['mysql/mysql-server:5.7.41','mysql:8.0.41','mysql:8.4.3','percona/percona-server:8.0.41-32'] env: TEST_MYSQL_IMAGE: ${{ matrix.image }} steps: - uses: actions/checkout@v4 - name: Install sysbench run: | sudo apt-get update sudo apt-get install -y sysbench - name: Setup environment run: script/docker-gh-ost-replica-tests up - name: Run tests run: script/docker-gh-ost-replica-tests run - name: Set artifact name if: failure() run: | ARTIFACT_NAME=$(echo "${{ matrix.image }}" | tr '/:' '-') echo "ARTIFACT_NAME=test-logs-${ARTIFACT_NAME}" >> $GITHUB_ENV - name: Upload test logs on failure if: failure() uses: actions/upload-artifact@v4 with: name: ${{ env.ARTIFACT_NAME }} path: /tmp/gh-ost-test.* retention-days: 7 - name: Teardown environment if: always() run: script/docker-gh-ost-replica-tests down ================================================ FILE: .gitignore ================================================ /.gopath/ /bin/ /libexec/ /.vendor/ .idea/ *.tmp ================================================ FILE: .golangci.yml ================================================ run: timeout: 5m linters: disable: - errcheck enable: - bodyclose - containedctx - contextcheck - dogsled - durationcheck - errname - errorlint - gofmt - misspell - nilerr - nilnil - noctx - nolintlint - nosprintfhostport - prealloc - rowserrcheck - sqlclosecheck - unconvert - unparam - unused - wastedassign - whitespace ================================================ FILE: Dockerfile.packaging ================================================ FROM golang:1.23-bullseye RUN apt-get update RUN apt-get install -y ruby ruby-dev rubygems build-essential RUN gem install fpm ENV GOPATH=/tmp/go RUN apt-get install -y curl RUN apt-get install -y rsync RUN apt-get install -y gcc RUN apt-get install -y g++ RUN apt-get install -y bash RUN apt-get install -y git RUN apt-get install -y tar RUN apt-get install -y rpm RUN mkdir -p $GOPATH/src/github.com/github/gh-ost WORKDIR $GOPATH/src/github.com/github/gh-ost COPY . . RUN bash build.sh ================================================ FILE: Dockerfile.test ================================================ FROM golang:1.23-bullseye LABEL maintainer="github@github.com" RUN apt-get update RUN apt-get install -y lsb-release RUN rm -rf /var/lib/apt/lists/* COPY . /go/src/github.com/github/gh-ost WORKDIR /go/src/github.com/github/gh-ost CMD ["script/test"] ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2016 GitHub Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # gh-ost [![ci](https://github.com/github/gh-ost/actions/workflows/ci.yml/badge.svg)](https://github.com/github/gh-ost/actions/workflows/ci.yml) [![replica-tests](https://github.com/github/gh-ost/actions/workflows/replica-tests.yml/badge.svg)](https://github.com/github/gh-ost/actions/workflows/replica-tests.yml) [![downloads](https://img.shields.io/github/downloads/github/gh-ost/total.svg)](https://github.com/github/gh-ost/releases) [![release](https://img.shields.io/github/release/github/gh-ost.svg)](https://github.com/github/gh-ost/releases) #### GitHub's online schema migration for MySQL `gh-ost` is a triggerless online schema migration solution for MySQL. It is testable and provides pausability, dynamic control/reconfiguration, auditing, and many operational perks. `gh-ost` produces a light workload on the master throughout the migration, decoupled from the existing workload on the migrated table. It has been designed based on years of experience with existing solutions, and changes the paradigm of table migrations. ## How? All existing online-schema-change tools operate in similar manner: they create a _ghost_ table in the likeness of your original table, migrate that table while empty, slowly and incrementally copy data from your original table to the _ghost_ table, meanwhile propagating ongoing changes (any `INSERT`, `DELETE`, `UPDATE` applied to your table) to the _ghost_ table. Finally, at the right time, they replace your original table with the _ghost_ table. `gh-ost` uses the same pattern. However it differs from all existing tools by not using triggers. We have recognized the triggers to be the source of [many limitations and risks](doc/why-triggerless.md). Instead, `gh-ost` [uses the binary log stream](doc/triggerless-design.md) to capture table changes, and asynchronously applies them onto the _ghost_ table. `gh-ost` takes upon itself some tasks that other tools leave for the database to perform. As result, `gh-ost` has greater control over the migration process; can truly suspend it; can truly decouple the migration's write load from the master's workload. In addition, it offers many [operational perks](doc/perks.md) that make it safer, trustworthy and fun to use. ![gh-ost general flow](doc/images/gh-ost-general-flow.png) ## Highlights - Build your trust in `gh-ost` by testing it on replicas. `gh-ost` will issue same flow as it would have on the master, to migrate a table on a replica, without actually replacing the original table, leaving the replica with two tables you can then compare and satisfy yourself that the tool operates correctly. This is how we continuously test `gh-ost` in production. - True pause: when `gh-ost` [throttles](doc/throttle.md), it truly ceases writes on master: no row copies and no ongoing events processing. By throttling, you return your master to its original workload - Dynamic control: you can [interactively](doc/interactive-commands.md) reconfigure `gh-ost`, even as migration still runs. You may forcibly initiate throttling. - Auditing: you may query `gh-ost` for status. `gh-ost` listens on unix socket or TCP. - Control over cut-over phase: `gh-ost` can be instructed to postpone what is probably the most critical step: the swap of tables, until such time that you're comfortably available. No need to worry about ETA being outside office hours. - External [hooks](doc/hooks.md) can couple `gh-ost` with your particular environment. Please refer to the [docs](doc) for more information. No, really, read the [docs](doc). ## Usage The [cheatsheet](doc/cheatsheet.md) has it all. You may be interested in invoking `gh-ost` in various modes: - a _noop_ migration (merely testing that the migration is valid and good to go) - a real migration, utilizing a replica (the migration runs on the master; `gh-ost` figures out identities of servers involved. Required mode if your master uses Statement Based Replication) - a real migration, run directly on the master (but `gh-ost` prefers the former) - a real migration on a replica (master untouched) - a test migration on a replica, the way for you to build trust with `gh-ost`'s operation. Our tips: - [Testing above all](doc/testing-on-replica.md), try out `--test-on-replica` first few times. Better yet, make it continuous. We have multiple replicas where we iterate our entire fleet of production tables, migrating them one by one, checksumming the results, verifying migration is good. - For each master migration, first issue a _noop_ - Then issue the real thing via `--execute`. More tips: - Use `--exact-rowcount` for accurate progress indication - Use `--postpone-cut-over-flag-file` to gain control over cut-over timing - Get familiar with the [interactive commands](doc/interactive-commands.md) Also see: - [requirements and limitations](doc/requirements-and-limitations.md) - [common questions](doc/questions.md) - [what if?](doc/what-if.md) - [the fine print](doc/the-fine-print.md) - [Community questions](https://github.com/github/gh-ost/issues?q=label%3Aquestion) - [Using `gh-ost` on AWS RDS](doc/rds.md) - [Using `gh-ost` on Azure Database for MySQL](doc/azure.md) ## What's in a name? Originally this was named `gh-osc`: GitHub Online Schema Change, in the likes of [Facebook online schema change](https://www.facebook.com/notes/mysql-at-facebook/online-schema-change-for-mysql/430801045932/) and [pt-online-schema-change](https://www.percona.com/doc/percona-toolkit/2.2/pt-online-schema-change.html). But then a rare genetic mutation happened, and the `c` transformed into `t`. And that sent us down the path of trying to figure out a new acronym. `gh-ost` (pronounce: _Ghost_), stands for GitHub's Online Schema Transmogrifier/Translator/Transformer/Transfigurator ## License `gh-ost` is licensed under the [MIT license](https://github.com/github/gh-ost/blob/master/LICENSE) `gh-ost` uses 3rd party libraries, each with their own license. These are found [here](https://github.com/github/gh-ost/tree/master/vendor). ## Community `gh-ost` is released at a stable state, but with mileage to go. We are [open to pull requests](https://github.com/github/gh-ost/blob/master/.github/CONTRIBUTING.md). Please first discuss your intentions via [Issues](https://github.com/github/gh-ost/issues). We develop `gh-ost` at GitHub and for the community. We may have different priorities than others. From time to time we may suggest a contribution that is not on our immediate roadmap but which may appeal to others. Please see [Coding gh-ost](doc/coding-ghost.md) for a guide to getting started developing with gh-ost. ## Download/binaries/source `gh-ost` is now GA and stable. `gh-ost` is available in binary format for Linux and Mac OS/X [Download latest release here](https://github.com/github/gh-ost/releases/latest) `gh-ost` is a Go project; it is built with Go `1.15` and above. To build on your own, use either: - [script/build](https://github.com/github/gh-ost/blob/master/script/build) - this is the same build script used by CI hence the authoritative; artifact is `./bin/gh-ost` binary. - [build.sh](https://github.com/github/gh-ost/blob/master/build.sh) for building `tar.gz` artifacts in `/tmp/gh-ost-release` Generally speaking, `master` branch is stable, but only [releases](https://github.com/github/gh-ost/releases) are to be used in production. ## Authors `gh-ost` is designed, authored, reviewed and tested by the database infrastructure team at GitHub: - [@jonahberquist](https://github.com/jonahberquist) - [@ggunson](https://github.com/ggunson) - [@tomkrouper](https://github.com/tomkrouper) - [@shlomi-noach](https://github.com/shlomi-noach) - [@jessbreckenridge](https://github.com/jessbreckenridge) - [@gtowey](https://github.com/gtowey) - [@timvaillancourt](https://github.com/timvaillancourt) ================================================ FILE: build.sh ================================================ #!/bin/bash RELEASE_VERSION= buildpath= function setuptree() { b=$( mktemp -d $buildpath/gh-ostXXXXXX ) || return 1 mkdir -p $b/gh-ost mkdir -p $b/gh-ost/usr/bin echo $b } function build { osname=$1 osshort=$2 GOOS=$3 GOARCH=$4 if ! go version | egrep -q 'go1\.(1[5-9]|[2-9][0-9]{1})' ; then echo "go version must be 1.15 or above" exit 1 fi echo "Building ${osname}-${GOARCH} binary" export GOOS export GOARCH go build -ldflags "$ldflags" -o $buildpath/$target go/cmd/gh-ost/main.go if [ $? -ne 0 ]; then echo "Build failed for ${osname} ${GOARCH}." exit 1 fi (cd $buildpath && tar cfz ./gh-ost-binary-${osshort}-${GOARCH}-${timestamp}.tar.gz $target) # build RPM and deb for Linux, x86-64 only if [ "$GOOS" == "linux" ] && [ "$GOARCH" == "amd64" ] ; then echo "Creating Distro full packages" builddir=$(setuptree) cp $buildpath/$target $builddir/gh-ost/usr/bin cd $buildpath fpm -v "${RELEASE_VERSION}" --epoch 1 -f -s dir -n gh-ost -m 'GitHub' --description "GitHub's Online Schema Migrations for MySQL " --url "https://github.com/github/gh-ost" --vendor "GitHub" --license "Apache 2.0" -C $builddir/gh-ost --prefix=/ -t rpm --rpm-rpmbuild-define "_build_id_links none" --rpm-os linux . fpm -v "${RELEASE_VERSION}" --epoch 1 -f -s dir -n gh-ost -m 'GitHub' --description "GitHub's Online Schema Migrations for MySQL " --url "https://github.com/github/gh-ost" --vendor "GitHub" --license "Apache 2.0" -C $builddir/gh-ost --prefix=/ -t deb --deb-no-default-config-files . cd - fi } main() { if [ -z "${RELEASE_VERSION}" ] ; then RELEASE_VERSION=$(git describe --abbrev=0 --tags | tr -d 'v') fi if [ -z "${RELEASE_VERSION}" ] ; then echo "RELEASE_VERSION must be set" exit 1 fi if [ -z "${GIT_COMMIT}" ]; then GIT_COMMIT=$(git rev-parse HEAD) fi buildpath=/tmp/gh-ost-release target=gh-ost timestamp=$(date "+%Y%m%d%H%M%S") ldflags="-X main.AppVersion=${RELEASE_VERSION} -X main.GitCommit=${GIT_COMMIT}" mkdir -p ${buildpath} rm -rf ${buildpath:?}/* build GNU/Linux linux linux amd64 build GNU/Linux linux linux arm64 build macOS osx darwin amd64 build macOS osx darwin arm64 bin_files=$(find $buildpath/gh-ost* -type f -maxdepth 1) echo "Binaries found in:" echo "$bin_files" echo "Checksums:" (shasum -a256 $bin_files 2>/dev/null) echo "Build Success!" } main "$@" ================================================ FILE: doc/azure.md ================================================ `gh-ost` has been updated to work with Azure Database for MySQL however due to GitHub does not use it, this documentation is community driven so if you find a bug please [open an issue][new_issue]! # Azure Database for MySQL ## Limitations - `gh-ost` runs should be setup use [`--assume-rbr`][assume_rbr_docs] and use `binlog_row_image=FULL`. - Azure Database for MySQL does not use same user name suffix for master and replica, so master host, user and password need to be pointed out. ## Step 1. Change the replica server's `binlog_row_image` from `MINIMAL` to `FULL`. See [guide](https://docs.microsoft.com/en-us/azure/mysql/howto-server-parameters) on Azure document. 2. Use your `gh-ost` always with additional 5 parameter ```{bash} gh-ost \ --azure \ --assume-master-host=master-server-dns-name \ --master-user="master-user-name" \ --master-password="master-password" \ --assume-rbr \ [-- other parameters you need] ``` [new_issue]: https://github.com/github/gh-ost/issues/new [assume_rbr_docs]: https://github.com/github/gh-ost/blob/master/doc/command-line-flags.md#assume-rbr [migrate_test_on_replica_docs]: https://github.com/github/gh-ost/blob/master/doc/cheatsheet.md#c-migratetest-on-replica ================================================ FILE: doc/cheatsheet.md ================================================ # Cheatsheet ### Operation modes ![operation modes](images/gh-ost-operation-modes.png) `gh-ost` operates by connecting to potentially multiple servers, as well as imposing itself as a replica in order to streamline binary log events directly from one of those servers. There are various operation modes, which depend on your setup, configuration, and where you want to run the migration. #### a. Connect to replica, migrate on master This is the mode `gh-ost` expects by default. `gh-ost` will investigate the replica, crawl up to find the topology's master, and will hook onto it as well. Migration will: - Read and write row-data on master - Read binary logs events on the replica, apply the changes onto the master - Investigates table format, columns & keys, count rows on the replica - Read internal changelog events (such as heartbeat) from the replica - Cut-over (switch tables) on the master If your master works with SBR, this is the mode to work with. The replica must be configured with binary logs enabled (`log_bin`, `log_slave_updates`) and should have `binlog_format=ROW` (`gh-ost` can apply the latter for you). However even with RBR we suggest this is the least master-intrusive operation mode. ```shell gh-ost \ --max-load=Threads_running=25 \ --critical-load=Threads_running=1000 \ --chunk-size=1000 \ --throttle-control-replicas="myreplica.1.com,myreplica.2.com" \ --max-lag-millis=1500 \ --user="gh-ost" \ --password="123456" \ --host=replica.with.rbr.com \ --database="my_schema" \ --table="my_table" \ --verbose \ --alter="engine=innodb" \ --switch-to-rbr \ --allow-master-master \ --cut-over=default \ --exact-rowcount \ --concurrent-rowcount \ --default-retries=120 \ --panic-flag-file=/tmp/ghost.panic.flag \ --postpone-cut-over-flag-file=/tmp/ghost.postpone.flag \ [--execute] ``` With `--execute`, migration actually copies data and flips tables. Without it this is a `noop` run. #### b. Connect to master If you don't have replicas, or do not wish to use them, you are still able to operate directly on the master. `gh-ost` will do all operations directly on the master. You may still ask it to be considerate of replication lag. - Your master must produce binary logs in RBR format. - You must approve this mode via `--allow-on-master`. ```shell gh-ost \ --max-load=Threads_running=25 \ --critical-load=Threads_running=1000 \ --chunk-size=1000 \ --throttle-control-replicas="myreplica.1.com,myreplica.2.com" \ --max-lag-millis=1500 \ --user="gh-ost" \ --password="123456" \ --host=master.with.rbr.com \ --allow-on-master \ --database="my_schema" \ --table="my_table" \ --verbose \ --alter="engine=innodb" \ --switch-to-rbr \ --allow-master-master \ --cut-over=default \ --exact-rowcount \ --concurrent-rowcount \ --default-retries=120 \ --panic-flag-file=/tmp/ghost.panic.flag \ --postpone-cut-over-flag-file=/tmp/ghost.postpone.flag \ [--execute] ``` #### c. Migrate/test on replica This will perform a migration on the replica. `gh-ost` will briefly connect to the master but will thereafter perform all operations on the replica without modifying anything on the master. Throughout the operation, `gh-ost` will throttle such that the replica is up to date. - `--migrate-on-replica` indicates to `gh-ost` that it must migrate the table directly on the replica. It will perform the cut-over phase even while replication is running. - `--test-on-replica` indicates the migration is for purpose of testing only. Before cut-over takes place, replication is stopped. Tables are swapped and then swapped back: your original table returns to its original place. Both tables are left with replication stopped. You may examine the two and compare data. Test on replica cheatsheet: ```shell gh-ost \ --user="gh-ost" \ --password="123456" \ --host=replica.with.rbr.com \ --test-on-replica \ --database="my_schema" \ --table="my_table" \ --verbose \ --alter="engine=innodb" \ --initially-drop-ghost-table \ --initially-drop-old-table \ --max-load=Threads_running=30 \ --switch-to-rbr \ --chunk-size=500 \ --cut-over=default \ --exact-rowcount \ --concurrent-rowcount \ --serve-socket-file=/tmp/gh-ost.test.sock \ --panic-flag-file=/tmp/gh-ost.panic.flag \ --execute ``` ### cnf file You may use a `cnf` file in the following format: ``` [client] user=gh-ost password=123456 ``` You may then remove `--user=gh-ost --password=123456` and specify `--conf=/path/to/config/file.cnf` ### Special configurations #### Master-master Master-master setups are supported, but at this time only active-passive. An active-active setup, where both masters write to the migrated table, is not supported at this stage. `gh-ost` requires you to acknowledge master-master via: ``` gh-ost --allow-master-master ``` `gh-ost` will pick one of the masters to work on. You may additionally force `gh-ost` to pick a particular master of your choice: ``` gh-ost --allow-master-master --assume-master-host=a.specific.master.com ``` #### Tungsten Topologies using _tungsten replicator_ are peculiar in that the participating servers are not actually aware they are replicating. The _tungsten replicator_ looks just like another app issuing queries on those hosts. `gh-ost` is unable to identify that a server participates in a _tungsten_ topology. If you choose to migrate directly on master (see above), there's nothing special you need to do. If you choose to migrate via replica, then you need to make sure Tungsten is configured with log-slave-updates parameter (note this is different from MySQL's own log-slave-updates parameter), otherwise changes will not be in the replica's binlog, causing data to be corrupted after table swap. You must also supply the identity of the master, and indicate this is a tungsten setup, as follows: ``` gh-ost --tungsten --assume-master-host=the.topology.master.com ``` Also note that `--switch-to-rbr` does not work for a Tungsten setup as the replication process is external, so you need to make sure `binlog_format` is set to ROW before Tungsten Replicator connects to the server and starts applying events from the master. ### Concurrent migrations It is possible to run concurrent `gh-ost` migrations. - Never on the exact same table. - If running on different replicas, (e.g. `table1` on `replica1` and `table2` on `replica2`) then no further configuration required. - If running from same server (binaries run on same server, regardless of which replica/replicas are used): - Make sure not to specify same `-serve-socket-file` (or let `gh-ost` pick one for you). - You may choose to use same `-throttle-flag-file` (preferably use `-throttle-additional-flag-file`, this is exactly the reason there's two, this latter file is for sharing). - You may choose to use same `-panic-flag-file`. This all depends on your flow and how you'd like to control your migrations. - If using same inspected box (either master or replica, `--host=everyone.uses.this.host`) then for each `gh-ost` process you must also provide a different, unique `--replica-server-id`. Optionally use process ID (`$$` in shell) ; but it's on you to choose a number that does not collide with another `gh-ost` or another running replica. ================================================ FILE: doc/coding-ghost.md ================================================ # Getting started with gh-ost development. ## Overview Getting started with gh-ost development is simple! - First obtain the repository with `git clone` or `go get`. - From inside of the repository run `script/cibuild`. - This will bootstrap the environment if needed, format the code, build the code, and then run the unit test. ## CI build workflow `script/cibuild` performs the following actions will bootstrap the environment to build `gh-ost` correctly, build, perform syntax checks and run unit tests. If additional steps are needed, please add them into this workflow so that the workflow remains simple. ## `golang-ci` linter To enfore best-practices, Pull Requests are automatically linted by [`golang-ci`](https://golangci-lint.run/). The linter config is located at [`.golangci.yml`](https://github.com/github/gh-ost/blob/master/.golangci.yml) and the `golangci-lint` GitHub Action is located at [`.github/workflows/golangci-lint.yml`](https://github.com/github/gh-ost/blob/master/.github/workflows/golangci-lint.yml). To run the `golang-ci` linters locally _(recommended before push)_, use `script/lint`. ## Notes: Currently, `script/ensure-go-installed` will install `go` for Mac OS X and Linux. We welcome PR's to add other platforms. ================================================ FILE: doc/command-line-flags.md ================================================ # Command line flags A more in-depth discussion of various `gh-ost` command line flags: implementation, implication, use cases. ### aliyun-rds Add this flag when executing on Aliyun RDS. ### allow-zero-in-date Allows the user to make schema changes that include a zero date or zero in date (e.g. adding a `datetime default '0000-00-00 00:00:00'` column), even if global `sql_mode` on MySQL has `NO_ZERO_IN_DATE,NO_ZERO_DATE`. ### azure Add this flag when executing on Azure Database for MySQL. ### allow-master-master See [`--assume-master-host`](#assume-master-host). ### allow-on-master By default, `gh-ost` would like you to connect to a replica, from where it figures out the master by itself. This wiring is required should your master execute using `binlog_format=STATEMENT`. If, for some reason, you do not wish `gh-ost` to connect to a replica, you may connect it directly to the master and approve this via `--allow-on-master`. ### allow-setup-metadata-lock-instruments `--allow-setup-metadata-lock-instruments` allows gh-ost to enable the [`metadata_locks`](https://dev.mysql.com/doc/refman/8.0/en/performance-schema-metadata-locks-table.html) table in `performance_schema`, if it is not already enabled. This is used for a safety check before cut-over. See also: [`skip-metadata-lock-check`](#skip-metadata-lock-check) ### approve-renamed-columns When your migration issues a column rename (`change column old_name new_name ...`) `gh-ost` analyzes the statement to try and associate the old column name with new column name. Otherwise, the new structure may also look like some column was dropped and another was added. `gh-ost` will print out what it thinks the _rename_ implied, but will not issue the migration unless you provide with `--approve-renamed-columns`. If you think `gh-ost` is mistaken and that there's actually no _rename_ involved, you may pass [`--skip-renamed-columns`](#skip-renamed-columns) instead. This will cause `gh-ost` to disassociate the column values; data will not be copied between those columns. ### assume-master-host `gh-ost` infers the identity of the master server by crawling up the replication topology. You may explicitly tell `gh-ost` the identity of the master host via `--assume-master-host=the.master.com`. This is useful in: - _master-master_ topologies (together with [`--allow-master-master`](#allow-master-master)), where `gh-ost` can arbitrarily pick one of the co-masters, and you prefer that it picks a specific one - _tungsten replicator_ topologies (together with [`--tungsten`](#tungsten)), where `gh-ost` is unable to crawl and detect the master ### assume-rbr If you happen to _know_ your servers use RBR (Row Based Replication, i.e. `binlog_format=ROW`), you may specify `--assume-rbr`. This skips a verification step where `gh-ost` would issue a `STOP SLAVE; START SLAVE`. Skipping this step means `gh-ost` would not need the `SUPER` privilege in order to operate. You may want to use this on Amazon RDS. ### attempt-instant-ddl MySQL 8.0 supports "instant DDL" for some operations. If an alter statement can be completed with instant DDL, only a metadata change is required internally. Instant operations include: - Adding a column - Dropping a column - Dropping an index - Extending a varchar column - Adding a virtual generated column It is not reliable to parse the `ALTER` statement to determine if it is instant or not. This is because the table might be in an older row format, or have some other incompatibility that is difficult to identify. `--attempt-instant-ddl` is disabled by default, but the risks of enabling it are relatively minor: `gh-ost` may need to acquire a metadata lock at the start of the operation. This is not a problem for most scenarios, but it could be a problem for users that start the DDL during a period with long running transactions. `gh-ost` will automatically fallback to the normal DDL process if the attempt to use instant DDL is unsuccessful. ### binlogsyncer-max-reconnect-attempts `--binlogsyncer-max-reconnect-attempts=0`, the maximum number of attempts to re-establish a broken inspector connection for sync binlog. `0` or `negative number` means infinite retry, default `0` ### checkpoint `--checkpoint` enables periodic checkpoints of the gh-ost's state so that gh-ost can resume a migration from the checkpoint with `--resume`. Checkpoints are written to a separate table named `_${original_table_name}_ghk`. It is recommended to use with `--gtid` for checkpoints. See also: [`resuming-migrations`](resume.md) ### checkpoint-seconds `--checkpoint-seconds` specifies the seconds between checkpoints. Default is 300. ### conf `--conf=/path/to/my.cnf`: file where credentials are specified. Should be in (or contain) the following format: ``` [client] user=gromit password=123456 ``` ### concurrent-rowcount Defaults to `true`. See [`exact-rowcount`](#exact-rowcount) ### critical-load Comma delimited status-name=threshold, same format as [`--max-load`](#max-load). `--critical-load` defines a threshold that, when met, `gh-ost` panics and bails out. The default behavior is to bail out immediately when meeting this threshold. This may sometimes lead to migrations bailing out on a very short spike, that, while in itself is impacting production and is worth investigating, isn't reason enough to kill a 10-hour migration. ### critical-load-hibernate-seconds When `--critical-load-hibernate-seconds` is non-zero (e.g. `--critical-load-hibernate-seconds=300`), `critical-load` does not panic and bail out; instead, `gh-ost` goes into hibernation for the specified duration. It will not read/write anything from/to any server during this time. Execution then continues upon waking from hibernation. If `critical-load` is met again, `gh-ost` will repeat this cycle, and never panic and bail out. ### critical-load-interval-millis When `--critical-load-interval-millis` is specified (e.g. `--critical-load-interval-millis=2500`), `gh-ost` gives a second chance: when it meets `critical-load` threshold, it doesn't bail out. Instead, it starts a timer (in this example: `2.5` seconds) and re-checks `critical-load` when the timer expires. If `critical-load` is met again, `gh-ost` panics and bails out. If not, execution continues. This is somewhat similar to a Nagios `n`-times test, where `n` in our case is always `2`. ### cut-over Optional. Default is `safe`. See more discussion in [`cut-over`](cut-over.md) ### cut-over-lock-timeout-seconds Default `3`. Max number of seconds to hold locks on tables while attempting to cut-over (retry attempted when lock exceeds timeout). ### discard-foreign-keys **Danger**: this flag will _silently_ discard any foreign keys existing on your table. At this time (10-2016) `gh-ost` does not support foreign keys on migrated tables (it bails out when it notices a FK on the migrated table). However, it is able to support _dropping_ of foreign keys via this flag. If you're trying to get rid of foreign keys in your environment, this is a useful flag. See also: [`skip-foreign-key-checks`](#skip-foreign-key-checks) ### dml-batch-size `gh-ost` reads event from the binary log and applies them onto the _ghost_ table. It does so in batched writes: grouping multiple events to apply in a single transaction. This gives better write throughput as we don't need to sync the transaction log to disk for each event. The `--dml-batch-size` flag controls the size of the batched write. Allowed values are `1 - 1000`, where `1` means no batching (every event from the binary log is applied onto the _ghost_ table on its own transaction). Default value is `10`. Why is this behavior configurable? Different workloads have different characteristics. Some workloads have very large writes, such that aggregating even `50` writes into a transaction makes for a significant transaction size. On other workloads write rate is high such that one just can't allow for a hundred more syncs to disk per second. The default value of `10` is a modest compromise that should probably work very well for most workloads. Your mileage may vary. Noteworthy is that setting `--dml-batch-size` to higher value _does not_ mean `gh-ost` blocks or waits on writes. The batch size is an upper limit on transaction size, not a minimal one. If `gh-ost` doesn't have "enough" events in the pipe, it does not wait on the binary log, it just writes what it already has. This conveniently suggests that if write load is light enough for `gh-ost` to only see a few events in the binary log at a given time, then it is also light enough for `gh-ost` to apply a fraction of the batch size. ### exact-rowcount A `gh-ost` execution need to copy whatever rows you have in your existing table onto the ghost table. This can and often will be, a large number. Exactly what that number is? `gh-ost` initially estimates the number of rows in your table by issuing an `explain select * from your_table`. This will use statistics on your table and return with a rough estimate. How rough? It might go as low as half or as high as double the actual number of rows in your table. This is the same method as used in [`pt-online-schema-change`](https://www.percona.com/doc/percona-toolkit/2.2/pt-online-schema-change.html). `gh-ost` also supports the `--exact-rowcount` flag. When this flag is given, two things happen: - An initial, authoritative `select count(*) from your_table`. This query may take a long time to complete, but is performed before we begin the massive operations. When [`--concurrent-rowcount`](#concurrent-rowcount) is also specified, this runs in parallel to row copy. Note: [`--concurrent-rowcount`](#concurrent-rowcount) now defaults to `true`. - A continuous update to the estimate as we make progress applying events. We heuristically update the number of rows based on the queries we process from the binlogs. While the ongoing estimated number of rows is still heuristic, it's almost exact, such that the reported [ETA](understanding-output.md) or percentage progress is typically accurate to the second throughout a multiple-hour operation. ### execute Without this parameter, migration is a _noop_: testing table creation and validity of migration, but not touching data. ### force-named-cut-over If given, a `cut-over` command must name the migrated table, or else ignored. ### force-named-panic If given, a `panic` command must name the migrated table, or else ignored. ### force-table-names Table name prefix to be used on the temporary tables. ### gcp Add this flag when executing on a 1st generation Google Cloud Platform (GCP). ### gtid Add this flag to enable support for [MySQL replication GTIDs](https://dev.mysql.com/doc/refman/5.7/en/replication-gtids-concepts.html) for replication positioning. This requires `gtid_mode` and `enforce_gtid_consistency` to be set to `ON`. ### heartbeat-interval-millis Default 100. See [`subsecond-lag`](subsecond-lag.md) for details. ### hooks-status-interval Defaults to 60 seconds. Configures how often the `gh-ost-on-status` hook is called, see [`hooks`](hooks.md) for full details on how to use hooks. ### initially-drop-ghost-table `gh-ost` maintains two tables while migrating: the _ghost_ table (which is synced from your original table and finally replaces it) and a changelog table, which is used internally for bookkeeping. By default, it panics and aborts if it sees those tables upon startup. Provide `--initially-drop-ghost-table` and `--initially-drop-old-table` to let `gh-ost` know it's OK to drop them beforehand. We think `gh-ost` should not take chances or make assumptions about the user's tables. Dropping tables can be a dangerous, locking operation. We let the user explicitly approve such operations. ### initially-drop-old-table See [`initially-drop-ghost-table`](#initially-drop-ghost-table) ### initially-drop-socket-file Default False. Should `gh-ost` forcibly delete an existing socket file. Be careful: this might drop the socket file of a running migration! ### max-lag-millis On a replication topology, this is perhaps the most important migration throttling factor: the maximum lag allowed for migration to work. If lag exceeds this value, migration throttles. When using [Connect to replica, migrate on master](cheatsheet.md#a-connect-to-replica-migrate-on-master), this lag is primarily tested on the very replica `gh-ost` operates on. Lag is measured by checking the heartbeat events injected by `gh-ost` itself on the utility changelog table. That is, to measure this replica's lag, `gh-ost` doesn't need to issue `show slave status` nor have any external heartbeat mechanism. When [`--throttle-control-replicas`](#throttle-control-replicas) is provided, throttling also considers lag on specified hosts. Lag measurements on listed hosts is done by querying `gh-ost`'s _changelog_ table, where `gh-ost` injects a heartbeat. When using on master or when `--allow-on-master` is provided, `max-lag-millis` is also considered a threshold for starting the cutover stage of the migration. If the row copy is complete and the heartbeat lag is less than `max-lag-millis` cutover phase of the migration will start. See also: [Sub-second replication lag throttling](subsecond-lag.md) ### max-load List of metrics and threshold values; topping the threshold of any will cause throttler to kick in. See also: [`throttling`](throttle.md#status-thresholds) ### migrate-on-replica Typically `gh-ost` is used to migrate tables on a master. If you wish to only perform the migration in full on a replica, connect `gh-ost` to said replica and pass `--migrate-on-replica`. `gh-ost` will briefly connect to the master but otherwise will make no changes on the master. Migration will be fully executed on the replica, while making sure to maintain a small replication lag. ### panic-on-warnings When this flag is set, `gh-ost` will panic when SQL warnings indicating data loss are encountered when copying data. This flag helps prevent data loss scenarios with migrations touching unique keys, column collation and types, as well as `NOT NULL` constraints, where `MySQL` will silently drop inserted rows that no longer satisfy the updated constraint (also dependent on the configured `sql_mode`). While `panic-on-warnings` is currently disabled by defaults, it will default to `true` in a future version of `gh-ost`. ### postpone-cut-over-flag-file Indicate a file name, such that the final [cut-over](cut-over.md) step does not take place as long as the file exists. When this flag is set, `gh-ost` expects the file to exist on startup, or else tries to create it. `gh-ost` exits with error if the file does not exist and `gh-ost` is unable to create it. With this flag set, the migration will cut-over upon deletion of the file or upon `cut-over` [interactive command](interactive-commands.md). ### replica-server-id Defaults to 99999. If you run multiple migrations then you must provide a different, unique `--replica-server-id` for each `gh-ost` process. Optionally involve the process ID, for example: `--replica-server-id=$((1000000000+$$))`. It's on you to choose a number that does not collide with another `gh-ost` or another running replica. See also: [`concurrent-migrations`](cheatsheet.md#concurrent-migrations) on the cheatsheet. ### resume `--resume` attempts to resume a migration that was previously interrupted from the last checkpoint. The first `gh-ost` invocation must run with `--checkpoint` and have successfully written a checkpoint in order for `--resume` to work. See also: [`resuming-migrations`](resume.md) ### serve-socket-file Defaults to an auto-determined and advertised upon startup file. Defines Unix socket file to serve on. ### skip-foreign-key-checks By default `gh-ost` verifies no foreign keys exist on the migrated table. On servers with large number of tables this check can take a long time. If you're absolutely certain no foreign keys exist (table does not reference other table nor is referenced by other tables) and wish to save the check time, provide with `--skip-foreign-key-checks`. ### skip-metadata-lock-check By default `gh-ost` performs a check before the cut-over to ensure the rename session holds the exclusive metadata lock on the table. In case `performance_schema.metadata_locks` cannot be enabled on your setup, this check can be skipped with `--skip-metadata-lock-check`. :warning: Disabling this check involves the small chance of data loss in case a session accesses the ghost table during cut-over. See https://github.com/github/gh-ost/pull/1536 for details. See also: [`allow-setup-metadata-lock-instruments`](#allow-setup-metadata-lock-instruments) ### skip-strict-mode By default `gh-ost` enforces STRICT_ALL_TABLES sql_mode as a safety measure. In some cases this changes the behaviour of other modes (namely ERROR_FOR_DIVISION_BY_ZERO, NO_ZERO_DATE, and NO_ZERO_IN_DATE) which may lead to errors during migration. Use `--skip-strict-mode` to explicitly tell `gh-ost` not to enforce this. **Danger** This may have some unexpected disastrous side effects. ### skip-renamed-columns See [`approve-renamed-columns`](#approve-renamed-columns) ### ssl By default `gh-ost` does not use ssl/tls connections to the database servers when performing migrations. This flag instructs `gh-ost` to use encrypted connections. If enabled, `gh-ost` will use the system's ca certificate pool for server certificate verification. If a different certificate is needed for server verification, see `--ssl-ca`. If you wish to skip server verification, but still use encrypted connections, use with `--ssl-allow-insecure`. ### ssl-allow-insecure Allows `gh-ost` to connect to the MySQL servers using encrypted connections, but without verifying the validity of the certificate provided by the server during the connection. Requires `--ssl`. ### ssl-ca `--ssl-ca=/path/to/ca-cert.pem`: ca certificate file (in PEM format) to use for server certificate verification. If specified, the default system ca cert pool will not be used for verification, only the ca cert provided here. Requires `--ssl`. ### ssl-cert `--ssl-cert=/path/to/ssl-cert.crt`: SSL public key certificate file (in PEM format). ### ssl-key `--ssl-key=/path/to/ssl-key.key`: SSL private key file (in PEM format). ### storage-engine Default is `innodb`, and `rocksdb` support is currently experimental. InnoDB and RocksDB are both transactional engines, supporting both shared and exclusive row locks. But RocksDB currently lacks a few features support compared to InnoDB: - Gap Locks - Foreign Key - Generated Columns - Spatial - Geometry When `--storage-engine=rocksdb`, `gh-ost` will make some changes necessary (e.g. sets isolation level to `READ_COMMITTED`) to support RocksDB. ### charset The default charset for the database connection is utf8mb4, utf8, latin1. The ability to specify character set and collation is supported, eg: utf8mb4_general_ci,utf8_general_ci,latin1. ### test-on-replica Issue the migration on a replica; do not modify data on master. Useful for validating, testing and benchmarking. See [`testing-on-replica`](testing-on-replica.md) ### test-on-replica-skip-replica-stop Default `False`. When `--test-on-replica` is enabled, do not issue commands stop replication (requires `--test-on-replica`). ### throttle-control-replicas Provide a command delimited list of replicas; `gh-ost` will throttle when any of the given replicas lag beyond [`--max-lag-millis`](#max-lag-millis). The list can be queried and updated dynamically via [interactive commands](interactive-commands.md) ### throttle-http Provide an HTTP endpoint; `gh-ost` will issue `HEAD` requests on given URL and throttle whenever response status code is not `200`. The URL can be queried and updated dynamically via [interactive commands](interactive-commands.md). Empty URL disables the HTTP check. ### throttle-http-interval-millis Defaults to 100. Configures the HTTP throttle check interval in milliseconds. ### throttle-http-timeout-millis Defaults to 1000 (1 second). Configures the HTTP throttler check timeout in milliseconds. ### timestamp-old-table Makes the _old_ table include a timestamp value. The _old_ table is what the original table is renamed to at the end of a successful migration. For example, if the table is `gh_ost_test`, then the _old_ table would normally be `_gh_ost_test_del`. With `--timestamp-old-table` it would be, for example, `_gh_ost_test_20170221103147_del`. ### tungsten See [`tungsten`](cheatsheet.md#tungsten) on the cheatsheet. ================================================ FILE: doc/cut-over.md ================================================ # Cut-over step The cut-over is the final major step of the migration: it's the moment where your original table is pushed aside, and the ghost table (the one we secretly altered and operated on throughout the process) takes its place. MySQL poses some limitations on how the table swap can take place. While it supports an atomic swap, it does not allow a connection to swap tables it holds under lock. The [facebook OSC](https://www.facebook.com/notes/mysql-at-facebook/online-schema-change-for-mysql/430801045932/) tool documents this nicely. Look for **"Cut-over phase"**. The Facebook solution uses a non-atomic swap: the original table is first renamed and pushed aside, then the ghost table is renamed to take its place. In between the two renames there's a brief period of time where your table just does not exist, and queries will fail. `gh-ost` solves this by using an atomic, two-step blocking swap: while one connection holds the lock, another attempts the atomic `RENAME`. The `RENAME` is guaranteed to not be executed prematurely by positioning a sentry table which blocks the `RENAME` operation until `gh-ost` is satisfied all is in order. This solution either: - executes successfully, in which case the tables are swapped atomically and pending connections are blocked for a brief period of time, proceeding to operate on the newly migrated table - or fails, due to timeout or death of some connection, in which case we are naturally returning to pre-cut-over phase, where the original table is still in place and accessible. This releases the pending connections, which are able again to write to the table, and `gh-ost` is then able to make another attempt at the cut-over. Also note: - With `--migrate-on-replica` the cut-over is executed in exactly the same way as on master. - With `--test-on-replica` the replication is first stopped; then the cut-over is executed just as on master, but then reverted (tables rename forth then back again). Internals of the atomic cut-over are discussed in [Issue #82](https://github.com/github/gh-ost/issues/82). At this time the command-line argument `--cut-over` is supported, and defaults to the atomic cut-over algorithm described above. Also supported is `--cut-over=two-step`, which uses the FB non-atomic algorithm. We recommend using the default cut-over that has been battle tested in our production environments. ================================================ FILE: doc/hooks.md ================================================ # Hooks `gh-ost` supports _hooks_: external processes which `gh-ost` executes at particular points of interest. Use cases include: - You wish to be notified by mail when a migration completes/fails - You wish to be notified when `gh-ost` postpones cut-over (at your demand), thus ready to complete (at your leisure) - RDS users who wish to `--test-on-replica`, but who cannot have `gh-ost` issue a `STOP SLAVE`, would use a hook to command RDS to stop replication - Send a status message to your chatops every hour - Perform cleanup on the _ghost_ table (drop/rename/nibble) once migration completes - etc. `gh-ost` defines certain points of interest (event types), and executes hooks at those points. Notes: - You may have more than one hook per event type. - `gh-ost` will invoke relevant hooks _sequentially_ and _synchronously_ - thus, you would generally like the hooks to execute as fast as possible, or otherwise issue tasks in the background - A hook returning with error code will propagate the error in `gh-ost`. Thus, you are able to force `gh-ost` to fail migration on your conditions. - Make sure to only return an error code when you do indeed wish to fail the rest of the migration ### Creating hooks All hooks are expected to reside in a single directory. This directory is indicated by `--hooks-path`. When not provided, no hooks are executed. `gh-ost` will dynamically search for hooks in said directory. You may add and remove hooks to/from this directory as `gh-ost` makes progress (though likely you don't want to). Hook files are expected to be executable processes. In an effort to simplify code and to standardize usage, `gh-ost` expects hooks in explicit naming conventions. As an example, the `onStartup` hook expects processes named `gh-ost-on-startup*`. It will match and accept files named: - `gh-ost-on-startup` - `gh-ost-on-startup--send-notification-mail` - `gh-ost-on-startup12345` - etc. The full list of supported hooks is best found in code: [hooks.go](https://github.com/github/gh-ost/blob/master/go/logic/hooks.go). Documentation will always be a bit behind. At this time, though, the following are recognized: - `gh-ost-on-startup` - `gh-ost-on-validated` - `gh-ost-on-rowcount-complete` - `gh-ost-on-before-row-copy` - `gh-ost-on-status` - `gh-ost-on-interactive-command` - `gh-ost-on-row-copy-complete` - `gh-ost-on-stop-replication` - `gh-ost-on-start-replication` - `gh-ost-on-begin-postponed` - `gh-ost-on-before-cut-over` - `gh-ost-on-success` - `gh-ost-on-failure` - `gh-ost-on-batch-copy-retry` ### Context `gh-ost` will set environment variables per hook invocation. Hooks are then able to read those variables, indicating schema name, table name, `alter` statement, migrated host name etc. Some variables are available on all hooks, and some are available on relevant hooks. The following variables are available on all hooks: - `GH_OST_DATABASE_NAME` - `GH_OST_TABLE_NAME` - `GH_OST_GHOST_TABLE_NAME` - `GH_OST_OLD_TABLE_NAME` - the name the original table will be renamed to at the end of operation - `GH_OST_DDL` - `GH_OST_ELAPSED_SECONDS` - total runtime - `GH_OST_ELAPSED_COPY_SECONDS` - row-copy time (excluding startup, row-count and postpone time) - `GH_OST_ESTIMATED_ROWS` - estimated total rows in table - `GH_OST_COPIED_ROWS` - number of rows copied by `gh-ost` - `GH_OST_INSPECTED_LAG` - lag in seconds (floating point) of inspected server - `GH_OST_HEARTBEAT_LAG` - lag in seconds (floating point) of heartbeat - `GH_OST_PROGRESS` - progress pct ([0..100], floating point) of migration - `GH_OST_ETA_SECONDS` - estimated duration until migration finishes in seconds - `GH_OST_MIGRATED_HOST` - `GH_OST_INSPECTED_HOST` - `GH_OST_EXECUTING_HOST` - `GH_OST_HOOKS_HINT` - copy of `--hooks-hint` value - `GH_OST_HOOKS_HINT_OWNER` - copy of `--hooks-hint-owner` value - `GH_OST_HOOKS_HINT_TOKEN` - copy of `--hooks-hint-token` value - `GH_OST_DRY_RUN` - whether or not the `gh-ost` run is a dry run - `GH_OST_REVERT` - whether or not `gh-ost` is running in revert mode The following variable are available on particular hooks: - `GH_OST_COMMAND` is only available in `gh-ost-on-interactive-command` - `GH_OST_STATUS` is only available in `gh-ost-on-status` - `GH_OST_LAST_BATCH_COPY_ERROR` is only available in `gh-ost-on-batch-copy-retry` ### Examples See [sample hooks](https://github.com/github/gh-ost/tree/master/resources/hooks-sample), as `bash` implementation samples. ================================================ FILE: doc/interactive-commands.md ================================================ # Interactive commands `gh-ost` is designed to be operations friendly. To that effect, it allows the user to control its behavior even while it is running. ### Interactive interfaces `gh-ost` listens on: - Unix socket file: either provided via `--serve-socket-file` or determined by `gh-ost`, this interface is always up. When self-determined, `gh-ost` will advertise the identify of socket file upon start up and throughout the migration. - TCP: if `--serve-tcp-port` is provided Both interfaces may serve at the same time. Both respond to simple text command, which makes it easy to interact via shell. ### Known commands - `help`: shows a brief list of available commands - `status`: returns a detailed status summary of migration progress and configuration - `sup`: returns a brief status summary of migration progress - `cpu-profile`: returns a base64-encoded [`runtime/pprof`](https://pkg.go.dev/runtime/pprof) CPU profile using a duration, default: `30s`. Comma-separated options `gzip` and/or `block` (blocked profile) may follow the profile duration - `coordinates`: returns recent (though not exactly up to date) binary log coordinates of the inspected server - `applier`: returns the hostname of the applier - `inspector`: returns the hostname of the inspector - `chunk-size=`: modify the `chunk-size`; applies on next running copy-iteration - `dml-batch-size=`: modify the `dml-batch-size`; applies on next applying of binary log events - `max-lag-millis=`: modify the maximum replication lag threshold (milliseconds, minimum value is `100`, i.e. `0.1` second) - `max-load=`: modify the `max-load` config; applies on next running copy-iteration - The `max-load` format must be: `some_status=[,some_status=...]`' - For example: `Threads_running=50,threads_connected=1000`, and you would then write/echo `max-load=Threads_running=50,threads_connected=1000` to the socket. - `critical-load=`: modify the `critical-load` config (exceeding these thresholds aborts the operation) - The `critical-load` format must be: `some_status=[,some_status=...]`' - For example: `Threads_running=1000,threads_connected=5000`, and you would then write/echo `critical-load=Threads_running=1000,threads_connected=5000` to the socket. - `nice-ratio=`: change _nice_ ratio: 0 for aggressive (not nice, not sleeping), positive integer `n`: - For any `1ms` spent copying rows, spend `n*1ms` units of time sleeping. - Examples: assume a single rows chunk copy takes `100ms` to complete. - `nice-ratio=0.5` will cause `gh-ost` to sleep for `50ms` immediately following. - `nice-ratio=1` will cause `gh-ost` to sleep for `100ms`, effectively doubling runtime - value of `2` will effectively triple the runtime; etc. - `throttle-http`: change throttle HTTP endpoint - `throttle-query`: change throttle query - `throttle-control-replicas='replica1,replica2'`: change list of throttle-control replicas, these are replicas `gh-ost` will check. This takes a comma separated list of replica's to check and replaces the previous list. - `throttle`: force migration suspend - `no-throttle`: cancel forced suspension (though other throttling reasons may still apply) - `postpone-cut-over-flag-file=`: Postpone the [cut-over](cut-over.md) phase, writing a cut over flag file to the given path - `unpostpone`: at a time where `gh-ost` is postponing the [cut-over](cut-over.md) phase, instruct `gh-ost` to stop postponing and proceed immediately to cut-over. - `panic`: immediately panic and abort operation ### Querying for data For commands that accept an argument as value, pass `?` (question mark) to _get_ current value rather than _set_ a new one. ### Examples While migration is running: ```shell $ echo status | nc -U /tmp/gh-ost.test.sample_data_0.sock # Migrating `test`.`sample_data_0`; Ghost table is `test`.`_sample_data_0_gst` # Migration started at Tue Jun 07 11:45:16 +0200 2016 # chunk-size: 200; max lag: 1500ms; dml-batch-size: 10; max-load: map[Threads_connected:20] # Throttle additional flag file: /tmp/gh-ost.throttle # Serving on unix socket: /tmp/gh-ost.test.sample_data_0.sock # Serving on TCP port: 10001 Copy: 0/2915 0.0%; Applied: 0; Backlog: 0/100; Time: 41s(total), 40s(copy); streamer: mysql-bin.000550:49942; Lag: 0.01s, HeartbeatLag: 0.01s, State: throttled, flag-file; ETA: N/A ``` ```shell $ echo "chunk-size=250" | nc -U /tmp/gh-ost.test.sample_data_0.sock # Migrating `test`.`sample_data_0`; Ghost table is `test`.`_sample_data_0_gst` # Migration started at Tue Jun 07 11:56:03 +0200 2016 # chunk-size: 250; max lag: 1500ms; dml-batch-size: 10; max-load: map[Threads_connected:20] # Throttle additional flag file: /tmp/gh-ost.throttle # Serving on unix socket: /tmp/gh-ost.test.sample_data_0.sock # Serving on TCP port: 10001 ``` ```shell $ echo "chunk-size=?" | nc -U /tmp/gh-ost.test.sample_data_0.sock 250 ``` ```shell $ echo throttle | nc -U /tmp/gh-ost.test.sample_data_0.sock $ echo status | nc -U /tmp/gh-ost.test.sample_data_0.sock # Migrating `test`.`sample_data_0`; Ghost table is `test`.`_sample_data_0_gst` # Migration started at Tue Jun 07 11:56:03 +0200 2016 # chunk-size: 250; max lag: 1500ms; max-load: map[Threads_connected:20] # Throttle additional flag file: /tmp/gh-ost.throttle # Serving on unix socket: /tmp/gh-ost.test.sample_data_0.sock # Serving on TCP port: 10001 Copy: 0/2915 0.0%; Applied: 0; Backlog: 0/100; Time: 59s(total), 59s(copy); streamer: mysql-bin.000551:68067; Lag: 0.01s, HeartbeatLag: 0.01s, State: throttled, commanded by user; ETA: N/A ``` ================================================ FILE: doc/local-tests.md ================================================ # Local tests `gh-ost` is continuously tested in production via `--test-on-replica alter='engine=innodb'`. These tests check the GitHub workload and usage, but not necessarily the general case. Local tests are an additional layer of tests used for continuous integration tests and local development. Local tests test explicit use cases, such as column renames, mix of time zones, special types and alters. Traits of a single test: - Composed of a single table. - A single alter. - By default the alter is `engine=innodb`, but this can be overridden per-test - Scheduled DML operations, executed via `event_scheduler`. - `gh-ost` is set to execute and throttle for `5` seconds, at which time all tested DMLs are expected to operate. - The test requires a replication topology and utilizes `--test-on-replica` - The test checksums the two tables (original and _ghost_) and expects identical checksum - By default the test selects all (`*`) columns, but this can be overridden per-test Tests are found under [localtests](https://github.com/github/gh-ost/tree/master/localtests). A single test is a subdirectory and tests are iterated alphabetically. New data-integrity, synchronization issues or otherwise concerns are expected to be tested by new test cases. ## Run with docker compose Local tests can be run locally with docker compose using the helper script [script/docker-gh-ost-replica-tests](https://github.com/github/gh-ost/tree/master/script/docker-gh-ost-replica-tests). Example usage: ```shell # create primary-replica containers with specified mysql image TEST_MYSQL_IMAGE="mysql-server:8.0.16" ./script/docker-gh-ost-replica-tests up # run all tests ./script/docker-gh-ost-replica-tests run # cleanup containers ./script/docker-gh-ost-replica-tests down ``` Pass the `-t` flag to run the tests with a toxiproxy between gh-ost and the MySQL replica. This simulates network conditions where MySQL connections are closed unexpectedly. ```shell # run tests with toxiproxy ./script/docker-gh-ost-replica-tests up -t ./script/docker-gh-ost-replica-tests run -t ``` ================================================ FILE: doc/migrating-with-sbr.md ================================================ # Migrating with Statement Based Replication Even though `gh-ost` relies on Row Based Replication (RBR), it does not mean you can't keep your Statement Based Replication (SBR). `gh-ost` is happy to, and actually prefers and suggests to, connect to a replica. On this replica, it is happy to: - issue the heavyweight `INFORMATION_SCHEMA` queries that make a table structure analysis - issue a `select count(*) from mydb.mytable`, should `--exact-rowcount` be provided - connect itself as a fake replica to get the binary log stream All of the above can be executed on the master, but we're more comfortable that they execute on a replica. Please note the third item: `gh-ost` connects as a fake replica and pulls the binary logs. This is how `gh-ost` finds the table's changelog: it looks up entries in the binary log. The magic is that your master can still produce SBR, but if you have a replica with `log-slave-updates`, you can also configure it to have `binlog_format='ROW'`. Such a replica accepts SBR statements from its master, and produces RBR statements onto its binary logs. `gh-ost` is happy to modify the `binlog_format` on the replica for you: - If you supply `--switch-to-rbr`, `gh-ost` will convert the binlog format for you, and restart replication to make sure this takes effect. - If your replica is an intermediate master, i.e. further serves as a master to other replicas, `gh-ost` will not convert the `binlog_format`. - At any case, `gh-ost` **will not** convert back to `STATEMENT` (SBR). This is because you may be running multiple migrations concurrently. Being able to run concurrent migrations is one of the design goals of this tool. It's your own responsibility to switch back to SBR once all pending migrations are complete. ### Summary - If you're already using RBR, all is well for you - If not, convert one of your replicas to `binlog_format='ROW'`, or let `gh-ost` do this for you. ================================================ FILE: doc/perks.md ================================================ # Perks Listed below are some operation perks that make the DBA happy. ### Dynamic reconfiguration You started with a `chunk-size=5000` but you find out it's too much. You want to reduce it. There is no need to kill and restart the migration with a new configuration. You may change the `chunk-size` dynamically. `gh-ost` listens on a unix socket file, and optionally via `TCP` as well. You may, for example: ```shell $ echo "chunk-size=250" | nc -U /tmp/gh-ost.test.sample_data_0.sock ``` Likewise, you can change the `max-load` configuration: ```shell $ echo "max-load=Threads_running=50,threads_connected=1000" | nc -U /tmp/gh-ost.test.sample_data_0.sock ``` The `max-load` format must be: `some_status=[,some_status=...]`. In case of parsing error the command is ignored. Read more about [interactive commands](interactive-commands.md) ### What's the status? You do not have to have access to the `screen` where the migration is issued. You have two ways to get current status: 1. Use [interactive commands](interactive-commands.md). Via unix socket file or via `TCP` you can get current status: ```shell $ echo status | nc -U /tmp/gh-ost.test.sample_data_0.sock # Migrating `test`.`sample_data_0`; Ghost table is `test`.`_sample_data_0_gst` # Migration started at Tue Jun 07 11:45:16 +0200 2016 # chunk-size: 200; max lag: 1500ms; max-load: map[Threads_connected:20] # Throttle additional flag file: /tmp/gh-ost.throttle # Serving on unix socket: /tmp/gh-ost.test.sample_data_0.sock # Serving on TCP port: 10001 Copy: 0/2915 0.0%; Applied: 0; Backlog: 0/100; Time: 41s(total), 40s(copy); streamer: mysql-bin.000550:49942; Lag: 0.01s, HeartbeatLag: 0.01s, State: throttled, flag-file; ETA: N/A ``` 1. `gh-ost` creates and uses a changelog table for internal bookkeeping. This table has the `_osc` suffix (the tool creates and announces this table upon startup) If you like, you can SQL your status: ``` > select * from _sample_data_0_osc order by id desc limit 1 \G *************************** 1. row *************************** id: 325 last_update: 2016-06-08 15:52:13 hint: copy iteration 0 at 1465393933 value: throttled, flag-file ``` ### Postpone the cut-over phase You begin a migration, and the ETA is for it to complete at 04:00am. Not a good time for you, because you happen to want to have eyes on things as the migration completes (ideally, you shouldn't need to, but life is hard). Today, DBAs are coordinating the migration start time such that it completes in a convenient hour. `gh-ost` offers an alternative: postpone the final cut-over phase till you're ready. Execute `gh-ost` with `--postpone-cut-over-flag-file=/path/to/flag.file`. As long as this file exists, `gh-ost` will not take the final cut-over step. It will complete the row copy, and continue to synchronize the tables by continuously applying changes made on the original table onto the ghost table. It can do so on and on and on. When you're finally ready, remove the file and cut-over will take place. ### Sub-second lag throttling With sub-second replication lag measurements, `gh-ost` is able to keep a fleet of replicas well below `1sec` lag throughout the migration. We encourage you to issue sub-second heartbeats. Read more on [sub-second replication lag throttling](subsecond-lag.md) ================================================ FILE: doc/questions.md ================================================ # How? ### How does the cut-over work? Is it really atomic? The cut-over phase, where the original table is swapped away, and the _ghost_ table takes its place, is an atomic, blocking, controlled operation. - Atomic: the tables are swapped together. There is no gap where your table does not exist. - Blocking: all app queries involving the migrated (original) table are either operate on the original table, or are blocked, or proceed to operate on the _new_ table (formerly the _ghost_ table, now swapped in). - Controlled: the cut-over times out at pre-defined threshold, and is atomically aborted, then re-attempted. Cut-over only takes place when no lags are present, and otherwise no throttling reason is found. Cut-over step itself gets high priority and is never throttled. Read more on [cut-over](cut-over.md) and on the [cut-over design Issue](https://github.com/github/gh-ost/issues/82) # Is it possible to? ### Is it possible to add a UNIQUE KEY? Adding a `UNIQUE KEY` is possible, in the condition that no violation will occur. That is, you must make sure there aren't any violating rows on your table before, and during the migration. At this time there is no equivalent to `ALTER IGNORE`, where duplicates are implicitly and silently thrown away. The MySQL `5.7` docs say: > As of MySQL 5.7.4, the IGNORE clause for ALTER TABLE is removed and its use produces an error. It is therefore unlikely that `gh-ost` will support this behavior. ### Run concurrent migrations? Yes. TL;DR if running all on same replica/master, make sure to provide `--replica-server-id`. [Read more](cheatsheet.md#concurrent-migrations) # Why ### Why Is the "Connect to Replica" mode preferred? To avoid placing extra load on the master. `gh-ost` connects as a replication client. Each additional replica adds some load to the master. To monitor replication lag from a replica. This makes the replication lag throttle, `--max-lag-millis`, more representative of the lag experienced by other replicas following the master (perhaps N levels deep in a tree of replicas). ================================================ FILE: doc/rds.md ================================================ `gh-ost` has been updated to work with Amazon RDS however due to GitHub not using AWS for databases, this documentation is community driven so if you find a bug please [open an issue][new_issue]! # Amazon RDS ## Limitations - No `SUPER` privileges. - `gh-ost` runs should be setup use [`--assume-rbr`][assume_rbr_docs] and use `binlog_format=ROW`. - Aurora does not allow editing of the `read_only` parameter. While it is defined as `{TrueIfReplica}`, the parameter is non-modifiable field. ## Aurora #### Replication In Aurora replication, you have separate reader and writer endpoints however because the cluster shares the underlying storage layer, `gh-ost` will detect it is running on the master. This becomes an issue when you wish to use [migrate/test on replica][migrate_test_on_replica_docs] because you won't be able to use a single cluster in the same way you would with MySQL RDS. To work around this, you can follow along the [AWS replication between clusters documentation][aws_replication_docs] for Aurora with one small caveat. For the "Create a Snapshot of Your Replication Master" step, the binlog position is not available in the AWS console. You will need to issue the SQL query `SHOW SLAVE STATUS` or `aws rds describe-events` API call to get the correct position. #### Percona Toolkit If you use `pt-table-checksum` as a part of your data integrity checks, you might want to check out [this patch][percona_toolkit_patch] which will enable you to run `pt-table-checksum` with the `--no-binlog-format-check` flag and prevent errors like the following: ``` 03-24T12:51:06 Failed to /*!50108 SET @@binlog_format := 'STATEMENT'*/: DBD::mysql::db do failed: Access denied; you need (at least one of) the SUPER privilege(s) for this operation [for Statement "/*!50108 SET @@binlog_format := 'STATEMENT'*/"] at pt-table-checksum line 9292. This tool requires binlog_format=STATEMENT, but the current binlog_format is set to ROW and an error occurred while attempting to change it. If running MySQL 5.1.29 or newer, setting binlog_format requires the SUPER privilege. You will need to manually set binlog_format to 'STATEMENT' before running this tool. ``` #### Binlog filtering In Aurora, the [binlog filtering feature][aws_replication_docs_bin_log_filtering] is enabled by default. This becomes an issue when gh-ost tries to do the cut-over, because gh-ost waits for an entry in the binlog to proceed but this entry will never end up in the binlog because it gets filtered out by the binlog filtering feature. You need to turn this feature off during the migration process. Set the `aurora_enable_repl_bin_log_filtering` parameter to 0 in the Parameter Group for your cluster. When the migration is done, set it back to 1 (default). #### Preflight checklist Before trying to run any `gh-ost` migrations you will want to confirm the following: - [ ] You have a secondary cluster available that will act as a replica. Rule of thumb here has been a 1 instance per cluster to mimic MySQL-style replication as opposed to Aurora style. - [ ] The database instance parameters and database cluster parameters are consistent between your master and replicas - [ ] Executing `SHOW SLAVE STATUS\G` on your replica cluster displays the correct master host, binlog position, etc. - [ ] Database backup retention is greater than 1 day to enable binlogs - [ ] You have setup [`hooks`][ghost_hooks] to issue RDS procedures for stopping and starting replication. (see [github/gh-ost#163][ghost_rds_issue_tracking] for examples) - [ ] The parameter `aurora_enable_repl_bin_log_filtering` is set to 0 [new_issue]: https://github.com/github/gh-ost/issues/new [assume_rbr_docs]: https://github.com/github/gh-ost/blob/master/doc/command-line-flags.md#assume-rbr [migrate_test_on_replica_docs]: https://github.com/github/gh-ost/blob/master/doc/cheatsheet.md#c-migratetest-on-replica [aws_replication_docs]: http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Aurora.Overview.Replication.MySQLReplication.html [percona_toolkit_patch]: https://github.com/jacobbednarz/percona-toolkit/commit/0271ba6a094da446a5e5bb8d99b5c26f1777f2b9 [ghost_hooks]: https://github.com/github/gh-ost/blob/master/doc/hooks.md [ghost_rds_issue_tracking]: https://github.com/github/gh-ost/issues/163 [aws_replication_docs_bin_log_filtering]: https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Replication.html#AuroraMySQL.Replication.Performance ================================================ FILE: doc/requirements-and-limitations.md ================================================ # Requirements and limitations ### Requirements - `gh-ost` currently requires MySQL versions 5.7 and greater. - You will need to have one server serving Row Based Replication (RBR) format binary logs. Right now `FULL` row image is supported. `MINIMAL` to be supported in the near future. `gh-ost` prefers to work with replicas. You may [still have your master configured with Statement Based Replication](migrating-with-sbr.md) (SBR). - If you are using a replica, the table must have an identical schema between the master and replica. - `gh-ost` requires an account with these privileges: - `ALTER, CREATE, DELETE, DROP, INDEX, INSERT, LOCK TABLES, SELECT, TRIGGER, UPDATE` on the database (schema) where your migrated table is, or of course on `*.*` - either: - `SUPER, REPLICATION SLAVE` on `*.*`, or: - `REPLICATION CLIENT, REPLICATION SLAVE` on `*.*` The `SUPER` privilege is required for `STOP SLAVE`, `START SLAVE` operations. These are used on: - Switching your `binlog_format` to `ROW`, in the case where it is _not_ `ROW` and you explicitly specified `--switch-to-rbr` - If your replication is already in RBR (`binlog_format=ROW`) you can specify `--assume-rbr` to avoid the `STOP SLAVE/START SLAVE` operations, hence no need for `SUPER`. - `gh-ost` uses the `REPEATABLE_READ` transaction isolation level for all MySQL connections, regardless of the server default. - Running `--test-on-replica`: before the cut-over phase, `gh-ost` stops replication so that you can compare the two tables and satisfy that the migration is sound. ### Limitations - Foreign key constraints are not supported. They may be supported in the future, to some extent. - Triggers are not supported. They may be supported in the future. - MySQL 5.7 `JSON` columns are supported but not as part of `PRIMARY KEY` - The two _before_ & _after_ tables must share a `PRIMARY KEY` or other `UNIQUE KEY`. This key will be used by `gh-ost` to iterate through the table rows when copying. [Read more](shared-key.md) - The migration key must not include columns with NULL values. This means either: 1. The columns are `NOT NULL`, or 2. The columns are nullable but don't contain any NULL values. - by default, `gh-ost` will not run if the only `UNIQUE KEY` includes nullable columns. - You may override this via `--allow-nullable-unique-key` but make sure there are no actual `NULL` values in those columns. Existing NULL values can't guarantee data integrity on the migrated table. - It is not allowed to migrate a table where another table exists with same name and different upper/lower case. - For example, you may not migrate `MyTable` if another table called `MYtable` exists in the same schema. - Amazon RDS works, but has its own [limitations](rds.md). - Google Cloud SQL works, `--gcp` flag required. - Aliyun RDS works, `--aliyun-rds` flag required. - Azure Database for MySQL works, `--azure` flag required, and have detailed document about it. (azure.md) - Multisource is not supported when migrating via replica. It _should_ work (but never tested) when connecting directly to master (`--allow-on-master`) - Master-master setup is only supported in active-passive setup. Active-active (where table is being written to on both masters concurrently) is unsupported. It may be supported in the future. - If you have an `enum` field as part of your migration key (typically the `PRIMARY KEY`), migration performance will be degraded and potentially bad. [Read more](https://github.com/github/gh-ost/pull/277#issuecomment-254811520) - Migrating a `FEDERATED` table is unsupported and is irrelevant to the problem `gh-ost` tackles. - [Encrypted binary logs](https://www.percona.com/blog/2018/03/08/binlog-encryption-percona-server-mysql/) are not supported. - `ALTER TABLE ... RENAME TO some_other_name` is not supported (and you shouldn't use `gh-ost` for such a trivial operation). ================================================ FILE: doc/resume.md ================================================ # Resuming Migrations `gh-ost` can attempt to resume an interrupted migration from a checkpoint if the following conditions are met: - The first `gh-ost` process was invoked with `--checkpoint` - The first `gh-ost` process had at least one successful checkpoint - The binlogs from the last checkpoint's binlog coordinates still exist on the replica gh-ost is inspecting (specified by `--host`) - The checkpoint table (name ends with `_ghk`) still exists To resume, invoke `gh-ost` again with the same arguments with the `--resume` flag. > [!WARNING] > It is recommended use `--checkpoint` with `--gtid` enabled so that checkpoint binlog coordinates store GTID sets rather than file positions. In that case, `gh-ost` can resume using a different replica than it originally attached to. ## Example The migration starts with a `gh-ost` invocation such as: ```shell gh-ost \ --chunk-size=100 \ --host=replica1.company.com \ --database="mydb" \ --table="mytable" \ --alter="add column mycol varchar(20)" --gtid \ --checkpoint \ --checkpoint-seconds=60 \ --execute ``` In this example `gh-ost` writes a checkpoint to a table `_mytable_ghk` every 60 seconds. After `gh-ost` is interrupted/killed, the migration can be resumed with: ```shell # resume migration gh-ost \ --chunk-size=100 --host=replica1.company.com \ --database="mydb" \ --table="mytable" \ --alter="add column mycol varchar(20)" --gtid \ --resume \ --execute ``` `gh-ost` then reconnects at the binlog coordinates of the last checkpoint and resumes copying rows at the chunk specified by the checkpoint. The data integrity of the ghost table is preserved because `gh-ost` applies row DMLs and copies row in an idempotent way. ================================================ FILE: doc/revert.md ================================================ # Reverting Migrations `gh-ost` can attempt to revert a previously completed migration if the follow conditions are met: - The first `gh-ost` process was invoked with `--checkpoint` - The checkpoint table (name ends with `_ghk`) still exists - The binlogs from the time of the migration's cut-over still exist on the replica gh-ost is inspecting (specified by `--host`) To revert, find the name of the "old" table from the original migration e.g. `_mytable_del`. Then invoke `gh-ost` with the same arguments and the flags `--revert` and `--old-table="_mytable_del"`. gh-ost will read the binlog coordinates of the original cut-over from the checkpoint table and bring the old table up to date. Then it performs another cut-over to complete the reversion. Note that the checkpoint table (name ends with _ghk) will not be automatically dropped unless `--ok-to-drop-table` is provided. > [!WARNING] > It is recommended use `--checkpoint` with `--gtid` enabled so that checkpoint binlog coordinates store GTID sets rather than file positions. In that case, `gh-ost` can revert using a different replica than it originally attached to. ### ❗ Note ❗ Reverting is roughly equivalent to applying the "reverse" migration. _Before attempting to revert you should determine if the reverse migration is possible and does not involve any unacceptable data loss._ For example: if the original migration drops a `NOT NULL` column that has no `DEFAULT` then the reverse migration adds the column. In this case, the reverse migration is impossible if rows were added after the original cut-over and the revert will fail. Another example: if the original migration modifies a `VARCHAR(32)` column to `VARCHAR(64)`, the reverse migration truncates the `VARCHAR(64)` column to `VARCHAR(32)`. If values were inserted with length > 32 after the cut-over then the revert will fail. ## Example The migration starts with a `gh-ost` invocation such as: ```shell gh-ost \ --chunk-size=100 \ --host=replica1.company.com \ --database="mydb" \ --table="mytable" \ --alter="drop key idx1" --gtid \ --checkpoint \ --checkpoint-seconds=60 \ --execute ``` In this example `gh-ost` writes a cut-over checkpoint to `_mytable_ghk` after the cut-over is successful. The original table is renamed to `_mytable_del`. Suppose that dropping the index causes problems, the migration can be revert with: ```shell # revert migration gh-ost \ --chunk-size=100 \ --host=replica1.company.com \ --database="mydb" \ --table="mytable" \ --old-table="_mytable_del" --gtid \ --checkpoint \ --checkpoint-seconds=60 \ --revert \ --execute ``` gh-ost then reconnects at the binlog coordinates stored in the cut-over checkpoint and applies DMLs until the old table is up-to-date. Note that the "reverse" migration is `ADD KEY idx(...)` so there is no potential data loss to consider in this case. ================================================ FILE: doc/shared-key.md ================================================ # Shared key gh-ost requires for every migration that both the _before_ and _after_ versions of the table share the same unique not-null key columns. This page illustrates this rule. ### Introduction Consider a simple migration, with a normal table, ```sql CREATE TABLE tbl ( id bigint unsigned not null auto_increment, data varchar(255), more_data int, PRIMARY KEY(id) ) ``` and the migration `add column ts timestamp`. The _after_ table version would be: ```sql CREATE TABLE tbl ( id bigint unsigned not null auto_increment, data varchar(255), more_data int, ts timestamp, PRIMARY KEY(id) ) ``` (This is also the definition of the _ghost_ table, except that that table would be called `_tbl_gho`). In this migration, the _before_ and _after_ versions contain the same unique not-null key (the PRIMARY KEY). To run this migration, `gh-ost` would iterate through the `tbl` table using the primary key, copy rows from `tbl` to the _ghost_ table `_tbl_gho` in primary key order, while also applying the binlog event writes from `tbl` onto `_tbl_gho`. The applying of the binlog events is what requires the shared unique key. For example, an `UPDATE` statement to `tbl` translates to a `REPLACE` statement which `gh-ost` applies to `_tbl_gho`. A `REPLACE` statement expects to insert or replace an existing row based on its row's values and the table's unique key constraints. In particular, if inserting that row would result in a unique key violation (e.g., a row with that primary key already exists), it would _replace_ that existing row with the new values. So `gh-ost` correlates `tbl` and `_tbl_gho` rows one to one using a unique key. In the above example that would be the `PRIMARY KEY`. ### Interpreting the rule The _before_ and _after_ versions of the table share the same unique not-null key, but: - the key doesn't have to be the PRIMARY KEY - the key can have a different name between the _before_ and _after_ versions (e.g., renamed via DROP INDEX and ADD INDEX) so long as it contains the exact same column(s) At the start of the migration, `gh-ost` inspects both the original and _ghost_ table it created, and attempts to find at least one such unique key (or rather, a set of columns) that is shared between the two. Typically this would just be the `PRIMARY KEY`, but some tables don't have primary keys, or sometimes it is the primary key that is being modified by the migration. In these cases `gh-ost` will look for other options. `gh-ost` expects unique keys where no `NULL` values are found, i.e. all columns contained in the unique key are defined as `NOT NULL`. This is implicitly true for primary keys. If no such key can be found, `gh-ost` bails out. If the table contains a unique key with nullable columns, but you know your columns contain no `NULL` values, use the `--allow-nullable-unique-key` option. The migration will run well as long as no `NULL` values are found in the unique key's columns. **Any actual `NULL`s may corrupt the migration.** ### Examples: Allowed and Not Allowed ```sql create table some_table ( id int not null auto_increment, ts timestamp, name varchar(128) not null, owner_id int not null, loc_id int not null, primary key(id), unique key name_uidx(name) ) ``` Note the two unique, not-null indexes: the primary key and `name_uidx`. Allowed migrations: - `add column i int` - `add key owner_idx (owner_id)` - `add unique key owner_name_idx (owner_id, name)` - **be careful not to write conflicting rows while this migration runs** - `drop key name_uidx` - `primary key` is shared between the tables - `drop primary key, add primary key(owner_id, loc_id)` - `name_uidx` is shared between the tables - `change id bigint unsigned not null auto_increment` - the `primary key` changes datatype but not value, and can be used - `drop primary key, drop key name_uidx, add primary key(name), add unique key id_uidx(id)` - swapping the two keys. Either `id` or `name` could be used Not allowed: - `drop primary key, drop key name_uidx` - the _ghost_ table has no unique key - `drop primary key, drop key name_uidx, create primary key(name, owner_id)` - no shared columns to the unique keys on both tables. Even though `name` exists in the _ghost_ table's `primary key`, it is only part of the key and in itself does not guarantee uniqueness in the _ghost_ table. ### Workarounds If you need to change your primary key or only not-null unique index to use different columns, you will want to do it as two separate migrations: 1. `ADD UNIQUE KEY temp_pk (temp_pk_column,...)` 1. `DROP PRIMARY KEY, DROP KEY temp_pk, ADD PRIMARY KEY (temp_pk_column,...)` ================================================ FILE: doc/subsecond-lag.md ================================================ # Sub-second replication lag throttling `gh-ost` is able to utilize sub-second replication lag measurements. At GitHub, small replication lag is crucial, and we like to keep it below `1s` at all times. `gh-ost` will do sub-second throttling when `--max-lag-millis` is smaller than `1000`, i.e. smaller than `1sec`. Replication lag is measured on: - The "inspected" server (the server `gh-ost` connects to; replica is desired but not mandatory) - The `throttle-control-replicas` list In both cases, `gh-ost` uses an internal heartbeat mechanism. It injects heartbeat events onto the utility changelog table, then reads those entries on replicas, and compares times. This measurement is on by default and by definition supports sub-second resolution. You can explicitly define how frequently will `gh-ost` inject heartbeat events, via `heartbeat-interval-millis`. You should set `heartbeat-interval-millis <= max-lag-millis`. It still works if not, but loses granularity and effect. In earlier versions, the `--throttle-control-replicas` list was subjected to `1` second resolution or to 3rd party heartbeat injections such as `pt-heartbeat`. This is no longer the case. The argument `--replication-lag-query` has been deprecated and is no longer needed. Our production migrations use sub-second lag throttling and are able to keep our entire fleet of replicas well below `1sec` lag. We use `--heartbeat-interval-millis=100` on our production migrations with a `--max-lag-millis` value of between `300` and `500`. ================================================ FILE: doc/testing-on-replica.md ================================================ # Testing on replica `gh-ost`'s design allows for trusted and reliable tests of the migration without compromising production data integrity. Test on replica if you: - Are unsure of `gh-ost`, have not gained confidence into its workings - Just want to experiment with a real migration without affecting production (maybe measure migration time?) - Wish to observe data change impact ## What testing on replica means TL;DR `gh-ost` will make all changes on a replica and leave both original and ghost tables for you to compare. ## Issuing a test drive Apply `--test-on-replica --host=`. - `gh-ost` would connect to the indicated server - Will verify this is indeed a replica and not a master - Will perform _everything_ on this replica. Other then checking who the master is, it will otherwise not touch it. - All `INFORMATION_SCHEMA` and `SELECT` queries run on the replica - Ghost table is created on the replica - Rows are copied onto the ghost table on the replica - Binlog events are read from the replica and applied to ghost table on the replica - So... everything `gh-ost` will sync the ghost table with the original table. - When it is satisfied, it will issue a `STOP SLAVE`, stopping replication - Will finalize last few statements - Will swap tables via normal [cut-over](cut-over.md), and immediately revert the swap. - Will terminate. No table is dropped. You are now left with the original table **and** the ghost table. When using a trivial `alter` statement, such as `engine-innodb`, both tables _should_ be identical. You now have the time to verify the tool works correctly. You may checksum the entire table data if you like. - e.g. `mysql -e 'select * from mydb.mytable order by id' | md5sum` `mysql -e 'select * from mydb._mytable_gst order by id' | md5sum` - or of course only select the shared columns before/after the migration - We use the trivial `engine=innodb` for `alter` when testing. This way the resulting ghost table is identical in structure to the original table (including indexes) and we expect data to be completely identical. We use `md5sum` on the entire dataset to confirm the test result. - When adding/dropping columns, you will want to use the explicit list of shared columns before/after migration. This list is printed by `gh-ost` at the beginning of the migration. ### Cleanup It's your job to: - Drop the ghost table (at your leisure, you should be aware that a `DROP` can be a lengthy operation) - Start replication back (via `START SLAVE`) ### Examples Simple: ```shell $ gh-ost --host=myhost.com --conf=/etc/gh-ost.cnf --database=test --table=sample_table --alter="engine=innodb" --chunk-size=2000 --max-load=Threads_connected=20 --initially-drop-ghost-table --initially-drop-old-table --test-on-replica --verbose --execute ``` Elaborate: ```shell $ gh-ost --host=myhost.com --conf=/etc/gh-ost.cnf --database=test --table=sample_table --alter="engine=innodb" --chunk-size=2000 --max-load=Threads_connected=20 --switch-to-rbr --initially-drop-ghost-table --initially-drop-old-table --test-on-replica --postpone-cut-over-flag-file=/tmp/ghost-postpone.flag --exact-rowcount --concurrent-rowcount --allow-nullable-unique-key --verbose --execute ``` - Count exact number of rows (makes ETA estimation very good). This goes at the expense of paying the time for issuing a `SELECT COUNT(*)` on your table. We use this lovingly. - Automatically switch to `RBR` if replica is configured as `SBR`. See also: [migrating with SBR](migrating-with-sbr.md) - allow iterating on a `UNIQUE KEY` that has `NULL`able columns (at your own risk) ### Further notes Do not confuse `--test-on-replica` with `--migrate-on-replica`; the latter performs the migration and _keeps it that way_ (does not revert the table swap nor stops replication) As part of testing on replica, `gh-ost` issues a `STOP SLAVE`. This requires the `SUPER` privilege. See related discussion on https://github.com/github/gh-ost/issues/162 ================================================ FILE: doc/the-fine-print.md ================================================ # The Fine Print: What are You Not Telling Me? Here are technical considerations you may be interested in. We write here things that are not an obvious [Requirements & Limitations](requirements-and-limitations.md) # Connecting to replica `gh-ost` prefers connecting to a replica. If your master uses Statement Based Replication, this is a _requirement_. What does "connect to replica" mean? - `gh-ost` connects to the replica as a normal client - It additionally connects as a replica to the replica (pretends to be a MySQL replica itself) - It auto-detects the master `gh-ost` reads the RBR binary logs from the replica, and applies events onto the master as part of the table migration. THE FINE PRINT: - You trust the replica's binary logs to represent events applied on master. - If you don't trust the replica, or if you suspect there's data drift between replica & master, take notice. - If the table on the replica has a different schema than the master, `gh-ost` likely won't work correctly. - Our take: we trust replica data; if master dies in production, we promote a replica. Our read serving is based on replica(s). - If your master is RBR, do instead connect `gh-ost` to master, via `--allow-on-master` (see [cheatsheet](cheatsheet.md)). - Replication needs to run. - This is an obvious, but worth stating. You cannot perform a migration with "connect to replica" if your replica lags. `gh-ost` will actually do all it can so that replication does not lag, and avoid critical operations if replication is lagging. # Network usage `gh-ost` reads binary logs and then applies them onto the migrated server. THE FINE PRINT: - `gh-ost` delivers more network traffic than other online-schema-change tools, that let MySQL handle all data transfer internally. This is part of the [triggerless design](triggerless-design.md). - Our take: we deal with cross-DC migration traffic and this is working well for us. # Impersonating as a replica `gh-ost` impersonates as a replica: it connects to a MySQL server, says "oh hey, I'm a replica, please send me binary logs kthx". THE FINE PRINT: - `SHOW SLAVE HOSTS` or `SHOW PROCESSLIST` will list this strange "replica" that you can't really connect to. ================================================ FILE: doc/throttle.md ================================================ # Throttle Throughout a migration operation, `gh-ost` is either actively copying and applying data, or is _throttling_. When _throttled_, `gh-ost` ceases to write row data and ceases to inspect binary log entries. It pauses all writes except for the low-volume changelog status writes and the heartbeat writes. As compared with trigger-based solutions, when `gh-ost` is throttled, the write load on the master is truly removed. Typically, throttling is based on replication lag or on master load. At such time, you wish to reduce load from master and from replication by pausing the _ghost_ writes. However, with a trigger based solution this is impossible to achieve: the triggers must remain in place and they continue to generate excess writes while the table is being used. Since `gh-ost` is not based on triggers, but of reading binary logs, it controls its own writes. Each and every write on the master comes from the `gh-ost` app, which means `gh-ost` is able to reduce writes to a bare minimum when it wishes so. `gh-ost` supports various means for controlling throttling behavior; it is operations friendly in that it allows the user greater, dynamic control of throttler behavior. ### Throttling parameters and factors Throttling is controlled via the following explicit and implicit factors: #### Replication-lag The recommended way of running `gh-ost` is by connecting it to a replica. It will figure out the master by traversing the topology. It is by design that `gh-ost` is throttle aware: it generates its own _heartbeat_ mechanism; while it is running the migration, it is self-checking the replica to which it is connected for replication lag. Otherwise you may specify your own list of replica servers you wish it to observe. - `--throttle-control-replicas`: list of replicas you explicitly wish `gh-ost` to check for replication lag. Example: `--throttle-control-replicas=myhost1.com:3306,myhost2.com,myhost3.com:3307` - `--max-lag-millis`: maximum allowed lag; any controlled replica lagging more than this value will cause throttling to kick in. When all control replicas have smaller lag than indicated, operation resumes. Note that you may dynamically change both `--max-lag-millis` and the `throttle-control-replicas` list via [interactive commands](interactive-commands.md) #### Status thresholds - `--max-load`: list of metrics and threshold values; topping the threshold of any will cause throttler to kick in. Example: `--max-load='Threads_running=100,Threads_connected=500'` Metrics must be valid, numeric [status variables](https://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html) #### Throttle query - When provided, the `--throttle-query` is expected to return a scalar integer. A return value `> 0` implies `gh-ost` should throttle. A return value `<= 0` implied `gh-ost` is free to proceed (pending other throttling factors). An example query could be: `--throttle-query="select hour(now()) between 8 and 17"` which implies throttling auto-starts `8:00am` and migration auto-resumes at `18:00pm`. #### HTTP Throttle The `--throttle-http` flag allows for throttling via HTTP. Every 100ms `gh-ost` issues a `HEAD` request to the provided URL. If the response status code is not `200` throttling will kick in until a `200` response status code is returned. If no URL is provided or the URL provided doesn't contain the scheme then the HTTP check will be disabled. For example `--throttle-http="http://1.2.3.4:6789/throttle"` will enable the HTTP check/throttling, but `--throttle-http="1.2.3.4:6789/throttle"` will not. The URL can be queried and updated dynamically via [interactive interface](interactive-commands.md). #### Manual control In addition to the above, you are able to take control and throttle the operation any time you like. - `--throttle-flag-file`: when this file exists, throttling kicks in. Just `touch` the file to begin throttling. - `--throttle-additional-flag-file`: similar to the above. When this file exists, throttling kicks in. Default: `/tmp/gh-ost.throttle` The reason for having two files has to do with the intent of being able to run multiple migrations concurrently. The setup we wish to use is that each migration would have its own, specific `throttle-flag-file`, but all would use the same `throttle-additional-flag-file`. Thus, we are able to throttle specific migrations by touching their specific files, or we are able to throttle all migrations at once, by touching the shared file. - `throttle` command via [interactive interface](interactive-commands.md). Example: ``` echo throttle | nc -U /tmp/gh-ost.test.sample_data_0.sock echo no-throttle | nc -U /tmp/gh-ost.test.sample_data_0.sock ``` ### Throttle precedence Any single factor in the above that suggests the migration should throttle - causes throttling. That is, once some component decides to throttle, you cannot override it; you cannot force continued execution of the migration. `gh-ost` collects different throttle-related metrics at different times, independently. It asynchronously reads the collected metrics and checks if they satisfy conditions/thresholds. The first check to suggest throttling stops the check; the status message will note the reason for throttling as the first satisfied check. ### Throttle status The throttle status is printed as part of the periodic [status message](understanding-output.md): ``` Copy: 0/2915 0.0%; Applied: 0; Backlog: 0/100; Time: 41s(total), 41s(copy); streamer: mysql-bin.000551:47983; Lag: 0.01s, HeartbeatLag: 0.01s, State: throttled, flag-file; ETA: N/A Copy: 0/2915 0.0%; Applied: 0; Backlog: 0/100; Time: 42s(total), 42s(copy); streamer: mysql-bin.000551:49370; Lag: 0.01s, HeartbeatLag: 0.01s, State: throttled, commanded by user; ETA: N/A ``` ### How long can you throttle for? Throttling time is limited by the availability of the binary logs. When throttling begins, `gh-ost` suspends reading the binary logs, and expects to resume reading from same binary log where it paused. Your availability of binary logs is typically determined by the [expire_logs_days](https://dev.mysql.com/doc/refman/5.7/en/replication-options-binary-log.html#sysvar_expire_logs_days) variable. If you have `expire_logs_days = 10` (or check `select @@global.expire_logs_days`), then you should be able to throttle for up to `10` days. Having said that, throttling for so long is far fetching, in that the `gh-ost` process itself must be kept alive during that time; and the amount of binary logs to process once it resumes will potentially take days to replay. It is worth mentioning that some deployments have external scheduled scripts that purge binary logs, regardless of the `expire_logs_days` configuration. Please verify your own deployment configuration. To clarify, you only need to keep binary logs on the single server `gh-ost` connects to. ================================================ FILE: doc/triggerless-design.md ================================================ # Triggerless design A breakdown of the logic and algorithm behind `gh-ost`'s triggerless design, followed by the implications, advantages and disadvantages of such design. ### Trigger-based migrations background It is worthwhile to consider two popular existing online schema change solutions: - [pt-online-schema-change](https://www.percona.com/doc/percona-toolkit/2.2/pt-online-schema-change.html) - [Facebook OSC](https://www.facebook.com/notes/mysql-at-facebook/online-schema-change-for-mysql/430801045932/) The former uses a synchronous design: it adds three triggers (`AFTER INSERT`, `AFTER UPDATE`, `AFTER DELETE`) on the original table. Each such trigger relays the operation onto the ghost table. So for every `UPDATE` on the original table, an `UPDATE` executes on the ghost table. A `DELETE` on the original table triggers a `DELETE` on the ghost table. Same for `INSERT`. The triggers live in the same transaction space as the original query. The latter uses an asynchronous design: it adds three triggers (`AFTER INSERT`, `AFTER UPDATE`, `AFTER DELETE`) on the original table. It also creates a _changelog_ table. The triggers do not relay operations directly to the ghost table. Instead, they each add an entry to the changelog table. An `UPDATE` on the original table makes for an `INSERT` on the changelog table saying "There was an UPDATE on the original table with this and that values"; likewise for `INSERT` and `DELETE`. A background process tails the changelog table and applies the changes onto the ghost table. This approach is asynchronous in that the applier does not live in the same transaction space as the original table, and may operate on a change event seconds or more after said event was written. It is noteworthy that the writes to the changelog table still live in the same transaction space as the writes on the original table. ### Triggerless based asynchronous migrations `gh-ost`'s triggerless design uses an asynchronous approach. However it does not require triggers because it does not require having a _changelog_ table like the FB tool does. The reason it does not require a changelog table is that it finds the changelog in another place: the binary logs. In particular, it reads Row Based Replication (RBR) entries (you can still [use it with Statement Based Replication!](migrating-with-sbr.md)) and searches for entries that apply to the original table. RBR entries are very convenient for this job: they break complex statements, potentially multi-table, into distinct, per-table, per-row entries, which are easy to read and apply. `gh-ost` pretends to be a MySQL replica: it connects to the MySQL server and begins requesting for binlog events as though it were a real replication server. Thus, it gets a continuous streaming of the binary logs, and filters out those events that apply to the original table. `gh-ost` can connect directly to the master, but prefers to connect to one of its replicas. Such a replica would need to use `log-slave-updates` and use `binlog-format=ROW` (`gh-ost` can change the latter setting for you). Reading from the binary log, specially in the case of reading those on a replica, further stresses the asynchronous nature of the algorithm. While the transaction _may_ (based on configuration) be synced with the binlog entry write, it will take time until `gh-ost` - pretending to be a replica - will get notification for that, copy the event downstream and apply it. The asynchronous design implies many noteworthy outcomes, to be discussed later on. ### Workflow overview The workflow includes reading table data from the server, reading event data from the binary log, checking for replication lag or other throttling parameters, applying changes onto the server (typically the master), sending hints through the binary log stream and more. Some flow breakdown: #### Initial setup & validation Initial setup is a no-concurrency operation - Connecting to replica/master, detecting master identify - Pre-validating `alter` statement - Initial sanity: privileges, existence of tables - Creation of changelog and ghost tables. - Applying `alter` on ghost table - Comparing structure of original & ghost table. Looking for shared columns, shared unique keys, validating foreign keys. Choosing shared unique key, the key by which we chunk the table and process it. - Setting up the binlog listener; begin listening on changelog events - Injecting a "good to go" entry onto the changelog table (to be intercepted via binary logs) - Begin listening on binlog events for original table DMLs - Reading original table's chosen key min/max values #### Copy flow This setup includes multiple moving parts, all acting concurrently with some coordination - Setting up a heartbeat mechanism: frequent writes on the changelog table (we consider this to be low, negligible write load for throttling purposes) - Continuously updating status - Periodically (frequently) checking for potential throttle scenarios or hints - Work through the original table's rows range, chunk by chunk, queueing copy tasks onto the ghost table - Reading DML events from the binlogs, queueing apply tasks onto the ghost table - Processing the copy tasks queue and the apply tasks queue and sequentially applying onto ghost table - Suspending by throttle state - Injecting/intercepting "copy all done" once full row-copy range has been exhausted - Stall/postpone while `postpone-cut-over-flag-file` exists (we keep apply ongoing DMLs) #### Cut-over and completion - Locking the original table for writes, working on what remains on the binlog event backlog (recall this is an asynchronous operation, and so even as the table is locked, we still have unhandled events in our pipe). - Swapping the original table out, the ghost table in - Cleanup: potential drop of tables ### Asynchronous design implications #### Cut-over phase A complication the asynchronous approach presents is the cut-over phase: the swapping of the tables. In the synchronous approach, the two tables are kept in sync thanks to the transaction-space in which the triggers operate. Thus, a simple, atomic `rename table original to _original_old, ghost to original` suffices and is valid. In the asynchronous approach, as we lock the original table, we often still have events in the pipeline, changes in the binary log we still need to apply onto the ghost table. An atomic swap would be a premature and incorrect solution, since it would imply the write load would immediately proceed to operate on what used to be the ghost table, even before we completed applying those last changes. The Facebook solution uses an "outage", two-step rename: - Lock the original table, work on backlog - Rename original table to `_old` - Rename ghost table to original In between those two renames there's a point in time where the table does not exist, hence there's a "table outage". `gh-ost` solves this by using a two-step algorithm that blocks writes to the table, then issues an atomic swap. It uses safety latches such that the operation either succeeds, atomically, or fails, bringing us back to pre-cut-over stage. Read more on the [cut-over](cut-over.md) documentation. #### Decoupling The most impacting change the triggerless, asynchronous approach provides is the decoupling of workload. With triggers, either synchronous or asynchronous, every write on your table implied an immediate write on another table. We will break down the meaning of workload decoupling, shortly. But it is important to understand that `gh-ost` interprets the situation in its own time and acts in its own time, yet still makes this an online operation. The decoupling is important not only as the tool's logic goes, but very importantly as the master server sees it. As far as the master knows, write to the table and writes to the ghost table are unrelated. #### Writer load Not using triggers means the master no longer needs to overload multiple, concurrent writes with stored routine interpretation combined with lock contention on the ghost table. The responsibility for applying data to the ghost table is completely `gh-ost`'s. As such, `gh-ost` decides which data gets to be written to the ghost table and when. We are decoupled from the original table's write load, and choose to write to the ghost table in a single thread. MySQL does not perform well on multiple concurrent massive writes to a specific table. Locking becomes an issue. This is why we choose to alternate between the massive row-copy and the ongoing binlog events backlog such that the server only sees writes from a single connection. It is also interesting to observe that `gh-ost` is the only application writing to the ghost table. No one else is even aware of its existence. Thus, the trigger originated problem of high concurrency, high contention writes simply does not exist in `gh-ost`. #### Pausability When `gh-ost` pauses (throttles), it issues no writes on the ghost table. Because there are no triggers, write workload is decoupled from the `gh-ost` write workload. And because we're using an asynchronous approach, the algorithm already handles a time difference between a master write time and the ghost apply time. A difference of a few microseconds is no different from a difference of minutes or hours. When `gh-ost` [throttles](throttle.md), either by replication lag, `max-load` setting or an explicit [interactive user command](interactive-commands.md), the master is back to normal. It sees no more writes on the ghost table. An exception is the ongoing heartbeat writes onto the changelog table, which we consider to be negligible. #### Testability We are able to test the migration process: as we've decoupled the migration operation from the master's workload, we are good to apply the changes not to the master, but to one of its replicas. We are able to migrate a table on a replica. This in itself is a nice feature; but it also presents us with testability: just as we complete the migration, we stop replication on the replica. We cut-over but rollback again. We do not drop any table. The result is both the original and ghost table exist on the replica, which is not taking any further changes. We have time to examine the two tables and compare them to our satisfaction. This is the method used by GitHub to continuously validate the tool's integrity: multiple production replicas are continuously and repeatedly doing a "trivial migration" (no actually change of column) on all our production tables. Each migration is followed by a checksum of the entire table data, on both original and ghost tables. We expect the checksums to be identical and we log the results. We expect zero failures. #### Multiple, concurrent migrations `gh-ost` was designed with having multiple concurrent migration running in parallel (no two on the same table, of course). The asynchronous approach supports that design by not caring when data is being shipped to the ghost table. The fact no triggers exist means multiple migrations appear to the master (or other migrated host) just as multiple connections, each writing to some otherwise unknown table. Each can throttle in its own time, or we can throttle all together. #### Going outside the server space More to come as we make progress. ### No free meals #### Increased traffic The existing tools utilize triggers to propagate data changes. `gh-ost` takes upon itself to read the data, then write it back. `gh-ost` actually prefers to read from a replica and write to the master. This implies data transfers between hosts, and certainly in/out the MySQL server daemon. At this time the MySQL client library used by `gh-ost` does not support compression, and so during a migration you can expect the full volume of a table to transfer on the wire. #### Code complexity With the synchronous, trigger based approach, the role of the migration tool is relatively small. A lot of the migration is based on the triggers doing their job within the transaction space. Issues such as rollback, datatypes, cut-over are implicitly taken care of by the database. With `gh-ost`'s asynchronous approach, the tool turns complex. It connects to the master and onto a replica; it imposes as a replicating server; it writes heartbeat events; it reads binlog data into the app to be written again onto the migrated host; it need to manage connection failures, replication lag, and more. The tool has therefore a larger codebase and a more complicated asynchronous, concurrent logic. But we jumped the opportunity to add some [perks](perks.md) and completely redesign how an online migration tool should work. ================================================ FILE: doc/understanding-output.md ================================================ # Understanding gh-ost output `gh-ost` attempts to be verbose to the point where you really know what it's doing, without completely spamming you. You can control output levels: - `--verbose`: common use. Useful output, not tons of it - `--debug`: everything. Tons of output. Initial output lines may look like this: ``` 2016-05-19 17:57:04 INFO starting gh-ost 0.7.14 2016-05-19 17:57:04 INFO Migrating `mydb`.`mytable` 2016-05-19 17:57:04 INFO connection validated on 127.0.0.1:3306 2016-05-19 17:57:04 INFO User has ALL privileges 2016-05-19 17:57:04 INFO binary logs validated on 127.0.0.1:3306 2016-05-19 17:57:04 INFO Restarting replication on 127.0.0.1:3306 to make sure binlog settings apply to replication thread 2016-05-19 17:57:04 INFO Table found. Engine=InnoDB 2016-05-19 17:57:05 INFO As instructed, I'm issuing a SELECT COUNT(*) on the table. This may take a while 2016-05-19 17:57:11 INFO Exact number of rows via COUNT: 4466810 2016-05-19 17:57:11 INFO --test-on-replica given. Will not execute on master the.master:3306 but rather on replica 127.0.0.1:3306 itself 2016-05-19 17:57:11 INFO Master found to be 127.0.0.1:3306 2016-05-19 17:57:11 INFO connection validated on 127.0.0.1:3306 2016-05-19 17:57:11 INFO Registering replica at 127.0.0.1:3306 2016-05-19 17:57:11 INFO Connecting binlog streamer at mysql-bin.002587:348694066 2016-05-19 17:57:11 INFO connection validated on 127.0.0.1:3306 2016-05-19 17:57:11 INFO rotate to next log name: mysql-bin.002587 2016-05-19 17:57:11 INFO connection validated on 127.0.0.1:3306 2016-05-19 17:57:11 INFO Dropping table `mydb`.`_mytable_gst` 2016-05-19 17:57:11 INFO Table dropped 2016-05-19 17:57:11 INFO Dropping table `mydb`.`_mytable_old` 2016-05-19 17:57:11 INFO Table dropped 2016-05-19 17:57:11 INFO Creating ghost table `mydb`.`_mytable_gst` 2016-05-19 17:57:11 INFO Ghost table created 2016-05-19 17:57:11 INFO Altering ghost table `mydb`.`_mytable_gst` 2016-05-19 17:57:11 INFO Ghost table altered 2016-05-19 17:57:11 INFO Dropping table `mydb`.`_mytable_osc` 2016-05-19 17:57:11 INFO Table dropped 2016-05-19 17:57:11 INFO Creating changelog table `mydb`.`_mytable_osc` 2016-05-19 17:57:11 INFO Changelog table created 2016-05-19 17:57:11 INFO Chosen shared unique key is PRIMARY 2016-05-19 17:57:11 INFO Shared columns are id,name,ref,col4,col5,col6 ``` Those are relatively self explanatory. Mostly they indicate that all goes well. You will be mostly interested in following up on the migration and understanding whether it goes well. Once migration actually begins, you will see output as follows: ``` Copy: 0/752865 0.0%; Applied: 0; Backlog: 0/100; Time: 29s(total), 0s(copy); streamer: mysql-bin.007068:846528615; Lag: 0.01s, HeartbeatLag: 0.01s, State: migrating; ETA: N/A Copy: 0/752865 0.0%; Applied: 0; Backlog: 0/100; Time: 30s(total), 1s(copy); streamer: mysql-bin.007068:846875570; Lag: 0.01s, HeartbeatLag: 0.01s, State: migrating; ETA: N/A Copy: 7300/752865 1.0%; Applied: 0; Backlog: 0/100; Time: 31s(total), 2s(copy); streamer: mysql-bin.007068:855439063; Lag: 0.01s, HeartbeatLag: 0.01s, State: migrating; ETA: N/A Copy: 14100/752865 1.9%; Applied: 0; Backlog: 0/100; Time: 32s(total), 3s(copy); streamer: mysql-bin.007068:864722759; Lag: 0.01s, HeartbeatLag: 0.01s, State: migrating; ETA: 2m37s Copy: 20100/752865 2.7%; Applied: 0; Backlog: 0/100; Time: 33s(total), 4s(copy); streamer: mysql-bin.007068:874346340; Lag: 0.01s, HeartbeatLag: 0.01s, State: migrating; ETA: 2m26s Copy: 27000/752865 3.6%; Applied: 0; Backlog: 0/100; Time: 34s(total), 5s(copy); streamer: mysql-bin.007068:886997306; Lag: 0.01s, HeartbeatLag: 0.01s, State: migrating; ETA: 2m14s ... ``` In the above some time was spent on counting table rows. `29s` have elapsed before actual rowcopy began. `gh-ost` will not deliver ETA before `1%` of the copy is complete. ``` Copy: 460900/752865 61.2%; Applied: 0; Backlog: 0/100; Time: 2m35s(total), 2m6s(copy); streamer: mysql-bin.007069:596112173; Lag: 0.01s, HeartbeatLag: 0.01s, State: migrating; ETA: 1m19s Copy: 466600/752865 62.0%; Applied: 0; Backlog: 0/100; Time: 2m40s(total), 2m11s(copy); streamer: mysql-bin.007069:622646704; Lag: 0.01s, HeartbeatLag: 0.01s, State: throttled, my.replica-01.com:3306 replica-lag=3.000000s; ETA: 1m19s Copy: 478500/752865 63.6%; Applied: 0; Backlog: 0/100; Time: 2m45s(total), 2m16s(copy); streamer: mysql-bin.007069:641258880; Lag: 0.01s, HeartbeatLag: 0.01s, State: migrating; ETA: 1m17s Copy: 496900/752865 66.0%; Applied: 0; Backlog: 0/100; Time: 2m50s(total), 2m21s(copy); streamer: mysql-bin.007069:678956577; Lag: 0.01s, HeartbeatLag: 0.01s, State: throttled, my.replica-01.com:3306 replica-lag=2.000000s; ETA: 1m17s Copy: 496900/752865 66.0%; Applied: 0; Backlog: 0/100; Time: 2m55s(total), 2m26s(copy); streamer: mysql-bin.007069:681610879; Lag: 0.01s, HeartbeatLag: 0.01s, State: throttled, max-load Threads_running=26 >= 25; ETA: 1m17s Copy: 528000/752865 70.1%; Applied: 0; Backlog: 0/100; Time: 3m0s(total), 2m31s(copy); streamer: mysql-bin.007069:711177703; Lag: 2.483039s, HeartbeatLag: 0.01s, State: throttled, lag=2.483039s; ETA: 1m17s Copy: 564900/752865 75.0%; Applied: 0; Backlog: 0/100; Time: 3m30s(total), 3m1s(copy); streamer: mysql-bin.007069:795150744; Lag: 3.482914s, HeartbeatLag: 0.01s, State: throttled, lag=3.482914s; ETA: 1m17s Copy: 577200/752865 76.7%; Applied: 0; Backlog: 0/100; Time: 3m39s(total), 3m10s(copy); streamer: mysql-bin.007069:819956052; Lag: 0.01s, HeartbeatLag: 0.01s, State: migrating; ETA: 57s Copy: 589300/752865 78.3%; Applied: 0; Backlog: 0/100; Time: 3m56s(total), 3m27s(copy); streamer: mysql-bin.007069:858738375; Lag: 0.01s, HeartbeatLag: 0.01s, State: migrating; ETA: 57s Copy: 595700/752865 79.1%; Applied: 0; Backlog: 0/100; Time: 3m57s(total), 3m28s(copy); streamer: mysql-bin.007069:860745762; Lag: 0.01s, HeartbeatLag: 0.01s, State: migrating; ETA: 54s ``` In the above migration is throttled on occasion. - A few times because one of the control replicas, specifically `my.replica-01.com:3306`, was lagging - Once because `max-load` threshold has been reached (`Threads_running=26 >= 25`) - Once because the migration replica itself (the server into which `gh-ost` connected) lagged. `gh-ost` will always specify the reason for throttling. ### Progress - `Copy: 595700/752865 79.1%` indicates the number of existing table rows copied onto the _ghost_ table, out of an estimate of the total row count. - `Applied: 0` indicates the number of entries processed in the binary log and applied onto the _ghost_ table. In the examples above there was no traffic on the migrated table, hence no rows processed. A migration on a more intensively used table may look like this: ``` Copy: 30713100/43138319 71.2%; Applied: 381910; Backlog: 0/100; Time: 2h6m30s(total), 2h3m20s(copy); streamer: mysql-bin.006792:1001340307; Lag: 0.01s, HeartbeatLag: 0.01s, State: migrating; ETA: 49m53s Copy: 30852500/43138338 71.5%; Applied: 383365; Backlog: 0/100; Time: 2h7m0s(total), 2h3m50s(copy); streamer: mysql-bin.006792:1050191186; Lag: 0.01s, HeartbeatLag: 0.01s, State: migrating; ETA: 49m18s 2016-07-25 03:20:41 INFO rotate to next log name: mysql-bin.006793 2016-07-25 03:20:41 INFO rotate to next log name: mysql-bin.006793 Copy: 30925700/43138360 71.7%; Applied: 384873; Backlog: 0/100; Time: 2h7m30s(total), 2h4m20s(copy); streamer: mysql-bin.006793:9144080; Lag: 0.01s, HeartbeatLag: 0.01s, State: migrating; ETA: 49m5s Copy: 31028800/43138380 71.9%; Applied: 386325; Backlog: 0/100; Time: 2h8m0s(total), 2h4m50s(copy); streamer: mysql-bin.006793:47984430; Lag: 0.01s, HeartbeatLag: 0.01s, State: migrating; ETA: 48m43s Copy: 31165600/43138397 72.2%; Applied: 387787; Backlog: 0/100; Time: 2h8m30s(total), 2h5m20s(copy); streamer: mysql-bin.006793:96139474; Lag: 0.01s, HeartbeatLag: 0.01s, State: migrating; ETA: 48m8s Copy: 31291200/43138418 72.5%; Applied: 389257; Backlog: 7/100; Time: 2h9m0s(total), 2h5m50s(copy); streamer: mysql-bin.006793:141094700; Lag: 0.01s, HeartbeatLag: 0.01s, State: migrating; ETA: 47m38s Copy: 31389700/43138432 72.8%; Applied: 390629; Backlog: 100/100; Time: 2h9m30s(total), 2h6m20s(copy); streamer: mysql-bin.006793:179473435; Lag: 1.548707s, HeartbeatLag: 0.01s, State: throttled, lag=1.548707s; ETA: 47m38s ``` Notes: - `Applied: 381910`: `381910` events in the binary logs presenting changes to the migrated table have been processed and applied on the _ghost_ table since beginning of migration. - `Backlog: 0/100`: we are performing well on reading the binary log. There's nothing known in the binary log queue that awaits processing. - `Backlog: 7/100`: while copying rows, a few events have piled up in the binary log _modifying our table_ that we spotted, and still need to apply. - `Backlog: 100/100`: our buffer of `100` events is full; you may see this during or right after throttling (the binary logs keep filling up with relevant queries that are not being processed), or immediately following a high workload. `gh-ost` will always prioritize binlog event processing (backlog) over row-copy; when next possible (throttling completes, in our example), `gh-ost` will drain the queue first, and only then proceed to resume row copy. There is nothing wrong with seeing `100/100`; it just indicates we're behind at that point in time. - `Copy: 31291200/43138418`, `Copy: 31389700/43138432`: this migration executed with `--exact-rowcount`. `gh-ost` continuously heuristically updates the total number of expected row copies as migration proceeds, hence the change from `43138418` to `43138432` - `streamer: mysql-bin.006793:179473435` tells us which binary log entry is `gh-ost` processing at this time. ### Status hint In addition, once every `10` minutes, a friendly reminder is printed, in the following form: ``` # Migrating `mydb`.`mytable`; Ghost table is `mydb`.`_mytable_gst` # Migrating mysql.master-01.com:3306; inspecting mysql.replica-05.com:3306; executing on some.host-17.com # Migration started at Mon Jul 25 01:13:19 2016 # chunk-size: 500; max lag: 1000ms; max-load: Threads_running=25; critical-load: Threads_running=1000; nice-ratio: 0 # throttle-additional-flag-file: /tmp/gh-ost.throttle.flag.file # postpone-cut-over-flag-file: /tmp/gh-ost.postpone.flag.file [set] # panic-flag-file: /tmp/gh-ost.panic.flag.file # Serving on unix socket: /tmp/gh-ost.mydb.mytable.sock ``` - The above mostly print out the current configuration. Remember you can [dynamically control](interactive-commands.md) most of them. - `gh-ost` notes that the `postpone-cut-over-flag-file` file actually exists by printing `[set]` ================================================ FILE: doc/what-if.md ================================================ # What if? Technical questions and answers. This document will be updated as we go ### What if I'm using Statement Based Replication? You can still migrate tables with `gh-ost`. We do that. What you will need is a replica configured with: - `log_bin` - `log_slave_updates` - `binlog_format=ROW` Thus, the replica will transform the master's SBR binlogs into RBR binlogs. `gh-ost` is happy to read the binary logs from the replica. [Read more](migrating-with-sbr.md) ### What if gh-ost crashes halfway through, or I kill it? Unlike trigger-based solutions, there's nothing urgent to clean up in the event `gh-ost` bails out or gets killed. There are the two tables creates by `gh-ost`: - The _ghost_ table: `_yourtablename_gho` - The _changelog_ table: `_yourtablename_ghc` You may instruct `gh-ost` to drop these tables upon startup; or better yet, you drop them. ### What if the cut-over (table switch) is unable to proceed due to locks/timeout? There is a `lock_wait_timeout` explicitly associated with the cut-over operation. If your table suddenly suffers from a long running query, the cut-over (involving `LOCK` and `RENAME` statements) may be unable to proceed. There's a finite number of retries, and if none of these succeeds, `gh-ost` bails out. ### What if the migration is causing a high load on my master? This is where `gh-ost` shines. There is no need to kill it as you may be used to with other tools. You can reconfigure `gh-ost` [on the fly](https://github.com/github/gh-ost/blob/master/doc/interactive-commands.md) to be nicer. You're always able to actively begin [throttling](throttle.md). Just touch the `throttle-file` or `echo throttle` into `gh-ost`. Otherwise, reconfigure your `max-load`, the `nice-ratio`, the `throttle-query` to gain better thresholds that would suit your needs. ### What if my replicas don't use binary logs? If the master is running Row Based Replication (RBR) - point `gh-ost` to the master, and specify `--allow-on-master`. See [cheatsheets](cheatsheet.md) If the master is running Statement Based Replication (SBR) - you have no alternative but to reconfigure a replica with: - `log_bin` - `log_slave_updates` - `binlog_format=ROW` ================================================ FILE: doc/why-triggerless.md ================================================ # Why triggerless? Existing MySQL schema migration tools: - [pt-online-schema-change](https://www.percona.com/doc/percona-toolkit/2.2/pt-online-schema-change.html) - [Facebook OSC](https://www.facebook.com/notes/mysql-at-facebook/online-schema-change-for-mysql/430801045932/) - [LHM](https://github.com/soundcloud/lhm) - [oak-online-alter-table](https://github.com/shlomi-noach/openarkkit) are all using [triggers](https://dev.mysql.com/doc/refman/5.7/en/triggers.html) to propagate live changes on your table onto a ghost/shadow table that is slowly being synchronized. The tools not all work the same: while most use a synchronous approach (all changes applied on the ghost table), the Facebook tool uses an asynchronous approach (changes are appended to a changelog table, later reviewed and applied on ghost table). Use of triggers simplifies a lot of the flow in doing a live table migration, but also poses some limitations or difficulties. Here are reasons why we choose to [design a triggerless solution](triggerless-design.md) to schema migrations. ## Overview Triggers are stored routines which are invoked on a per-row operation upon `INSERT`, `DELETE`, `UPDATE` on a table. They were introduced in MySQL `5.0`. A trigger may contain a set of queries, and these queries run in the same transaction space as the query that manipulates the table. This makes for an atomicity of both the original operation on the table and the trigger-invoked operations. ### Triggers, overhead A trigger in MySQL is a stored routine. MySQL stored routines are interpreted, never compiled. With triggers, for every `INSERT`, `DELETE`, `UPDATE` on our often busy table, we pay the necessary price of the additional write (onto ghost or changelog table), but also the price of interpreting the trigger body. We know this to be a visible overhead on very busy or very large tables. ### Triggers, locks When a table with triggers is concurrently being written to, the triggers, being in same transaction space as the incoming queries, are also executed concurrently. While concurrent queries compete for resources via locks (e.g. the `auto_increment` value), the triggers need to _simultaneously_ compete for their own locks (e.g., likewise on the `auto_increment` value on the ghost table, in a synchronous solution). These competitions are non-coordinated. We have evidenced near or complete lock downs in production, to the effect of rendering the table or the entire database inaccessible due to lock contention. ### Trigger based migration, no pause The triggers are used to either apply or record ongoing changes to your original table. Existing online migration tools (notably `pt-online-schema-change`) support the notion of _throttling_: suspending execution when the master becomes too busy, or replication is unable to catch up. However this throttling is partial. The tool may only suspend the tedious and long task of copying the millions of rows from the original table onto the ghost table, but the tool may not, at any stage, cancel the triggers. Cancelling the triggers means loss of information, leading to incorrect data. Thus, triggers must keep operating. On busy servers, we have seen that even as the online operation throttles, the master is brought down by the load of the triggers. Read more about [`gh-ost` throttling](throttle.md) ### Triggers, multiple migrations We are interested in being able to run multiple concurrent migrations (not on the same table, of course). Given all the above, we do not have trust that running multiple trigger-based migrations is a safe operation. In our current, past and shared experiences we have never done so; we are unaware of anyone who is doing so. ### Trigger based migration, no reliable production test We sometimes wish to experiment with a migration, or know in advance how much time it would take. A trigger-based solution allows us to run a migration on a replica, provided it uses Statement Based Replication. With Row Based Replication there is no option at all to use triggers on a replica, since the triggers only execute on the master (replica only gets notified of the _effect_ of the trigger). But even with Statement Based Replication we do not get a reliable representation of the migration as it would have executed on the master. MySQL replication (up to and including `5.6`) is single threaded, given a table, and even in `5.7` we do not expect to find concurrency on a single table. The triggers on the replica will invoke sequentially ; they will not simulate the same load, and they will not suffer from the same concurrency and locking problems depicted above, as on the master. ### Trigger based migration, bound to server The trigger space is within a MySQL service. It cannot go beyond that space. We are working towards a multi-server solution. More discussion as we make progress. ## Triggerless design Proceed to read about the [triggerless design](triggerless-design.md) ================================================ FILE: docker-compose.yml ================================================ version: "3.5" services: app: image: app build: context: . dockerfile: Dockerfile.test ================================================ FILE: go/base/context.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package base import ( "context" "fmt" "math" "os" "regexp" "strings" "sync" "sync/atomic" "time" "unicode/utf8" uuid "github.com/google/uuid" "github.com/github/gh-ost/go/mysql" "github.com/github/gh-ost/go/sql" "github.com/openark/golib/log" "github.com/go-ini/ini" ) // RowsEstimateMethod is the type of row number estimation type RowsEstimateMethod string const ( TableStatusRowsEstimate RowsEstimateMethod = "TableStatusRowsEstimate" ExplainRowsEstimate RowsEstimateMethod = "ExplainRowsEstimate" CountRowsEstimate RowsEstimateMethod = "CountRowsEstimate" ) type CutOver int const ( CutOverAtomic CutOver = iota CutOverTwoStep ) type ThrottleReasonHint string const ( NoThrottleReasonHint ThrottleReasonHint = "NoThrottleReasonHint" UserCommandThrottleReasonHint ThrottleReasonHint = "UserCommandThrottleReasonHint" LeavingHibernationThrottleReasonHint ThrottleReasonHint = "LeavingHibernationThrottleReasonHint" ) const ( HTTPStatusOK = 200 MaxEventsBatchSize = 1000 ETAUnknown = math.MinInt64 ) var ( envVariableRegexp = regexp.MustCompile("[$][{](.*)[}]") ) type ThrottleCheckResult struct { ShouldThrottle bool Reason string ReasonHint ThrottleReasonHint } func NewThrottleCheckResult(throttle bool, reason string, reasonHint ThrottleReasonHint) *ThrottleCheckResult { return &ThrottleCheckResult{ ShouldThrottle: throttle, Reason: reason, ReasonHint: reasonHint, } } // MigrationContext has the general, global state of migration. It is used by // all components throughout the migration process. type MigrationContext struct { Uuid string DatabaseName string OriginalTableName string AlterStatement string AlterStatementOptions string // anything following the 'ALTER TABLE [schema.]table' from AlterStatement countMutex sync.Mutex countTableRowsCancelFunc func() CountTableRows bool ConcurrentCountTableRows bool AllowedRunningOnMaster bool AllowedMasterMaster bool SwitchToRowBinlogFormat bool AssumeRBR bool SkipForeignKeyChecks bool SkipStrictMode bool AllowZeroInDate bool NullableUniqueKeyAllowed bool ApproveRenamedColumns bool SkipRenamedColumns bool IsTungsten bool DiscardForeignKeys bool AliyunRDS bool GoogleCloudPlatform bool AzureMySQL bool AttemptInstantDDL bool Resume bool Revert bool OldTableName string // SkipPortValidation allows skipping the port validation in `ValidateConnection` // This is useful when connecting to a MySQL instance where the external port // may not match the internal port. SkipPortValidation bool UseGTIDs bool config ContextConfig configMutex *sync.Mutex ConfigFile string CliUser string CliPassword string UseTLS bool TLSAllowInsecure bool TLSCACertificate string TLSCertificate string TLSKey string CliMasterUser string CliMasterPassword string HeartbeatIntervalMilliseconds int64 defaultNumRetries int64 ChunkSize int64 niceRatio float64 MaxLagMillisecondsThrottleThreshold int64 throttleControlReplicaKeys *mysql.InstanceKeyMap ThrottleFlagFile string ThrottleAdditionalFlagFile string throttleQuery string throttleHTTP string IgnoreHTTPErrors bool ThrottleCommandedByUser int64 HibernateUntil int64 maxLoad LoadMap criticalLoad LoadMap CriticalLoadIntervalMilliseconds int64 CriticalLoadHibernateSeconds int64 PostponeCutOverFlagFile string CutOverLockTimeoutSeconds int64 CutOverExponentialBackoff bool ExponentialBackoffMaxInterval int64 ForceNamedCutOverCommand bool ForceNamedPanicCommand bool PanicFlagFile string HooksPath string HooksHintMessage string HooksHintOwner string HooksHintToken string HooksStatusIntervalSec int64 PanicOnWarnings bool Checkpoint bool CheckpointIntervalSeconds int64 DropServeSocket bool ServeSocketFile string ServeTCPPort int64 Noop bool TestOnReplica bool MigrateOnReplica bool TestOnReplicaSkipReplicaStop bool OkToDropTable bool InitiallyDropOldTable bool InitiallyDropGhostTable bool TimestampOldTable bool // Should old table name include a timestamp CutOverType CutOver ReplicaServerId uint Hostname string AssumeMasterHostname string ApplierTimeZone string ApplierWaitTimeout int64 TableEngine string RowsEstimate int64 RowsDeltaEstimate int64 UsedRowsEstimateMethod RowsEstimateMethod HasSuperPrivilege bool OriginalBinlogFormat string OriginalBinlogRowImage string InspectorConnectionConfig *mysql.ConnectionConfig InspectorMySQLVersion string ApplierConnectionConfig *mysql.ConnectionConfig ApplierMySQLVersion string StartTime time.Time RowCopyStartTime time.Time RowCopyEndTime time.Time LockTablesStartTime time.Time RenameTablesStartTime time.Time RenameTablesEndTime time.Time pointOfInterestTime time.Time pointOfInterestTimeMutex *sync.Mutex lastHeartbeatOnChangelogTime time.Time lastHeartbeatOnChangelogMutex *sync.Mutex CurrentLag int64 currentProgress uint64 etaNanoseonds int64 EtaRowsPerSecond int64 ThrottleHTTPIntervalMillis int64 ThrottleHTTPStatusCode int64 ThrottleHTTPTimeoutMillis int64 controlReplicasLagResult mysql.ReplicationLagResult TotalRowsCopied int64 TotalDMLEventsApplied int64 DMLBatchSize int64 isThrottled bool throttleReason string throttleReasonHint ThrottleReasonHint throttleGeneralCheckResult ThrottleCheckResult throttleMutex *sync.Mutex throttleHTTPMutex *sync.Mutex IsPostponingCutOver int64 CountingRowsFlag int64 AllEventsUpToLockProcessedInjectedFlag int64 CleanupImminentFlag int64 UserCommandedUnpostponeFlag int64 CutOverCompleteFlag int64 InCutOverCriticalSectionFlag int64 PanicAbort chan error // Context for cancellation signaling across all goroutines // Stored in struct as it spans the entire migration lifecycle, not per-function. // context.Context is safe for concurrent use by multiple goroutines. ctx context.Context //nolint:containedctx cancelFunc context.CancelFunc // Stores the fatal error that triggered abort AbortError error abortMutex *sync.Mutex OriginalTableColumnsOnApplier *sql.ColumnList OriginalTableColumns *sql.ColumnList OriginalTableVirtualColumns *sql.ColumnList OriginalTableUniqueKeys [](*sql.UniqueKey) OriginalTableAutoIncrement uint64 GhostTableColumns *sql.ColumnList GhostTableVirtualColumns *sql.ColumnList GhostTableUniqueKeys [](*sql.UniqueKey) UniqueKey *sql.UniqueKey SharedColumns *sql.ColumnList ColumnRenameMap map[string]string DroppedColumnsMap map[string]bool MappedSharedColumns *sql.ColumnList MigrationLastInsertSQLWarnings []string MigrationRangeMinValues *sql.ColumnValues MigrationRangeMaxValues *sql.ColumnValues Iteration int64 MigrationIterationRangeMinValues *sql.ColumnValues MigrationIterationRangeMaxValues *sql.ColumnValues InitialStreamerCoords mysql.BinlogCoordinates ForceTmpTableName string IncludeTriggers bool RemoveTriggerSuffix bool TriggerSuffix string Triggers []mysql.Trigger recentBinlogCoordinates mysql.BinlogCoordinates BinlogSyncerMaxReconnectAttempts int AllowSetupMetadataLockInstruments bool SkipMetadataLockCheck bool IsOpenMetadataLockInstruments bool Log Logger } type Logger interface { Debug(args ...interface{}) Debugf(format string, args ...interface{}) Info(args ...interface{}) Infof(format string, args ...interface{}) Warning(args ...interface{}) error Warningf(format string, args ...interface{}) error Error(args ...interface{}) error Errorf(format string, args ...interface{}) error Errore(err error) error Fatal(args ...interface{}) error Fatalf(format string, args ...interface{}) error Fatale(err error) error SetLevel(level log.LogLevel) SetPrintStackTrace(printStackTraceFlag bool) } type ContextConfig struct { Client struct { User string Password string } Osc struct { Chunk_Size int64 Max_Lag_Millis int64 Replication_Lag_Query string Max_Load string } } func NewMigrationContext() *MigrationContext { ctx, cancelFunc := context.WithCancel(context.Background()) return &MigrationContext{ Uuid: uuid.NewString(), defaultNumRetries: 60, ChunkSize: 1000, InspectorConnectionConfig: mysql.NewConnectionConfig(), ApplierConnectionConfig: mysql.NewConnectionConfig(), MaxLagMillisecondsThrottleThreshold: 1500, CutOverLockTimeoutSeconds: 3, DMLBatchSize: 10, etaNanoseonds: ETAUnknown, maxLoad: NewLoadMap(), criticalLoad: NewLoadMap(), throttleMutex: &sync.Mutex{}, throttleHTTPMutex: &sync.Mutex{}, throttleControlReplicaKeys: mysql.NewInstanceKeyMap(), configMutex: &sync.Mutex{}, pointOfInterestTimeMutex: &sync.Mutex{}, lastHeartbeatOnChangelogMutex: &sync.Mutex{}, ColumnRenameMap: make(map[string]string), PanicAbort: make(chan error), ctx: ctx, cancelFunc: cancelFunc, abortMutex: &sync.Mutex{}, Log: NewDefaultLogger(), } } func (this *MigrationContext) SetConnectionConfig(storageEngine string) error { var transactionIsolation string switch storageEngine { case "rocksdb": transactionIsolation = "READ-COMMITTED" default: transactionIsolation = "REPEATABLE-READ" } this.InspectorConnectionConfig.TransactionIsolation = transactionIsolation this.ApplierConnectionConfig.TransactionIsolation = transactionIsolation return nil } func (this *MigrationContext) SetConnectionCharset(charset string) { if charset == "" { charset = "utf8mb4,utf8,latin1" } this.InspectorConnectionConfig.Charset = charset this.ApplierConnectionConfig.Charset = charset } func getSafeTableName(baseName string, suffix string) string { name := fmt.Sprintf("_%s_%s", baseName, suffix) if len(name) <= mysql.MaxTableNameLength { return name } extraCharacters := len(name) - mysql.MaxTableNameLength return fmt.Sprintf("_%s_%s", baseName[0:len(baseName)-extraCharacters], suffix) } // GetGhostTableName generates the name of ghost table, based on original table name // or a given table name func (this *MigrationContext) GetGhostTableName() string { if this.Revert { // When reverting the "ghost" table is the _del table from the original migration. return this.OldTableName } if this.ForceTmpTableName != "" { return getSafeTableName(this.ForceTmpTableName, "gho") } else { return getSafeTableName(this.OriginalTableName, "gho") } } // GetOldTableName generates the name of the "old" table, into which the original table is renamed. func (this *MigrationContext) GetOldTableName() string { var tableName string if this.ForceTmpTableName != "" { tableName = this.ForceTmpTableName } else { tableName = this.OriginalTableName } suffix := "del" if this.Revert { suffix = "rev_del" } if this.TimestampOldTable { t := this.StartTime timestamp := fmt.Sprintf("%d%02d%02d%02d%02d%02d", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second()) return getSafeTableName(tableName, fmt.Sprintf("%s_%s", timestamp, suffix)) } return getSafeTableName(tableName, suffix) } // GetChangelogTableName generates the name of changelog table, based on original table name // or a given table name. func (this *MigrationContext) GetChangelogTableName() string { if this.ForceTmpTableName != "" { return getSafeTableName(this.ForceTmpTableName, "ghc") } else { return getSafeTableName(this.OriginalTableName, "ghc") } } // GetCheckpointTableName generates the name of checkpoint table. func (this *MigrationContext) GetCheckpointTableName() string { if this.ForceTmpTableName != "" { return getSafeTableName(this.ForceTmpTableName, "ghk") } else { return getSafeTableName(this.OriginalTableName, "ghk") } } // GetVoluntaryLockName returns a name of a voluntary lock to be used throughout // the swap-tables process. func (this *MigrationContext) GetVoluntaryLockName() string { return fmt.Sprintf("%s.%s.lock", this.DatabaseName, this.OriginalTableName) } // RequiresBinlogFormatChange is `true` when the original binlog format isn't `ROW` func (this *MigrationContext) RequiresBinlogFormatChange() bool { return this.OriginalBinlogFormat != "ROW" } // GetApplierHostname is a safe access method to the applier hostname func (this *MigrationContext) GetApplierHostname() string { if this.ApplierConnectionConfig == nil { return "" } if this.ApplierConnectionConfig.ImpliedKey == nil { return "" } return this.ApplierConnectionConfig.ImpliedKey.Hostname } // GetInspectorHostname is a safe access method to the inspector hostname func (this *MigrationContext) GetInspectorHostname() string { if this.InspectorConnectionConfig == nil { return "" } if this.InspectorConnectionConfig.ImpliedKey == nil { return "" } return this.InspectorConnectionConfig.ImpliedKey.Hostname } // InspectorIsAlsoApplier is `true` when the both inspector and applier are the // same database instance. This would be true when running directly on master or when // testing on replica. func (this *MigrationContext) InspectorIsAlsoApplier() bool { return this.InspectorConnectionConfig.Equals(this.ApplierConnectionConfig) } // HasMigrationRange tells us whether there's a range to iterate for copying rows. // It will be `false` if the table is initially empty func (this *MigrationContext) HasMigrationRange() bool { return this.MigrationRangeMinValues != nil && this.MigrationRangeMaxValues != nil } func (this *MigrationContext) SetCutOverLockTimeoutSeconds(timeoutSeconds int64) error { if timeoutSeconds < 1 { return fmt.Errorf("Minimal timeout is 1sec. Timeout remains at %d", this.CutOverLockTimeoutSeconds) } if timeoutSeconds > 10 { return fmt.Errorf("Maximal timeout is 10sec. Timeout remains at %d", this.CutOverLockTimeoutSeconds) } this.CutOverLockTimeoutSeconds = timeoutSeconds return nil } func (this *MigrationContext) SetExponentialBackoffMaxInterval(intervalSeconds int64) error { if intervalSeconds < 2 { return fmt.Errorf("Minimal maximum interval is 2sec. Timeout remains at %d", this.ExponentialBackoffMaxInterval) } this.ExponentialBackoffMaxInterval = intervalSeconds return nil } func (this *MigrationContext) SetDefaultNumRetries(retries int64) { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() if retries > 0 { this.defaultNumRetries = retries } } func (this *MigrationContext) MaxRetries() int64 { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() retries := this.defaultNumRetries return retries } func (this *MigrationContext) IsTransactionalTable() bool { switch strings.ToLower(this.TableEngine) { case "innodb": { return true } case "tokudb": { return true } case "rocksdb": { return true } } return false } // SetCountTableRowsCancelFunc sets the cancel function for the CountTableRows query context func (this *MigrationContext) SetCountTableRowsCancelFunc(f func()) { this.countMutex.Lock() defer this.countMutex.Unlock() this.countTableRowsCancelFunc = f } // IsCountingTableRows returns true if the migration has a table count query running func (this *MigrationContext) IsCountingTableRows() bool { this.countMutex.Lock() defer this.countMutex.Unlock() return this.countTableRowsCancelFunc != nil } // CancelTableRowsCount cancels the CountTableRows query context. It is safe to // call function even when IsCountingTableRows is false. func (this *MigrationContext) CancelTableRowsCount() { this.countMutex.Lock() defer this.countMutex.Unlock() if this.countTableRowsCancelFunc == nil { return } this.countTableRowsCancelFunc() this.countTableRowsCancelFunc = nil } // ElapsedTime returns time since very beginning of the process func (this *MigrationContext) ElapsedTime() time.Duration { return time.Since(this.StartTime) } // MarkRowCopyStartTime func (this *MigrationContext) MarkRowCopyStartTime() { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() this.RowCopyStartTime = time.Now() } // ElapsedRowCopyTime returns time since starting to copy chunks of rows func (this *MigrationContext) ElapsedRowCopyTime() time.Duration { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() if this.RowCopyStartTime.IsZero() { // Row copy hasn't started yet return 0 } if this.RowCopyEndTime.IsZero() { return time.Since(this.RowCopyStartTime) } return this.RowCopyEndTime.Sub(this.RowCopyStartTime) } // ElapsedRowCopyTime returns time since starting to copy chunks of rows func (this *MigrationContext) MarkRowCopyEndTime() { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() this.RowCopyEndTime = time.Now() } func (this *MigrationContext) TimeSinceLastHeartbeatOnChangelog() time.Duration { return time.Since(this.GetLastHeartbeatOnChangelogTime()) } func (this *MigrationContext) GetCurrentLagDuration() time.Duration { return time.Duration(atomic.LoadInt64(&this.CurrentLag)) } func (this *MigrationContext) GetProgressPct() float64 { return math.Float64frombits(atomic.LoadUint64(&this.currentProgress)) } func (this *MigrationContext) SetProgressPct(progressPct float64) { atomic.StoreUint64(&this.currentProgress, math.Float64bits(progressPct)) } func (this *MigrationContext) GetETADuration() time.Duration { return time.Duration(atomic.LoadInt64(&this.etaNanoseonds)) } func (this *MigrationContext) SetETADuration(etaDuration time.Duration) { atomic.StoreInt64(&this.etaNanoseonds, etaDuration.Nanoseconds()) } func (this *MigrationContext) GetETASeconds() int64 { nano := atomic.LoadInt64(&this.etaNanoseonds) if nano < 0 { return ETAUnknown } return nano / int64(time.Second) } // math.Float64bits([f=0..100]) // GetTotalRowsCopied returns the accurate number of rows being copied (affected) // This is not exactly the same as the rows being iterated via chunks, but potentially close enough func (this *MigrationContext) GetTotalRowsCopied() int64 { return atomic.LoadInt64(&this.TotalRowsCopied) } func (this *MigrationContext) GetIteration() int64 { return atomic.LoadInt64(&this.Iteration) } func (this *MigrationContext) SetNextIterationRangeMinValues() { this.MigrationIterationRangeMinValues = this.MigrationIterationRangeMaxValues if this.MigrationIterationRangeMinValues == nil { this.MigrationIterationRangeMinValues = this.MigrationRangeMinValues } } func (this *MigrationContext) MarkPointOfInterest() int64 { this.pointOfInterestTimeMutex.Lock() defer this.pointOfInterestTimeMutex.Unlock() this.pointOfInterestTime = time.Now() return atomic.LoadInt64(&this.Iteration) } func (this *MigrationContext) TimeSincePointOfInterest() time.Duration { this.pointOfInterestTimeMutex.Lock() defer this.pointOfInterestTimeMutex.Unlock() return time.Since(this.pointOfInterestTime) } func (this *MigrationContext) SetLastHeartbeatOnChangelogTime(t time.Time) { this.lastHeartbeatOnChangelogMutex.Lock() defer this.lastHeartbeatOnChangelogMutex.Unlock() this.lastHeartbeatOnChangelogTime = t } func (this *MigrationContext) GetLastHeartbeatOnChangelogTime() time.Time { this.lastHeartbeatOnChangelogMutex.Lock() defer this.lastHeartbeatOnChangelogMutex.Unlock() return this.lastHeartbeatOnChangelogTime } func (this *MigrationContext) SetHeartbeatIntervalMilliseconds(heartbeatIntervalMilliseconds int64) { if heartbeatIntervalMilliseconds < 100 { heartbeatIntervalMilliseconds = 100 } if heartbeatIntervalMilliseconds > 1000 { heartbeatIntervalMilliseconds = 1000 } this.HeartbeatIntervalMilliseconds = heartbeatIntervalMilliseconds } func (this *MigrationContext) SetMaxLagMillisecondsThrottleThreshold(maxLagMillisecondsThrottleThreshold int64) { if maxLagMillisecondsThrottleThreshold < 100 { maxLagMillisecondsThrottleThreshold = 100 } atomic.StoreInt64(&this.MaxLagMillisecondsThrottleThreshold, maxLagMillisecondsThrottleThreshold) } func (this *MigrationContext) SetChunkSize(chunkSize int64) { if chunkSize < 10 { chunkSize = 10 } if chunkSize > 100000 { chunkSize = 100000 } atomic.StoreInt64(&this.ChunkSize, chunkSize) } func (this *MigrationContext) SetDMLBatchSize(batchSize int64) { if batchSize < 1 { batchSize = 1 } if batchSize > MaxEventsBatchSize { batchSize = MaxEventsBatchSize } atomic.StoreInt64(&this.DMLBatchSize, batchSize) } func (this *MigrationContext) SetThrottleGeneralCheckResult(checkResult *ThrottleCheckResult) *ThrottleCheckResult { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() this.throttleGeneralCheckResult = *checkResult return checkResult } func (this *MigrationContext) GetThrottleGeneralCheckResult() *ThrottleCheckResult { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() result := this.throttleGeneralCheckResult return &result } func (this *MigrationContext) SetThrottled(throttle bool, reason string, reasonHint ThrottleReasonHint) { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() this.isThrottled = throttle this.throttleReason = reason this.throttleReasonHint = reasonHint } func (this *MigrationContext) IsThrottled() (bool, string, ThrottleReasonHint) { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() // we don't throttle when cutting over. We _do_ throttle: // - during copy phase // - just before cut-over // - in between cut-over retries // When cutting over, we need to be aggressive. Cut-over holds table locks. // We need to release those asap. if atomic.LoadInt64(&this.InCutOverCriticalSectionFlag) > 0 { return false, "critical section", NoThrottleReasonHint } return this.isThrottled, this.throttleReason, this.throttleReasonHint } func (this *MigrationContext) GetThrottleQuery() string { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() var query = this.throttleQuery return query } func (this *MigrationContext) SetThrottleQuery(newQuery string) { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() this.throttleQuery = newQuery } func (this *MigrationContext) GetThrottleHTTP() string { this.throttleHTTPMutex.Lock() defer this.throttleHTTPMutex.Unlock() var throttleHTTP = this.throttleHTTP return throttleHTTP } func (this *MigrationContext) SetThrottleHTTP(throttleHTTP string) { this.throttleHTTPMutex.Lock() defer this.throttleHTTPMutex.Unlock() this.throttleHTTP = throttleHTTP } func (this *MigrationContext) SetIgnoreHTTPErrors(ignoreHTTPErrors bool) { this.throttleHTTPMutex.Lock() defer this.throttleHTTPMutex.Unlock() this.IgnoreHTTPErrors = ignoreHTTPErrors } func (this *MigrationContext) GetMaxLoad() LoadMap { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() return this.maxLoad.Duplicate() } func (this *MigrationContext) GetCriticalLoad() LoadMap { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() return this.criticalLoad.Duplicate() } func (this *MigrationContext) GetNiceRatio() float64 { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() return this.niceRatio } func (this *MigrationContext) SetNiceRatio(newRatio float64) { if newRatio < 0.0 { newRatio = 0.0 } if newRatio > 100.0 { newRatio = 100.0 } this.throttleMutex.Lock() defer this.throttleMutex.Unlock() this.niceRatio = newRatio } func (this *MigrationContext) GetRecentBinlogCoordinates() mysql.BinlogCoordinates { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() return this.recentBinlogCoordinates } func (this *MigrationContext) SetRecentBinlogCoordinates(coordinates mysql.BinlogCoordinates) { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() this.recentBinlogCoordinates = coordinates } // ReadMaxLoad parses the `--max-load` flag, which is in multiple key-value format, // such as: 'Threads_running=100,Threads_connected=500' // It only applies changes in case there's no parsing error. func (this *MigrationContext) ReadMaxLoad(maxLoadList string) error { loadMap, err := ParseLoadMap(maxLoadList) if err != nil { return err } this.throttleMutex.Lock() defer this.throttleMutex.Unlock() this.maxLoad = loadMap return nil } // ReadCriticalLoad parses the `--max-load` flag, which is in multiple key-value format, // such as: 'Threads_running=100,Threads_connected=500' // It only applies changes in case there's no parsing error. func (this *MigrationContext) ReadCriticalLoad(criticalLoadList string) error { loadMap, err := ParseLoadMap(criticalLoadList) if err != nil { return err } this.throttleMutex.Lock() defer this.throttleMutex.Unlock() this.criticalLoad = loadMap return nil } func (this *MigrationContext) GetControlReplicasLagResult() mysql.ReplicationLagResult { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() lagResult := this.controlReplicasLagResult return lagResult } func (this *MigrationContext) SetControlReplicasLagResult(lagResult *mysql.ReplicationLagResult) { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() if lagResult == nil { this.controlReplicasLagResult = *mysql.NewNoReplicationLagResult() } else { this.controlReplicasLagResult = *lagResult } } func (this *MigrationContext) GetThrottleControlReplicaKeys() *mysql.InstanceKeyMap { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() keys := mysql.NewInstanceKeyMap() keys.AddKeys(this.throttleControlReplicaKeys.GetInstanceKeys()) return keys } func (this *MigrationContext) ReadThrottleControlReplicaKeys(throttleControlReplicas string) error { keys := mysql.NewInstanceKeyMap() if err := keys.ReadCommaDelimitedList(throttleControlReplicas); err != nil { return err } this.throttleMutex.Lock() defer this.throttleMutex.Unlock() this.throttleControlReplicaKeys = keys return nil } func (this *MigrationContext) AddThrottleControlReplicaKey(key mysql.InstanceKey) error { this.throttleMutex.Lock() defer this.throttleMutex.Unlock() this.throttleControlReplicaKeys.AddKey(key) return nil } // ApplyCredentials sorts out the credentials between the config file and the CLI flags func (this *MigrationContext) ApplyCredentials() { this.configMutex.Lock() defer this.configMutex.Unlock() if this.config.Client.User != "" { this.InspectorConnectionConfig.User = this.config.Client.User } if this.CliUser != "" { // Override this.InspectorConnectionConfig.User = this.CliUser } if this.config.Client.Password != "" { this.InspectorConnectionConfig.Password = this.config.Client.Password } if this.CliPassword != "" { // Override this.InspectorConnectionConfig.Password = this.CliPassword } } func (this *MigrationContext) SetupTLS() error { if this.UseTLS { return this.InspectorConnectionConfig.UseTLS(this.TLSCACertificate, this.TLSCertificate, this.TLSKey, this.TLSAllowInsecure) } return nil } // ReadConfigFile attempts to read the config file, if it exists func (this *MigrationContext) ReadConfigFile() error { this.configMutex.Lock() defer this.configMutex.Unlock() if this.ConfigFile == "" { return nil } cfg, err := ini.Load(this.ConfigFile) if err != nil { return err } if cfg.Section("client").HasKey("user") { this.config.Client.User = cfg.Section("client").Key("user").String() } if cfg.Section("client").HasKey("password") { this.config.Client.Password = cfg.Section("client").Key("password").String() } if cfg.Section("osc").HasKey("chunk_size") { this.config.Osc.Chunk_Size, err = cfg.Section("osc").Key("chunk_size").Int64() if err != nil { return fmt.Errorf("Unable to read osc chunk size: %w", err) } } if cfg.Section("osc").HasKey("max_load") { this.config.Osc.Max_Load = cfg.Section("osc").Key("max_load").String() } if cfg.Section("osc").HasKey("replication_lag_query") { this.config.Osc.Replication_Lag_Query = cfg.Section("osc").Key("replication_lag_query").String() } if cfg.Section("osc").HasKey("max_lag_millis") { this.config.Osc.Max_Lag_Millis, err = cfg.Section("osc").Key("max_lag_millis").Int64() if err != nil { return fmt.Errorf("Unable to read max lag millis: %w", err) } } // We accept user & password in the form "${SOME_ENV_VARIABLE}" in which case we pull // the given variable from os env if submatch := envVariableRegexp.FindStringSubmatch(this.config.Client.User); len(submatch) > 1 { this.config.Client.User = os.Getenv(submatch[1]) } if submatch := envVariableRegexp.FindStringSubmatch(this.config.Client.Password); len(submatch) > 1 { this.config.Client.Password = os.Getenv(submatch[1]) } return nil } // getGhostTriggerName generates the name of a ghost trigger, based on original trigger name // or a given trigger name func (this *MigrationContext) GetGhostTriggerName(triggerName string) string { if this.RemoveTriggerSuffix && strings.HasSuffix(triggerName, this.TriggerSuffix) { return strings.TrimSuffix(triggerName, this.TriggerSuffix) } // else return triggerName + this.TriggerSuffix } // ValidateGhostTriggerLengthBelowMaxLength checks if the given trigger name (already transformed // by GetGhostTriggerName) does not exceed the maximum allowed length. func (this *MigrationContext) ValidateGhostTriggerLengthBelowMaxLength(triggerName string) bool { return utf8.RuneCountInString(triggerName) <= mysql.MaxTableNameLength } // GetContext returns the migration context for cancellation checking func (this *MigrationContext) GetContext() context.Context { return this.ctx } // SetAbortError stores the fatal error that triggered abort // Only the first error is stored (subsequent errors are ignored) func (this *MigrationContext) SetAbortError(err error) { this.abortMutex.Lock() defer this.abortMutex.Unlock() if this.AbortError == nil { this.AbortError = err } } // GetAbortError retrieves the stored abort error func (this *MigrationContext) GetAbortError() error { this.abortMutex.Lock() defer this.abortMutex.Unlock() return this.AbortError } // CancelContext cancels the migration context to signal all goroutines to stop // The cancel function is safe to call multiple times and from multiple goroutines. func (this *MigrationContext) CancelContext() { if this.cancelFunc != nil { this.cancelFunc() } } // SendWithContext attempts to send a value to a channel, but returns early // if the context is cancelled. This prevents goroutine deadlocks when the // channel receiver has exited due to an error. // // Use this instead of bare channel sends (ch <- val) in goroutines to ensure // proper cleanup when the migration is aborted. // // Example: // // if err := base.SendWithContext(ctx, ch, value); err != nil { // return err // context was cancelled // } func SendWithContext[T any](ctx context.Context, ch chan<- T, val T) error { select { case ch <- val: return nil case <-ctx.Done(): return ctx.Err() } } ================================================ FILE: go/base/context_test.go ================================================ /* Copyright 2021 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package base import ( "errors" "os" "strings" "sync" "testing" "time" "github.com/openark/golib/log" "github.com/stretchr/testify/require" ) func init() { log.SetLevel(log.ERROR) } func TestGetTableNames(t *testing.T) { { context := NewMigrationContext() context.OriginalTableName = "some_table" require.Equal(t, "_some_table_del", context.GetOldTableName()) require.Equal(t, "_some_table_gho", context.GetGhostTableName()) require.Equal(t, "_some_table_ghc", context.GetChangelogTableName(), "_some_table_ghc") } { context := NewMigrationContext() context.OriginalTableName = "a123456789012345678901234567890123456789012345678901234567890" require.Equal(t, "_a1234567890123456789012345678901234567890123456789012345678_del", context.GetOldTableName()) require.Equal(t, "_a1234567890123456789012345678901234567890123456789012345678_gho", context.GetGhostTableName()) require.Equal(t, "_a1234567890123456789012345678901234567890123456789012345678_ghc", context.GetChangelogTableName()) } { context := NewMigrationContext() context.OriginalTableName = "a123456789012345678901234567890123456789012345678901234567890123" oldTableName := context.GetOldTableName() require.Equal(t, "_a1234567890123456789012345678901234567890123456789012345678_del", oldTableName) } { context := NewMigrationContext() context.OriginalTableName = "a123456789012345678901234567890123456789012345678901234567890123" context.TimestampOldTable = true longForm := "Jan 2, 2006 at 3:04pm (MST)" context.StartTime, _ = time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)") oldTableName := context.GetOldTableName() require.Equal(t, "_a1234567890123456789012345678901234567890123_20130203195400_del", oldTableName) } { context := NewMigrationContext() context.OriginalTableName = "foo_bar_baz" context.ForceTmpTableName = "tmp" require.Equal(t, "_tmp_del", context.GetOldTableName()) require.Equal(t, "_tmp_gho", context.GetGhostTableName()) require.Equal(t, "_tmp_ghc", context.GetChangelogTableName()) } } func TestGetTriggerNames(t *testing.T) { { context := NewMigrationContext() context.TriggerSuffix = "_gho" require.Equal(t, "my_trigger"+context.TriggerSuffix, context.GetGhostTriggerName("my_trigger")) } { context := NewMigrationContext() context.TriggerSuffix = "_gho" context.RemoveTriggerSuffix = true require.Equal(t, "my_trigger"+context.TriggerSuffix, context.GetGhostTriggerName("my_trigger")) } { context := NewMigrationContext() context.TriggerSuffix = "_gho" context.RemoveTriggerSuffix = true require.Equal(t, "my_trigger", context.GetGhostTriggerName("my_trigger_gho")) } { context := NewMigrationContext() context.TriggerSuffix = "_gho" context.RemoveTriggerSuffix = false require.Equal(t, "my_trigger_gho_gho", context.GetGhostTriggerName("my_trigger_gho")) } } func TestValidateGhostTriggerLengthBelowMaxLength(t *testing.T) { // Tests simulate the real call pattern: GetGhostTriggerName first, then validate the result. { // Short trigger name with suffix appended: well under 64 chars context := NewMigrationContext() context.TriggerSuffix = "_gho" ghostName := context.GetGhostTriggerName("my_trigger") // "my_trigger_gho" = 14 chars require.True(t, context.ValidateGhostTriggerLengthBelowMaxLength(ghostName)) } { // 64-char original + "_ghost" suffix = 70 chars → exceeds limit context := NewMigrationContext() context.TriggerSuffix = "_ghost" ghostName := context.GetGhostTriggerName(strings.Repeat("my_trigger_ghost", 4)) // 64 + 6 = 70 require.False(t, context.ValidateGhostTriggerLengthBelowMaxLength(ghostName)) } { // 48-char original + "_ghost" suffix = 54 chars → valid context := NewMigrationContext() context.TriggerSuffix = "_ghost" ghostName := context.GetGhostTriggerName(strings.Repeat("my_trigger_ghost", 3)) // 48 + 6 = 54 require.True(t, context.ValidateGhostTriggerLengthBelowMaxLength(ghostName)) } { // RemoveTriggerSuffix: 64-char name ending in "_ghost" → suffix removed → 58 chars → valid context := NewMigrationContext() context.TriggerSuffix = "_ghost" context.RemoveTriggerSuffix = true ghostName := context.GetGhostTriggerName(strings.Repeat("my_trigger_ghost", 4)) // suffix removed → 58 require.True(t, context.ValidateGhostTriggerLengthBelowMaxLength(ghostName)) } { // RemoveTriggerSuffix: name doesn't end in suffix → suffix appended → 65 + 6 = 71 chars → exceeds context := NewMigrationContext() context.TriggerSuffix = "_ghost" context.RemoveTriggerSuffix = true ghostName := context.GetGhostTriggerName(strings.Repeat("my_trigger_ghost", 4) + "X") // no match, appended → 71 require.False(t, context.ValidateGhostTriggerLengthBelowMaxLength(ghostName)) } { // RemoveTriggerSuffix: 70-char name ending in "_ghost" → suffix removed → 64 chars → exactly at limit → valid context := NewMigrationContext() context.TriggerSuffix = "_ghost" context.RemoveTriggerSuffix = true ghostName := context.GetGhostTriggerName(strings.Repeat("my_trigger_ghost", 4) + "_ghost") // suffix removed → 64 require.True(t, context.ValidateGhostTriggerLengthBelowMaxLength(ghostName)) } { // Edge case: exactly 64 chars after transformation → valid (boundary test) context := NewMigrationContext() context.TriggerSuffix = "_ght" originalName := strings.Repeat("x", 60) // 60 chars ghostName := context.GetGhostTriggerName(originalName) // 60 + 4 = 64 require.Equal(t, 64, len(ghostName)) require.True(t, context.ValidateGhostTriggerLengthBelowMaxLength(ghostName)) } { // Edge case: 65 chars after transformation → exceeds (boundary test) context := NewMigrationContext() context.TriggerSuffix = "_ght" originalName := strings.Repeat("x", 61) // 61 chars ghostName := context.GetGhostTriggerName(originalName) // 61 + 4 = 65 require.Equal(t, 65, len(ghostName)) require.False(t, context.ValidateGhostTriggerLengthBelowMaxLength(ghostName)) } } func TestReadConfigFile(t *testing.T) { { context := NewMigrationContext() context.ConfigFile = "/does/not/exist" if err := context.ReadConfigFile(); err == nil { t.Fatal("Expected .ReadConfigFile() to return an error, got nil") } } { f, err := os.CreateTemp("", t.Name()) if err != nil { t.Fatalf("Failed to create tmp file: %v", err) } defer os.Remove(f.Name()) f.Write([]byte("[client]")) context := NewMigrationContext() context.ConfigFile = f.Name() if err := context.ReadConfigFile(); err != nil { t.Fatalf(".ReadConfigFile() failed: %v", err) } } { f, err := os.CreateTemp("", t.Name()) if err != nil { t.Fatalf("Failed to create tmp file: %v", err) } defer os.Remove(f.Name()) f.Write([]byte("[client]\nuser=test\npassword=123456")) context := NewMigrationContext() context.ConfigFile = f.Name() if err := context.ReadConfigFile(); err != nil { t.Fatalf(".ReadConfigFile() failed: %v", err) } if context.config.Client.User != "test" { t.Fatalf("Expected client user %q, got %q", "test", context.config.Client.User) } else if context.config.Client.Password != "123456" { t.Fatalf("Expected client password %q, got %q", "123456", context.config.Client.Password) } } { f, err := os.CreateTemp("", t.Name()) if err != nil { t.Fatalf("Failed to create tmp file: %v", err) } defer os.Remove(f.Name()) f.Write([]byte("[osc]\nmax_load=10")) context := NewMigrationContext() context.ConfigFile = f.Name() if err := context.ReadConfigFile(); err != nil { t.Fatalf(".ReadConfigFile() failed: %v", err) } if context.config.Osc.Max_Load != "10" { t.Fatalf("Expected osc 'max_load' %q, got %q", "10", context.config.Osc.Max_Load) } } } func TestSetAbortError_StoresFirstError(t *testing.T) { ctx := NewMigrationContext() err1 := errors.New("first error") err2 := errors.New("second error") ctx.SetAbortError(err1) ctx.SetAbortError(err2) got := ctx.GetAbortError() if got != err1 { //nolint:errorlint // Testing pointer equality for sentinel error t.Errorf("Expected first error %v, got %v", err1, got) } } func TestSetAbortError_ThreadSafe(t *testing.T) { ctx := NewMigrationContext() var wg sync.WaitGroup errs := []error{ errors.New("error 1"), errors.New("error 2"), errors.New("error 3"), } // Launch 3 goroutines trying to set error concurrently for _, err := range errs { wg.Add(1) go func(e error) { defer wg.Done() ctx.SetAbortError(e) }(err) } wg.Wait() // Should store exactly one of the errors got := ctx.GetAbortError() if got == nil { t.Fatal("Expected error to be stored, got nil") } // Verify it's one of the errors we sent found := false for _, err := range errs { if got == err { //nolint:errorlint // Testing pointer equality for sentinel error found = true break } } if !found { t.Errorf("Stored error %v not in list of sent errors", got) } } ================================================ FILE: go/base/default_logger.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package base import ( "github.com/openark/golib/log" ) type simpleLogger struct{} func NewDefaultLogger() *simpleLogger { return &simpleLogger{} } func (*simpleLogger) Debug(args ...interface{}) { log.Debug(args[0].(string), args[1:]) } func (*simpleLogger) Debugf(format string, args ...interface{}) { log.Debugf(format, args...) } func (*simpleLogger) Info(args ...interface{}) { log.Info(args[0].(string), args[1:]) } func (*simpleLogger) Infof(format string, args ...interface{}) { log.Infof(format, args...) } func (*simpleLogger) Warning(args ...interface{}) error { return log.Warning(args[0].(string), args[1:]) } func (*simpleLogger) Warningf(format string, args ...interface{}) error { return log.Warningf(format, args...) } func (*simpleLogger) Error(args ...interface{}) error { return log.Error(args[0].(string), args[1:]) } func (*simpleLogger) Errorf(format string, args ...interface{}) error { return log.Errorf(format, args...) } func (*simpleLogger) Errore(err error) error { return log.Errore(err) } func (*simpleLogger) Fatal(args ...interface{}) error { return log.Fatal(args[0].(string), args[1:]) } func (*simpleLogger) Fatalf(format string, args ...interface{}) error { return log.Fatalf(format, args...) } func (*simpleLogger) Fatale(err error) error { return log.Fatale(err) } func (*simpleLogger) SetLevel(level log.LogLevel) { log.SetLevel(level) } func (*simpleLogger) SetPrintStackTrace(printStackTraceFlag bool) { log.SetPrintStackTrace(printStackTraceFlag) } ================================================ FILE: go/base/load_map.go ================================================ /* Copyright 2016 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package base import ( "fmt" "sort" "strconv" "strings" ) // LoadMap is a mapping of status variable & threshold // e.g. [Threads_connected: 100, Threads_running: 50] type LoadMap map[string]int64 func NewLoadMap() LoadMap { result := make(map[string]int64) return result } // NewLoadMap parses a `--*-load` flag (e.g. `--max-load`), which is in multiple // key-value format, such as: // // 'Threads_running=100,Threads_connected=500' func ParseLoadMap(loadList string) (LoadMap, error) { result := NewLoadMap() if loadList == "" { return result, nil } loadConditions := strings.Split(loadList, ",") for _, loadCondition := range loadConditions { loadTokens := strings.Split(loadCondition, "=") if len(loadTokens) != 2 { return result, fmt.Errorf("Error parsing load condition: %s", loadCondition) } if loadTokens[0] == "" { return result, fmt.Errorf("Error parsing status variable in load condition: %s", loadCondition) } if n, err := strconv.ParseInt(loadTokens[1], 10, 0); err != nil { return result, fmt.Errorf("Error parsing numeric value in load condition: %s", loadCondition) } else { result[loadTokens[0]] = n } } return result, nil } // Duplicate creates a clone of this map func (this *LoadMap) Duplicate() LoadMap { dup := make(map[string]int64) for k, v := range *this { dup[k] = v } return dup } // String() returns a string representation of this map func (this *LoadMap) String() string { tokens := []string{} for key, val := range *this { token := fmt.Sprintf("%s=%d", key, val) tokens = append(tokens, token) } sort.Strings(tokens) return strings.Join(tokens, ",") } ================================================ FILE: go/base/load_map_test.go ================================================ /* Copyright 2016 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package base import ( "testing" "github.com/openark/golib/log" "github.com/stretchr/testify/require" ) func init() { log.SetLevel(log.ERROR) } func TestParseLoadMap(t *testing.T) { { loadList := "" m, err := ParseLoadMap(loadList) require.NoError(t, err) require.Len(t, m, 0) } { loadList := "threads_running=20,threads_connected=10" m, err := ParseLoadMap(loadList) require.NoError(t, err) require.Len(t, m, 2) require.Equal(t, int64(20), m["threads_running"]) require.Equal(t, int64(10), m["threads_connected"]) } { loadList := "threads_running=20=30,threads_connected=10" _, err := ParseLoadMap(loadList) require.Error(t, err) } { loadList := "threads_running=20,threads_connected" _, err := ParseLoadMap(loadList) require.Error(t, err) } } func TestString(t *testing.T) { { m, _ := ParseLoadMap("") s := m.String() require.Equal(t, "", s) } { loadList := "threads_running=20,threads_connected=10" m, _ := ParseLoadMap(loadList) s := m.String() require.Equal(t, "threads_connected=10,threads_running=20", s) } } ================================================ FILE: go/base/utils.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package base import ( "fmt" "os" "regexp" "strings" "time" gosql "database/sql" "github.com/github/gh-ost/go/mysql" ) var ( prettifyDurationRegexp = regexp.MustCompile("([.][0-9]+)") ) func PrettifyDurationOutput(d time.Duration) string { if d < time.Second { return "0s" } return prettifyDurationRegexp.ReplaceAllString(d.String(), "") } func FileExists(fileName string) bool { if _, err := os.Stat(fileName); err == nil { return true } return false } func TouchFile(fileName string) error { f, err := os.OpenFile(fileName, os.O_APPEND|os.O_CREATE, 0755) if err != nil { return err } return f.Close() } // StringContainsAll returns true if `s` contains all non empty given `substrings` // The function returns `false` if no non-empty arguments are given. func StringContainsAll(s string, substrings ...string) bool { nonEmptyStringsFound := false for _, substring := range substrings { if substring == "" { continue } if strings.Contains(s, substring) { nonEmptyStringsFound = true } else { // Immediate failure return false } } return nonEmptyStringsFound } func ValidateConnection(db *gosql.DB, connectionConfig *mysql.ConnectionConfig, migrationContext *MigrationContext, name string) (string, error) { versionQuery := `select @@global.version` var version string if err := db.QueryRow(versionQuery).Scan(&version); err != nil { return "", err } if migrationContext.SkipPortValidation { return version, nil } var extraPort int extraPortQuery := `select @@global.extra_port` if err := db.QueryRow(extraPortQuery).Scan(&extraPort); err != nil { //nolint:staticcheck // swallow this error. not all servers support extra_port } // AliyunRDS set users port to "NULL", replace it by gh-ost param // GCP set users port to "NULL", replace it by gh-ost param // Azure MySQL set users port to a different value by design, replace it by gh-ost para var port int if migrationContext.AliyunRDS || migrationContext.GoogleCloudPlatform || migrationContext.AzureMySQL { port = connectionConfig.Key.Port } else { portQuery := `select @@global.port` if err := db.QueryRow(portQuery).Scan(&port); err != nil { return "", err } } if connectionConfig.Key.Port == port || (extraPort > 0 && connectionConfig.Key.Port == extraPort) { migrationContext.Log.Infof("%s connection validated on %+v", name, connectionConfig.Key) return version, nil } else if extraPort == 0 { return "", fmt.Errorf("Unexpected database port reported: %+v", port) } else { return "", fmt.Errorf("Unexpected database port reported: %+v / extra_port: %+v", port, extraPort) } } ================================================ FILE: go/base/utils_test.go ================================================ /* Copyright 2016 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package base import ( "testing" "github.com/openark/golib/log" "github.com/stretchr/testify/require" ) func init() { log.SetLevel(log.ERROR) } func TestStringContainsAll(t *testing.T) { s := `insert,delete,update` require.False(t, StringContainsAll(s)) require.False(t, StringContainsAll(s, "")) require.False(t, StringContainsAll(s, "drop")) require.True(t, StringContainsAll(s, "insert")) require.False(t, StringContainsAll(s, "insert", "drop")) require.True(t, StringContainsAll(s, "insert", "")) require.True(t, StringContainsAll(s, "insert", "update", "delete")) } ================================================ FILE: go/binlog/binlog_dml_event.go ================================================ /* Copyright 2016 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package binlog import ( "fmt" "strings" "github.com/github/gh-ost/go/sql" ) type EventDML string const ( NotDML EventDML = "NoDML" InsertDML EventDML = "Insert" UpdateDML EventDML = "Update" DeleteDML EventDML = "Delete" ) func ToEventDML(description string) EventDML { // description can be a statement (`UPDATE my_table ...`) or a RBR event name (`UpdateRowsEventV2`) description = strings.TrimSpace(strings.Split(description, " ")[0]) switch strings.ToLower(description) { case "insert": return InsertDML case "update": return UpdateDML case "delete": return DeleteDML } if strings.HasPrefix(description, "WriteRows") { return InsertDML } if strings.HasPrefix(description, "UpdateRows") { return UpdateDML } if strings.HasPrefix(description, "DeleteRows") { return DeleteDML } return NotDML } // BinlogDMLEvent is a binary log rows (DML) event entry, with data type BinlogDMLEvent struct { DatabaseName string TableName string DML EventDML WhereColumnValues *sql.ColumnValues NewColumnValues *sql.ColumnValues } func NewBinlogDMLEvent(databaseName, tableName string, dml EventDML) *BinlogDMLEvent { event := &BinlogDMLEvent{ DatabaseName: databaseName, TableName: tableName, DML: dml, } return event } func (this *BinlogDMLEvent) String() string { return fmt.Sprintf("[%+v on %s:%s]", this.DML, this.DatabaseName, this.TableName) } ================================================ FILE: go/binlog/binlog_entry.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package binlog import ( "fmt" "github.com/github/gh-ost/go/mysql" ) // BinlogEntry describes an entry in the binary log type BinlogEntry struct { Coordinates mysql.BinlogCoordinates DmlEvent *BinlogDMLEvent } // NewBinlogEntryAt creates an empty, ready to go BinlogEntry object func NewBinlogEntryAt(coordinates mysql.BinlogCoordinates) *BinlogEntry { binlogEntry := &BinlogEntry{ Coordinates: coordinates, } return binlogEntry } // String() returns a string representation of this binlog entry func (this *BinlogEntry) String() string { return fmt.Sprintf("[BinlogEntry at %+v; dml:%+v]", this.Coordinates, this.DmlEvent) } ================================================ FILE: go/binlog/binlog_reader.go ================================================ /* Copyright 2016 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package binlog // BinlogReader is a general interface whose implementations can choose their methods of reading // a binary log file and parsing it into binlog entries type BinlogReader interface { StreamEvents(canStopStreaming func() bool, entriesChannel chan<- *BinlogEntry) error Reconnect() error } ================================================ FILE: go/binlog/gomysql_reader.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package binlog import ( "fmt" "sync" "github.com/github/gh-ost/go/base" "github.com/github/gh-ost/go/mysql" "github.com/github/gh-ost/go/sql" "time" gomysql "github.com/go-mysql-org/go-mysql/mysql" "github.com/go-mysql-org/go-mysql/replication" uuid "github.com/google/uuid" "golang.org/x/net/context" ) type GoMySQLReader struct { migrationContext *base.MigrationContext connectionConfig *mysql.ConnectionConfig binlogSyncer *replication.BinlogSyncer binlogStreamer *replication.BinlogStreamer currentCoordinates mysql.BinlogCoordinates currentCoordinatesMutex *sync.Mutex // LastTrxCoords are the coordinates of the last transaction completely read. // If using the file coordinates it is binlog position of the transaction's XID event. LastTrxCoords mysql.BinlogCoordinates } func NewGoMySQLReader(migrationContext *base.MigrationContext) *GoMySQLReader { connectionConfig := migrationContext.InspectorConnectionConfig return &GoMySQLReader{ migrationContext: migrationContext, connectionConfig: connectionConfig, currentCoordinatesMutex: &sync.Mutex{}, binlogSyncer: replication.NewBinlogSyncer(replication.BinlogSyncerConfig{ ServerID: uint32(migrationContext.ReplicaServerId), Flavor: gomysql.MySQLFlavor, Host: connectionConfig.Key.Hostname, Port: uint16(connectionConfig.Key.Port), User: connectionConfig.User, Password: connectionConfig.Password, TLSConfig: connectionConfig.TLSConfig(), UseDecimal: true, TimestampStringLocation: time.UTC, MaxReconnectAttempts: migrationContext.BinlogSyncerMaxReconnectAttempts, }), } } // ConnectBinlogStreamer func (this *GoMySQLReader) ConnectBinlogStreamer(coordinates mysql.BinlogCoordinates) (err error) { if coordinates.IsEmpty() { return this.migrationContext.Log.Errorf("Empty coordinates at ConnectBinlogStreamer()") } this.currentCoordinatesMutex.Lock() defer this.currentCoordinatesMutex.Unlock() this.currentCoordinates = coordinates this.migrationContext.Log.Infof("Connecting binlog streamer at %+v", coordinates) // Start sync with specified GTID set or binlog file and position if this.migrationContext.UseGTIDs { coords := coordinates.(*mysql.GTIDBinlogCoordinates) this.binlogStreamer, err = this.binlogSyncer.StartSyncGTID(coords.GTIDSet) } else { coords := this.currentCoordinates.(*mysql.FileBinlogCoordinates) this.binlogStreamer, err = this.binlogSyncer.StartSync(gomysql.Position{ Name: coords.LogFile, Pos: uint32(coords.LogPos)}, ) } return err } func (this *GoMySQLReader) GetCurrentBinlogCoordinates() mysql.BinlogCoordinates { this.currentCoordinatesMutex.Lock() defer this.currentCoordinatesMutex.Unlock() return this.currentCoordinates.Clone() } func (this *GoMySQLReader) handleRowsEvent(ev *replication.BinlogEvent, rowsEvent *replication.RowsEvent, entriesChannel chan<- *BinlogEntry) error { currentCoords := this.GetCurrentBinlogCoordinates() dml := ToEventDML(ev.Header.EventType.String()) if dml == NotDML { return fmt.Errorf("Unknown DML type: %s", ev.Header.EventType.String()) } for i, row := range rowsEvent.Rows { if dml == UpdateDML && i%2 == 1 { // An update has two rows (WHERE+SET) // We do both at the same time continue } binlogEntry := NewBinlogEntryAt(currentCoords) binlogEntry.DmlEvent = NewBinlogDMLEvent( string(rowsEvent.Table.Schema), string(rowsEvent.Table.Table), dml, ) switch dml { case InsertDML: { binlogEntry.DmlEvent.NewColumnValues = sql.ToColumnValues(row) } case UpdateDML: { binlogEntry.DmlEvent.WhereColumnValues = sql.ToColumnValues(row) binlogEntry.DmlEvent.NewColumnValues = sql.ToColumnValues(rowsEvent.Rows[i+1]) } case DeleteDML: { binlogEntry.DmlEvent.WhereColumnValues = sql.ToColumnValues(row) } } // The channel will do the throttling. Whoever is reading from the channel // decides whether action is taken synchronously (meaning we wait before // next iteration) or asynchronously (we keep pushing more events) // In reality, reads will be synchronous entriesChannel <- binlogEntry } return nil } // StreamEvents func (this *GoMySQLReader) StreamEvents(canStopStreaming func() bool, entriesChannel chan<- *BinlogEntry) error { if canStopStreaming() { return nil } for { if canStopStreaming() { break } ev, err := this.binlogStreamer.GetEvent(context.Background()) if err != nil { return err } // Update binlog coords if using file-based coords. // GTID coordinates are updated on receiving GTID events. if !this.migrationContext.UseGTIDs { this.currentCoordinatesMutex.Lock() coords := this.currentCoordinates.(*mysql.FileBinlogCoordinates) prevCoords := coords.Clone().(*mysql.FileBinlogCoordinates) coords.LogPos = int64(ev.Header.LogPos) coords.EventSize = int64(ev.Header.EventSize) if coords.IsLogPosOverflowBeyond4Bytes(prevCoords) { this.currentCoordinatesMutex.Unlock() return fmt.Errorf("Unexpected rows event at %+v, the binlog end_log_pos is overflow 4 bytes", coords) } this.currentCoordinatesMutex.Unlock() } switch event := ev.Event.(type) { case *replication.GTIDEvent: if !this.migrationContext.UseGTIDs { continue } sid, err := uuid.FromBytes(event.SID) if err != nil { return err } this.currentCoordinatesMutex.Lock() if this.LastTrxCoords != nil { this.currentCoordinates = this.LastTrxCoords.Clone() } coords := this.currentCoordinates.(*mysql.GTIDBinlogCoordinates) trxGset := gomysql.NewUUIDSet(sid, gomysql.Interval{Start: event.GNO, Stop: event.GNO + 1}) coords.GTIDSet.AddSet(trxGset) this.currentCoordinatesMutex.Unlock() case *replication.RotateEvent: if this.migrationContext.UseGTIDs { continue } this.currentCoordinatesMutex.Lock() coords := this.currentCoordinates.(*mysql.FileBinlogCoordinates) coords.LogFile = string(event.NextLogName) this.migrationContext.Log.Infof("rotate to next log from %s:%d to %s", coords.LogFile, int64(ev.Header.LogPos), event.NextLogName) this.currentCoordinatesMutex.Unlock() case *replication.XIDEvent: if this.migrationContext.UseGTIDs { this.LastTrxCoords = &mysql.GTIDBinlogCoordinates{GTIDSet: event.GSet.(*gomysql.MysqlGTIDSet)} } else { this.LastTrxCoords = this.currentCoordinates.Clone() } case *replication.RowsEvent: if err := this.handleRowsEvent(ev, event, entriesChannel); err != nil { return err } } } this.migrationContext.Log.Debugf("done streaming events") return nil } func (this *GoMySQLReader) Close() error { this.binlogSyncer.Close() return nil } ================================================ FILE: go/binlog/testdata/rbr-sample-0.txt ================================================ /* these are the statements that were used to execute the RBR log: create table samplet(id int primary key, license int, name varchar(64), unique key license_uidx(license)) engine=innodb; insert into samplet values (1,1,'a'); insert into samplet values (2,2,'extended'),(3,3,'extended'); begin; insert into samplet values (4,4,'transaction'); insert into samplet values (5,5,'transaction'); insert into samplet values (6,6,'transaction'); commit; update samplet set name='update' where id=5; replace into samplet values (2,4,'replaced 2,4'); insert into samplet values (7,7,'7'); insert into samplet values (8,8,'8'); delete from samplet where id >= 7; insert into samplet values (9,9,'9'); begin; update samplet set name='update 9' where id=9; delete from samplet where license=3; insert into samplet values (10,10,'10'); commit; */ /*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/; /*!40019 SET @@session.max_insert_delayed_threads=0*/; /*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/; DELIMITER /*!*/; # at 4 #160324 11:06:59 server id 104 end_log_pos 120 CRC32 0x059174d8 Start: binlog v 4, server v 5.6.28-log created 160324 11:06:59 # at 120 #160324 11:09:53 server id 1 end_log_pos 313 CRC32 0x511efdf1 Query thread_id=7940 exec_time=0 error_code=0 use `test`/*!*/; SET TIMESTAMP=1458814193/*!*/; SET @@session.pseudo_thread_id=7940/*!*/; SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/; SET @@session.sql_mode=1073741824/*!*/; SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/; /*!\C utf8 *//*!*/; SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=8/*!*/; SET @@session.lc_time_names=0/*!*/; SET @@session.collation_database=DEFAULT/*!*/; create table samplet(id int primary key, license int, name varchar(64), unique key license_uidx(license)) engine=innodb /*!*/; # at 313 #160324 11:10:13 server id 1 end_log_pos 385 CRC32 0x6b95100a Query thread_id=7940 exec_time=0 error_code=0 SET TIMESTAMP=1458814213/*!*/; BEGIN /*!*/; # at 385 #160324 11:10:13 server id 1 end_log_pos 439 CRC32 0xfa97ad69 Table_map: `test`.`samplet` mapped to number 72 # at 439 #160324 11:10:13 server id 1 end_log_pos 485 CRC32 0xae356826 Write_rows: table id 72 flags: STMT_END_F ### INSERT INTO `test`.`samplet` ### SET ### @1=1 ### @2=1 ### @3='a' # at 485 #160324 11:10:13 server id 1 end_log_pos 516 CRC32 0xf60389e3 Xid = 49802 COMMIT/*!*/; # at 516 #160324 11:10:35 server id 1 end_log_pos 588 CRC32 0x1a8730ad Query thread_id=7940 exec_time=0 error_code=0 SET TIMESTAMP=1458814235/*!*/; BEGIN /*!*/; # at 588 #160324 11:10:35 server id 1 end_log_pos 642 CRC32 0xac564207 Table_map: `test`.`samplet` mapped to number 72 # at 642 #160324 11:10:35 server id 1 end_log_pos 713 CRC32 0x3020ee9e Write_rows: table id 72 flags: STMT_END_F ### INSERT INTO `test`.`samplet` ### SET ### @1=2 ### @2=2 ### @3='extended' ### INSERT INTO `test`.`samplet` ### SET ### @1=3 ### @2=3 ### @3='extended' # at 713 #160324 11:10:35 server id 1 end_log_pos 744 CRC32 0x341f0c1d Xid = 49848 COMMIT/*!*/; # at 744 #160324 11:10:47 server id 1 end_log_pos 816 CRC32 0x2454c8aa Query thread_id=7940 exec_time=14 error_code=0 SET TIMESTAMP=1458814247/*!*/; BEGIN /*!*/; # at 816 #160324 11:10:47 server id 1 end_log_pos 870 CRC32 0x92018566 Table_map: `test`.`samplet` mapped to number 72 # at 870 #160324 11:10:47 server id 1 end_log_pos 926 CRC32 0x5b882310 Write_rows: table id 72 flags: STMT_END_F ### INSERT INTO `test`.`samplet` ### SET ### @1=4 ### @2=4 ### @3='transaction' # at 926 #160324 11:10:54 server id 1 end_log_pos 980 CRC32 0x374b624b Table_map: `test`.`samplet` mapped to number 72 # at 980 #160324 11:10:54 server id 1 end_log_pos 1036 CRC32 0xfff6a2b9 Write_rows: table id 72 flags: STMT_END_F ### INSERT INTO `test`.`samplet` ### SET ### @1=5 ### @2=5 ### @3='transaction' # at 1036 #160324 11:10:59 server id 1 end_log_pos 1090 CRC32 0x37e19690 Table_map: `test`.`samplet` mapped to number 72 # at 1090 #160324 11:10:59 server id 1 end_log_pos 1146 CRC32 0x58a01053 Write_rows: table id 72 flags: STMT_END_F ### INSERT INTO `test`.`samplet` ### SET ### @1=6 ### @2=6 ### @3='transaction' # at 1146 #160324 11:10:59 server id 1 end_log_pos 1177 CRC32 0xdd5de027 Xid = 49894 COMMIT/*!*/; # at 1177 #160324 11:11:16 server id 1 end_log_pos 1249 CRC32 0x5c4a609b Query thread_id=7940 exec_time=0 error_code=0 SET TIMESTAMP=1458814276/*!*/; BEGIN /*!*/; # at 1249 #160324 11:11:16 server id 1 end_log_pos 1303 CRC32 0x9d3c756b Table_map: `test`.`samplet` mapped to number 72 # at 1303 #160324 11:11:16 server id 1 end_log_pos 1352 CRC32 0x9b0d2ff4 Update_rows: table id 72 flags: STMT_END_F ### UPDATE `test`.`samplet` ### WHERE ### @1=5 ### SET ### @3='update' # at 1352 #160324 11:11:16 server id 1 end_log_pos 1383 CRC32 0x8e051bed Xid = 49931 COMMIT/*!*/; # at 1383 #160324 11:11:44 server id 1 end_log_pos 1455 CRC32 0xe9744e83 Query thread_id=7940 exec_time=0 error_code=0 SET TIMESTAMP=1458814304/*!*/; BEGIN /*!*/; # at 1455 #160324 11:11:44 server id 1 end_log_pos 1509 CRC32 0x34672cb1 Table_map: `test`.`samplet` mapped to number 72 # at 1509 #160324 11:11:44 server id 1 end_log_pos 1549 CRC32 0x4383e9ee Delete_rows: table id 72 # at 1549 #160324 11:11:44 server id 1 end_log_pos 1612 CRC32 0x899eb398 Update_rows: table id 72 flags: STMT_END_F ### DELETE FROM `test`.`samplet` ### WHERE ### @1=2 ### UPDATE `test`.`samplet` ### WHERE ### @1=4 ### SET ### @1=2 ### @2=4 ### @3='replaced 2,4' # at 1612 #160324 11:11:44 server id 1 end_log_pos 1643 CRC32 0x037a8fe1 Xid = 49977 COMMIT/*!*/; # at 1643 #160324 11:11:54 server id 1 end_log_pos 1715 CRC32 0xb02520cd Query thread_id=7940 exec_time=0 error_code=0 SET TIMESTAMP=1458814314/*!*/; BEGIN /*!*/; # at 1715 #160324 11:11:54 server id 1 end_log_pos 1769 CRC32 0xcbcf4323 Table_map: `test`.`samplet` mapped to number 72 # at 1769 #160324 11:11:54 server id 1 end_log_pos 1815 CRC32 0x4d52b057 Write_rows: table id 72 flags: STMT_END_F ### INSERT INTO `test`.`samplet` ### SET ### @1=7 ### @2=7 ### @3='7' # at 1815 #160324 11:11:54 server id 1 end_log_pos 1846 CRC32 0x5289b6a4 Xid = 50001 COMMIT/*!*/; # at 1846 #160324 11:11:59 server id 1 end_log_pos 1918 CRC32 0x1758ab97 Query thread_id=7940 exec_time=0 error_code=0 SET TIMESTAMP=1458814319/*!*/; BEGIN /*!*/; # at 1918 #160324 11:11:59 server id 1 end_log_pos 1972 CRC32 0xa4602796 Table_map: `test`.`samplet` mapped to number 72 # at 1972 #160324 11:11:59 server id 1 end_log_pos 2018 CRC32 0x6a6eb0c9 Write_rows: table id 72 flags: STMT_END_F ### INSERT INTO `test`.`samplet` ### SET ### @1=8 ### @2=8 ### @3='8' # at 2018 #160324 11:11:59 server id 1 end_log_pos 2049 CRC32 0x6d0fef4d Xid = 50014 COMMIT/*!*/; # at 2049 #160324 11:12:12 server id 1 end_log_pos 2121 CRC32 0x6cd5da13 Query thread_id=7940 exec_time=0 error_code=0 SET TIMESTAMP=1458814332/*!*/; BEGIN /*!*/; # at 2121 #160324 11:12:12 server id 1 end_log_pos 2175 CRC32 0x8339241f Table_map: `test`.`samplet` mapped to number 72 # at 2175 #160324 11:12:12 server id 1 end_log_pos 2220 CRC32 0x669385e1 Delete_rows: table id 72 flags: STMT_END_F ### DELETE FROM `test`.`samplet` ### WHERE ### @1=7 ### DELETE FROM `test`.`samplet` ### WHERE ### @1=8 # at 2220 #160324 11:12:12 server id 1 end_log_pos 2251 CRC32 0xba81d2b0 Xid = 50038 COMMIT/*!*/; # at 2251 #160324 11:12:20 server id 1 end_log_pos 2323 CRC32 0x4c58be8c Query thread_id=7940 exec_time=0 error_code=0 SET TIMESTAMP=1458814340/*!*/; BEGIN /*!*/; # at 2323 #160324 11:12:20 server id 1 end_log_pos 2377 CRC32 0x9eb23ab9 Table_map: `test`.`samplet` mapped to number 72 # at 2377 #160324 11:12:20 server id 1 end_log_pos 2423 CRC32 0xac8116ec Write_rows: table id 72 flags: STMT_END_F ### INSERT INTO `test`.`samplet` ### SET ### @1=9 ### @2=9 ### @3='9' # at 2423 #160324 11:12:20 server id 1 end_log_pos 2454 CRC32 0x5ce77ad6 Xid = 50051 COMMIT/*!*/; # at 2454 #160324 11:12:50 server id 1 end_log_pos 2526 CRC32 0xed19acbd Query thread_id=7940 exec_time=26 error_code=0 SET TIMESTAMP=1458814370/*!*/; BEGIN /*!*/; # at 2526 #160324 11:12:50 server id 1 end_log_pos 2580 CRC32 0x0bf6b98f Table_map: `test`.`samplet` mapped to number 72 # at 2580 #160324 11:12:50 server id 1 end_log_pos 2631 CRC32 0x263c4579 Update_rows: table id 72 flags: STMT_END_F ### UPDATE `test`.`samplet` ### WHERE ### @1=9 ### SET ### @3='update 9' # at 2631 #160324 11:13:00 server id 1 end_log_pos 2685 CRC32 0x94b24c8b Table_map: `test`.`samplet` mapped to number 72 # at 2685 #160324 11:13:00 server id 1 end_log_pos 2725 CRC32 0xca43fe3a Delete_rows: table id 72 flags: STMT_END_F ### DELETE FROM `test`.`samplet` ### WHERE ### @1=3 # at 2725 #160324 11:13:14 server id 1 end_log_pos 2779 CRC32 0xc36088a2 Table_map: `test`.`samplet` mapped to number 72 # at 2779 #160324 11:13:14 server id 1 end_log_pos 2826 CRC32 0x98fc9dea Write_rows: table id 72 flags: STMT_END_F ### INSERT INTO `test`.`samplet` ### SET ### @1=10 ### @2=10 ### @3='10' # at 2826 #160324 11:13:14 server id 1 end_log_pos 2857 CRC32 0x729c371f Xid = 50163 COMMIT/*!*/; # at 2857 #160324 11:13:31 server id 104 end_log_pos 2904 CRC32 0x38531c7d Rotate to mysql-bin.000053 pos: 4 DELIMITER ; # End of log file ROLLBACK /* added by mysqlbinlog */; /*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/; /*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/; ================================================ FILE: go/binlog/testdata/rbr-sample-1.txt ================================================ /* these are the statements that were used to execute the RBR log: drop table if exists samplet; create table samplet(id int primary key, license int, name varchar(64), unique key license_uidx(license)) engine=innodb; insert into samplet values (1,1,'a'); insert into samplet values (2,2,'extended'),(3,3,'extended'); begin; insert into samplet values (4,4,'transaction'); insert into samplet values (5,5,'transaction'); insert into samplet values (6,6,'transaction'); commit; update samplet set name='update' where id=5; replace into samplet values (2,4,'replaced 2,4'); insert into samplet values (7,7,'7'); insert into samplet values (8,8,'8'); delete from samplet where id >= 7; insert into samplet values (9,9,'9'); begin; update samplet set name='update 9' where id=9; delete from samplet where license=3; insert into samplet values (10,10,'10'); commit; update samplet set name='update 5,6' where id in (5,6); begin; delete from samplet where id=5; rollback; */ ================================================ FILE: go/binlog/testdata/rbr-sample-2.txt ================================================ drop table if exists samplet; create table samplet(id int primary key, license int, name varchar(64), b tinyblob, unique key license_uidx(license)) engine=innodb; insert into samplet (id, license, name) values (1,1,'a'); insert into samplet (id, license, name) values (2,2,'extended'),(3,3,'extended'); begin; insert into samplet (id, license, name) values (4,4,'transaction'); insert into samplet (id, license, name) values (5,5,'transaction'); insert into samplet (id, license, name) values (6,6,'transaction'); commit; update samplet set name='update' where id=5; replace into samplet (id, license, name) values (2,4,'replaced 2,4'); insert into samplet (id, license, name, b) values (7,7,'7', x'89504E470D0A1A0A0000000D494844520000001000000010080200000090916836000000017352474200AECE1CE90000000467414D410000B18F0BFC6105000000097048597300000EC300000EC301C76FA8640000001E49444154384F6350DAE843126220493550F1A80662426C349406472801006AC91F1040F796BD0000000049454E44AE426082'); insert into samplet (id, license, name) values (8,8,'8'); delete from samplet where id >= 7; insert into samplet (id, license, name) values (9,9,'9'); begin; update samplet set name='update 9', b=x'89504E470D0A1A0A0000000D494844520000001000000010080200000090916836000000017352474200AECE1CE90000000467414D410000B18F0BFC6105000000097048597300000EC300000EC301C76FA8640000001E49444154384F6350DAE843126220493550F1A80662426C349406472801006AC91F1040F796BD0000000049454E44AE426082' where id=9; update samplet set name='update 9b' where id=9; delete from samplet where license=3; insert into samplet (id, license, name) values (10,10,'10'); commit; update samplet set name='update 5,6' where id in (5,6); begin; delete from samplet where id=5; rollback; ================================================ FILE: go/cmd/gh-ost/main.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package main import ( "flag" "fmt" "net/url" "os" "os/signal" "regexp" "syscall" "github.com/github/gh-ost/go/base" "github.com/github/gh-ost/go/logic" "github.com/github/gh-ost/go/sql" _ "github.com/go-sql-driver/mysql" "github.com/openark/golib/log" "golang.org/x/term" ) var AppVersion, GitCommit string // acceptSignals registers for OS signals func acceptSignals(migrationContext *base.MigrationContext) { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGHUP) go func() { for sig := range c { switch sig { case syscall.SIGHUP: migrationContext.Log.Infof("Received SIGHUP. Reloading configuration") if err := migrationContext.ReadConfigFile(); err != nil { log.Errore(err) } else { migrationContext.MarkPointOfInterest() } } } }() } // main is the application's entry point. It will either spawn a CLI or HTTP interfaces. func main() { migrationContext := base.NewMigrationContext() flag.StringVar(&migrationContext.InspectorConnectionConfig.Key.Hostname, "host", "127.0.0.1", "MySQL hostname (preferably a replica, not the master)") flag.StringVar(&migrationContext.AssumeMasterHostname, "assume-master-host", "", "(optional) explicitly tell gh-ost the identity of the master. Format: some.host.com[:port] This is useful in master-master setups where you wish to pick an explicit master, or in a tungsten-replicator where gh-ost is unable to determine the master") flag.IntVar(&migrationContext.InspectorConnectionConfig.Key.Port, "port", 3306, "MySQL port (preferably a replica, not the master)") flag.Float64Var(&migrationContext.InspectorConnectionConfig.Timeout, "mysql-timeout", 0.0, "Connect, read and write timeout for MySQL") flag.StringVar(&migrationContext.CliUser, "user", "", "MySQL user") flag.StringVar(&migrationContext.CliPassword, "password", "", "MySQL password") flag.StringVar(&migrationContext.CliMasterUser, "master-user", "", "MySQL user on master, if different from that on replica. Requires --assume-master-host") flag.StringVar(&migrationContext.CliMasterPassword, "master-password", "", "MySQL password on master, if different from that on replica. Requires --assume-master-host") flag.StringVar(&migrationContext.ConfigFile, "conf", "", "Config file") askPass := flag.Bool("ask-pass", false, "prompt for MySQL password") charset := flag.String("charset", "utf8mb4,utf8,latin1", "The default charset for the database connection is utf8mb4, utf8, latin1.") flag.BoolVar(&migrationContext.UseTLS, "ssl", false, "Enable SSL encrypted connections to MySQL hosts") flag.StringVar(&migrationContext.TLSCACertificate, "ssl-ca", "", "CA certificate in PEM format for TLS connections to MySQL hosts. Requires --ssl") flag.StringVar(&migrationContext.TLSCertificate, "ssl-cert", "", "Certificate in PEM format for TLS connections to MySQL hosts. Requires --ssl") flag.StringVar(&migrationContext.TLSKey, "ssl-key", "", "Key in PEM format for TLS connections to MySQL hosts. Requires --ssl") flag.BoolVar(&migrationContext.TLSAllowInsecure, "ssl-allow-insecure", false, "Skips verification of MySQL hosts' certificate chain and host name. Requires --ssl") flag.StringVar(&migrationContext.DatabaseName, "database", "", "database name (mandatory)") flag.StringVar(&migrationContext.OriginalTableName, "table", "", "table name (mandatory)") flag.StringVar(&migrationContext.AlterStatement, "alter", "", "alter statement (mandatory)") flag.BoolVar(&migrationContext.AttemptInstantDDL, "attempt-instant-ddl", false, "Attempt to use instant DDL for this migration first") storageEngine := flag.String("storage-engine", "innodb", "Specify table storage engine (default: 'innodb'). When 'rocksdb': the session transaction isolation level is changed from REPEATABLE_READ to READ_COMMITTED.") flag.BoolVar(&migrationContext.CountTableRows, "exact-rowcount", false, "actually count table rows as opposed to estimate them (results in more accurate progress estimation)") flag.BoolVar(&migrationContext.ConcurrentCountTableRows, "concurrent-rowcount", true, "(with --exact-rowcount), when true (default): count rows after row-copy begins, concurrently, and adjust row estimate later on; when false: first count rows, then start row copy") flag.BoolVar(&migrationContext.AllowedRunningOnMaster, "allow-on-master", false, "allow this migration to run directly on master. Preferably it would run on a replica") flag.BoolVar(&migrationContext.AllowedMasterMaster, "allow-master-master", false, "explicitly allow running in a master-master setup") flag.BoolVar(&migrationContext.NullableUniqueKeyAllowed, "allow-nullable-unique-key", false, "allow gh-ost to migrate based on a unique key with nullable columns. As long as no NULL values exist, this should be OK. If NULL values exist in chosen key, data may be corrupted. Use at your own risk!") flag.BoolVar(&migrationContext.ApproveRenamedColumns, "approve-renamed-columns", false, "in case your `ALTER` statement renames columns, gh-ost will note that and offer its interpretation of the rename. By default gh-ost does not proceed to execute. This flag approves that gh-ost's interpretation is correct") flag.BoolVar(&migrationContext.SkipRenamedColumns, "skip-renamed-columns", false, "in case your `ALTER` statement renames columns, gh-ost will note that and offer its interpretation of the rename. By default gh-ost does not proceed to execute. This flag tells gh-ost to skip the renamed columns, i.e. to treat what gh-ost thinks are renamed columns as unrelated columns. NOTE: you may lose column data") flag.BoolVar(&migrationContext.IsTungsten, "tungsten", false, "explicitly let gh-ost know that you are running on a tungsten-replication based topology (you are likely to also provide --assume-master-host)") flag.BoolVar(&migrationContext.DiscardForeignKeys, "discard-foreign-keys", false, "DANGER! This flag will migrate a table that has foreign keys and will NOT create foreign keys on the ghost table, thus your altered table will have NO foreign keys. This is useful for intentional dropping of foreign keys") flag.BoolVar(&migrationContext.SkipForeignKeyChecks, "skip-foreign-key-checks", false, "set to 'true' when you know for certain there are no foreign keys on your table, and wish to skip the time it takes for gh-ost to verify that") flag.BoolVar(&migrationContext.SkipStrictMode, "skip-strict-mode", false, "explicitly tell gh-ost binlog applier not to enforce strict sql mode") flag.BoolVar(&migrationContext.AllowZeroInDate, "allow-zero-in-date", false, "explicitly tell gh-ost binlog applier to ignore NO_ZERO_IN_DATE,NO_ZERO_DATE in sql_mode") flag.BoolVar(&migrationContext.AliyunRDS, "aliyun-rds", false, "set to 'true' when you execute on Aliyun RDS.") flag.BoolVar(&migrationContext.GoogleCloudPlatform, "gcp", false, "set to 'true' when you execute on a 1st generation Google Cloud Platform (GCP).") flag.BoolVar(&migrationContext.AzureMySQL, "azure", false, "set to 'true' when you execute on Azure Database on MySQL.") flag.BoolVar(&migrationContext.UseGTIDs, "gtid", false, "(experimental) set to 'true' to use MySQL GTIDs for binlog positioning.") executeFlag := flag.Bool("execute", false, "actually execute the alter & migrate the table. Default is noop: do some tests and exit") flag.BoolVar(&migrationContext.TestOnReplica, "test-on-replica", false, "Have the migration run on a replica, not on the master. At the end of migration replication is stopped, and tables are swapped and immediately swap-revert. Replication remains stopped and you can compare the two tables for building trust") flag.BoolVar(&migrationContext.TestOnReplicaSkipReplicaStop, "test-on-replica-skip-replica-stop", false, "When --test-on-replica is enabled, do not issue commands stop replication (requires --test-on-replica)") flag.BoolVar(&migrationContext.MigrateOnReplica, "migrate-on-replica", false, "Have the migration run on a replica, not on the master. This will do the full migration on the replica including cut-over (as opposed to --test-on-replica)") flag.BoolVar(&migrationContext.OkToDropTable, "ok-to-drop-table", false, "Shall the tool drop the old table at end of operation. DROPping tables can be a long locking operation, which is why I'm not doing it by default. I'm an online tool, yes?") flag.BoolVar(&migrationContext.InitiallyDropOldTable, "initially-drop-old-table", false, "Drop a possibly existing OLD table (remains from a previous run?) before beginning operation. Default is to panic and abort if such table exists") flag.BoolVar(&migrationContext.InitiallyDropGhostTable, "initially-drop-ghost-table", false, "Drop a possibly existing Ghost table (remains from a previous run?) before beginning operation. Default is to panic and abort if such table exists") flag.BoolVar(&migrationContext.TimestampOldTable, "timestamp-old-table", false, "Use a timestamp in old table name. This makes old table names unique and non conflicting cross migrations") cutOver := flag.String("cut-over", "atomic", "choose cut-over type (default|atomic, two-step)") flag.BoolVar(&migrationContext.ForceNamedCutOverCommand, "force-named-cut-over", false, "When true, the 'unpostpone|cut-over' interactive command must name the migrated table") flag.BoolVar(&migrationContext.ForceNamedPanicCommand, "force-named-panic", false, "When true, the 'panic' interactive command must name the migrated table") flag.BoolVar(&migrationContext.SwitchToRowBinlogFormat, "switch-to-rbr", false, "let this tool automatically switch binary log format to 'ROW' on the replica, if needed. The format will NOT be switched back. I'm too scared to do that, and wish to protect you if you happen to execute another migration while this one is running") flag.BoolVar(&migrationContext.AssumeRBR, "assume-rbr", false, "set to 'true' when you know for certain your server uses 'ROW' binlog_format. gh-ost is unable to tell, event after reading binlog_format, whether the replication process does indeed use 'ROW', and restarts replication to be certain RBR setting is applied. Such operation requires SUPER privileges which you might not have. Setting this flag avoids restarting replication and you can proceed to use gh-ost without SUPER privileges") flag.BoolVar(&migrationContext.CutOverExponentialBackoff, "cut-over-exponential-backoff", false, "Wait exponentially longer intervals between failed cut-over attempts. Wait intervals obey a maximum configurable with 'exponential-backoff-max-interval').") exponentialBackoffMaxInterval := flag.Int64("exponential-backoff-max-interval", 64, "Maximum number of seconds to wait between attempts when performing various operations with exponential backoff.") chunkSize := flag.Int64("chunk-size", 1000, "amount of rows to handle in each iteration (allowed range: 10-100,000)") dmlBatchSize := flag.Int64("dml-batch-size", 10, "batch size for DML events to apply in a single transaction (range 1-1000)") defaultRetries := flag.Int64("default-retries", 60, "Default number of retries for various operations before panicking") flag.BoolVar(&migrationContext.PanicOnWarnings, "panic-on-warnings", false, "Panic when SQL warnings are encountered when copying a batch indicating data loss") cutOverLockTimeoutSeconds := flag.Int64("cut-over-lock-timeout-seconds", 3, "Max number of seconds to hold locks on tables while attempting to cut-over (retry attempted when lock exceeds timeout) or attempting instant DDL") niceRatio := flag.Float64("nice-ratio", 0, "force being 'nice', imply sleep time per chunk time; range: [0.0..100.0]. Example values: 0 is aggressive. 1: for every 1ms spent copying rows, sleep additional 1ms (effectively doubling runtime); 0.7: for every 10ms spend in a rowcopy chunk, spend 7ms sleeping immediately after") maxLagMillis := flag.Int64("max-lag-millis", 1500, "replication lag at which to throttle operation") replicationLagQuery := flag.String("replication-lag-query", "", "Deprecated. gh-ost uses an internal, subsecond resolution query") throttleControlReplicas := flag.String("throttle-control-replicas", "", "List of replicas on which to check for lag; comma delimited. Example: myhost1.com:3306,myhost2.com,myhost3.com:3307") throttleQuery := flag.String("throttle-query", "", "when given, issued (every second) to check if operation should throttle. Expecting to return zero for no-throttle, >0 for throttle. Query is issued on the migrated server. Make sure this query is lightweight") throttleHTTP := flag.String("throttle-http", "", "when given, gh-ost checks given URL via HEAD request; any response code other than 200 (OK) causes throttling; make sure it has low latency response") flag.Int64Var(&migrationContext.ThrottleHTTPIntervalMillis, "throttle-http-interval-millis", 100, "Number of milliseconds to wait before triggering another HTTP throttle check") flag.Int64Var(&migrationContext.ThrottleHTTPTimeoutMillis, "throttle-http-timeout-millis", 1000, "Number of milliseconds to use as an HTTP throttle check timeout") ignoreHTTPErrors := flag.Bool("ignore-http-errors", false, "ignore HTTP connection errors during throttle check") heartbeatIntervalMillis := flag.Int64("heartbeat-interval-millis", 100, "how frequently would gh-ost inject a heartbeat value") flag.StringVar(&migrationContext.ThrottleFlagFile, "throttle-flag-file", "", "operation pauses when this file exists; hint: use a file that is specific to the table being altered") flag.StringVar(&migrationContext.ThrottleAdditionalFlagFile, "throttle-additional-flag-file", "/tmp/gh-ost.throttle", "operation pauses when this file exists; hint: keep default, use for throttling multiple gh-ost operations") flag.StringVar(&migrationContext.PostponeCutOverFlagFile, "postpone-cut-over-flag-file", "", "while this file exists, migration will postpone the final stage of swapping tables, and will keep on syncing the ghost table. Cut-over/swapping would be ready to perform the moment the file is deleted.") flag.StringVar(&migrationContext.PanicFlagFile, "panic-flag-file", "", "when this file is created, gh-ost will immediately terminate, without cleanup") flag.BoolVar(&migrationContext.DropServeSocket, "initially-drop-socket-file", false, "Should gh-ost forcibly delete an existing socket file. Be careful: this might drop the socket file of a running migration!") flag.StringVar(&migrationContext.ServeSocketFile, "serve-socket-file", "", "Unix socket file to serve on. Default: auto-determined and advertised upon startup") flag.Int64Var(&migrationContext.ServeTCPPort, "serve-tcp-port", 0, "TCP port to serve on. Default: disabled") flag.StringVar(&migrationContext.HooksPath, "hooks-path", "", "directory where hook files are found (default: empty, ie. hooks disabled). Hook files found on this path, and conforming to hook naming conventions will be executed") flag.StringVar(&migrationContext.HooksHintMessage, "hooks-hint", "", "arbitrary message to be injected to hooks via GH_OST_HOOKS_HINT, for your convenience") flag.StringVar(&migrationContext.HooksHintOwner, "hooks-hint-owner", "", "arbitrary name of owner to be injected to hooks via GH_OST_HOOKS_HINT_OWNER, for your convenience") flag.StringVar(&migrationContext.HooksHintToken, "hooks-hint-token", "", "arbitrary token to be injected to hooks via GH_OST_HOOKS_HINT_TOKEN, for your convenience") flag.Int64Var(&migrationContext.HooksStatusIntervalSec, "hooks-status-interval", 60, "how many seconds to wait between calling onStatus hook") flag.UintVar(&migrationContext.ReplicaServerId, "replica-server-id", 99999, "server id used by gh-ost process. Default: 99999") flag.BoolVar(&migrationContext.AllowSetupMetadataLockInstruments, "allow-setup-metadata-lock-instruments", false, "Validate rename session hold the MDL of original table before unlock tables in cut-over phase") flag.BoolVar(&migrationContext.SkipMetadataLockCheck, "skip-metadata-lock-check", false, "Skip metadata lock check at cut-over time. The checks require performance_schema.metadata_lock to be enabled") flag.IntVar(&migrationContext.BinlogSyncerMaxReconnectAttempts, "binlogsyncer-max-reconnect-attempts", 0, "when master node fails, the maximum number of binlog synchronization attempts to reconnect. 0 is unlimited") flag.BoolVar(&migrationContext.IncludeTriggers, "include-triggers", false, "When true, the triggers (if exist) will be created on the new table") flag.StringVar(&migrationContext.TriggerSuffix, "trigger-suffix", "", "Add a suffix to the trigger name (i.e '_v2'). Requires '--include-triggers'") flag.BoolVar(&migrationContext.RemoveTriggerSuffix, "remove-trigger-suffix-if-exists", false, "Remove given suffix from name of trigger. Requires '--include-triggers' and '--trigger-suffix'") flag.BoolVar(&migrationContext.SkipPortValidation, "skip-port-validation", false, "Skip port validation for MySQL connections") flag.BoolVar(&migrationContext.Checkpoint, "checkpoint", false, "Enable migration checkpoints") flag.Int64Var(&migrationContext.CheckpointIntervalSeconds, "checkpoint-seconds", 300, "The number of seconds between checkpoints") flag.BoolVar(&migrationContext.Resume, "resume", false, "Attempt to resume migration from checkpoint") flag.BoolVar(&migrationContext.Revert, "revert", false, "Attempt to revert completed migration") flag.StringVar(&migrationContext.OldTableName, "old-table", "", "The name of the old table when using --revert, e.g. '_mytable_del'") maxLoad := flag.String("max-load", "", "Comma delimited status-name=threshold. e.g: 'Threads_running=100,Threads_connected=500'. When status exceeds threshold, app throttles writes") criticalLoad := flag.String("critical-load", "", "Comma delimited status-name=threshold, same format as --max-load. When status exceeds threshold, app panics and quits") flag.Int64Var(&migrationContext.CriticalLoadIntervalMilliseconds, "critical-load-interval-millis", 0, "When 0, migration immediately bails out upon meeting critical-load. When non-zero, a second check is done after given interval, and migration only bails out if 2nd check still meets critical load") flag.Int64Var(&migrationContext.CriticalLoadHibernateSeconds, "critical-load-hibernate-seconds", 0, "When non-zero, critical-load does not panic and bail out; instead, gh-ost goes into hibernation for the specified duration. It will not read/write anything from/to any server") quiet := flag.Bool("quiet", false, "quiet") verbose := flag.Bool("verbose", false, "verbose") debug := flag.Bool("debug", false, "debug mode (very verbose)") stack := flag.Bool("stack", false, "add stack trace upon error") help := flag.Bool("help", false, "Display usage") version := flag.Bool("version", false, "Print version & exit") checkFlag := flag.Bool("check-flag", false, "Check if another flag exists/supported. This allows for cross-version scripting. Exits with 0 when all additional provided flags exist, nonzero otherwise. You must provide (dummy) values for flags that require a value. Example: gh-ost --check-flag --cut-over-lock-timeout-seconds --nice-ratio 0") flag.StringVar(&migrationContext.ForceTmpTableName, "force-table-names", "", "table name prefix to be used on the temporary tables") flag.CommandLine.SetOutput(os.Stdout) flag.Parse() if *checkFlag { return } if *help { fmt.Fprintf(os.Stdout, "Usage of gh-ost:\n") flag.PrintDefaults() return } if AppVersion == "" { AppVersion = "unversioned" } if GitCommit == "" { GitCommit = "unknown" } if *version { fmt.Printf("%s (git commit: %s)\n", AppVersion, GitCommit) return } migrationContext.Log.SetLevel(log.ERROR) if *verbose { migrationContext.Log.SetLevel(log.INFO) } if *debug { migrationContext.Log.SetLevel(log.DEBUG) } if *stack { migrationContext.Log.SetPrintStackTrace(*stack) } if *quiet { // Override!! migrationContext.Log.SetLevel(log.ERROR) } if err := migrationContext.SetConnectionConfig(*storageEngine); err != nil { migrationContext.Log.Fatale(err) } migrationContext.SetConnectionCharset(*charset) if migrationContext.AlterStatement == "" && !migrationContext.Revert { log.Fatal("--alter must be provided and statement must not be empty") } parser := sql.NewParserFromAlterStatement(migrationContext.AlterStatement) migrationContext.AlterStatementOptions = parser.GetAlterStatementOptions() if migrationContext.Revert { if migrationContext.Resume { log.Fatal("--revert cannot be used with --resume") } if migrationContext.OldTableName == "" { migrationContext.Log.Fatalf("--revert must be called with --old-table") } // options irrelevant to revert mode if migrationContext.AlterStatement != "" { log.Warning("--alter was provided with --revert, it will be ignored") } if migrationContext.AttemptInstantDDL { log.Warning("--attempt-instant-ddl was provided with --revert, it will be ignored") } if migrationContext.IncludeTriggers { log.Warning("--include-triggers was provided with --revert, it will be ignored") } if migrationContext.DiscardForeignKeys { log.Warning("--discard-foreign-keys was provided with --revert, it will be ignored") } } if migrationContext.DatabaseName == "" { if parser.HasExplicitSchema() { migrationContext.DatabaseName = parser.GetExplicitSchema() } else { log.Fatal("--database must be provided and database name must not be empty, or --alter must specify database name") } } if err := flag.Set("database", url.QueryEscape(migrationContext.DatabaseName)); err != nil { migrationContext.Log.Fatale(err) } if migrationContext.OriginalTableName == "" { if parser.HasExplicitTable() { migrationContext.OriginalTableName = parser.GetExplicitTable() } else { log.Fatal("--table must be provided and table name must not be empty, or --alter must specify table name") } } migrationContext.Noop = !(*executeFlag) if migrationContext.AllowedRunningOnMaster && migrationContext.TestOnReplica { migrationContext.Log.Fatal("--allow-on-master and --test-on-replica are mutually exclusive") } if migrationContext.AllowedRunningOnMaster && migrationContext.MigrateOnReplica { migrationContext.Log.Fatal("--allow-on-master and --migrate-on-replica are mutually exclusive") } if migrationContext.MigrateOnReplica && migrationContext.TestOnReplica { migrationContext.Log.Fatal("--migrate-on-replica and --test-on-replica are mutually exclusive") } if migrationContext.SwitchToRowBinlogFormat && migrationContext.AssumeRBR { migrationContext.Log.Fatal("--switch-to-rbr and --assume-rbr are mutually exclusive") } if migrationContext.TestOnReplicaSkipReplicaStop { if !migrationContext.TestOnReplica { migrationContext.Log.Fatal("--test-on-replica-skip-replica-stop requires --test-on-replica to be enabled") } migrationContext.Log.Warning("--test-on-replica-skip-replica-stop enabled. We will not stop replication before cut-over. Ensure you have a plugin that does this.") } if migrationContext.CliMasterUser != "" && migrationContext.AssumeMasterHostname == "" { migrationContext.Log.Fatal("--master-user requires --assume-master-host") } if migrationContext.CliMasterPassword != "" && migrationContext.AssumeMasterHostname == "" { migrationContext.Log.Fatal("--master-password requires --assume-master-host") } if migrationContext.TLSCACertificate != "" && !migrationContext.UseTLS { migrationContext.Log.Fatal("--ssl-ca requires --ssl") } if migrationContext.TLSCertificate != "" && !migrationContext.UseTLS { migrationContext.Log.Fatal("--ssl-cert requires --ssl") } if migrationContext.TLSKey != "" && !migrationContext.UseTLS { migrationContext.Log.Fatal("--ssl-key requires --ssl") } if migrationContext.TLSAllowInsecure && !migrationContext.UseTLS { migrationContext.Log.Fatal("--ssl-allow-insecure requires --ssl") } if *replicationLagQuery != "" { migrationContext.Log.Warningf("--replication-lag-query is deprecated") } if migrationContext.IncludeTriggers && migrationContext.TriggerSuffix == "" { migrationContext.Log.Fatalf("--trigger-suffix must be used with --include-triggers") } if !migrationContext.IncludeTriggers && migrationContext.TriggerSuffix != "" { migrationContext.Log.Fatalf("--trigger-suffix cannot be be used without --include-triggers") } if migrationContext.TriggerSuffix != "" { regex := regexp.MustCompile(`^[\da-zA-Z_]+$`) if !regex.Match([]byte(migrationContext.TriggerSuffix)) { migrationContext.Log.Fatalf("--trigger-suffix must contain only alpha numeric characters and underscore (0-9,a-z,A-Z,_)") } } if *storageEngine == "rocksdb" { migrationContext.Log.Warning("RocksDB storage engine support is experimental") } if migrationContext.CheckpointIntervalSeconds < 10 { migrationContext.Log.Fatalf("--checkpoint-seconds should be >=10") } switch *cutOver { case "atomic", "default", "": migrationContext.CutOverType = base.CutOverAtomic case "two-step": migrationContext.CutOverType = base.CutOverTwoStep default: migrationContext.Log.Fatalf("Unknown cut-over: %s", *cutOver) } if err := migrationContext.ReadConfigFile(); err != nil { migrationContext.Log.Fatale(err) } if err := migrationContext.ReadThrottleControlReplicaKeys(*throttleControlReplicas); err != nil { migrationContext.Log.Fatale(err) } if err := migrationContext.ReadMaxLoad(*maxLoad); err != nil { migrationContext.Log.Fatale(err) } if err := migrationContext.ReadCriticalLoad(*criticalLoad); err != nil { migrationContext.Log.Fatale(err) } if migrationContext.ServeSocketFile == "" { migrationContext.ServeSocketFile = fmt.Sprintf("/tmp/gh-ost.%s.%s.sock", migrationContext.DatabaseName, migrationContext.OriginalTableName) } if *askPass { fmt.Println("Password:") bytePassword, err := term.ReadPassword(syscall.Stdin) if err != nil { migrationContext.Log.Fatale(err) } migrationContext.CliPassword = string(bytePassword) } migrationContext.SetHeartbeatIntervalMilliseconds(*heartbeatIntervalMillis) migrationContext.SetNiceRatio(*niceRatio) migrationContext.SetChunkSize(*chunkSize) migrationContext.SetDMLBatchSize(*dmlBatchSize) migrationContext.SetMaxLagMillisecondsThrottleThreshold(*maxLagMillis) migrationContext.SetThrottleQuery(*throttleQuery) migrationContext.SetThrottleHTTP(*throttleHTTP) migrationContext.SetIgnoreHTTPErrors(*ignoreHTTPErrors) migrationContext.SetDefaultNumRetries(*defaultRetries) migrationContext.ApplyCredentials() if err := migrationContext.SetupTLS(); err != nil { migrationContext.Log.Fatale(err) } if err := migrationContext.SetCutOverLockTimeoutSeconds(*cutOverLockTimeoutSeconds); err != nil { migrationContext.Log.Errore(err) } if err := migrationContext.SetExponentialBackoffMaxInterval(*exponentialBackoffMaxInterval); err != nil { migrationContext.Log.Errore(err) } log.Infof("starting gh-ost %+v (git commit: %s)", AppVersion, GitCommit) acceptSignals(migrationContext) migrator := logic.NewMigrator(migrationContext, AppVersion) var err error if migrationContext.Revert { err = migrator.Revert() } else { err = migrator.Migrate() } if err != nil { migrator.ExecOnFailureHook() migrationContext.Log.Fatale(err) } fmt.Fprintln(os.Stdout, "# Done") } ================================================ FILE: go/logic/applier.go ================================================ /* Copyright 2025 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package logic import ( gosql "database/sql" "fmt" "reflect" "regexp" "strings" "sync/atomic" "time" "github.com/github/gh-ost/go/base" "github.com/github/gh-ost/go/binlog" "github.com/github/gh-ost/go/sql" "context" "database/sql/driver" "errors" "sync" "github.com/github/gh-ost/go/mysql" drivermysql "github.com/go-sql-driver/mysql" "github.com/openark/golib/sqlutils" ) const ( GhostChangelogTableComment = "gh-ost changelog" atomicCutOverMagicHint = "ghost-cut-over-sentry" ) // ErrNoCheckpointFound is returned when an empty checkpoint table is queried. var ErrNoCheckpointFound = errors.New("no checkpoint found in _ghk table") type dmlBuildResult struct { query string args []interface{} rowsDelta int64 err error } func newDmlBuildResult(query string, args []interface{}, rowsDelta int64, err error) *dmlBuildResult { return &dmlBuildResult{ query: query, args: args, rowsDelta: rowsDelta, err: err, } } func newDmlBuildResultError(err error) *dmlBuildResult { return &dmlBuildResult{ err: err, } } // Applier connects and writes the applier-server, which is the server where migration // happens. This is typically the master, but could be a replica when `--test-on-replica` or // `--execute-on-replica` are given. // Applier is the one to actually write row data and apply binlog events onto the ghost table. // It is where the ghost & changelog tables get created. It is where the cut-over phase happens. type Applier struct { connectionConfig *mysql.ConnectionConfig db *gosql.DB singletonDB *gosql.DB migrationContext *base.MigrationContext finishedMigrating int64 name string CurrentCoordinatesMutex sync.Mutex CurrentCoordinates mysql.BinlogCoordinates LastIterationRangeMutex sync.Mutex LastIterationRangeMinValues *sql.ColumnValues LastIterationRangeMaxValues *sql.ColumnValues dmlDeleteQueryBuilder *sql.DMLDeleteQueryBuilder dmlInsertQueryBuilder *sql.DMLInsertQueryBuilder dmlUpdateQueryBuilder *sql.DMLUpdateQueryBuilder checkpointInsertQueryBuilder *sql.CheckpointInsertQueryBuilder } func NewApplier(migrationContext *base.MigrationContext) *Applier { return &Applier{ connectionConfig: migrationContext.ApplierConnectionConfig, migrationContext: migrationContext, finishedMigrating: 0, name: "applier", } } // compileMigrationKeyWarningRegex compiles a regex pattern that matches duplicate key warnings // for the migration's unique key. Duplicate warnings are formatted differently across MySQL versions, // hence the optional table name prefix. Metacharacters in table/index names are escaped to avoid // regex syntax errors. func (this *Applier) compileMigrationKeyWarningRegex() (*regexp.Regexp, error) { escapedTable := regexp.QuoteMeta(this.migrationContext.GetGhostTableName()) escapedKey := regexp.QuoteMeta(this.migrationContext.UniqueKey.NameInGhostTable) migrationUniqueKeyPattern := fmt.Sprintf(`for key '(%s\.)?%s'`, escapedTable, escapedKey) migrationKeyRegex, err := regexp.Compile(migrationUniqueKeyPattern) if err != nil { return nil, fmt.Errorf("failed to compile migration key pattern: %w", err) } return migrationKeyRegex, nil } func (this *Applier) InitDBConnections() (err error) { applierUri := this.connectionConfig.GetDBUri(this.migrationContext.DatabaseName) uriWithMulti := fmt.Sprintf("%s&multiStatements=true", applierUri) if this.db, _, err = mysql.GetDB(this.migrationContext.Uuid, uriWithMulti); err != nil { return err } singletonApplierUri := fmt.Sprintf("%s&timeout=0", applierUri) if this.singletonDB, _, err = mysql.GetDB(this.migrationContext.Uuid, singletonApplierUri); err != nil { return err } this.singletonDB.SetMaxOpenConns(1) version, err := base.ValidateConnection(this.db, this.connectionConfig, this.migrationContext, this.name) if err != nil { return err } if _, err := base.ValidateConnection(this.singletonDB, this.connectionConfig, this.migrationContext, this.name); err != nil { return err } this.migrationContext.ApplierMySQLVersion = version if err := this.validateAndReadGlobalVariables(); err != nil { return err } if !this.migrationContext.AliyunRDS && !this.migrationContext.GoogleCloudPlatform && !this.migrationContext.AzureMySQL { if impliedKey, err := mysql.GetInstanceKey(this.db); err != nil { return err } else { this.connectionConfig.ImpliedKey = impliedKey } } if err := this.readTableColumns(); err != nil { return err } this.migrationContext.Log.Infof("Applier initiated on %+v, version %+v", this.connectionConfig.ImpliedKey, this.migrationContext.ApplierMySQLVersion) return nil } func (this *Applier) prepareQueries() (err error) { if this.dmlDeleteQueryBuilder, err = sql.NewDMLDeleteQueryBuilder( this.migrationContext.DatabaseName, this.migrationContext.GetGhostTableName(), this.migrationContext.OriginalTableColumns, &this.migrationContext.UniqueKey.Columns, ); err != nil { return err } if this.dmlInsertQueryBuilder, err = sql.NewDMLInsertQueryBuilder( this.migrationContext.DatabaseName, this.migrationContext.GetGhostTableName(), this.migrationContext.OriginalTableColumns, this.migrationContext.SharedColumns, this.migrationContext.MappedSharedColumns, ); err != nil { return err } if this.dmlUpdateQueryBuilder, err = sql.NewDMLUpdateQueryBuilder( this.migrationContext.DatabaseName, this.migrationContext.GetGhostTableName(), this.migrationContext.OriginalTableColumns, this.migrationContext.SharedColumns, this.migrationContext.MappedSharedColumns, &this.migrationContext.UniqueKey.Columns, ); err != nil { return err } if this.migrationContext.Checkpoint { if this.checkpointInsertQueryBuilder, err = sql.NewCheckpointQueryBuilder( this.migrationContext.DatabaseName, this.migrationContext.GetCheckpointTableName(), &this.migrationContext.UniqueKey.Columns, ); err != nil { return err } } return nil } // validateAndReadGlobalVariables potentially reads server global variables, such as the time_zone and wait_timeout. func (this *Applier) validateAndReadGlobalVariables() error { query := `select /* gh-ost */ @@global.time_zone, @@global.wait_timeout` if err := this.db.QueryRow(query).Scan( &this.migrationContext.ApplierTimeZone, &this.migrationContext.ApplierWaitTimeout, ); err != nil { return err } this.migrationContext.Log.Infof("will use time_zone='%s' on applier", this.migrationContext.ApplierTimeZone) return nil } // generateSqlModeQuery return a `sql_mode = ...` query, to be wrapped with a `set session` or `set global`, // based on gh-ost configuration: // - User may skip strict mode // - User may allow zero dats or zero in dates func (this *Applier) generateSqlModeQuery() string { sqlModeAddendum := []string{`NO_AUTO_VALUE_ON_ZERO`} if !this.migrationContext.SkipStrictMode { sqlModeAddendum = append(sqlModeAddendum, `STRICT_ALL_TABLES`) } sqlModeQuery := fmt.Sprintf("CONCAT(@@session.sql_mode, ',%s')", strings.Join(sqlModeAddendum, ",")) if this.migrationContext.AllowZeroInDate { sqlModeQuery = fmt.Sprintf("REPLACE(REPLACE(%s, 'NO_ZERO_IN_DATE', ''), 'NO_ZERO_DATE', '')", sqlModeQuery) } return fmt.Sprintf("sql_mode = %s", sqlModeQuery) } // generateInstantDDLQuery returns the SQL for this ALTER operation // with an INSTANT assertion (requires MySQL 8.0+) func (this *Applier) generateInstantDDLQuery() string { return fmt.Sprintf(`ALTER /* gh-ost */ TABLE %s.%s %s, ALGORITHM=INSTANT`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName), this.migrationContext.AlterStatementOptions, ) } // readTableColumns reads table columns on applier func (this *Applier) readTableColumns() (err error) { this.migrationContext.Log.Infof("Examining table structure on applier") this.migrationContext.OriginalTableColumnsOnApplier, _, err = mysql.GetTableColumns(this.db, this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName) if err != nil { return err } return nil } // showTableStatus returns the output of `show table status like '...'` command func (this *Applier) showTableStatus(tableName string) (rowMap sqlutils.RowMap) { query := fmt.Sprintf(`show /* gh-ost */ table status from %s like '%s'`, sql.EscapeName(this.migrationContext.DatabaseName), tableName) sqlutils.QueryRowsMap(this.db, query, func(m sqlutils.RowMap) error { rowMap = m return nil }) return rowMap } // tableExists checks if a given table exists in database func (this *Applier) tableExists(tableName string) (tableFound bool) { m := this.showTableStatus(tableName) return (m != nil) } // ValidateOrDropExistingTables verifies ghost and changelog tables do not exist, // or attempts to drop them if instructed to. func (this *Applier) ValidateOrDropExistingTables() error { if this.migrationContext.InitiallyDropGhostTable { if err := this.DropGhostTable(); err != nil { return err } } if this.tableExists(this.migrationContext.GetGhostTableName()) { return fmt.Errorf("Table %s already exists. Panicking. Use --initially-drop-ghost-table to force dropping it, though I really prefer that you drop it or rename it away", sql.EscapeName(this.migrationContext.GetGhostTableName())) } if this.migrationContext.InitiallyDropOldTable { if err := this.DropOldTable(); err != nil { return err } } if len(this.migrationContext.GetOldTableName()) > mysql.MaxTableNameLength { this.migrationContext.Log.Fatalf("--timestamp-old-table defined, but resulting table name (%s) is too long (only %d characters allowed)", this.migrationContext.GetOldTableName(), mysql.MaxTableNameLength) } if this.tableExists(this.migrationContext.GetOldTableName()) { return fmt.Errorf("Table %s already exists. Panicking. Use --initially-drop-old-table to force dropping it, though I really prefer that you drop it or rename it away", sql.EscapeName(this.migrationContext.GetOldTableName())) } return nil } // AttemptInstantDDL attempts to use instant DDL (from MySQL 8.0, and earlier in Aurora and some others). // If successful, the operation is only a meta-data change so a lot of time is saved! // The risk of attempting to instant DDL when not supported is that a metadata lock may be acquired. // This is minor, since gh-ost will eventually require a metadata lock anyway, but at the cut-over stage. // Instant operations include: // - Adding a column // - Dropping a column // - Dropping an index // - Extending a VARCHAR column // - Adding a virtual generated column // It is not reliable to parse the `alter` statement to determine if it is instant or not. // This is because the table might be in an older row format, or have some other incompatibility // that is difficult to identify. func (this *Applier) AttemptInstantDDL() error { query := this.generateInstantDDLQuery() this.migrationContext.Log.Infof("INSTANT DDL query is: %s", query) // Reuse cut-over-lock-timeout from regular migration process to reduce risk // in situations where there may be long-running transactions. tableLockTimeoutSeconds := this.migrationContext.CutOverLockTimeoutSeconds * 2 this.migrationContext.Log.Infof("Setting LOCK timeout as %d seconds", tableLockTimeoutSeconds) lockTimeoutQuery := fmt.Sprintf(`set /* gh-ost */ session lock_wait_timeout:=%d`, tableLockTimeoutSeconds) if _, err := this.db.Exec(lockTimeoutQuery); err != nil { return err } // We don't need a trx, because for instant DDL the SQL mode doesn't matter. return retryOnLockWaitTimeout(func() error { _, err := this.db.Exec(query) return err }, this.migrationContext.Log) } // retryOnLockWaitTimeout retries the given operation on MySQL lock wait timeout // (errno 1205). Non-timeout errors return immediately. This is used for instant // DDL attempts where the operation may be blocked by a long-running transaction. func retryOnLockWaitTimeout(operation func() error, logger base.Logger) error { const maxRetries = 5 var err error for i := 0; i < maxRetries; i++ { if i != 0 { logger.Infof("Retrying after lock wait timeout (attempt %d/%d)", i+1, maxRetries) RetrySleepFn(time.Duration(i) * 5 * time.Second) } err = operation() if err == nil { return nil } var mysqlErr *drivermysql.MySQLError if !errors.As(err, &mysqlErr) || mysqlErr.Number != 1205 { return err } } return err } // CreateGhostTable creates the ghost table on the applier host func (this *Applier) CreateGhostTable() error { query := fmt.Sprintf(`create /* gh-ost */ table %s.%s like %s.%s`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetGhostTableName()), sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName), ) this.migrationContext.Log.Infof("Creating ghost table %s.%s", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetGhostTableName()), ) err := func() error { tx, err := this.db.Begin() if err != nil { return err } defer tx.Rollback() sessionQuery := fmt.Sprintf(`SET SESSION time_zone = '%s'`, this.migrationContext.ApplierTimeZone) sessionQuery = fmt.Sprintf("%s, %s", sessionQuery, this.generateSqlModeQuery()) if _, err := tx.Exec(sessionQuery); err != nil { return err } if _, err := tx.Exec(query); err != nil { return err } this.migrationContext.Log.Infof("Ghost table created") if err := tx.Commit(); err != nil { // Neither SET SESSION nor ALTER are really transactional, so strictly speaking // there's no need to commit; but let's do this the legit way anyway. return err } return nil }() return err } // AlterGhost applies `alter` statement on ghost table func (this *Applier) AlterGhost() error { query := fmt.Sprintf(`alter /* gh-ost */ table %s.%s %s`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetGhostTableName()), this.migrationContext.AlterStatementOptions, ) this.migrationContext.Log.Infof("Altering ghost table %s.%s", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetGhostTableName()), ) this.migrationContext.Log.Debugf("ALTER statement: %s", query) err := func() error { tx, err := this.db.Begin() if err != nil { return err } defer tx.Rollback() sessionQuery := fmt.Sprintf(`SET SESSION time_zone = '%s'`, this.migrationContext.ApplierTimeZone) sessionQuery = fmt.Sprintf("%s, %s", sessionQuery, this.generateSqlModeQuery()) if _, err := tx.Exec(sessionQuery); err != nil { return err } if _, err := tx.Exec(query); err != nil { return err } this.migrationContext.Log.Infof("Ghost table altered") if err := tx.Commit(); err != nil { // Neither SET SESSION nor ALTER are really transactional, so strictly speaking // there's no need to commit; but let's do this the legit way anyway. return err } return nil }() return err } // AlterGhost applies `alter` statement on ghost table func (this *Applier) AlterGhostAutoIncrement() error { query := fmt.Sprintf(`alter /* gh-ost */ table %s.%s AUTO_INCREMENT=%d`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetGhostTableName()), this.migrationContext.OriginalTableAutoIncrement, ) this.migrationContext.Log.Infof("Altering ghost table AUTO_INCREMENT value %s.%s", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetGhostTableName()), ) this.migrationContext.Log.Debugf("AUTO_INCREMENT ALTER statement: %s", query) if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil { return err } this.migrationContext.Log.Infof("Ghost table AUTO_INCREMENT altered") return nil } // CreateChangelogTable creates the changelog table on the applier host func (this *Applier) CreateChangelogTable() error { if err := this.DropChangelogTable(); err != nil { return err } query := fmt.Sprintf(`create /* gh-ost */ table %s.%s ( id bigint unsigned auto_increment, last_update timestamp not null DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, hint varchar(64) charset ascii not null, value varchar(4096) charset ascii not null, primary key(id), unique key hint_uidx(hint) ) auto_increment=256 comment='%s'`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetChangelogTableName()), GhostChangelogTableComment, ) this.migrationContext.Log.Infof("Creating changelog table %s.%s", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetChangelogTableName()), ) if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil { return err } this.migrationContext.Log.Infof("Changelog table created") return nil } // Create the checkpoint table to store the chunk copy and applier state. // There are two sets of columns with the same types as the shared unique key, // one for IterationMinValues and one for IterationMaxValues. func (this *Applier) CreateCheckpointTable() error { if err := this.DropCheckpointTable(); err != nil { return err } colDefs := []string{ "`gh_ost_chk_id` bigint auto_increment primary key", "`gh_ost_chk_timestamp` bigint", "`gh_ost_chk_coords` text charset ascii", "`gh_ost_chk_iteration` bigint", "`gh_ost_rows_copied` bigint", "`gh_ost_dml_applied` bigint", "`gh_ost_is_cutover` tinyint(1) DEFAULT '0'", } for _, col := range this.migrationContext.UniqueKey.Columns.Columns() { if col.MySQLType == "" { return fmt.Errorf("CreateCheckpoinTable: column %s has no type information. applyColumnTypes must be called", sql.EscapeName(col.Name)) } minColName := sql.TruncateColumnName(col.Name, sql.MaxColumnNameLength-4) + "_min" colDef := fmt.Sprintf("%s %s", sql.EscapeName(minColName), col.MySQLType) colDefs = append(colDefs, colDef) } for _, col := range this.migrationContext.UniqueKey.Columns.Columns() { maxColName := sql.TruncateColumnName(col.Name, sql.MaxColumnNameLength-4) + "_max" colDef := fmt.Sprintf("%s %s", sql.EscapeName(maxColName), col.MySQLType) colDefs = append(colDefs, colDef) } query := fmt.Sprintf("create /* gh-ost */ table %s.%s (\n %s\n)", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetCheckpointTableName()), strings.Join(colDefs, ",\n "), ) this.migrationContext.Log.Infof("Created checkpoint table") if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil { return err } return nil } // dropTable drops a given table on the applied host func (this *Applier) dropTable(tableName string) error { query := fmt.Sprintf(`drop /* gh-ost */ table if exists %s.%s`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(tableName), ) this.migrationContext.Log.Infof("Dropping table %s.%s", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(tableName), ) if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil { return err } this.migrationContext.Log.Infof("Table dropped") return nil } // StateMetadataLockInstrument checks if metadata_locks is enabled in performance_schema. // If not it attempts to enable metadata_locks if this is allowed. func (this *Applier) StateMetadataLockInstrument() error { query := `select /*+ MAX_EXECUTION_TIME(300) */ ENABLED, TIMED from performance_schema.setup_instruments WHERE NAME = 'wait/lock/metadata/sql/mdl'` var enabled, timed string if err := this.db.QueryRow(query).Scan(&enabled, &timed); err != nil { if errors.Is(err, gosql.ErrNoRows) { // performance_schema may be disabled. return nil } return this.migrationContext.Log.Errorf("query performance_schema.setup_instruments with name wait/lock/metadata/sql/mdl error: %s", err) } if strings.EqualFold(enabled, "YES") && strings.EqualFold(timed, "YES") { this.migrationContext.IsOpenMetadataLockInstruments = true return nil } if !this.migrationContext.AllowSetupMetadataLockInstruments { return nil } this.migrationContext.Log.Infof("instrument wait/lock/metadata/sql/mdl state: enabled %s, timed %s", enabled, timed) if _, err := this.db.Exec(`UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME = 'wait/lock/metadata/sql/mdl'`); err != nil { return this.migrationContext.Log.Errorf("enable instrument wait/lock/metadata/sql/mdl error: %s", err) } this.migrationContext.IsOpenMetadataLockInstruments = true this.migrationContext.Log.Infof("instrument wait/lock/metadata/sql/mdl enabled") return nil } // dropTriggers drop the triggers on the applied host func (this *Applier) DropTriggersFromGhost() error { if len(this.migrationContext.Triggers) > 0 { for _, trigger := range this.migrationContext.Triggers { triggerName := this.migrationContext.GetGhostTriggerName(trigger.Name) query := fmt.Sprintf("drop trigger if exists %s", sql.EscapeName(triggerName)) _, err := sqlutils.ExecNoPrepare(this.db, query) if err != nil { return err } this.migrationContext.Log.Infof("Trigger '%s' dropped", triggerName) } } return nil } // createTriggers creates the triggers on the applied host func (this *Applier) createTriggers(tableName string) error { if len(this.migrationContext.Triggers) > 0 { for _, trigger := range this.migrationContext.Triggers { triggerName := this.migrationContext.GetGhostTriggerName(trigger.Name) query := fmt.Sprintf(`create /* gh-ost */ trigger %s %s %s on %s.%s for each row %s`, sql.EscapeName(triggerName), trigger.Timing, trigger.Event, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(tableName), trigger.Statement, ) this.migrationContext.Log.Infof("Createing trigger %s on %s.%s", sql.EscapeName(triggerName), sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(tableName), ) if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil { return err } } this.migrationContext.Log.Infof("Triggers created on %s", tableName) } return nil } // CreateTriggers creates the original triggers on applier host func (this *Applier) CreateTriggersOnGhost() error { err := this.createTriggers(this.migrationContext.GetGhostTableName()) return err } // DropChangelogTable drops the changelog table on the applier host func (this *Applier) DropChangelogTable() error { return this.dropTable(this.migrationContext.GetChangelogTableName()) } // DropCheckpointTable drops the checkpoint table on applier host func (this *Applier) DropCheckpointTable() error { return this.dropTable(this.migrationContext.GetCheckpointTableName()) } // DropOldTable drops the _Old table on the applier host func (this *Applier) DropOldTable() error { return this.dropTable(this.migrationContext.GetOldTableName()) } // DropGhostTable drops the ghost table on the applier host func (this *Applier) DropGhostTable() error { return this.dropTable(this.migrationContext.GetGhostTableName()) } // WriteChangelog writes a value to the changelog table. // It returns the hint as given, for convenience func (this *Applier) WriteChangelog(hint, value string) (string, error) { explicitId := 0 switch hint { case "heartbeat": explicitId = 1 case "state": explicitId = 2 case "throttle": explicitId = 3 } query := fmt.Sprintf(` insert /* gh-ost */ into %s.%s (id, hint, value) values (NULLIF(?, 0), ?, ?) on duplicate key update last_update=NOW(), value=VALUES(value)`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetChangelogTableName()), ) _, err := sqlutils.ExecNoPrepare(this.db, query, explicitId, hint, value) return hint, err } func (this *Applier) WriteAndLogChangelog(hint, value string) (string, error) { this.WriteChangelog(hint, value) return this.WriteChangelog(fmt.Sprintf("%s at %d", hint, time.Now().UnixNano()), value) } func (this *Applier) WriteChangelogState(value string) (string, error) { return this.WriteAndLogChangelog("state", value) } // WriteCheckpoints writes a checkpoint to the _ghk table. func (this *Applier) WriteCheckpoint(chk *Checkpoint) (int64, error) { var insertId int64 uniqueKeyArgs := sqlutils.Args(chk.IterationRangeMin.AbstractValues()...) uniqueKeyArgs = append(uniqueKeyArgs, chk.IterationRangeMax.AbstractValues()...) query, uniqueKeyArgs, err := this.checkpointInsertQueryBuilder.BuildQuery(uniqueKeyArgs) if err != nil { return insertId, err } args := sqlutils.Args(chk.LastTrxCoords.String(), chk.Iteration, chk.RowsCopied, chk.DMLApplied, chk.IsCutover) args = append(args, uniqueKeyArgs...) res, err := this.db.Exec(query, args...) if err != nil { return insertId, err } return res.LastInsertId() } func (this *Applier) ReadLastCheckpoint() (*Checkpoint, error) { row := this.db.QueryRow(fmt.Sprintf(`select /* gh-ost */ * from %s.%s order by gh_ost_chk_id desc limit 1`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetCheckpointTableName()))) chk := &Checkpoint{ IterationRangeMin: sql.NewColumnValues(this.migrationContext.UniqueKey.Columns.Len()), IterationRangeMax: sql.NewColumnValues(this.migrationContext.UniqueKey.Columns.Len()), } var coordStr string var timestamp int64 ptrs := []interface{}{&chk.Id, ×tamp, &coordStr, &chk.Iteration, &chk.RowsCopied, &chk.DMLApplied, &chk.IsCutover} ptrs = append(ptrs, chk.IterationRangeMin.ValuesPointers...) ptrs = append(ptrs, chk.IterationRangeMax.ValuesPointers...) err := row.Scan(ptrs...) if err != nil { if errors.Is(err, gosql.ErrNoRows) { return nil, ErrNoCheckpointFound } return nil, err } chk.Timestamp = time.Unix(timestamp, 0) if this.migrationContext.UseGTIDs { gtidCoords, err := mysql.NewGTIDBinlogCoordinates(coordStr) if err != nil { return nil, err } chk.LastTrxCoords = gtidCoords } else { fileCoords, err := mysql.ParseFileBinlogCoordinates(coordStr) if err != nil { return nil, err } chk.LastTrxCoords = fileCoords } return chk, nil } // InitiateHeartbeat creates a heartbeat cycle, writing to the changelog table. // This is done asynchronously func (this *Applier) InitiateHeartbeat() { var numSuccessiveFailures int64 injectHeartbeat := func() error { if atomic.LoadInt64(&this.migrationContext.HibernateUntil) > 0 { return nil } if _, err := this.WriteChangelog("heartbeat", time.Now().Format(time.RFC3339Nano)); err != nil { numSuccessiveFailures++ if numSuccessiveFailures > this.migrationContext.MaxRetries() { return this.migrationContext.Log.Errore(err) } } else { numSuccessiveFailures = 0 } return nil } injectHeartbeat() ticker := time.NewTicker(time.Duration(this.migrationContext.HeartbeatIntervalMilliseconds) * time.Millisecond) defer ticker.Stop() for { // Check for context cancellation each iteration ctx := this.migrationContext.GetContext() select { case <-ctx.Done(): this.migrationContext.Log.Debugf("Heartbeat injection cancelled") return case <-ticker.C: // Process heartbeat } if atomic.LoadInt64(&this.finishedMigrating) > 0 { return } // Generally speaking, we would issue a goroutine, but I'd actually rather // have this block the loop rather than spam the master in the event something // goes wrong if throttle, _, reasonHint := this.migrationContext.IsThrottled(); throttle && (reasonHint == base.UserCommandThrottleReasonHint) { continue } if err := injectHeartbeat(); err != nil { // Use helper to prevent deadlock if listenOnPanicAbort already exited _ = base.SendWithContext(this.migrationContext.GetContext(), this.migrationContext.PanicAbort, fmt.Errorf("injectHeartbeat writing failed %d times, last error: %w", numSuccessiveFailures, err)) return } } } // ExecuteThrottleQuery executes the `--throttle-query` and returns its results. func (this *Applier) ExecuteThrottleQuery() (int64, error) { throttleQuery := this.migrationContext.GetThrottleQuery() if throttleQuery == "" { return 0, nil } var result int64 if err := this.db.QueryRow(throttleQuery).Scan(&result); err != nil { return 0, this.migrationContext.Log.Errore(err) } return result, nil } // readMigrationMinValues returns the minimum values to be iterated on rowcopy func (this *Applier) readMigrationMinValues(tx *gosql.Tx, uniqueKey *sql.UniqueKey) error { this.migrationContext.Log.Debugf("Reading migration range according to key: %s", uniqueKey.Name) query, err := sql.BuildUniqueKeyMinValuesPreparedQuery(this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName, uniqueKey) if err != nil { return err } rows, err := tx.Query(query) if err != nil { return err } defer rows.Close() for rows.Next() { this.migrationContext.MigrationRangeMinValues = sql.NewColumnValues(uniqueKey.Len()) if err = rows.Scan(this.migrationContext.MigrationRangeMinValues.ValuesPointers...); err != nil { return err } } this.migrationContext.Log.Infof("Migration min values: [%s]", this.migrationContext.MigrationRangeMinValues) return rows.Err() } // readMigrationMaxValues returns the maximum values to be iterated on rowcopy func (this *Applier) readMigrationMaxValues(tx *gosql.Tx, uniqueKey *sql.UniqueKey) error { this.migrationContext.Log.Debugf("Reading migration range according to key: %s", uniqueKey.Name) query, err := sql.BuildUniqueKeyMaxValuesPreparedQuery(this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName, uniqueKey) if err != nil { return err } rows, err := tx.Query(query) if err != nil { return err } defer rows.Close() for rows.Next() { this.migrationContext.MigrationRangeMaxValues = sql.NewColumnValues(uniqueKey.Len()) if err = rows.Scan(this.migrationContext.MigrationRangeMaxValues.ValuesPointers...); err != nil { return err } } this.migrationContext.Log.Infof("Migration max values: [%s]", this.migrationContext.MigrationRangeMaxValues) return rows.Err() } // ReadMigrationRangeValues reads min/max values that will be used for rowcopy. // Before read min/max, write a changelog state into the ghc table to avoid lost data in mysql two-phase commit. /* Detail description of the lost data in mysql two-phase commit issue by @Fanduzi: When using semi-sync and setting rpl_semi_sync_master_wait_point=AFTER_SYNC, if an INSERT statement is being committed but blocks due to an unmet ack count, the data inserted by the transaction is not visible to ReadMigrationRangeValues, so the copy of the existing data in the table does not include the new row inserted by the transaction. However, the binlog event for the transaction is already written to the binlog, so the addDMLEventsListener only captures the binlog event after the transaction, and thus the transaction's binlog event is not captured, resulting in data loss. If write a changelog into ghc table before ReadMigrationRangeValues, and the transaction commit blocks because the ack is not met, then the changelog will not be able to write, so the ReadMigrationRangeValues will not be run. When the changelog writes successfully, the ReadMigrationRangeValues will read the newly inserted data, thus Avoiding data loss due to the above problem. */ func (this *Applier) ReadMigrationRangeValues() error { if _, err := this.WriteChangelogState(string(ReadMigrationRangeValues)); err != nil { return err } tx, err := this.db.Begin() if err != nil { return err } defer tx.Rollback() if err := this.readMigrationMinValues(tx, this.migrationContext.UniqueKey); err != nil { return err } if err := this.readMigrationMaxValues(tx, this.migrationContext.UniqueKey); err != nil { return err } return tx.Commit() } // CalculateNextIterationRangeEndValues reads the next-iteration-range-end unique key values, // which will be used for copying the next chunk of rows. Ir returns "false" if there is // no further chunk to work through, i.e. we're past the last chunk and are done with // iterating the range (and thus done with copying row chunks) func (this *Applier) CalculateNextIterationRangeEndValues() (hasFurtherRange bool, err error) { for i := 0; i < 2; i++ { buildFunc := sql.BuildUniqueKeyRangeEndPreparedQueryViaOffset if i == 1 { buildFunc = sql.BuildUniqueKeyRangeEndPreparedQueryViaTemptable } query, explodedArgs, err := buildFunc( this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName, &this.migrationContext.UniqueKey.Columns, this.migrationContext.MigrationIterationRangeMinValues.AbstractValues(), this.migrationContext.MigrationRangeMaxValues.AbstractValues(), atomic.LoadInt64(&this.migrationContext.ChunkSize), this.migrationContext.GetIteration() == 0, fmt.Sprintf("iteration:%d", this.migrationContext.GetIteration()), ) if err != nil { return hasFurtherRange, err } rows, err := this.db.Query(query, explodedArgs...) if err != nil { return hasFurtherRange, err } defer rows.Close() iterationRangeMaxValues := sql.NewColumnValues(this.migrationContext.UniqueKey.Len()) for rows.Next() { if err = rows.Scan(iterationRangeMaxValues.ValuesPointers...); err != nil { return hasFurtherRange, err } hasFurtherRange = true } if err = rows.Err(); err != nil { return hasFurtherRange, err } if hasFurtherRange { this.migrationContext.MigrationIterationRangeMaxValues = iterationRangeMaxValues return hasFurtherRange, nil } } this.migrationContext.Log.Debugf("Iteration complete: no further range to iterate") return hasFurtherRange, nil } // ApplyIterationInsertQuery issues a chunk-INSERT query on the ghost table. It is where // data actually gets copied from original table. func (this *Applier) ApplyIterationInsertQuery() (chunkSize int64, rowsAffected int64, duration time.Duration, err error) { startTime := time.Now() chunkSize = atomic.LoadInt64(&this.migrationContext.ChunkSize) query, explodedArgs, err := sql.BuildRangeInsertPreparedQuery( this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName, this.migrationContext.GetGhostTableName(), this.migrationContext.SharedColumns.Names(), this.migrationContext.MappedSharedColumns.Names(), this.migrationContext.UniqueKey.Name, &this.migrationContext.UniqueKey.Columns, this.migrationContext.MigrationIterationRangeMinValues.AbstractValues(), this.migrationContext.MigrationIterationRangeMaxValues.AbstractValues(), this.migrationContext.GetIteration() == 0, this.migrationContext.IsTransactionalTable(), // TODO: Don't hardcode this strings.HasPrefix(this.migrationContext.ApplierMySQLVersion, "8."), ) if err != nil { return chunkSize, rowsAffected, duration, err } sqlResult, err := func() (gosql.Result, error) { tx, err := this.db.Begin() if err != nil { return nil, err } defer tx.Rollback() sessionQuery := fmt.Sprintf(`SET SESSION time_zone = '%s'`, this.migrationContext.ApplierTimeZone) sessionQuery = fmt.Sprintf("%s, %s", sessionQuery, this.generateSqlModeQuery()) if _, err := tx.Exec(sessionQuery); err != nil { return nil, err } result, err := tx.Exec(query, explodedArgs...) if err != nil { return nil, err } if this.migrationContext.PanicOnWarnings { //nolint:execinquery rows, err := tx.Query("SHOW WARNINGS") if err != nil { return nil, err } defer rows.Close() if err = rows.Err(); err != nil { return nil, err } // Compile regex once before loop to avoid performance penalty and handle errors properly migrationKeyRegex, err := this.compileMigrationKeyWarningRegex() if err != nil { return nil, err } var sqlWarnings []string for rows.Next() { var level, message string var code int if err := rows.Scan(&level, &code, &message); err != nil { this.migrationContext.Log.Warningf("Failed to read SHOW WARNINGS row") continue } if strings.Contains(message, "Duplicate entry") && migrationKeyRegex.MatchString(message) { continue } sqlWarnings = append(sqlWarnings, fmt.Sprintf("%s: %s (%d)", level, message, code)) } this.migrationContext.MigrationLastInsertSQLWarnings = sqlWarnings } if err := tx.Commit(); err != nil { return nil, err } return result, nil }() if err != nil { return chunkSize, rowsAffected, duration, err } rowsAffected, _ = sqlResult.RowsAffected() duration = time.Since(startTime) this.migrationContext.Log.Debugf( "Issued INSERT on range: [%s]..[%s]; iteration: %d; chunk-size: %d", this.migrationContext.MigrationIterationRangeMinValues, this.migrationContext.MigrationIterationRangeMaxValues, this.migrationContext.GetIteration(), chunkSize) return chunkSize, rowsAffected, duration, nil } // LockOriginalTable places a write lock on the original table func (this *Applier) LockOriginalTable() error { query := fmt.Sprintf(`lock /* gh-ost */ tables %s.%s write`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName), ) this.migrationContext.Log.Infof("Locking %s.%s", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName), ) this.migrationContext.LockTablesStartTime = time.Now() if _, err := sqlutils.ExecNoPrepare(this.singletonDB, query); err != nil { return err } this.migrationContext.Log.Infof("Table locked") return nil } // UnlockTables makes tea. No wait, it unlocks tables. func (this *Applier) UnlockTables() error { query := `unlock /* gh-ost */ tables` this.migrationContext.Log.Infof("Unlocking tables") if _, err := sqlutils.ExecNoPrepare(this.singletonDB, query); err != nil { return err } this.migrationContext.Log.Infof("Tables unlocked") return nil } // SwapTablesQuickAndBumpy issues a two-step swap table operation: // - rename original table to _old // - rename ghost table to original // There is a point in time in between where the table does not exist. func (this *Applier) SwapTablesQuickAndBumpy() error { query := fmt.Sprintf(`alter /* gh-ost */ table %s.%s rename %s`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName), sql.EscapeName(this.migrationContext.GetOldTableName()), ) this.migrationContext.Log.Infof("Renaming original table") this.migrationContext.RenameTablesStartTime = time.Now() if _, err := sqlutils.ExecNoPrepare(this.singletonDB, query); err != nil { return err } query = fmt.Sprintf(`alter /* gh-ost */ table %s.%s rename %s`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetGhostTableName()), sql.EscapeName(this.migrationContext.OriginalTableName), ) this.migrationContext.Log.Infof("Renaming ghost table") if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil { return err } this.migrationContext.RenameTablesEndTime = time.Now() this.migrationContext.Log.Infof("Tables renamed") return nil } // RenameTablesRollback renames back both table: original back to ghost, // _old back to original. This is used by `--test-on-replica` func (this *Applier) RenameTablesRollback() (renameError error) { // Restoring tables to original names. // We prefer the single, atomic operation: query := fmt.Sprintf(`rename /* gh-ost */ table %s.%s to %s.%s, %s.%s to %s.%s`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName), sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetGhostTableName()), sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetOldTableName()), sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName), ) this.migrationContext.Log.Infof("Renaming back both tables") if _, err := sqlutils.ExecNoPrepare(this.db, query); err == nil { return nil } // But, if for some reason the above was impossible to do, we rename one by one. query = fmt.Sprintf(`rename /* gh-ost */ table %s.%s to %s.%s`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName), sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetGhostTableName()), ) this.migrationContext.Log.Infof("Renaming back to ghost table") if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil { renameError = err } query = fmt.Sprintf(`rename /* gh-ost */ table %s.%s to %s.%s`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetOldTableName()), sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName), ) this.migrationContext.Log.Infof("Renaming back to original table") if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil { renameError = err } return this.migrationContext.Log.Errore(renameError) } // StopSlaveIOThread is applicable with --test-on-replica; it stops the IO thread, duh. // We need to keep the SQL thread active so as to complete processing received events, // and have them written to the binary log, so that we can then read them via streamer. func (this *Applier) StopSlaveIOThread() error { replicaTerm := mysql.ReplicaTermFor(this.migrationContext.ApplierMySQLVersion, `slave`) query := fmt.Sprintf("stop /* gh-ost */ %s io_thread", replicaTerm) this.migrationContext.Log.Infof("Stopping replication IO thread") if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil { return err } this.migrationContext.Log.Infof("Replication IO thread stopped") return nil } // StartSlaveIOThread is applicable with --test-on-replica func (this *Applier) StartSlaveIOThread() error { replicaTerm := mysql.ReplicaTermFor(this.migrationContext.ApplierMySQLVersion, `slave`) query := fmt.Sprintf("start /* gh-ost */ %s io_thread", replicaTerm) this.migrationContext.Log.Infof("Starting replication IO thread") if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil { return err } this.migrationContext.Log.Infof("Replication IO thread started") return nil } // StopSlaveSQLThread is applicable with --test-on-replica func (this *Applier) StopSlaveSQLThread() error { replicaTerm := mysql.ReplicaTermFor(this.migrationContext.ApplierMySQLVersion, `slave`) query := fmt.Sprintf("stop /* gh-ost */ %s sql_thread", replicaTerm) this.migrationContext.Log.Infof("Verifying SQL thread is stopped") if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil { return err } this.migrationContext.Log.Infof("SQL thread stopped") return nil } // StartSlaveSQLThread is applicable with --test-on-replica func (this *Applier) StartSlaveSQLThread() error { replicaTerm := mysql.ReplicaTermFor(this.migrationContext.ApplierMySQLVersion, `slave`) query := fmt.Sprintf("start /* gh-ost */ %s sql_thread", replicaTerm) this.migrationContext.Log.Infof("Verifying SQL thread is running") if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil { return err } this.migrationContext.Log.Infof("SQL thread started") return nil } // StopReplication is used by `--test-on-replica` and stops replication. func (this *Applier) StopReplication() error { if err := this.StopSlaveIOThread(); err != nil { return err } if err := this.StopSlaveSQLThread(); err != nil { return err } readBinlogCoordinates, executeBinlogCoordinates, err := mysql.GetReplicationBinlogCoordinates(this.migrationContext.ApplierMySQLVersion, this.db, this.migrationContext.UseGTIDs) if err != nil { return err } this.migrationContext.Log.Infof("Replication IO thread at %+v. SQL thread is at %+v", readBinlogCoordinates, executeBinlogCoordinates) return nil } // StartReplication is used by `--test-on-replica` on cut-over failure func (this *Applier) StartReplication() error { if err := this.StartSlaveIOThread(); err != nil { return err } if err := this.StartSlaveSQLThread(); err != nil { return err } this.migrationContext.Log.Infof("Replication started") return nil } // GetSessionLockName returns a name for the special hint session voluntary lock func (this *Applier) GetSessionLockName(sessionId int64) string { return fmt.Sprintf("gh-ost.%d.lock", sessionId) } // ExpectUsedLock expects the special hint voluntary lock to exist on given session func (this *Applier) ExpectUsedLock(sessionId int64) error { var result int64 query := `select /* gh-ost */ is_used_lock(?)` lockName := this.GetSessionLockName(sessionId) this.migrationContext.Log.Infof("Checking session lock: %s", lockName) if err := this.db.QueryRow(query, lockName).Scan(&result); err != nil || result != sessionId { return fmt.Errorf("Session lock %s expected to be found but wasn't", lockName) } return nil } // ExpectProcess expects a process to show up in `SHOW PROCESSLIST` that has given characteristics func (this *Applier) ExpectProcess(sessionId int64, stateHint, infoHint string) error { found := false query := ` select /* gh-ost */ id from information_schema.processlist where id != connection_id() and ? in (0, id) and state like concat('%', ?, '%') and info like concat('%', ?, '%')` err := sqlutils.QueryRowsMap(this.db, query, func(m sqlutils.RowMap) error { found = true return nil }, sessionId, stateHint, infoHint) if err != nil { return err } if !found { return fmt.Errorf("Cannot find process. Hints: %s, %s", stateHint, infoHint) } return nil } // DropAtomicCutOverSentryTableIfExists checks if the "old" table name // happens to be a cut-over magic table; if so, it drops it. func (this *Applier) DropAtomicCutOverSentryTableIfExists() error { this.migrationContext.Log.Infof("Looking for magic cut-over table") tableName := this.migrationContext.GetOldTableName() rowMap := this.showTableStatus(tableName) if rowMap == nil { // Table does not exist return nil } if rowMap["Comment"].String != atomicCutOverMagicHint { return fmt.Errorf("Expected magic comment on %s, did not find it", tableName) } this.migrationContext.Log.Infof("Dropping magic cut-over table") return this.dropTable(tableName) } // CreateAtomicCutOverSentryTable func (this *Applier) CreateAtomicCutOverSentryTable() error { if err := this.DropAtomicCutOverSentryTableIfExists(); err != nil { return err } tableName := this.migrationContext.GetOldTableName() query := fmt.Sprintf(` create /* gh-ost */ table %s.%s ( id int auto_increment primary key ) engine=%s comment='%s'`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(tableName), this.migrationContext.TableEngine, atomicCutOverMagicHint, ) this.migrationContext.Log.Infof("Creating magic cut-over table %s.%s", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(tableName), ) if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil { return err } this.migrationContext.Log.Infof("Magic cut-over table created") return nil } // InitAtomicCutOverWaitTimeout sets the cut-over session wait_timeout in order to reduce the // time an unresponsive (but still connected) gh-ost process can hold the cut-over lock. func (this *Applier) InitAtomicCutOverWaitTimeout(tx *gosql.Tx) error { cutOverWaitTimeoutSeconds := this.migrationContext.CutOverLockTimeoutSeconds * 3 this.migrationContext.Log.Infof("Setting cut-over idle timeout as %d seconds", cutOverWaitTimeoutSeconds) query := fmt.Sprintf(`set /* gh-ost */ session wait_timeout:=%d`, cutOverWaitTimeoutSeconds) _, err := tx.Exec(query) return err } // RevertAtomicCutOverWaitTimeout restores the original wait_timeout for the applier session post-cut-over. func (this *Applier) RevertAtomicCutOverWaitTimeout() { this.migrationContext.Log.Infof("Reverting cut-over idle timeout to %d seconds", this.migrationContext.ApplierWaitTimeout) query := fmt.Sprintf(`set /* gh-ost */ session wait_timeout:=%d`, this.migrationContext.ApplierWaitTimeout) if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil { this.migrationContext.Log.Errorf("Failed to restore applier wait_timeout to %d seconds: %v", this.migrationContext.ApplierWaitTimeout, err, ) } } // AtomicCutOverMagicLock func (this *Applier) AtomicCutOverMagicLock(sessionIdChan chan int64, tableLocked chan<- error, okToUnlockTable <-chan bool, tableUnlocked chan<- error, renameLockSessionId *int64) error { tx, err := this.db.Begin() if err != nil { tableLocked <- err return err } defer func() { sessionIdChan <- -1 tableLocked <- fmt.Errorf("Unexpected error in AtomicCutOverMagicLock(), injected to release blocking channel reads") tableUnlocked <- fmt.Errorf("Unexpected error in AtomicCutOverMagicLock(), injected to release blocking channel reads") tx.Rollback() this.DropAtomicCutOverSentryTableIfExists() }() var sessionId int64 if err := tx.QueryRow(`select /* gh-ost */ connection_id()`).Scan(&sessionId); err != nil { tableLocked <- err return err } sessionIdChan <- sessionId lockResult := 0 query := `select /* gh-ost */ get_lock(?, 0)` lockName := this.GetSessionLockName(sessionId) this.migrationContext.Log.Infof("Grabbing voluntary lock: %s", lockName) if err := tx.QueryRow(query, lockName).Scan(&lockResult); err != nil || lockResult != 1 { err := fmt.Errorf("Unable to acquire lock %s", lockName) tableLocked <- err return err } tableLockTimeoutSeconds := this.migrationContext.CutOverLockTimeoutSeconds * 2 this.migrationContext.Log.Infof("Setting LOCK timeout as %d seconds", tableLockTimeoutSeconds) query = fmt.Sprintf(`set /* gh-ost */ session lock_wait_timeout:=%d`, tableLockTimeoutSeconds) if _, err := tx.Exec(query); err != nil { tableLocked <- err return err } if err := this.CreateAtomicCutOverSentryTable(); err != nil { tableLocked <- err return err } if err := this.InitAtomicCutOverWaitTimeout(tx); err != nil { tableLocked <- err return err } defer this.RevertAtomicCutOverWaitTimeout() query = fmt.Sprintf(`lock /* gh-ost */ tables %s.%s write, %s.%s write`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName), sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetOldTableName()), ) this.migrationContext.Log.Infof("Locking %s.%s, %s.%s", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName), sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetOldTableName()), ) this.migrationContext.LockTablesStartTime = time.Now() if _, err := tx.Exec(query); err != nil { tableLocked <- err return err } this.migrationContext.Log.Infof("Tables locked") tableLocked <- nil // No error. // From this point on, we are committed to UNLOCK TABLES. No matter what happens, // the UNLOCK must execute (or, alternatively, this connection dies, which gets the same impact) // The cut-over phase will proceed to apply remaining backlog onto ghost table, // and issue RENAME. We wait here until told to proceed. <-okToUnlockTable this.migrationContext.Log.Infof("Will now proceed to drop magic table and unlock tables") // The magic table is here because we locked it. And we are the only ones allowed to drop it. // And in fact, we will: this.migrationContext.Log.Infof("Dropping magic cut-over table") query = fmt.Sprintf(`drop /* gh-ost */ table if exists %s.%s`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetOldTableName()), ) if _, err := tx.Exec(query); err != nil { this.migrationContext.Log.Errore(err) // We DO NOT return here because we must `UNLOCK TABLES`! } this.migrationContext.Log.Infof("Session renameLockSessionId is %+v", *renameLockSessionId) // Checking the lock is held by rename session if *renameLockSessionId > 0 && this.migrationContext.IsOpenMetadataLockInstruments && !this.migrationContext.SkipMetadataLockCheck { sleepDuration := time.Duration(10*this.migrationContext.CutOverLockTimeoutSeconds) * time.Millisecond for i := 1; i <= 100; i++ { err := this.ExpectMetadataLock(*renameLockSessionId) if err == nil { this.migrationContext.Log.Infof("Rename session is pending lock on the origin table !") break } else { time.Sleep(sleepDuration) } } } // Tables still locked this.migrationContext.Log.Infof("Releasing lock from %s.%s, %s.%s", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName), sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetOldTableName()), ) query = `unlock /* gh-ost */ tables` if _, err := tx.Exec(query); err != nil { tableUnlocked <- err return this.migrationContext.Log.Errore(err) } this.migrationContext.Log.Infof("Tables unlocked") tableUnlocked <- nil return nil } // AtomicCutoverRename func (this *Applier) AtomicCutoverRename(sessionIdChan chan int64, tablesRenamed chan<- error) error { tx, err := this.db.Begin() if err != nil { return err } defer func() { tx.Rollback() sessionIdChan <- -1 tablesRenamed <- fmt.Errorf("Unexpected error in AtomicCutoverRename(), injected to release blocking channel reads") }() var sessionId int64 if err := tx.QueryRow(`select /* gh-ost */ connection_id()`).Scan(&sessionId); err != nil { return err } sessionIdChan <- sessionId this.migrationContext.Log.Infof("Setting RENAME timeout as %d seconds", this.migrationContext.CutOverLockTimeoutSeconds) query := fmt.Sprintf(`set /* gh-ost */ session lock_wait_timeout:=%d`, this.migrationContext.CutOverLockTimeoutSeconds) if _, err := tx.Exec(query); err != nil { return err } query = fmt.Sprintf(`rename /* gh-ost */ table %s.%s to %s.%s, %s.%s to %s.%s`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName), sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetOldTableName()), sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetGhostTableName()), sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName), ) this.migrationContext.Log.Infof("Issuing and expecting this to block: %s", query) if _, err := tx.Exec(query); err != nil { tablesRenamed <- err return this.migrationContext.Log.Errore(err) } tablesRenamed <- nil this.migrationContext.Log.Infof("Tables renamed") return nil } func (this *Applier) ShowStatusVariable(variableName string) (result int64, err error) { query := fmt.Sprintf(`show /* gh-ost */ global status like '%s'`, variableName) if err := this.db.QueryRow(query).Scan(&variableName, &result); err != nil { return 0, err } return result, nil } // updateModifiesUniqueKeyColumns checks whether a UPDATE DML event actually // modifies values of the migration's unique key (the iterated key). This will call // for special handling. func (this *Applier) updateModifiesUniqueKeyColumns(dmlEvent *binlog.BinlogDMLEvent) (modifiedColumn string, isModified bool) { for _, column := range this.migrationContext.UniqueKey.Columns.Columns() { tableOrdinal := this.migrationContext.OriginalTableColumns.Ordinals[column.Name] whereColumnValue := dmlEvent.WhereColumnValues.AbstractValues()[tableOrdinal] newColumnValue := dmlEvent.NewColumnValues.AbstractValues()[tableOrdinal] if !reflect.DeepEqual(whereColumnValue, newColumnValue) { return column.Name, true } } return "", false } // buildDMLEventQuery creates a query to operate on the ghost table, based on an intercepted binlog // event entry on the original table. func (this *Applier) buildDMLEventQuery(dmlEvent *binlog.BinlogDMLEvent) []*dmlBuildResult { switch dmlEvent.DML { case binlog.DeleteDML: { query, uniqueKeyArgs, err := this.dmlDeleteQueryBuilder.BuildQuery(dmlEvent.WhereColumnValues.AbstractValues()) return []*dmlBuildResult{newDmlBuildResult(query, uniqueKeyArgs, -1, err)} } case binlog.InsertDML: { query, sharedArgs, err := this.dmlInsertQueryBuilder.BuildQuery(dmlEvent.NewColumnValues.AbstractValues()) return []*dmlBuildResult{newDmlBuildResult(query, sharedArgs, 1, err)} } case binlog.UpdateDML: { if _, isModified := this.updateModifiesUniqueKeyColumns(dmlEvent); isModified { results := make([]*dmlBuildResult, 0, 2) dmlEvent.DML = binlog.DeleteDML results = append(results, this.buildDMLEventQuery(dmlEvent)...) dmlEvent.DML = binlog.InsertDML results = append(results, this.buildDMLEventQuery(dmlEvent)...) return results } query, updateArgs, err := this.dmlUpdateQueryBuilder.BuildQuery(dmlEvent.NewColumnValues.AbstractValues(), dmlEvent.WhereColumnValues.AbstractValues()) args := sqlutils.Args() args = append(args, updateArgs...) return []*dmlBuildResult{newDmlBuildResult(query, args, 0, err)} } } return []*dmlBuildResult{newDmlBuildResultError(fmt.Errorf("Unknown dml event type: %+v", dmlEvent.DML))} } // ApplyDMLEventQueries applies multiple DML queries onto the _ghost_ table func (this *Applier) ApplyDMLEventQueries(dmlEvents [](*binlog.BinlogDMLEvent)) error { var totalDelta int64 ctx := context.Background() err := func() error { conn, err := this.db.Conn(ctx) if err != nil { return err } defer conn.Close() sessionQuery := "SET /* gh-ost */ SESSION time_zone = '+00:00'" sessionQuery = fmt.Sprintf("%s, %s", sessionQuery, this.generateSqlModeQuery()) if _, err := conn.ExecContext(ctx, sessionQuery); err != nil { return err } tx, err := conn.BeginTx(ctx, nil) if err != nil { return err } rollback := func(err error) error { tx.Rollback() return err } buildResults := make([]*dmlBuildResult, 0, len(dmlEvents)) nArgs := 0 for _, dmlEvent := range dmlEvents { for _, buildResult := range this.buildDMLEventQuery(dmlEvent) { if buildResult.err != nil { return rollback(buildResult.err) } nArgs += len(buildResult.args) buildResults = append(buildResults, buildResult) } } // We batch together the DML queries into multi-statements to minimize network trips. // We have to use the raw driver connection to access the rows affected // for each statement in the multi-statement. execErr := conn.Raw(func(driverConn any) error { ex := driverConn.(driver.ExecerContext) nvc := driverConn.(driver.NamedValueChecker) multiArgs := make([]driver.NamedValue, 0, nArgs) multiQueryBuilder := strings.Builder{} for _, buildResult := range buildResults { for _, arg := range buildResult.args { nv := driver.NamedValue{Value: driver.Value(arg)} nvc.CheckNamedValue(&nv) multiArgs = append(multiArgs, nv) } multiQueryBuilder.WriteString(buildResult.query) multiQueryBuilder.WriteString(";\n") } res, err := ex.ExecContext(ctx, multiQueryBuilder.String(), multiArgs) if err != nil { err = fmt.Errorf("%w; query=%s; args=%+v", err, multiQueryBuilder.String(), multiArgs) return err } mysqlRes := res.(drivermysql.Result) // each DML is either a single insert (delta +1), update (delta +0) or delete (delta -1). // multiplying by the rows actually affected (either 0 or 1) will give an accurate row delta for this DML event for i, rowsAffected := range mysqlRes.AllRowsAffected() { totalDelta += buildResults[i].rowsDelta * rowsAffected } return nil }) if execErr != nil { return rollback(execErr) } // Check for warnings when PanicOnWarnings is enabled if this.migrationContext.PanicOnWarnings { //nolint:execinquery rows, err := tx.Query("SHOW WARNINGS") if err != nil { return rollback(err) } defer rows.Close() if err = rows.Err(); err != nil { return rollback(err) } // Compile regex once before loop to avoid performance penalty and handle errors properly migrationKeyRegex, err := this.compileMigrationKeyWarningRegex() if err != nil { return rollback(err) } var sqlWarnings []string for rows.Next() { var level, message string var code int if err := rows.Scan(&level, &code, &message); err != nil { this.migrationContext.Log.Warningf("Failed to read SHOW WARNINGS row") continue } if strings.Contains(message, "Duplicate entry") && migrationKeyRegex.MatchString(message) { // Duplicate entry on migration unique key is expected during binlog replay // (row was already copied during bulk copy phase) continue } sqlWarnings = append(sqlWarnings, fmt.Sprintf("%s: %s (%d)", level, message, code)) } if len(sqlWarnings) > 0 { warningMsg := fmt.Sprintf("Warnings detected during DML event application: %v", sqlWarnings) return rollback(errors.New(warningMsg)) } } if err := tx.Commit(); err != nil { return err } return nil }() if err != nil { return this.migrationContext.Log.Errore(err) } // no error atomic.AddInt64(&this.migrationContext.TotalDMLEventsApplied, int64(len(dmlEvents))) if this.migrationContext.CountTableRows { atomic.AddInt64(&this.migrationContext.RowsDeltaEstimate, totalDelta) } this.migrationContext.Log.Debugf("ApplyDMLEventQueries() applied %d events in one transaction", len(dmlEvents)) return nil } func (this *Applier) Teardown() { this.migrationContext.Log.Debugf("Tearing down...") this.db.Close() this.singletonDB.Close() atomic.StoreInt64(&this.finishedMigrating, 1) } func (this *Applier) ExpectMetadataLock(sessionId int64) error { found := false query := ` select /* gh-ost */ m.owner_thread_id from performance_schema.metadata_locks m join performance_schema.threads t on m.owner_thread_id=t.thread_id where m.object_type = 'TABLE' and m.object_schema = ? and m.object_name = ? and m.lock_type = 'EXCLUSIVE' and m.lock_status = 'PENDING' and t.processlist_id = ? ` err := sqlutils.QueryRowsMap(this.db, query, func(m sqlutils.RowMap) error { found = true return nil }, this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName, sessionId) if err != nil { return err } if !found { err = fmt.Errorf("cannot find PENDING metadata lock on original table: `%s`.`%s`", this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName) return this.migrationContext.Log.Errore(err) } return nil } ================================================ FILE: go/logic/applier_test.go ================================================ /* Copyright 2025 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package logic import ( "context" gosql "database/sql" "errors" "strings" "testing" "time" drivermysql "github.com/go-sql-driver/mysql" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/testcontainers/testcontainers-go" testmysql "github.com/testcontainers/testcontainers-go/modules/mysql" "fmt" "github.com/github/gh-ost/go/base" "github.com/github/gh-ost/go/binlog" "github.com/github/gh-ost/go/mysql" "github.com/github/gh-ost/go/sql" ) func TestApplierGenerateSqlModeQuery(t *testing.T) { migrationContext := base.NewMigrationContext() applier := NewApplier(migrationContext) { require.Equal(t, `sql_mode = CONCAT(@@session.sql_mode, ',NO_AUTO_VALUE_ON_ZERO,STRICT_ALL_TABLES')`, applier.generateSqlModeQuery(), ) } { migrationContext.SkipStrictMode = true migrationContext.AllowZeroInDate = false require.Equal(t, `sql_mode = CONCAT(@@session.sql_mode, ',NO_AUTO_VALUE_ON_ZERO')`, applier.generateSqlModeQuery(), ) } { migrationContext.SkipStrictMode = false migrationContext.AllowZeroInDate = true require.Equal(t, `sql_mode = REPLACE(REPLACE(CONCAT(@@session.sql_mode, ',NO_AUTO_VALUE_ON_ZERO,STRICT_ALL_TABLES'), 'NO_ZERO_IN_DATE', ''), 'NO_ZERO_DATE', '')`, applier.generateSqlModeQuery(), ) } { migrationContext.SkipStrictMode = true migrationContext.AllowZeroInDate = true require.Equal(t, `sql_mode = REPLACE(REPLACE(CONCAT(@@session.sql_mode, ',NO_AUTO_VALUE_ON_ZERO'), 'NO_ZERO_IN_DATE', ''), 'NO_ZERO_DATE', '')`, applier.generateSqlModeQuery(), ) } } func TestApplierUpdateModifiesUniqueKeyColumns(t *testing.T) { columns := sql.NewColumnList([]string{"id", "item_id", "item_text"}) columnValues := sql.ToColumnValues([]interface{}{123456, 42, []uint8{116, 101, 115, 116}}) migrationContext := base.NewMigrationContext() migrationContext.OriginalTableColumns = columns migrationContext.UniqueKey = &sql.UniqueKey{ Name: t.Name(), Columns: *columns, } applier := NewApplier(migrationContext) t.Run("unmodified", func(t *testing.T) { modifiedColumn, isModified := applier.updateModifiesUniqueKeyColumns(&binlog.BinlogDMLEvent{ DatabaseName: "test", DML: binlog.UpdateDML, NewColumnValues: columnValues, WhereColumnValues: columnValues, }) require.Equal(t, "", modifiedColumn) require.False(t, isModified) }) t.Run("modified", func(t *testing.T) { modifiedColumn, isModified := applier.updateModifiesUniqueKeyColumns(&binlog.BinlogDMLEvent{ DatabaseName: "test", DML: binlog.UpdateDML, NewColumnValues: sql.ToColumnValues([]interface{}{123456, 24}), WhereColumnValues: columnValues, }) require.Equal(t, "item_id", modifiedColumn) require.True(t, isModified) }) } func TestApplierBuildDMLEventQuery(t *testing.T) { columns := sql.NewColumnList([]string{"id", "item_id"}) columnValues := sql.ToColumnValues([]interface{}{123456, 42}) migrationContext := base.NewMigrationContext() migrationContext.DatabaseName = "test" migrationContext.OriginalTableName = "test" migrationContext.OriginalTableColumns = columns migrationContext.SharedColumns = columns migrationContext.MappedSharedColumns = columns migrationContext.UniqueKey = &sql.UniqueKey{ Name: t.Name(), Columns: *columns, } applier := NewApplier(migrationContext) applier.prepareQueries() t.Run("delete", func(t *testing.T) { binlogEvent := &binlog.BinlogDMLEvent{ DatabaseName: "test", DML: binlog.DeleteDML, WhereColumnValues: columnValues, } res := applier.buildDMLEventQuery(binlogEvent) require.Len(t, res, 1) require.NoError(t, res[0].err) require.Equal(t, `delete /* gh-ost `+"`test`.`_test_gho`"+` */ from `+"`test`.`_test_gho`"+` where ((`+"`id`"+` = ?) and (`+"`item_id`"+` = ?))`, strings.TrimSpace(res[0].query)) require.Len(t, res[0].args, 2) require.Equal(t, 123456, res[0].args[0]) require.Equal(t, 42, res[0].args[1]) }) t.Run("insert", func(t *testing.T) { binlogEvent := &binlog.BinlogDMLEvent{ DatabaseName: "test", DML: binlog.InsertDML, NewColumnValues: columnValues, } res := applier.buildDMLEventQuery(binlogEvent) require.Len(t, res, 1) require.NoError(t, res[0].err) require.Equal(t, `insert /* gh-ost `+"`test`.`_test_gho`"+` */ ignore into `+"`test`.`_test_gho`"+` `+"(`id`, `item_id`)"+` values (?, ?)`, strings.TrimSpace(res[0].query)) require.Len(t, res[0].args, 2) require.Equal(t, 123456, res[0].args[0]) require.Equal(t, 42, res[0].args[1]) }) t.Run("update", func(t *testing.T) { binlogEvent := &binlog.BinlogDMLEvent{ DatabaseName: "test", DML: binlog.UpdateDML, NewColumnValues: columnValues, WhereColumnValues: columnValues, } res := applier.buildDMLEventQuery(binlogEvent) require.Len(t, res, 1) require.NoError(t, res[0].err) require.Equal(t, `update /* gh-ost `+"`test`.`_test_gho`"+` */ `+"`test`.`_test_gho`"+` set `+"`id`"+`=?, `+"`item_id`"+`=? where ((`+"`id`"+` = ?) and (`+"`item_id`"+` = ?))`, strings.TrimSpace(res[0].query)) require.Len(t, res[0].args, 4) require.Equal(t, 123456, res[0].args[0]) require.Equal(t, 42, res[0].args[1]) require.Equal(t, 123456, res[0].args[2]) require.Equal(t, 42, res[0].args[3]) }) } func TestApplierInstantDDL(t *testing.T) { migrationContext := base.NewMigrationContext() migrationContext.DatabaseName = "test" migrationContext.SkipPortValidation = true migrationContext.OriginalTableName = "mytable" migrationContext.AlterStatementOptions = "ADD INDEX (foo)" applier := NewApplier(migrationContext) t.Run("instantDDLstmt", func(t *testing.T) { stmt := applier.generateInstantDDLQuery() require.Equal(t, "ALTER /* gh-ost */ TABLE `test`.`mytable` ADD INDEX (foo), ALGORITHM=INSTANT", stmt) }) } func TestRetryOnLockWaitTimeout(t *testing.T) { oldRetrySleepFn := RetrySleepFn defer func() { RetrySleepFn = oldRetrySleepFn }() RetrySleepFn = func(d time.Duration) {} // no-op for tests logger := base.NewMigrationContext().Log lockWaitTimeoutErr := &drivermysql.MySQLError{Number: 1205, Message: "Lock wait timeout exceeded"} nonRetryableErr := &drivermysql.MySQLError{Number: 1845, Message: "ALGORITHM=INSTANT is not supported"} t.Run("success on first attempt", func(t *testing.T) { calls := 0 err := retryOnLockWaitTimeout(func() error { calls++ return nil }, logger) require.NoError(t, err) require.Equal(t, 1, calls) }) t.Run("retry on lock wait timeout then succeed", func(t *testing.T) { calls := 0 err := retryOnLockWaitTimeout(func() error { calls++ if calls < 3 { return lockWaitTimeoutErr } return nil }, logger) require.NoError(t, err) require.Equal(t, 3, calls) }) t.Run("non-retryable error returns immediately", func(t *testing.T) { calls := 0 err := retryOnLockWaitTimeout(func() error { calls++ return nonRetryableErr }, logger) require.ErrorIs(t, err, nonRetryableErr) require.Equal(t, 1, calls) }) t.Run("non-mysql error returns immediately", func(t *testing.T) { calls := 0 genericErr := errors.New("connection refused") err := retryOnLockWaitTimeout(func() error { calls++ return genericErr }, logger) require.ErrorIs(t, err, genericErr) require.Equal(t, 1, calls) }) t.Run("exhausts all retries", func(t *testing.T) { calls := 0 err := retryOnLockWaitTimeout(func() error { calls++ return lockWaitTimeoutErr }, logger) require.ErrorIs(t, err, lockWaitTimeoutErr) require.Equal(t, 5, calls) }) } type ApplierTestSuite struct { suite.Suite mysqlContainer testcontainers.Container db *gosql.DB } func (suite *ApplierTestSuite) SetupSuite() { ctx := context.Background() mysqlContainer, err := testmysql.Run(ctx, testMysqlContainerImage, testmysql.WithDatabase(testMysqlDatabase), testmysql.WithUsername(testMysqlUser), testmysql.WithPassword(testMysqlPass), testmysql.WithConfigFile("my.cnf.test"), ) suite.Require().NoError(err) suite.mysqlContainer = mysqlContainer dsn, err := mysqlContainer.ConnectionString(ctx) suite.Require().NoError(err) db, err := gosql.Open("mysql", dsn) suite.Require().NoError(err) suite.db = db } func (suite *ApplierTestSuite) TeardownSuite() { suite.Assert().NoError(suite.db.Close()) suite.Assert().NoError(testcontainers.TerminateContainer(suite.mysqlContainer)) } func (suite *ApplierTestSuite) SetupTest() { ctx := context.Background() _, err := suite.db.ExecContext(ctx, fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", testMysqlDatabase)) suite.Require().NoError(err) } func (suite *ApplierTestSuite) TearDownTest() { ctx := context.Background() _, err := suite.db.ExecContext(ctx, "DROP TABLE IF EXISTS "+getTestTableName()) suite.Require().NoError(err) _, err = suite.db.ExecContext(ctx, "DROP TABLE IF EXISTS "+getTestGhostTableName()) suite.Require().NoError(err) } func (suite *ApplierTestSuite) TestInitDBConnections() { ctx := context.Background() var err error _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT, item_id INT);", getTestTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") applier := NewApplier(migrationContext) defer applier.Teardown() err = applier.InitDBConnections() suite.Require().NoError(err) mysqlVersion, _ := strings.CutPrefix(testMysqlContainerImage, "mysql:") suite.Require().Equal(mysqlVersion, migrationContext.ApplierMySQLVersion) suite.Require().Equal(int64(28800), migrationContext.ApplierWaitTimeout) suite.Require().Equal("+00:00", migrationContext.ApplierTimeZone) suite.Require().Equal(sql.NewColumnList([]string{"id", "item_id"}), migrationContext.OriginalTableColumnsOnApplier) } func (suite *ApplierTestSuite) TestApplyDMLEventQueries() { ctx := context.Background() var err error _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT, item_id INT);", getTestTableName())) suite.Require().NoError(err) _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT, item_id INT);", getTestGhostTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.OriginalTableColumns = sql.NewColumnList([]string{"id", "item_id"}) migrationContext.SharedColumns = sql.NewColumnList([]string{"id", "item_id"}) migrationContext.MappedSharedColumns = sql.NewColumnList([]string{"id", "item_id"}) migrationContext.UniqueKey = &sql.UniqueKey{ Name: "primary_key", Columns: *sql.NewColumnList([]string{"id"}), } applier := NewApplier(migrationContext) suite.Require().NoError(applier.prepareQueries()) defer applier.Teardown() err = applier.InitDBConnections() suite.Require().NoError(err) dmlEvents := []*binlog.BinlogDMLEvent{ { DatabaseName: testMysqlDatabase, TableName: testMysqlTableName, DML: binlog.InsertDML, NewColumnValues: sql.ToColumnValues([]interface{}{123456, 42}), }, } err = applier.ApplyDMLEventQueries(dmlEvents) suite.Require().NoError(err) // Check that the row was inserted rows, err := suite.db.Query("SELECT * FROM " + getTestGhostTableName()) suite.Require().NoError(err) defer rows.Close() var count, id, item_id int for rows.Next() { err = rows.Scan(&id, &item_id) suite.Require().NoError(err) count += 1 } suite.Require().NoError(rows.Err()) suite.Require().Equal(1, count) suite.Require().Equal(123456, id) suite.Require().Equal(42, item_id) suite.Require().Equal(int64(1), migrationContext.TotalDMLEventsApplied) suite.Require().Equal(int64(0), migrationContext.RowsDeltaEstimate) } func (suite *ApplierTestSuite) TestValidateOrDropExistingTables() { ctx := context.Background() var err error _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT, item_id INT);", getTestTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.OriginalTableColumns = sql.NewColumnList([]string{"id", "item_id"}) migrationContext.SharedColumns = sql.NewColumnList([]string{"id", "item_id"}) migrationContext.MappedSharedColumns = sql.NewColumnList([]string{"id", "item_id"}) applier := NewApplier(migrationContext) defer applier.Teardown() err = applier.InitDBConnections() suite.Require().NoError(err) err = applier.ValidateOrDropExistingTables() suite.Require().NoError(err) } func (suite *ApplierTestSuite) TestValidateOrDropExistingTablesWithGhostTableExisting() { ctx := context.Background() var err error _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT, item_id INT);", getTestTableName())) suite.Require().NoError(err) _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT, item_id INT);", getTestGhostTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.OriginalTableColumns = sql.NewColumnList([]string{"id", "item_id"}) migrationContext.SharedColumns = sql.NewColumnList([]string{"id", "item_id"}) migrationContext.MappedSharedColumns = sql.NewColumnList([]string{"id", "item_id"}) applier := NewApplier(migrationContext) defer applier.Teardown() err = applier.InitDBConnections() suite.Require().NoError(err) err = applier.ValidateOrDropExistingTables() suite.Require().Error(err) suite.Require().EqualError(err, "Table `_testing_gho` already exists. Panicking. Use --initially-drop-ghost-table to force dropping it, though I really prefer that you drop it or rename it away") } func (suite *ApplierTestSuite) TestValidateOrDropExistingTablesWithGhostTableExistingAndInitiallyDropGhostTableSet() { ctx := context.Background() var err error _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT, item_id INT);", getTestTableName())) suite.Require().NoError(err) _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT, item_id INT);", getTestGhostTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.InitiallyDropGhostTable = true applier := NewApplier(migrationContext) defer applier.Teardown() err = applier.InitDBConnections() suite.Require().NoError(err) err = applier.ValidateOrDropExistingTables() suite.Require().NoError(err) // Check that the ghost table was dropped var tableName string //nolint:execinquery err = suite.db.QueryRow(fmt.Sprintf("SHOW TABLES IN test LIKE '_%s_gho'", testMysqlTableName)).Scan(&tableName) suite.Require().Error(err) suite.Require().Equal(gosql.ErrNoRows, err) } func (suite *ApplierTestSuite) TestCreateGhostTable() { ctx := context.Background() var err error _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT, item_id INT);", getTestTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.OriginalTableColumns = sql.NewColumnList([]string{"id", "item_id"}) migrationContext.SharedColumns = sql.NewColumnList([]string{"id", "item_id"}) migrationContext.MappedSharedColumns = sql.NewColumnList([]string{"id", "item_id"}) migrationContext.InitiallyDropGhostTable = true applier := NewApplier(migrationContext) defer applier.Teardown() err = applier.InitDBConnections() suite.Require().NoError(err) err = applier.CreateGhostTable() suite.Require().NoError(err) // Check that the ghost table was created var tableName string //nolint:execinquery err = suite.db.QueryRow("SHOW TABLES IN test LIKE '_testing_gho'").Scan(&tableName) suite.Require().NoError(err) suite.Require().Equal("_testing_gho", tableName) // Check that the ghost table has the same columns as the original table var createDDL string //nolint:execinquery err = suite.db.QueryRow(fmt.Sprintf("SHOW CREATE TABLE %s", getTestGhostTableName())).Scan(&tableName, &createDDL) suite.Require().NoError(err) suite.Require().Equal("CREATE TABLE `_testing_gho` (\n `id` int DEFAULT NULL,\n `item_id` int DEFAULT NULL\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", createDDL) } func (suite *ApplierTestSuite) TestPanicOnWarningsInApplyIterationInsertQuerySucceedsWithUniqueKeyWarningInsertedByDMLEvent() { ctx := context.Background() var err error _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT, item_id INT, UNIQUE KEY (item_id));", getTestTableName())) suite.Require().NoError(err) _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT, item_id INT, UNIQUE KEY (item_id));", getTestGhostTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.PanicOnWarnings = true migrationContext.OriginalTableColumns = sql.NewColumnList([]string{"id", "item_id"}) migrationContext.SharedColumns = sql.NewColumnList([]string{"id", "item_id"}) migrationContext.MappedSharedColumns = sql.NewColumnList([]string{"id", "item_id"}) migrationContext.UniqueKey = &sql.UniqueKey{ Name: "item_id", NameInGhostTable: "item_id", Columns: *sql.NewColumnList([]string{"item_id"}), } applier := NewApplier(migrationContext) suite.Require().NoError(applier.prepareQueries()) defer applier.Teardown() err = applier.InitDBConnections() suite.Require().NoError(err) _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, item_id) VALUES (123456, 42);", getTestTableName())) suite.Require().NoError(err) dmlEvents := []*binlog.BinlogDMLEvent{ { DatabaseName: testMysqlDatabase, TableName: testMysqlTableName, DML: binlog.InsertDML, NewColumnValues: sql.ToColumnValues([]interface{}{123456, 42}), }, } err = applier.ApplyDMLEventQueries(dmlEvents) suite.Require().NoError(err) err = applier.CreateChangelogTable() suite.Require().NoError(err) err = applier.ReadMigrationRangeValues() suite.Require().NoError(err) migrationContext.SetNextIterationRangeMinValues() hasFurtherRange, err := applier.CalculateNextIterationRangeEndValues() suite.Require().NoError(err) suite.Require().True(hasFurtherRange) _, rowsAffected, _, err := applier.ApplyIterationInsertQuery() suite.Require().NoError(err) suite.Require().Equal(int64(0), rowsAffected) // Ensure Duplicate entry '42' for key '_testing_gho.item_id' is ignored correctly suite.Require().Empty(applier.migrationContext.MigrationLastInsertSQLWarnings) // Check that the row was inserted rows, err := suite.db.Query("SELECT * FROM " + getTestGhostTableName()) suite.Require().NoError(err) defer rows.Close() var count, id, item_id int for rows.Next() { err = rows.Scan(&id, &item_id) suite.Require().NoError(err) count += 1 } suite.Require().NoError(rows.Err()) suite.Require().Equal(1, count) suite.Require().Equal(123456, id) suite.Require().Equal(42, item_id) suite.Require(). Equal(int64(1), migrationContext.TotalDMLEventsApplied) suite.Require(). Equal(int64(0), migrationContext.RowsDeltaEstimate) } func (suite *ApplierTestSuite) TestPanicOnWarningsInApplyIterationInsertQueryFailsWithTruncationWarning() { ctx := context.Background() var err error _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id int not null, name varchar(20), primary key(id))", getTestTableName())) suite.Require().NoError(err) _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT, name varchar(20), primary key(id));", getTestGhostTableName())) suite.Require().NoError(err) _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, name) VALUES (1, 'this string is long')", getTestTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.AlterStatementOptions = "modify column name varchar(10)" migrationContext.OriginalTableColumns = sql.NewColumnList([]string{"id", "name"}) migrationContext.SharedColumns = sql.NewColumnList([]string{"id", "name"}) migrationContext.MappedSharedColumns = sql.NewColumnList([]string{"id", "name"}) migrationContext.UniqueKey = &sql.UniqueKey{ Name: "PRIMARY", NameInGhostTable: "PRIMARY", Columns: *sql.NewColumnList([]string{"id"}), } applier := NewApplier(migrationContext) err = applier.InitDBConnections() suite.Require().NoError(err) err = applier.CreateChangelogTable() suite.Require().NoError(err) err = applier.ReadMigrationRangeValues() suite.Require().NoError(err) err = applier.AlterGhost() suite.Require().NoError(err) migrationContext.SetNextIterationRangeMinValues() hasFurtherRange, err := applier.CalculateNextIterationRangeEndValues() suite.Require().NoError(err) suite.Require().True(hasFurtherRange) _, rowsAffected, _, err := applier.ApplyIterationInsertQuery() suite.Equal(int64(1), rowsAffected) suite.Require().NoError(err) // Verify the warning was recorded and will cause the migrator to panic suite.Require().NotEmpty(applier.migrationContext.MigrationLastInsertSQLWarnings) suite.Require().Contains(applier.migrationContext.MigrationLastInsertSQLWarnings[0], "Warning: Data truncated for column 'name' at row 1") } func (suite *ApplierTestSuite) TestWriteCheckpoint() { ctx := context.Background() var err error _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id int not null, id2 char(4) CHARACTER SET utf8mb4, primary key(id, id2))", getTestTableName())) suite.Require().NoError(err) _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT, id2 char(4) CHARACTER SET utf8mb4, name varchar(20), primary key(id, id2));", getTestGhostTableName())) suite.Require().NoError(err) _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, id2) VALUES (?,?), (?,?), (?,?)", getTestTableName()), 411, "君子懷德", 411, "小人懷土", 212, "君子不器") suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.InspectorConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.AlterStatementOptions = "add column name varchar(20)" migrationContext.OriginalTableColumns = sql.NewColumnList([]string{"id", "id2"}) migrationContext.SharedColumns = sql.NewColumnList([]string{"id", "id2"}) migrationContext.MappedSharedColumns = sql.NewColumnList([]string{"id", "id2"}) migrationContext.Checkpoint = true migrationContext.UniqueKey = &sql.UniqueKey{ Name: "PRIMARY", NameInGhostTable: "PRIMARY", Columns: *sql.NewColumnList([]string{"id", "id2"}), } inspector := NewInspector(migrationContext) suite.Require().NoError(inspector.InitDBConnections()) err = inspector.applyColumnTypes(testMysqlDatabase, testMysqlTableName, &migrationContext.UniqueKey.Columns) suite.Require().NoError(err) applier := NewApplier(migrationContext) err = applier.InitDBConnections() suite.Require().NoError(err) err = applier.CreateChangelogTable() suite.Require().NoError(err) err = applier.CreateCheckpointTable() suite.Require().NoError(err) err = applier.prepareQueries() suite.Require().NoError(err) err = applier.ReadMigrationRangeValues() suite.Require().NoError(err) // checkpoint table is empty _, err = applier.ReadLastCheckpoint() suite.Require().ErrorIs(err, ErrNoCheckpointFound) // write a checkpoint and read it back coords := mysql.NewFileBinlogCoordinates("mysql-bin.000003", int64(219202907)) chk := &Checkpoint{ LastTrxCoords: coords, IterationRangeMin: applier.migrationContext.MigrationRangeMinValues, IterationRangeMax: applier.migrationContext.MigrationRangeMaxValues, Iteration: 2, RowsCopied: 100000, DMLApplied: 200000, IsCutover: true, } id, err := applier.WriteCheckpoint(chk) suite.Require().NoError(err) suite.Require().Equal(int64(1), id) gotChk, err := applier.ReadLastCheckpoint() suite.Require().NoError(err) suite.Require().Equal(chk.Iteration, gotChk.Iteration) suite.Require().Equal(chk.LastTrxCoords.String(), gotChk.LastTrxCoords.String()) suite.Require().Equal(chk.IterationRangeMin.String(), gotChk.IterationRangeMin.String()) suite.Require().Equal(chk.IterationRangeMax.String(), gotChk.IterationRangeMax.String()) suite.Require().Equal(chk.RowsCopied, gotChk.RowsCopied) suite.Require().Equal(chk.DMLApplied, gotChk.DMLApplied) suite.Require().Equal(chk.IsCutover, gotChk.IsCutover) } func (suite *ApplierTestSuite) TestPanicOnWarningsWithDuplicateKeyOnNonMigrationIndex() { ctx := context.Background() var err error // Create table with id and email columns, where id is the primary key _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, email VARCHAR(100));", getTestTableName())) suite.Require().NoError(err) // Create ghost table with same schema plus a new unique index on email _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, email VARCHAR(100), UNIQUE KEY email_unique (email));", getTestGhostTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.PanicOnWarnings = true migrationContext.OriginalTableColumns = sql.NewColumnList([]string{"id", "email"}) migrationContext.SharedColumns = sql.NewColumnList([]string{"id", "email"}) migrationContext.MappedSharedColumns = sql.NewColumnList([]string{"id", "email"}) migrationContext.UniqueKey = &sql.UniqueKey{ Name: "PRIMARY", NameInGhostTable: "PRIMARY", Columns: *sql.NewColumnList([]string{"id"}), } applier := NewApplier(migrationContext) suite.Require().NoError(applier.prepareQueries()) defer applier.Teardown() err = applier.InitDBConnections() suite.Require().NoError(err) // Insert initial rows into ghost table (simulating bulk copy phase) _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, email) VALUES (1, 'user1@example.com'), (2, 'user2@example.com'), (3, 'user3@example.com');", getTestGhostTableName())) suite.Require().NoError(err) // Simulate binlog event: try to insert a row with duplicate email // This should fail with a warning because the ghost table has a unique index on email dmlEvents := []*binlog.BinlogDMLEvent{ { DatabaseName: testMysqlDatabase, TableName: testMysqlTableName, DML: binlog.InsertDML, NewColumnValues: sql.ToColumnValues([]interface{}{4, "user2@example.com"}), // duplicate email }, } // This should return an error when PanicOnWarnings is enabled err = applier.ApplyDMLEventQueries(dmlEvents) suite.Require().Error(err) suite.Require().Contains(err.Error(), "Duplicate entry") // Verify that the ghost table still has only the original 3 rows with correct data (no data loss) rows, err := suite.db.Query("SELECT id, email FROM " + getTestGhostTableName() + " ORDER BY id") suite.Require().NoError(err) defer rows.Close() var results []struct { id int email string } for rows.Next() { var id int var email string err = rows.Scan(&id, &email) suite.Require().NoError(err) results = append(results, struct { id int email string }{id, email}) } suite.Require().NoError(rows.Err()) // All 3 original rows should still be present with correct data suite.Require().Len(results, 3) suite.Require().Equal(1, results[0].id) suite.Require().Equal("user1@example.com", results[0].email) suite.Require().Equal(2, results[1].id) suite.Require().Equal("user2@example.com", results[1].email) suite.Require().Equal(3, results[2].id) suite.Require().Equal("user3@example.com", results[2].email) } func (suite *ApplierTestSuite) TestPanicOnWarningsWithDuplicateCompositeUniqueKey() { ctx := context.Background() var err error // Create table with id, email, and username columns _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, email VARCHAR(100), username VARCHAR(100));", getTestTableName())) suite.Require().NoError(err) // Create ghost table with same schema plus a composite unique index on (email, username) _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, email VARCHAR(100), username VARCHAR(100), UNIQUE KEY email_username_unique (email, username));", getTestGhostTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.PanicOnWarnings = true migrationContext.OriginalTableColumns = sql.NewColumnList([]string{"id", "email", "username"}) migrationContext.SharedColumns = sql.NewColumnList([]string{"id", "email", "username"}) migrationContext.MappedSharedColumns = sql.NewColumnList([]string{"id", "email", "username"}) migrationContext.UniqueKey = &sql.UniqueKey{ Name: "PRIMARY", NameInGhostTable: "PRIMARY", Columns: *sql.NewColumnList([]string{"id"}), } applier := NewApplier(migrationContext) suite.Require().NoError(applier.prepareQueries()) defer applier.Teardown() err = applier.InitDBConnections() suite.Require().NoError(err) // Insert initial rows into ghost table (simulating bulk copy phase) // alice@example.com + bob is ok due to composite unique index _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, email, username) VALUES (1, 'alice@example.com', 'alice'), (2, 'alice@example.com', 'bob'), (3, 'charlie@example.com', 'charlie');", getTestGhostTableName())) suite.Require().NoError(err) // Simulate binlog event: try to insert a row with duplicate composite key (email + username) // This should fail with a warning because the ghost table has a composite unique index dmlEvents := []*binlog.BinlogDMLEvent{ { DatabaseName: testMysqlDatabase, TableName: testMysqlTableName, DML: binlog.InsertDML, NewColumnValues: sql.ToColumnValues([]interface{}{4, "alice@example.com", "alice"}), // duplicate (email, username) }, } // This should return an error when PanicOnWarnings is enabled err = applier.ApplyDMLEventQueries(dmlEvents) suite.Require().Error(err) suite.Require().Contains(err.Error(), "Duplicate entry") // Verify that the ghost table still has only the original 3 rows with correct data (no data loss) rows, err := suite.db.Query("SELECT id, email, username FROM " + getTestGhostTableName() + " ORDER BY id") suite.Require().NoError(err) defer rows.Close() var results []struct { id int email string username string } for rows.Next() { var id int var email string var username string err = rows.Scan(&id, &email, &username) suite.Require().NoError(err) results = append(results, struct { id int email string username string }{id, email, username}) } suite.Require().NoError(rows.Err()) // All 3 original rows should still be present with correct data suite.Require().Len(results, 3) suite.Require().Equal(1, results[0].id) suite.Require().Equal("alice@example.com", results[0].email) suite.Require().Equal("alice", results[0].username) suite.Require().Equal(2, results[1].id) suite.Require().Equal("alice@example.com", results[1].email) suite.Require().Equal("bob", results[1].username) suite.Require().Equal(3, results[2].id) suite.Require().Equal("charlie@example.com", results[2].email) suite.Require().Equal("charlie", results[2].username) } // TestUpdateModifyingUniqueKeyWithDuplicateOnOtherIndex tests the scenario where: // 1. An UPDATE modifies the unique key (converted to DELETE+INSERT) // 2. The INSERT would create a duplicate on a NON-migration unique index // 3. Without warning detection: DELETE succeeds, INSERT IGNORE skips = DATA LOSS // 4. With PanicOnWarnings: Warning detected, transaction rolled back, no data loss // This test verifies that PanicOnWarnings correctly prevents the data loss scenario. func (suite *ApplierTestSuite) TestUpdateModifyingUniqueKeyWithDuplicateOnOtherIndex() { ctx := context.Background() var err error // Create table with id (PRIMARY) and email (NO unique constraint yet) _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, email VARCHAR(100));", getTestTableName())) suite.Require().NoError(err) // Create ghost table with id (PRIMARY) AND email unique index (being added) _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, email VARCHAR(100), UNIQUE KEY email_unique (email));", getTestGhostTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.PanicOnWarnings = true migrationContext.OriginalTableColumns = sql.NewColumnList([]string{"id", "email"}) migrationContext.SharedColumns = sql.NewColumnList([]string{"id", "email"}) migrationContext.MappedSharedColumns = sql.NewColumnList([]string{"id", "email"}) migrationContext.UniqueKey = &sql.UniqueKey{ Name: "PRIMARY", NameInGhostTable: "PRIMARY", Columns: *sql.NewColumnList([]string{"id"}), } applier := NewApplier(migrationContext) suite.Require().NoError(applier.prepareQueries()) defer applier.Teardown() err = applier.InitDBConnections() suite.Require().NoError(err) // Setup: Insert initial rows into ghost table // Row 1: id=1, email='bob@example.com' // Row 2: id=2, email='charlie@example.com' _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, email) VALUES (1, 'bob@example.com'), (2, 'charlie@example.com');", getTestGhostTableName())) suite.Require().NoError(err) // Simulate binlog event: UPDATE that changes BOTH PRIMARY KEY and email // From: id=2, email='charlie@example.com' // To: id=3, email='bob@example.com' (duplicate email with id=1) // This will be converted to DELETE (id=2) + INSERT (id=3, 'bob@example.com') // With INSERT IGNORE, the INSERT will skip because email='bob@example.com' already exists in id=1 // Result: id=2 deleted, id=3 never inserted = DATA LOSS dmlEvents := []*binlog.BinlogDMLEvent{ { DatabaseName: testMysqlDatabase, TableName: testMysqlTableName, DML: binlog.UpdateDML, NewColumnValues: sql.ToColumnValues([]interface{}{3, "bob@example.com"}), // new: id=3, email='bob@example.com' WhereColumnValues: sql.ToColumnValues([]interface{}{2, "charlie@example.com"}), // old: id=2, email='charlie@example.com' }, } // First verify this would be converted to DELETE+INSERT buildResults := applier.buildDMLEventQuery(dmlEvents[0]) suite.Require().Len(buildResults, 2, "UPDATE modifying unique key should be converted to DELETE+INSERT") // Apply the event - this should FAIL because INSERT will have duplicate email warning err = applier.ApplyDMLEventQueries(dmlEvents) suite.Require().Error(err, "Should fail when DELETE+INSERT causes duplicate on non-migration unique key") suite.Require().Contains(err.Error(), "Duplicate entry", "Error should mention duplicate entry") // Verify that BOTH rows still exist (transaction rolled back) rows, err := suite.db.Query("SELECT id, email FROM " + getTestGhostTableName() + " ORDER BY id") suite.Require().NoError(err) defer rows.Close() var count int var ids []int var emails []string for rows.Next() { var id int var email string err = rows.Scan(&id, &email) suite.Require().NoError(err) ids = append(ids, id) emails = append(emails, email) count++ } suite.Require().NoError(rows.Err()) // Transaction should have rolled back, so original 2 rows should still be there suite.Require().Equal(2, count, "Should still have 2 rows after failed transaction") suite.Require().Equal([]int{1, 2}, ids, "Should have original ids") suite.Require().Equal([]string{"bob@example.com", "charlie@example.com"}, emails) } // TestNormalUpdateWithPanicOnWarnings tests that normal UPDATEs (not modifying unique key) work correctly func (suite *ApplierTestSuite) TestNormalUpdateWithPanicOnWarnings() { ctx := context.Background() var err error // Create table with id (PRIMARY) and email _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, email VARCHAR(100));", getTestTableName())) suite.Require().NoError(err) // Create ghost table with same schema plus unique index on email _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, email VARCHAR(100), UNIQUE KEY email_unique (email));", getTestGhostTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.PanicOnWarnings = true migrationContext.OriginalTableColumns = sql.NewColumnList([]string{"id", "email"}) migrationContext.SharedColumns = sql.NewColumnList([]string{"id", "email"}) migrationContext.MappedSharedColumns = sql.NewColumnList([]string{"id", "email"}) migrationContext.UniqueKey = &sql.UniqueKey{ Name: "PRIMARY", NameInGhostTable: "PRIMARY", Columns: *sql.NewColumnList([]string{"id"}), } applier := NewApplier(migrationContext) suite.Require().NoError(applier.prepareQueries()) defer applier.Teardown() err = applier.InitDBConnections() suite.Require().NoError(err) // Setup: Insert initial rows into ghost table _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, email) VALUES (1, 'alice@example.com'), (2, 'bob@example.com');", getTestGhostTableName())) suite.Require().NoError(err) // Simulate binlog event: Normal UPDATE that only changes email (not PRIMARY KEY) // This should use UPDATE query, not DELETE+INSERT dmlEvents := []*binlog.BinlogDMLEvent{ { DatabaseName: testMysqlDatabase, TableName: testMysqlTableName, DML: binlog.UpdateDML, NewColumnValues: sql.ToColumnValues([]interface{}{2, "robert@example.com"}), // update email only WhereColumnValues: sql.ToColumnValues([]interface{}{2, "bob@example.com"}), }, } // Verify this generates a single UPDATE query (not DELETE+INSERT) buildResults := applier.buildDMLEventQuery(dmlEvents[0]) suite.Require().Len(buildResults, 1, "Normal UPDATE should generate single UPDATE query") // Apply the event - should succeed err = applier.ApplyDMLEventQueries(dmlEvents) suite.Require().NoError(err) // Verify the update was applied correctly rows, err := suite.db.Query("SELECT id, email FROM " + getTestGhostTableName() + " WHERE id = 2") suite.Require().NoError(err) defer rows.Close() var id int var email string suite.Require().True(rows.Next(), "Should find updated row") err = rows.Scan(&id, &email) suite.Require().NoError(err) suite.Require().Equal(2, id) suite.Require().Equal("robert@example.com", email) suite.Require().False(rows.Next(), "Should only have one row") suite.Require().NoError(rows.Err()) } // TestDuplicateOnMigrationKeyAllowedInBinlogReplay tests the positive case where // a duplicate on the migration unique key during binlog replay is expected and should be allowed func (suite *ApplierTestSuite) TestDuplicateOnMigrationKeyAllowedInBinlogReplay() { ctx := context.Background() var err error // Create table with id and email columns, where id is the primary key _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, email VARCHAR(100));", getTestTableName())) suite.Require().NoError(err) // Create ghost table with same schema plus a new unique index on email _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, email VARCHAR(100), UNIQUE KEY email_unique (email));", getTestGhostTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.PanicOnWarnings = true migrationContext.OriginalTableColumns = sql.NewColumnList([]string{"id", "email"}) migrationContext.SharedColumns = sql.NewColumnList([]string{"id", "email"}) migrationContext.MappedSharedColumns = sql.NewColumnList([]string{"id", "email"}) migrationContext.UniqueKey = &sql.UniqueKey{ Name: "PRIMARY", NameInGhostTable: "PRIMARY", Columns: *sql.NewColumnList([]string{"id"}), } applier := NewApplier(migrationContext) suite.Require().NoError(applier.prepareQueries()) defer applier.Teardown() err = applier.InitDBConnections() suite.Require().NoError(err) // Insert initial rows into ghost table (simulating bulk copy phase) _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, email) VALUES (1, 'alice@example.com'), (2, 'bob@example.com');", getTestGhostTableName())) suite.Require().NoError(err) // Simulate binlog event: try to insert the same row again (duplicate on PRIMARY KEY - the migration key) // This is expected during binlog replay when a row was already copied during bulk copy dmlEvents := []*binlog.BinlogDMLEvent{ { DatabaseName: testMysqlDatabase, TableName: testMysqlTableName, DML: binlog.InsertDML, NewColumnValues: sql.ToColumnValues([]interface{}{1, "alice@example.com"}), // duplicate PRIMARY KEY }, } // This should succeed - duplicate on migration unique key is expected and should be filtered out err = applier.ApplyDMLEventQueries(dmlEvents) suite.Require().NoError(err) // Verify that the ghost table still has only the original 2 rows with correct data rows, err := suite.db.Query("SELECT id, email FROM " + getTestGhostTableName() + " ORDER BY id") suite.Require().NoError(err) defer rows.Close() var results []struct { id int email string } for rows.Next() { var id int var email string err = rows.Scan(&id, &email) suite.Require().NoError(err) results = append(results, struct { id int email string }{id, email}) } suite.Require().NoError(rows.Err()) // Should still have exactly 2 rows with correct data suite.Require().Len(results, 2) suite.Require().Equal(1, results[0].id) suite.Require().Equal("alice@example.com", results[0].email) suite.Require().Equal(2, results[1].id) suite.Require().Equal("bob@example.com", results[1].email) } // TestRegexMetacharactersInIndexName tests that index names with regex metacharacters // are properly escaped. We test with a plus sign in the index name, which without // QuoteMeta would be treated as a regex quantifier (one or more of 'x' in this case). // This test verifies the pattern matches ONLY the exact index name, not a regex pattern. func (suite *ApplierTestSuite) TestRegexMetacharactersInIndexName() { ctx := context.Background() var err error // Create tables with an index name containing a plus sign // Without QuoteMeta, "idx+email" would be treated as a regex pattern where + is a quantifier _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, email VARCHAR(100), UNIQUE KEY `idx+email` (email));", getTestTableName())) suite.Require().NoError(err) // MySQL allows + in index names when quoted _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, email VARCHAR(100), UNIQUE KEY `idx+email` (email));", getTestGhostTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.PanicOnWarnings = true migrationContext.OriginalTableColumns = sql.NewColumnList([]string{"id", "email"}) migrationContext.SharedColumns = sql.NewColumnList([]string{"id", "email"}) migrationContext.MappedSharedColumns = sql.NewColumnList([]string{"id", "email"}) migrationContext.UniqueKey = &sql.UniqueKey{ Name: "idx+email", NameInGhostTable: "idx+email", Columns: *sql.NewColumnList([]string{"email"}), } applier := NewApplier(migrationContext) suite.Require().NoError(applier.prepareQueries()) defer applier.Teardown() err = applier.InitDBConnections() suite.Require().NoError(err) // Insert initial rows _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, email) VALUES (1, 'alice@example.com'), (2, 'bob@example.com');", getTestGhostTableName())) suite.Require().NoError(err) // Test: duplicate on idx+email (the migration key) should be allowed // This verifies our regex correctly identifies "idx+email" as the migration key // Without regexp.QuoteMeta, the + would be treated as a regex quantifier and might not match correctly dmlEvents := []*binlog.BinlogDMLEvent{ { DatabaseName: testMysqlDatabase, TableName: testMysqlTableName, DML: binlog.InsertDML, NewColumnValues: sql.ToColumnValues([]interface{}{3, "alice@example.com"}), }, } err = applier.ApplyDMLEventQueries(dmlEvents) suite.Require().NoError(err, "Duplicate on idx+email (migration key) should be allowed with PanicOnWarnings enabled") // Test: duplicate on PRIMARY (not the migration key) should fail dmlEvents = []*binlog.BinlogDMLEvent{ { DatabaseName: testMysqlDatabase, TableName: testMysqlTableName, DML: binlog.InsertDML, NewColumnValues: sql.ToColumnValues([]interface{}{1, "charlie@example.com"}), }, } err = applier.ApplyDMLEventQueries(dmlEvents) suite.Require().Error(err, "Duplicate on PRIMARY (not migration key) should fail with PanicOnWarnings enabled") suite.Require().Contains(err.Error(), "Duplicate entry") // Verify final state - should still have only the original 2 rows rows, err := suite.db.Query("SELECT id, email FROM " + getTestGhostTableName() + " ORDER BY id") suite.Require().NoError(err) defer rows.Close() var results []struct { id int email string } for rows.Next() { var id int var email string err = rows.Scan(&id, &email) suite.Require().NoError(err) results = append(results, struct { id int email string }{id, email}) } suite.Require().NoError(rows.Err()) suite.Require().Len(results, 2) suite.Require().Equal(1, results[0].id) suite.Require().Equal("alice@example.com", results[0].email) suite.Require().Equal(2, results[1].id) suite.Require().Equal("bob@example.com", results[1].email) } // TestPanicOnWarningsDisabled tests that when PanicOnWarnings is false, // warnings are not checked and duplicates are silently ignored func (suite *ApplierTestSuite) TestPanicOnWarningsDisabled() { ctx := context.Background() var err error // Create table with id and email columns _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, email VARCHAR(100));", getTestTableName())) suite.Require().NoError(err) // Create ghost table with unique index on email _, err = suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, email VARCHAR(100), UNIQUE KEY email_unique (email));", getTestGhostTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") // PanicOnWarnings is false (default) migrationContext.PanicOnWarnings = false migrationContext.OriginalTableColumns = sql.NewColumnList([]string{"id", "email"}) migrationContext.SharedColumns = sql.NewColumnList([]string{"id", "email"}) migrationContext.MappedSharedColumns = sql.NewColumnList([]string{"id", "email"}) migrationContext.UniqueKey = &sql.UniqueKey{ Name: "PRIMARY", NameInGhostTable: "PRIMARY", Columns: *sql.NewColumnList([]string{"id"}), } applier := NewApplier(migrationContext) suite.Require().NoError(applier.prepareQueries()) defer applier.Teardown() err = applier.InitDBConnections() suite.Require().NoError(err) // Insert initial rows into ghost table _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, email) VALUES (1, 'alice@example.com'), (2, 'bob@example.com');", getTestGhostTableName())) suite.Require().NoError(err) // Simulate binlog event: insert duplicate email on non-migration index // With PanicOnWarnings disabled, this should succeed (INSERT IGNORE skips it) dmlEvents := []*binlog.BinlogDMLEvent{ { DatabaseName: testMysqlDatabase, TableName: testMysqlTableName, DML: binlog.InsertDML, NewColumnValues: sql.ToColumnValues([]interface{}{3, "alice@example.com"}), // duplicate email }, } // Should succeed because PanicOnWarnings is disabled err = applier.ApplyDMLEventQueries(dmlEvents) suite.Require().NoError(err) // Verify that only 2 original rows exist with correct data (the duplicate was silently ignored) rows, err := suite.db.Query("SELECT id, email FROM " + getTestGhostTableName() + " ORDER BY id") suite.Require().NoError(err) defer rows.Close() var results []struct { id int email string } for rows.Next() { var id int var email string err = rows.Scan(&id, &email) suite.Require().NoError(err) results = append(results, struct { id int email string }{id, email}) } suite.Require().NoError(rows.Err()) // Should still have exactly 2 original rows (id=3 was silently ignored) suite.Require().Len(results, 2) suite.Require().Equal(1, results[0].id) suite.Require().Equal("alice@example.com", results[0].email) suite.Require().Equal(2, results[1].id) suite.Require().Equal("bob@example.com", results[1].email) } func TestApplier(t *testing.T) { suite.Run(t, new(ApplierTestSuite)) } ================================================ FILE: go/logic/checkpoint.go ================================================ /* Copyright 2025 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package logic import ( "time" "github.com/github/gh-ost/go/mysql" "github.com/github/gh-ost/go/sql" ) // Checkpoint holds state necessary to resume a migration. type Checkpoint struct { Id int64 Timestamp time.Time // LastTrxCoords are coordinates of a transaction // that has been applied on ghost table. LastTrxCoords mysql.BinlogCoordinates // IterationRangeMin is the min shared key value // for the chunk copier range. IterationRangeMin *sql.ColumnValues // IterationRangeMax is the max shared key value // for the chunk copier range. IterationRangeMax *sql.ColumnValues Iteration int64 RowsCopied int64 DMLApplied int64 IsCutover bool } ================================================ FILE: go/logic/hooks.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package logic import ( "fmt" "io" "os" "os/exec" "path/filepath" "sync/atomic" "github.com/github/gh-ost/go/base" "github.com/openark/golib/log" ) const ( onStartup = "gh-ost-on-startup" onValidated = "gh-ost-on-validated" onRowCountComplete = "gh-ost-on-rowcount-complete" onBeforeRowCopy = "gh-ost-on-before-row-copy" onRowCopyComplete = "gh-ost-on-row-copy-complete" onBeginPostponed = "gh-ost-on-begin-postponed" onBeforeCutOver = "gh-ost-on-before-cut-over" onInteractiveCommand = "gh-ost-on-interactive-command" onSuccess = "gh-ost-on-success" onFailure = "gh-ost-on-failure" onBatchCopyRetry = "gh-ost-on-batch-copy-retry" onStatus = "gh-ost-on-status" onStopReplication = "gh-ost-on-stop-replication" onStartReplication = "gh-ost-on-start-replication" ) type HooksExecutor struct { migrationContext *base.MigrationContext writer io.Writer } func NewHooksExecutor(migrationContext *base.MigrationContext) *HooksExecutor { return &HooksExecutor{ migrationContext: migrationContext, writer: os.Stderr, } } func (this *HooksExecutor) applyEnvironmentVariables(extraVariables ...string) []string { env := os.Environ() env = append(env, fmt.Sprintf("GH_OST_DATABASE_NAME=%s", this.migrationContext.DatabaseName)) env = append(env, fmt.Sprintf("GH_OST_TABLE_NAME=%s", this.migrationContext.OriginalTableName)) env = append(env, fmt.Sprintf("GH_OST_GHOST_TABLE_NAME=%s", this.migrationContext.GetGhostTableName())) env = append(env, fmt.Sprintf("GH_OST_OLD_TABLE_NAME=%s", this.migrationContext.GetOldTableName())) env = append(env, fmt.Sprintf("GH_OST_DDL=%s", this.migrationContext.AlterStatement)) env = append(env, fmt.Sprintf("GH_OST_ELAPSED_SECONDS=%f", this.migrationContext.ElapsedTime().Seconds())) env = append(env, fmt.Sprintf("GH_OST_ELAPSED_COPY_SECONDS=%f", this.migrationContext.ElapsedRowCopyTime().Seconds())) estimatedRows := atomic.LoadInt64(&this.migrationContext.RowsEstimate) + atomic.LoadInt64(&this.migrationContext.RowsDeltaEstimate) env = append(env, fmt.Sprintf("GH_OST_ESTIMATED_ROWS=%d", estimatedRows)) totalRowsCopied := this.migrationContext.GetTotalRowsCopied() env = append(env, fmt.Sprintf("GH_OST_COPIED_ROWS=%d", totalRowsCopied)) env = append(env, fmt.Sprintf("GH_OST_MIGRATED_HOST=%s", this.migrationContext.GetApplierHostname())) env = append(env, fmt.Sprintf("GH_OST_INSPECTED_HOST=%s", this.migrationContext.GetInspectorHostname())) env = append(env, fmt.Sprintf("GH_OST_EXECUTING_HOST=%s", this.migrationContext.Hostname)) env = append(env, fmt.Sprintf("GH_OST_INSPECTED_LAG=%f", this.migrationContext.GetCurrentLagDuration().Seconds())) env = append(env, fmt.Sprintf("GH_OST_HEARTBEAT_LAG=%f", this.migrationContext.TimeSinceLastHeartbeatOnChangelog().Seconds())) env = append(env, fmt.Sprintf("GH_OST_PROGRESS=%f", this.migrationContext.GetProgressPct())) env = append(env, fmt.Sprintf("GH_OST_ETA_SECONDS=%d", this.migrationContext.GetETASeconds())) env = append(env, fmt.Sprintf("GH_OST_HOOKS_HINT=%s", this.migrationContext.HooksHintMessage)) env = append(env, fmt.Sprintf("GH_OST_HOOKS_HINT_OWNER=%s", this.migrationContext.HooksHintOwner)) env = append(env, fmt.Sprintf("GH_OST_HOOKS_HINT_TOKEN=%s", this.migrationContext.HooksHintToken)) env = append(env, fmt.Sprintf("GH_OST_DRY_RUN=%t", this.migrationContext.Noop)) env = append(env, fmt.Sprintf("GH_OST_REVERT=%t", this.migrationContext.Revert)) env = append(env, extraVariables...) return env } // executeHook executes a command, and sets relevant environment variables // combined output & error are printed to the configured writer. func (this *HooksExecutor) executeHook(hook string, extraVariables ...string) error { this.migrationContext.Log.Infof("executing hook: %+v", hook) cmd := exec.Command(hook) cmd.Env = this.applyEnvironmentVariables(extraVariables...) combinedOutput, err := cmd.CombinedOutput() fmt.Fprintln(this.writer, string(combinedOutput)) return log.Errore(err) } func (this *HooksExecutor) detectHooks(baseName string) (hooks []string, err error) { if this.migrationContext.HooksPath == "" { return hooks, err } pattern := fmt.Sprintf("%s/%s*", this.migrationContext.HooksPath, baseName) hooks, err = filepath.Glob(pattern) return hooks, err } func (this *HooksExecutor) executeHooks(baseName string, extraVariables ...string) error { hooks, err := this.detectHooks(baseName) if err != nil { return err } for _, hook := range hooks { log.Infof("executing %+v hook: %+v", baseName, hook) if err := this.executeHook(hook, extraVariables...); err != nil { return err } } return nil } func (this *HooksExecutor) onStartup() error { return this.executeHooks(onStartup) } func (this *HooksExecutor) onValidated() error { return this.executeHooks(onValidated) } func (this *HooksExecutor) onRowCountComplete() error { return this.executeHooks(onRowCountComplete) } func (this *HooksExecutor) onBeforeRowCopy() error { return this.executeHooks(onBeforeRowCopy) } func (this *HooksExecutor) onBatchCopyRetry(errorMessage string) error { v := fmt.Sprintf("GH_OST_LAST_BATCH_COPY_ERROR=%s", errorMessage) return this.executeHooks(onBatchCopyRetry, v) } func (this *HooksExecutor) onRowCopyComplete() error { return this.executeHooks(onRowCopyComplete) } func (this *HooksExecutor) onBeginPostponed() error { return this.executeHooks(onBeginPostponed) } func (this *HooksExecutor) onBeforeCutOver() error { return this.executeHooks(onBeforeCutOver) } func (this *HooksExecutor) onInteractiveCommand(command string) error { v := fmt.Sprintf("GH_OST_COMMAND='%s'", command) return this.executeHooks(onInteractiveCommand, v) } func (this *HooksExecutor) onSuccess() error { return this.executeHooks(onSuccess) } func (this *HooksExecutor) onFailure() error { return this.executeHooks(onFailure) } func (this *HooksExecutor) onStatus(statusMessage string) error { v := fmt.Sprintf("GH_OST_STATUS='%s'", statusMessage) return this.executeHooks(onStatus, v) } func (this *HooksExecutor) onStopReplication() error { return this.executeHooks(onStopReplication) } func (this *HooksExecutor) onStartReplication() error { return this.executeHooks(onStartReplication) } ================================================ FILE: go/logic/hooks_test.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package logic import ( "bufio" "bytes" "fmt" "os" "path/filepath" "strconv" "strings" "testing" "time" "github.com/stretchr/testify/require" "github.com/github/gh-ost/go/base" ) func TestHooksExecutorExecuteHooks(t *testing.T) { migrationContext := base.NewMigrationContext() migrationContext.AlterStatement = "ENGINE=InnoDB" migrationContext.DatabaseName = "test" migrationContext.Hostname = "test.example.com" migrationContext.OriginalTableName = "tablename" migrationContext.RowsDeltaEstimate = 1 migrationContext.RowsEstimate = 122 migrationContext.TotalRowsCopied = 123456 migrationContext.SetETADuration(time.Minute) migrationContext.SetProgressPct(50) hooksExecutor := NewHooksExecutor(migrationContext) writeTmpHookFunc := func(testName, hookName, script string) (path string, err error) { if path, err = os.MkdirTemp("", testName); err != nil { return path, err } err = os.WriteFile(filepath.Join(path, hookName), []byte(script), 0777) return path, err } t.Run("does-not-exist", func(t *testing.T) { migrationContext.HooksPath = "/does/not/exist" require.Nil(t, hooksExecutor.executeHooks("test-hook")) }) t.Run("failed", func(t *testing.T) { var err error if migrationContext.HooksPath, err = writeTmpHookFunc( "TestHooksExecutorExecuteHooks-failed", "failed-hook", "#!/bin/sh\nexit 1", ); err != nil { panic(err) } defer os.RemoveAll(migrationContext.HooksPath) require.NotNil(t, hooksExecutor.executeHooks("failed-hook")) }) t.Run("success", func(t *testing.T) { var err error if migrationContext.HooksPath, err = writeTmpHookFunc( "TestHooksExecutorExecuteHooks-success", "success-hook", "#!/bin/sh\nenv", ); err != nil { panic(err) } defer os.RemoveAll(migrationContext.HooksPath) var buf bytes.Buffer hooksExecutor.writer = &buf require.Nil(t, hooksExecutor.executeHooks("success-hook", "TEST="+t.Name())) scanner := bufio.NewScanner(&buf) for scanner.Scan() { split := strings.SplitN(scanner.Text(), "=", 2) switch split[0] { case "GH_OST_COPIED_ROWS": copiedRows, _ := strconv.ParseInt(split[1], 10, 64) require.Equal(t, migrationContext.TotalRowsCopied, copiedRows) case "GH_OST_DATABASE_NAME": require.Equal(t, migrationContext.DatabaseName, split[1]) case "GH_OST_DDL": require.Equal(t, migrationContext.AlterStatement, split[1]) case "GH_OST_DRY_RUN": require.Equal(t, "false", split[1]) case "GH_OST_ESTIMATED_ROWS": estimatedRows, _ := strconv.ParseInt(split[1], 10, 64) require.Equal(t, int64(123), estimatedRows) case "GH_OST_ETA_SECONDS": etaSeconds, _ := strconv.ParseInt(split[1], 10, 64) require.Equal(t, int64(60), etaSeconds) case "GH_OST_EXECUTING_HOST": require.Equal(t, migrationContext.Hostname, split[1]) case "GH_OST_GHOST_TABLE_NAME": require.Equal(t, fmt.Sprintf("_%s_gho", migrationContext.OriginalTableName), split[1]) case "GH_OST_OLD_TABLE_NAME": require.Equal(t, fmt.Sprintf("_%s_del", migrationContext.OriginalTableName), split[1]) case "GH_OST_PROGRESS": progress, _ := strconv.ParseFloat(split[1], 64) require.Equal(t, 50.0, progress) case "GH_OST_TABLE_NAME": require.Equal(t, migrationContext.OriginalTableName, split[1]) case "TEST": require.Equal(t, t.Name(), split[1]) } } }) } ================================================ FILE: go/logic/inspect.go ================================================ /* Copyright 2025 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package logic import ( "context" gosql "database/sql" "errors" "fmt" "reflect" "strings" "sync/atomic" "time" "github.com/github/gh-ost/go/base" "github.com/github/gh-ost/go/mysql" "github.com/github/gh-ost/go/sql" "github.com/openark/golib/sqlutils" ) const startReplicationPostWait = 250 * time.Millisecond const startReplicationMaxWait = 2 * time.Second // Inspector reads data from the read-MySQL-server (typically a replica, but can be the master) // It is used for gaining initial status and structure, and later also follow up on progress and changelog type Inspector struct { connectionConfig *mysql.ConnectionConfig db *gosql.DB dbVersion string informationSchemaDb *gosql.DB migrationContext *base.MigrationContext name string } func NewInspector(migrationContext *base.MigrationContext) *Inspector { return &Inspector{ connectionConfig: migrationContext.InspectorConnectionConfig, migrationContext: migrationContext, name: "inspector", } } func (this *Inspector) InitDBConnections() (err error) { inspectorUri := this.connectionConfig.GetDBUri(this.migrationContext.DatabaseName) if this.db, _, err = mysql.GetDB(this.migrationContext.Uuid, inspectorUri); err != nil { return err } informationSchemaUri := this.connectionConfig.GetDBUri("information_schema") if this.informationSchemaDb, _, err = mysql.GetDB(this.migrationContext.Uuid, informationSchemaUri); err != nil { return err } if err := this.validateConnection(); err != nil { return err } this.dbVersion = this.migrationContext.InspectorMySQLVersion if !this.migrationContext.AliyunRDS && !this.migrationContext.GoogleCloudPlatform && !this.migrationContext.AzureMySQL { if impliedKey, err := mysql.GetInstanceKey(this.db); err != nil { return err } else { this.connectionConfig.ImpliedKey = impliedKey } } if err := this.validateGrants(); err != nil { return err } if err := this.validateBinlogs(); err != nil { return err } if this.migrationContext.UseGTIDs { if err := this.validateGTIDConfig(); err != nil { return err } } if err := this.applyBinlogFormat(); err != nil { return err } this.migrationContext.Log.Infof("Inspector initiated on %+v, version %+v", this.connectionConfig.ImpliedKey, this.migrationContext.InspectorMySQLVersion) return nil } func (this *Inspector) ValidateOriginalTable() (err error) { if err := this.validateTable(); err != nil { return err } if err := this.validateTableForeignKeys(this.migrationContext.DiscardForeignKeys); err != nil { return err } if err := this.validateTableTriggers(); err != nil { return err } if err := this.estimateTableRowsViaExplain(); err != nil { return err } return nil } func (this *Inspector) InspectTableColumnsAndUniqueKeys(tableName string) (columns *sql.ColumnList, virtualColumns *sql.ColumnList, uniqueKeys [](*sql.UniqueKey), err error) { uniqueKeys, err = this.getCandidateUniqueKeys(tableName) if err != nil { return columns, virtualColumns, uniqueKeys, err } if len(uniqueKeys) == 0 { return columns, virtualColumns, uniqueKeys, fmt.Errorf("No PRIMARY nor UNIQUE key found in table! Bailing out") } columns, virtualColumns, err = mysql.GetTableColumns(this.db, this.migrationContext.DatabaseName, tableName) if err != nil { return columns, virtualColumns, uniqueKeys, err } return columns, virtualColumns, uniqueKeys, nil } func (this *Inspector) InspectOriginalTable() (err error) { this.migrationContext.OriginalTableColumns, this.migrationContext.OriginalTableVirtualColumns, this.migrationContext.OriginalTableUniqueKeys, err = this.InspectTableColumnsAndUniqueKeys(this.migrationContext.OriginalTableName) if err != nil { return err } this.migrationContext.OriginalTableAutoIncrement, err = this.getAutoIncrementValue(this.migrationContext.OriginalTableName) if err != nil { return err } return nil } // inspectOriginalAndGhostTables compares original and ghost tables to see whether the migration // makes sense and is valid. It extracts the list of shared columns and the chosen migration unique key func (this *Inspector) inspectOriginalAndGhostTables() (err error) { originalNamesOnApplier := this.migrationContext.OriginalTableColumnsOnApplier.Names() originalNames := this.migrationContext.OriginalTableColumns.Names() if !reflect.DeepEqual(originalNames, originalNamesOnApplier) { return fmt.Errorf("It seems like table structure is not identical between master and replica. This scenario is not supported.") } this.migrationContext.GhostTableColumns, this.migrationContext.GhostTableVirtualColumns, this.migrationContext.GhostTableUniqueKeys, err = this.InspectTableColumnsAndUniqueKeys(this.migrationContext.GetGhostTableName()) if err != nil { return err } sharedUniqueKeys := this.getSharedUniqueKeys(this.migrationContext.OriginalTableUniqueKeys, this.migrationContext.GhostTableUniqueKeys) for i, sharedUniqueKey := range sharedUniqueKeys { this.applyColumnTypes(this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName, &sharedUniqueKey.Columns) uniqueKeyIsValid := true for _, column := range sharedUniqueKey.Columns.Columns() { switch column.Type { case sql.FloatColumnType: { this.migrationContext.Log.Warningf("Will not use %+v as shared key due to FLOAT data type", sharedUniqueKey.Name) uniqueKeyIsValid = false } case sql.JSONColumnType: { // Noteworthy that at this time MySQL does not allow JSON indexing anyhow, but this code // will remain in place to potentially handle the future case where JSON is supported in indexes. this.migrationContext.Log.Warningf("Will not use %+v as shared key due to JSON data type", sharedUniqueKey.Name) uniqueKeyIsValid = false } } } if uniqueKeyIsValid { this.migrationContext.UniqueKey = sharedUniqueKeys[i] break } } if this.migrationContext.UniqueKey == nil { return fmt.Errorf("No shared unique key can be found after ALTER! Bailing out") } this.migrationContext.Log.Infof("Chosen shared unique key is %s", this.migrationContext.UniqueKey.Name) if this.migrationContext.UniqueKey.HasNullable { if this.migrationContext.NullableUniqueKeyAllowed { this.migrationContext.Log.Warningf("Chosen key (%s) has nullable columns. You have supplied with --allow-nullable-unique-key and so this migration proceeds. As long as there aren't NULL values in this key's column, migration should be fine. NULL values will corrupt migration's data", this.migrationContext.UniqueKey) } else { return fmt.Errorf("Chosen key (%s) has nullable columns. Bailing out. To force this operation to continue, supply --allow-nullable-unique-key flag. Only do so if you are certain there are no actual NULL values in this key. As long as there aren't, migration should be fine. NULL values in columns of this key will corrupt migration's data", this.migrationContext.UniqueKey) } } this.migrationContext.SharedColumns, this.migrationContext.MappedSharedColumns = this.getSharedColumns(this.migrationContext.OriginalTableColumns, this.migrationContext.GhostTableColumns, this.migrationContext.OriginalTableVirtualColumns, this.migrationContext.GhostTableVirtualColumns, this.migrationContext.ColumnRenameMap) this.migrationContext.Log.Infof("Shared columns are %s", this.migrationContext.SharedColumns) // By fact that a non-empty unique key exists we also know the shared columns are non-empty // This additional step looks at which columns are unsigned. We could have merged this within // the `getTableColumns()` function, but it's a later patch and introduces some complexity; I feel // comfortable in doing this as a separate step. this.applyColumnTypes(this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName, this.migrationContext.OriginalTableColumns, this.migrationContext.SharedColumns, &this.migrationContext.UniqueKey.Columns) this.applyColumnTypes(this.migrationContext.DatabaseName, this.migrationContext.GetGhostTableName(), this.migrationContext.GhostTableColumns, this.migrationContext.MappedSharedColumns) for i := range this.migrationContext.SharedColumns.Columns() { column := this.migrationContext.SharedColumns.Columns()[i] mappedColumn := this.migrationContext.MappedSharedColumns.Columns()[i] if column.Name == mappedColumn.Name && column.Type == sql.DateTimeColumnType && mappedColumn.Type == sql.TimestampColumnType { this.migrationContext.MappedSharedColumns.SetConvertDatetimeToTimestamp(column.Name, this.migrationContext.ApplierTimeZone) } if column.Name == mappedColumn.Name && column.Type == sql.EnumColumnType && mappedColumn.Charset != "" { this.migrationContext.MappedSharedColumns.SetEnumToTextConversion(column.Name) this.migrationContext.MappedSharedColumns.SetEnumValues(column.Name, column.EnumValues) } if column.Name == mappedColumn.Name && column.Charset != mappedColumn.Charset { this.migrationContext.SharedColumns.SetCharsetConversion(column.Name, column.Charset, mappedColumn.Charset) } } for _, column := range this.migrationContext.UniqueKey.Columns.Columns() { if this.migrationContext.GhostTableVirtualColumns.GetColumn(column.Name) != nil { // this is a virtual column continue } if this.migrationContext.MappedSharedColumns.HasTimezoneConversion(column.Name) { return fmt.Errorf("No support at this time for converting a column from DATETIME to TIMESTAMP that is also part of the chosen unique key. Column: %s, key: %s", column.Name, this.migrationContext.UniqueKey.Name) } } return nil } // validateConnection issues a simple can-connect to MySQL func (this *Inspector) validateConnection() error { version, err := base.ValidateConnection(this.db, this.connectionConfig, this.migrationContext, this.name) this.migrationContext.InspectorMySQLVersion = version return err } // validateGrants verifies the user by which we're executing has necessary grants // to do its thing. func (this *Inspector) validateGrants() error { query := `show /* gh-ost */ grants for current_user()` foundAll := false foundSuper := false foundReplicationClient := false foundReplicationSlave := false foundDBAll := false err := sqlutils.QueryRowsMap(this.db, query, func(rowMap sqlutils.RowMap) error { for _, grantData := range rowMap { grant := grantData.String if strings.Contains(grant, `GRANT ALL PRIVILEGES ON *.*`) { foundAll = true } if strings.Contains(grant, `SUPER`) && strings.Contains(grant, ` ON *.*`) { foundSuper = true } if strings.Contains(grant, `REPLICATION CLIENT`) && strings.Contains(grant, ` ON *.*`) { foundReplicationClient = true } if strings.Contains(grant, `REPLICATION SLAVE`) && strings.Contains(grant, ` ON *.*`) { foundReplicationSlave = true } if strings.Contains(grant, fmt.Sprintf("GRANT ALL PRIVILEGES ON `%s`.*", this.migrationContext.DatabaseName)) { foundDBAll = true } if strings.Contains(grant, fmt.Sprintf("GRANT ALL PRIVILEGES ON `%s`.*", strings.Replace(this.migrationContext.DatabaseName, "_", "\\_", -1))) { foundDBAll = true } if base.StringContainsAll(grant, `ALTER`, `CREATE`, `DELETE`, `DROP`, `INDEX`, `INSERT`, `LOCK TABLES`, `SELECT`, `TRIGGER`, `UPDATE`, ` ON *.*`) { foundDBAll = true } if base.StringContainsAll(grant, `ALTER`, `CREATE`, `DELETE`, `DROP`, `INDEX`, `INSERT`, `LOCK TABLES`, `SELECT`, `TRIGGER`, `UPDATE`, fmt.Sprintf(" ON `%s`.*", this.migrationContext.DatabaseName)) { foundDBAll = true } } return nil }) if err != nil { return err } this.migrationContext.HasSuperPrivilege = foundSuper if foundAll { this.migrationContext.Log.Infof("User has ALL privileges") return nil } if foundSuper && foundReplicationSlave && foundDBAll { this.migrationContext.Log.Infof("User has SUPER, REPLICATION SLAVE privileges, and has ALL privileges on %s.*", sql.EscapeName(this.migrationContext.DatabaseName)) return nil } if foundReplicationClient && foundReplicationSlave && foundDBAll { this.migrationContext.Log.Infof("User has REPLICATION CLIENT, REPLICATION SLAVE privileges, and has ALL privileges on %s.*", sql.EscapeName(this.migrationContext.DatabaseName)) return nil } this.migrationContext.Log.Debugf("Privileges: Super: %t, REPLICATION CLIENT: %t, REPLICATION SLAVE: %t, ALL on *.*: %t, ALL on %s.*: %t", foundSuper, foundReplicationClient, foundReplicationSlave, foundAll, sql.EscapeName(this.migrationContext.DatabaseName), foundDBAll) return this.migrationContext.Log.Errorf("User has insufficient privileges for migration. Needed: SUPER|REPLICATION CLIENT, REPLICATION SLAVE and ALL on %s.*", sql.EscapeName(this.migrationContext.DatabaseName)) } // restartReplication is required so that we are _certain_ the binlog format and // row image settings have actually been applied to the replication thread. // It is entirely possible, for example, that the replication is using 'STATEMENT' // binlog format even as the variable says 'ROW' func (this *Inspector) restartReplication() error { this.migrationContext.Log.Infof("Restarting replication on %s to make sure binlog settings apply to replication thread", this.connectionConfig.Key.String()) masterKey, _ := mysql.GetMasterKeyFromSlaveStatus(this.dbVersion, this.connectionConfig) if masterKey == nil { // This is not a replica return nil } var stopError, startError error replicaTerm := mysql.ReplicaTermFor(this.dbVersion, `slave`) _, stopError = sqlutils.ExecNoPrepare(this.db, fmt.Sprintf("stop %s", replicaTerm)) _, startError = sqlutils.ExecNoPrepare(this.db, fmt.Sprintf("start %s", replicaTerm)) if stopError != nil { return stopError } if startError != nil { return startError } // loop until replication is running unless we hit a max timeout. startTime := time.Now() for { replicationRunning, err := this.validateReplicationRestarted() if err != nil { return fmt.Errorf("Failed to validate if replication had been restarted: %w", err) } if replicationRunning { break } if time.Since(startTime) > startReplicationMaxWait { return fmt.Errorf("Replication did not restart within the maximum wait time of %s", startReplicationMaxWait) } this.migrationContext.Log.Debugf("Replication not yet restarted, waiting...") time.Sleep(startReplicationPostWait) } this.migrationContext.Log.Debugf("Replication restarted") return nil } // validateReplicationRestarted checks that the Slave_IO_Running and Slave_SQL_Running are both 'Yes' // returns true if both are 'Yes', false otherwise func (this *Inspector) validateReplicationRestarted() (bool, error) { errNotRunning := fmt.Errorf("Replication not running on %s", this.connectionConfig.Key.String()) query := fmt.Sprintf("show /* gh-ost */ %s", mysql.ReplicaTermFor(this.dbVersion, "slave status")) err := sqlutils.QueryRowsMap(this.db, query, func(rowMap sqlutils.RowMap) error { ioRunningTerm := mysql.ReplicaTermFor(this.dbVersion, "Slave_IO_Running") sqlRunningTerm := mysql.ReplicaTermFor(this.dbVersion, "Slave_SQL_Running") if rowMap.GetString(ioRunningTerm) != "Yes" || rowMap.GetString(sqlRunningTerm) != "Yes" { return errNotRunning } return nil }) if err != nil { // If the error is that replication is not running, return that and not an error if errors.Is(err, errNotRunning) { return false, nil } return false, err } return true, nil } // applyBinlogFormat sets ROW binlog format and restarts replication to make // the replication thread apply it. func (this *Inspector) applyBinlogFormat() error { if this.migrationContext.RequiresBinlogFormatChange() { if !this.migrationContext.SwitchToRowBinlogFormat { return fmt.Errorf("Existing binlog_format is %s. Am not switching it to ROW unless you specify --switch-to-rbr", this.migrationContext.OriginalBinlogFormat) } if _, err := sqlutils.ExecNoPrepare(this.db, `set global binlog_format='ROW'`); err != nil { return err } if _, err := sqlutils.ExecNoPrepare(this.db, `set session binlog_format='ROW'`); err != nil { return err } if err := this.restartReplication(); err != nil { return err } this.migrationContext.Log.Debugf("'ROW' binlog format applied") return nil } // We already have RBR, no explicit switch if !this.migrationContext.AssumeRBR { if err := this.restartReplication(); err != nil { return err } } return nil } // validateBinlogs checks that binary log configuration is good to go func (this *Inspector) validateBinlogs() error { query := `select /* gh-ost */@@global.log_bin, @@global.binlog_format` var hasBinaryLogs bool if err := this.db.QueryRow(query).Scan(&hasBinaryLogs, &this.migrationContext.OriginalBinlogFormat); err != nil { return err } if !hasBinaryLogs { return fmt.Errorf("%s must have binary logs enabled", this.connectionConfig.Key.String()) } if this.migrationContext.RequiresBinlogFormatChange() { if !this.migrationContext.SwitchToRowBinlogFormat { return fmt.Errorf("You must be using ROW binlog format. I can switch it for you, provided --switch-to-rbr and that %s doesn't have replicas", this.connectionConfig.Key.String()) } query := fmt.Sprintf("show /* gh-ost */ %s", mysql.ReplicaTermFor(this.dbVersion, `slave hosts`)) countReplicas := 0 err := sqlutils.QueryRowsMap(this.db, query, func(rowMap sqlutils.RowMap) error { countReplicas++ return nil }) if err != nil { return err } if countReplicas > 0 { return fmt.Errorf("%s has %s binlog_format, but I'm too scared to change it to ROW because it has replicas. Bailing out", this.connectionConfig.Key.String(), this.migrationContext.OriginalBinlogFormat) } this.migrationContext.Log.Infof("%s has %s binlog_format. I will change it to ROW, and will NOT change it back, even in the event of failure.", this.connectionConfig.Key.String(), this.migrationContext.OriginalBinlogFormat) } query = `select /* gh-ost */ @@global.binlog_row_image` if err := this.db.QueryRow(query).Scan(&this.migrationContext.OriginalBinlogRowImage); err != nil { return err } this.migrationContext.OriginalBinlogRowImage = strings.ToUpper(this.migrationContext.OriginalBinlogRowImage) if this.migrationContext.OriginalBinlogRowImage != "FULL" { return fmt.Errorf("%s has '%s' binlog_row_image, and only 'FULL' is supported. This operation cannot proceed. You may `set global binlog_row_image='full'` and try again", this.connectionConfig.Key.String(), this.migrationContext.OriginalBinlogRowImage) } this.migrationContext.Log.Infof("binary logs validated on %s", this.connectionConfig.Key.String()) return nil } // validateGTIDConfig checks that the GTID configuration is good to go func (this *Inspector) validateGTIDConfig() error { var gtidMode, enforceGtidConsistency string query := `select @@global.gtid_mode, @@global.enforce_gtid_consistency` if err := this.db.QueryRow(query).Scan(>idMode, &enforceGtidConsistency); err != nil { return err } enforceGtidConsistency = strings.ToUpper(enforceGtidConsistency) if strings.ToUpper(gtidMode) != "ON" || (enforceGtidConsistency != "ON" && enforceGtidConsistency != "1") { return fmt.Errorf("%s must have gtid_mode=ON and enforce_gtid_consistency=ON to use GTID support", this.connectionConfig.Key.String()) } this.migrationContext.Log.Infof("gtid config validated on %s", this.connectionConfig.Key.String()) return nil } // validateLogSlaveUpdates checks that binary log log_slave_updates is set. This test is not required when migrating on replica or when migrating directly on master func (this *Inspector) validateLogSlaveUpdates() error { query := `select /* gh-ost */ @@global.log_slave_updates` var logSlaveUpdates bool if err := this.db.QueryRow(query).Scan(&logSlaveUpdates); err != nil { return err } if logSlaveUpdates { this.migrationContext.Log.Infof("log_slave_updates validated on %s", this.connectionConfig.Key.String()) return nil } if this.migrationContext.IsTungsten { this.migrationContext.Log.Warningf("log_slave_updates not found on %s, but --tungsten provided, so I'm proceeding", this.connectionConfig.Key.String()) return nil } if this.migrationContext.TestOnReplica || this.migrationContext.MigrateOnReplica { return fmt.Errorf("%s must have log_slave_updates enabled for testing/migrating on replica", this.connectionConfig.Key.String()) } if this.migrationContext.InspectorIsAlsoApplier() { this.migrationContext.Log.Warningf("log_slave_updates not found on %s, but executing directly on master, so I'm proceeding", this.connectionConfig.Key.String()) return nil } return fmt.Errorf("%s must have log_slave_updates enabled for executing migration", this.connectionConfig.Key.String()) } // validateTable makes sure the table we need to operate on actually exists func (this *Inspector) validateTable() error { query := fmt.Sprintf(`show /* gh-ost */ table status from %s like '%s'`, sql.EscapeName(this.migrationContext.DatabaseName), this.migrationContext.OriginalTableName) tableFound := false err := sqlutils.QueryRowsMap(this.db, query, func(rowMap sqlutils.RowMap) error { this.migrationContext.TableEngine = rowMap.GetString("Engine") this.migrationContext.RowsEstimate = rowMap.GetInt64("Rows") this.migrationContext.UsedRowsEstimateMethod = base.TableStatusRowsEstimate if rowMap.GetString("Comment") == "VIEW" { return fmt.Errorf("%s.%s is a VIEW, not a real table. Bailing out", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName)) } tableFound = true return nil }) if err != nil { return err } if !tableFound { return this.migrationContext.Log.Errorf("Cannot find table %s.%s!", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName)) } this.migrationContext.Log.Infof("Table found. Engine=%s", this.migrationContext.TableEngine) this.migrationContext.Log.Debugf("Estimated number of rows via STATUS: %d", this.migrationContext.RowsEstimate) return nil } // validateTableForeignKeys makes sure no foreign keys exist on the migrated table func (this *Inspector) validateTableForeignKeys(allowChildForeignKeys bool) error { if this.migrationContext.SkipForeignKeyChecks { this.migrationContext.Log.Warning("--skip-foreign-key-checks provided: will not check for foreign keys") return nil } query := ` SELECT /* gh-ost */ SUM(REFERENCED_TABLE_NAME IS NOT NULL AND TABLE_SCHEMA=? AND TABLE_NAME=?) as num_child_side_fk, SUM(REFERENCED_TABLE_NAME IS NOT NULL AND REFERENCED_TABLE_SCHEMA=? AND REFERENCED_TABLE_NAME=?) as num_parent_side_fk FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_NAME IS NOT NULL AND ( (TABLE_SCHEMA=? AND TABLE_NAME=?) OR (REFERENCED_TABLE_SCHEMA=? AND REFERENCED_TABLE_NAME=?) )` numParentForeignKeys := 0 numChildForeignKeys := 0 err := sqlutils.QueryRowsMap(this.db, query, func(m sqlutils.RowMap) error { numChildForeignKeys = m.GetInt("num_child_side_fk") numParentForeignKeys = m.GetInt("num_parent_side_fk") return nil }, this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName, this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName, this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName, this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName, ) if err != nil { return err } if numParentForeignKeys > 0 { return this.migrationContext.Log.Errorf("Found %d parent-side foreign keys on %s.%s. Parent-side foreign keys are not supported. Bailing out", numParentForeignKeys, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName)) } if numChildForeignKeys > 0 { if allowChildForeignKeys { this.migrationContext.Log.Debugf("Foreign keys found and will be dropped, as per given --discard-foreign-keys flag") return nil } return this.migrationContext.Log.Errorf("Found %d child-side foreign keys on %s.%s. Child-side foreign keys are not supported. Bailing out", numChildForeignKeys, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName)) } this.migrationContext.Log.Debugf("Validated no foreign keys exist on table") return nil } // validateTableTriggers makes sure no triggers exist on the migrated table. if --include_triggers is used then it fetches the triggers func (this *Inspector) validateTableTriggers() error { query := ` SELECT /* gh-ost */ COUNT(*) AS num_triggers FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA=? AND EVENT_OBJECT_TABLE=?` numTriggers := 0 err := sqlutils.QueryRowsMap(this.db, query, func(rowMap sqlutils.RowMap) error { numTriggers = rowMap.GetInt("num_triggers") return nil }, this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName, ) if err != nil { return err } if numTriggers > 0 { if this.migrationContext.IncludeTriggers { this.migrationContext.Log.Infof("Found %d triggers on %s.%s.", numTriggers, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName)) this.migrationContext.Triggers, err = mysql.GetTriggers(this.db, this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName) if err != nil { return err } if err := this.validateGhostTriggersDontExist(); err != nil { return err } if err := this.validateGhostTriggersLength(); err != nil { return err } return nil } return this.migrationContext.Log.Errorf("Found triggers on %s.%s. Tables with triggers are supported only when using \"include-triggers\" flag. Bailing out", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName)) } this.migrationContext.Log.Debugf("Validated no triggers exist on table") return nil } // verifyTriggersDontExist verifies before createing new triggers we want to make sure these triggers dont exist already in the DB func (this *Inspector) validateGhostTriggersDontExist() error { if len(this.migrationContext.Triggers) > 0 { var foundTriggers []string for _, trigger := range this.migrationContext.Triggers { triggerName := this.migrationContext.GetGhostTriggerName(trigger.Name) query := "select 1 from information_schema.triggers where trigger_name = ? and trigger_schema = ?" err := sqlutils.QueryRowsMap(this.db, query, func(rowMap sqlutils.RowMap) error { triggerExists := rowMap.GetInt("1") if triggerExists == 1 { foundTriggers = append(foundTriggers, triggerName) } return nil }, triggerName, this.migrationContext.DatabaseName, ) if err != nil { return err } } if len(foundTriggers) > 0 { return this.migrationContext.Log.Errorf("Found gh-ost triggers (%s). Please use a different suffix or drop them. Bailing out", strings.Join(foundTriggers, ",")) } } return nil } func (this *Inspector) validateGhostTriggersLength() error { if len(this.migrationContext.Triggers) > 0 { var foundTriggers []string for _, trigger := range this.migrationContext.Triggers { triggerName := this.migrationContext.GetGhostTriggerName(trigger.Name) if ok := this.migrationContext.ValidateGhostTriggerLengthBelowMaxLength(triggerName); !ok { foundTriggers = append(foundTriggers, triggerName) } } if len(foundTriggers) > 0 { return this.migrationContext.Log.Errorf("Gh-ost triggers (%s) length > %d characters. Bailing out", strings.Join(foundTriggers, ","), mysql.MaxTableNameLength) } } return nil } // estimateTableRowsViaExplain estimates number of rows on original table func (this *Inspector) estimateTableRowsViaExplain() error { query := fmt.Sprintf(`explain select /* gh-ost */ * from %s.%s where 1=1`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName)) outputFound := false err := sqlutils.QueryRowsMap(this.db, query, func(rowMap sqlutils.RowMap) error { this.migrationContext.RowsEstimate = rowMap.GetInt64("rows") this.migrationContext.UsedRowsEstimateMethod = base.ExplainRowsEstimate outputFound = true return nil }) if err != nil { return err } if !outputFound { return this.migrationContext.Log.Errorf("Cannot run EXPLAIN on %s.%s!", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName)) } this.migrationContext.Log.Infof("Estimated number of rows via EXPLAIN: %d", this.migrationContext.RowsEstimate) return nil } // CountTableRows counts exact number of rows on the original table func (this *Inspector) CountTableRows(ctx context.Context) error { atomic.StoreInt64(&this.migrationContext.CountingRowsFlag, 1) defer atomic.StoreInt64(&this.migrationContext.CountingRowsFlag, 0) this.migrationContext.Log.Infof("As instructed, I'm issuing a SELECT COUNT(*) on the table. This may take a while") conn, err := this.db.Conn(ctx) if err != nil { return err } defer conn.Close() var connectionID string if err := conn.QueryRowContext(ctx, `SELECT /* gh-ost */ CONNECTION_ID()`).Scan(&connectionID); err != nil { return err } query := fmt.Sprintf(`select /* gh-ost */ count(*) as count_rows from %s.%s`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName)) var rowsEstimate int64 if err := conn.QueryRowContext(ctx, query).Scan(&rowsEstimate); err != nil { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { this.migrationContext.Log.Infof("exact row count cancelled (%s), likely because I'm about to cut over. I'm going to kill that query.", ctx.Err()) return mysql.Kill(this.db, connectionID) } return err } // row count query finished. nil out the cancel func, so the main migration thread // doesn't bother calling it after row copy is done. this.migrationContext.SetCountTableRowsCancelFunc(nil) atomic.StoreInt64(&this.migrationContext.RowsEstimate, rowsEstimate) this.migrationContext.UsedRowsEstimateMethod = base.CountRowsEstimate this.migrationContext.Log.Infof("Exact number of rows via COUNT: %d", rowsEstimate) return nil } // applyColumnTypes func (this *Inspector) applyColumnTypes(databaseName, tableName string, columnsLists ...*sql.ColumnList) error { query := ` select /* gh-ost */ * from information_schema.columns where table_schema=? and table_name=?` err := sqlutils.QueryRowsMap(this.db, query, func(m sqlutils.RowMap) error { columnName := m.GetString("COLUMN_NAME") columnType := m.GetString("COLUMN_TYPE") columnOctetLength := m.GetUint("CHARACTER_OCTET_LENGTH") isNullable := m.GetString("IS_NULLABLE") extra := m.GetString("EXTRA") for _, columnsList := range columnsLists { column := columnsList.GetColumn(columnName) if column == nil { continue } column.MySQLType = columnType if isNullable == "YES" { column.Nullable = true } if strings.Contains(columnType, "unsigned") { column.IsUnsigned = true } if strings.Contains(columnType, "mediumint") { column.Type = sql.MediumIntColumnType } if strings.Contains(columnType, "timestamp") { column.Type = sql.TimestampColumnType } if strings.Contains(columnType, "datetime") { column.Type = sql.DateTimeColumnType } if strings.Contains(columnType, "json") { column.Type = sql.JSONColumnType } if strings.Contains(columnType, "float") { column.Type = sql.FloatColumnType } if strings.HasPrefix(columnType, "enum") { column.Type = sql.EnumColumnType column.EnumValues = sql.ParseEnumValues(m.GetString("COLUMN_TYPE")) } if strings.HasPrefix(columnType, "binary") { column.Type = sql.BinaryColumnType column.BinaryOctetLength = columnOctetLength } if strings.Contains(extra, " GENERATED") { column.IsVirtual = true } if charset := m.GetString("CHARACTER_SET_NAME"); charset != "" { column.Charset = charset } } return nil }, databaseName, tableName) return err } // getAutoIncrementValue get's the original table's AUTO_INCREMENT value, if exists (0 value if not exists) func (this *Inspector) getAutoIncrementValue(tableName string) (autoIncrement uint64, err error) { query := ` SELECT /* gh-ost */ AUTO_INCREMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLES.TABLE_SCHEMA = ? AND TABLES.TABLE_NAME = ? AND AUTO_INCREMENT IS NOT NULL` err = sqlutils.QueryRowsMap(this.db, query, func(m sqlutils.RowMap) error { autoIncrement = m.GetUint64("AUTO_INCREMENT") return nil }, this.migrationContext.DatabaseName, tableName) return autoIncrement, err } // getCandidateUniqueKeys investigates a table and returns the list of unique keys // candidate for chunking func (this *Inspector) getCandidateUniqueKeys(tableName string) (uniqueKeys [](*sql.UniqueKey), err error) { query := ` SELECT /* gh-ost */ COLUMNS.TABLE_SCHEMA, COLUMNS.TABLE_NAME, COLUMNS.COLUMN_NAME, UNIQUES.INDEX_NAME, UNIQUES.COLUMN_NAMES, UNIQUES.COUNT_COLUMN_IN_INDEX, COLUMNS.DATA_TYPE, COLUMNS.CHARACTER_SET_NAME, LOCATE('auto_increment', EXTRA) > 0 as is_auto_increment, has_nullable FROM INFORMATION_SCHEMA.COLUMNS INNER JOIN ( SELECT TABLE_SCHEMA, TABLE_NAME, INDEX_NAME, COUNT(*) AS COUNT_COLUMN_IN_INDEX, GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX ASC) AS COLUMN_NAMES, SUBSTRING_INDEX(GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX ASC), ',', 1) AS FIRST_COLUMN_NAME, SUM(NULLABLE='YES') > 0 AS has_nullable FROM INFORMATION_SCHEMA.STATISTICS WHERE NON_UNIQUE=0 AND TABLE_SCHEMA = ? AND TABLE_NAME = ? GROUP BY TABLE_SCHEMA, TABLE_NAME, INDEX_NAME ) AS UNIQUES ON ( COLUMNS.COLUMN_NAME = UNIQUES.FIRST_COLUMN_NAME ) WHERE COLUMNS.TABLE_SCHEMA = ? AND COLUMNS.TABLE_NAME = ? ORDER BY COLUMNS.TABLE_SCHEMA, COLUMNS.TABLE_NAME, CASE UNIQUES.INDEX_NAME WHEN 'PRIMARY' THEN 0 ELSE 1 END, CASE has_nullable WHEN 0 THEN 0 ELSE 1 END, CASE IFNULL(CHARACTER_SET_NAME, '') WHEN '' THEN 0 ELSE 1 END, CASE DATA_TYPE WHEN 'tinyint' THEN 0 WHEN 'smallint' THEN 1 WHEN 'int' THEN 2 WHEN 'bigint' THEN 3 ELSE 100 END, COUNT_COLUMN_IN_INDEX` err = sqlutils.QueryRowsMap(this.db, query, func(m sqlutils.RowMap) error { uniqueKey := &sql.UniqueKey{ Name: m.GetString("INDEX_NAME"), Columns: *sql.ParseColumnList(m.GetString("COLUMN_NAMES")), HasNullable: m.GetBool("has_nullable"), IsAutoIncrement: m.GetBool("is_auto_increment"), } uniqueKeys = append(uniqueKeys, uniqueKey) return nil }, this.migrationContext.DatabaseName, tableName, this.migrationContext.DatabaseName, tableName) if err != nil { return uniqueKeys, err } this.migrationContext.Log.Debugf("Potential unique keys in %+v: %+v", tableName, uniqueKeys) return uniqueKeys, nil } // getSharedUniqueKeys returns the intersection of two given unique keys, // testing by list of columns func (this *Inspector) getSharedUniqueKeys(originalUniqueKeys, ghostUniqueKeys []*sql.UniqueKey) (uniqueKeys []*sql.UniqueKey) { // We actually do NOT rely on key name, just on the set of columns. This is because maybe // the ALTER is on the name itself... for _, originalUniqueKey := range originalUniqueKeys { for _, ghostUniqueKey := range ghostUniqueKeys { if originalUniqueKey.Columns.IsSubsetOf(&ghostUniqueKey.Columns) { // In case the unique key gets renamed in -alter, PanicOnWarnings needs to rely on the new name // to check SQL warnings on the ghost table, so return new name here. originalUniqueKey.NameInGhostTable = ghostUniqueKey.Name uniqueKeys = append(uniqueKeys, originalUniqueKey) break } } } return uniqueKeys } // getSharedColumns returns the intersection of two lists of columns in same order as the first list func (this *Inspector) getSharedColumns(originalColumns, ghostColumns *sql.ColumnList, originalVirtualColumns, ghostVirtualColumns *sql.ColumnList, columnRenameMap map[string]string) (*sql.ColumnList, *sql.ColumnList) { sharedColumnNames := []string{} for _, originalColumn := range originalColumns.Names() { isSharedColumn := false for _, ghostColumn := range ghostColumns.Names() { if strings.EqualFold(originalColumn, ghostColumn) { isSharedColumn = true break } if strings.EqualFold(columnRenameMap[originalColumn], ghostColumn) { isSharedColumn = true break } } for droppedColumn := range this.migrationContext.DroppedColumnsMap { if strings.EqualFold(originalColumn, droppedColumn) { isSharedColumn = false break } } for _, virtualColumn := range originalVirtualColumns.Names() { if strings.EqualFold(originalColumn, virtualColumn) { isSharedColumn = false } } for _, virtualColumn := range ghostVirtualColumns.Names() { if strings.EqualFold(originalColumn, virtualColumn) { isSharedColumn = false } } if isSharedColumn { sharedColumnNames = append(sharedColumnNames, originalColumn) } } mappedSharedColumnNames := []string{} for _, columnName := range sharedColumnNames { if mapped, ok := columnRenameMap[columnName]; ok { mappedSharedColumnNames = append(mappedSharedColumnNames, mapped) } else { mappedSharedColumnNames = append(mappedSharedColumnNames, columnName) } } return sql.NewColumnList(sharedColumnNames), sql.NewColumnList(mappedSharedColumnNames) } // showCreateTable returns the `show create table` statement for given table func (this *Inspector) showCreateTable(tableName string) (createTableStatement string, err error) { var dummy string query := fmt.Sprintf(`show /* gh-ost */ create table %s.%s`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(tableName)) err = this.db.QueryRow(query).Scan(&dummy, &createTableStatement) return createTableStatement, err } // readChangelogState reads changelog hints func (this *Inspector) readChangelogState(hint string) (string, error) { query := fmt.Sprintf(` select /* gh-ost */ hint, value from %s.%s where hint = ? and id <= 255`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetChangelogTableName()), ) result := "" err := sqlutils.QueryRowsMap(this.db, query, func(m sqlutils.RowMap) error { result = m.GetString("value") return nil }, hint) return result, err } func (this *Inspector) getMasterConnectionConfig() (applierConfig *mysql.ConnectionConfig, err error) { this.migrationContext.Log.Infof("Recursively searching for replication master") visitedKeys := mysql.NewInstanceKeyMap() return mysql.GetMasterConnectionConfigSafe(this.dbVersion, this.connectionConfig, visitedKeys, this.migrationContext.AllowedMasterMaster) } func (this *Inspector) getReplicationLag() (replicationLag time.Duration, err error) { replicationLag, err = mysql.GetReplicationLagFromSlaveStatus( this.dbVersion, this.informationSchemaDb, ) return replicationLag, err } func (this *Inspector) Teardown() { this.db.Close() this.informationSchemaDb.Close() } ================================================ FILE: go/logic/inspect_test.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package logic import ( "testing" "github.com/github/gh-ost/go/sql" "github.com/stretchr/testify/require" ) func TestInspectGetSharedUniqueKeys(t *testing.T) { origUniqKeys := []*sql.UniqueKey{ {Columns: *sql.NewColumnList([]string{"id", "item_id"})}, {Columns: *sql.NewColumnList([]string{"id", "org_id"})}, {Columns: *sql.NewColumnList([]string{"id"})}, } ghostUniqKeys := []*sql.UniqueKey{ {Columns: *sql.NewColumnList([]string{"id", "item_id"})}, {Columns: *sql.NewColumnList([]string{"id", "org_id"})}, {Columns: *sql.NewColumnList([]string{"item_id", "user_id"})}, } inspector := &Inspector{} sharedUniqKeys := inspector.getSharedUniqueKeys(origUniqKeys, ghostUniqKeys) require.Len(t, sharedUniqKeys, 3) require.Equal(t, "id,item_id", sharedUniqKeys[0].Columns.String()) require.Equal(t, "id,org_id", sharedUniqKeys[1].Columns.String()) require.Equal(t, "id", sharedUniqKeys[2].Columns.String()) } ================================================ FILE: go/logic/migrator.go ================================================ /* Copyright 2025 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package logic import ( "context" "errors" "fmt" "io" "math" "os" "strings" "sync/atomic" "time" "github.com/github/gh-ost/go/base" "github.com/github/gh-ost/go/binlog" "github.com/github/gh-ost/go/mysql" "github.com/github/gh-ost/go/sql" ) var ( ErrMigratorUnsupportedRenameAlter = errors.New("ALTER statement seems to RENAME the table. This is not supported, and you should run your RENAME outside gh-ost.") ErrMigrationNotAllowedOnMaster = errors.New("It seems like this migration attempt to run directly on master. Preferably it would be executed on a replica (this reduces load from the master). To proceed please provide --allow-on-master.") RetrySleepFn = time.Sleep checkpointTimeout = 2 * time.Second ) type ChangelogState string const ( AllEventsUpToLockProcessed ChangelogState = "AllEventsUpToLockProcessed" GhostTableMigrated ChangelogState = "GhostTableMigrated" Migrated ChangelogState = "Migrated" ReadMigrationRangeValues ChangelogState = "ReadMigrationRangeValues" ) func ReadChangelogState(s string) ChangelogState { return ChangelogState(strings.Split(s, ":")[0]) } type tableWriteFunc func() error type lockProcessedStruct struct { state string coords mysql.BinlogCoordinates } type applyEventStruct struct { writeFunc *tableWriteFunc dmlEvent *binlog.BinlogDMLEvent coords mysql.BinlogCoordinates } func newApplyEventStructByFunc(writeFunc *tableWriteFunc) *applyEventStruct { result := &applyEventStruct{writeFunc: writeFunc} return result } func newApplyEventStructByDML(dmlEntry *binlog.BinlogEntry) *applyEventStruct { result := &applyEventStruct{dmlEvent: dmlEntry.DmlEvent, coords: dmlEntry.Coordinates} return result } type PrintStatusRule int const ( NoPrintStatusRule PrintStatusRule = iota HeuristicPrintStatusRule = iota ForcePrintStatusRule = iota ForcePrintStatusOnlyRule = iota ForcePrintStatusAndHintRule = iota ) // Migrator is the main schema migration flow manager. type Migrator struct { appVersion string parser *sql.AlterTableParser inspector *Inspector applier *Applier eventsStreamer *EventsStreamer server *Server throttler *Throttler hooksExecutor *HooksExecutor migrationContext *base.MigrationContext firstThrottlingCollected chan bool ghostTableMigrated chan bool rowCopyComplete chan error allEventsUpToLockProcessed chan *lockProcessedStruct lastLockProcessed *lockProcessedStruct rowCopyCompleteFlag int64 // copyRowsQueue should not be buffered; if buffered some non-damaging but // excessive work happens at the end of the iteration as new copy-jobs arrive before realizing the copy is complete copyRowsQueue chan tableWriteFunc applyEventsQueue chan *applyEventStruct finishedMigrating int64 } func NewMigrator(context *base.MigrationContext, appVersion string) *Migrator { migrator := &Migrator{ appVersion: appVersion, hooksExecutor: NewHooksExecutor(context), migrationContext: context, parser: sql.NewAlterTableParser(), ghostTableMigrated: make(chan bool), firstThrottlingCollected: make(chan bool, 3), rowCopyComplete: make(chan error), allEventsUpToLockProcessed: make(chan *lockProcessedStruct), copyRowsQueue: make(chan tableWriteFunc), applyEventsQueue: make(chan *applyEventStruct, base.MaxEventsBatchSize), finishedMigrating: 0, } return migrator } // sleepWhileTrue sleeps indefinitely until the given function returns 'false' // (or fails with error) func (this *Migrator) sleepWhileTrue(operation func() (bool, error)) error { for { // Check for abort before continuing if err := this.checkAbort(); err != nil { return err } shouldSleep, err := operation() if err != nil { return err } if !shouldSleep { return nil } time.Sleep(time.Second) } } func (this *Migrator) retryBatchCopyWithHooks(operation func() error, notFatalHint ...bool) (err error) { wrappedOperation := func() error { if err := operation(); err != nil { this.hooksExecutor.onBatchCopyRetry(err.Error()) return err } return nil } return this.retryOperation(wrappedOperation, notFatalHint...) } // retryOperation attempts up to `count` attempts at running given function, // exiting as soon as it returns with non-error. func (this *Migrator) retryOperation(operation func() error, notFatalHint ...bool) (err error) { maxRetries := int(this.migrationContext.MaxRetries()) for i := 0; i < maxRetries; i++ { if i != 0 { // sleep after previous iteration RetrySleepFn(1 * time.Second) } err = operation() if err == nil { return nil } // there's an error. Let's try again. } if len(notFatalHint) == 0 { // Use helper to prevent deadlock if listenOnPanicAbort already exited _ = base.SendWithContext(this.migrationContext.GetContext(), this.migrationContext.PanicAbort, err) } return err } // `retryOperationWithExponentialBackoff` attempts running given function, waiting 2^(n-1) // seconds between each attempt, where `n` is the running number of attempts. Exits // as soon as the function returns with non-error, or as soon as `MaxRetries` // attempts are reached. Wait intervals between attempts obey a maximum of // `ExponentialBackoffMaxInterval`. func (this *Migrator) retryOperationWithExponentialBackoff(operation func() error, notFatalHint ...bool) (err error) { maxRetries := int(this.migrationContext.MaxRetries()) maxInterval := this.migrationContext.ExponentialBackoffMaxInterval for i := 0; i < maxRetries; i++ { interval := math.Min( float64(maxInterval), math.Max(1, math.Exp2(float64(i-1))), ) if i != 0 { RetrySleepFn(time.Duration(interval) * time.Second) } err = operation() if err == nil { return nil } } if len(notFatalHint) == 0 { // Use helper to prevent deadlock if listenOnPanicAbort already exited _ = base.SendWithContext(this.migrationContext.GetContext(), this.migrationContext.PanicAbort, err) } return err } // consumeRowCopyComplete blocks on the rowCopyComplete channel once, and then // consumes and drops any further incoming events that may be left hanging. func (this *Migrator) consumeRowCopyComplete() { if err := <-this.rowCopyComplete; err != nil { // Abort synchronously to ensure checkAbort() sees the error immediately this.abort(err) // Don't mark row copy as complete if there was an error return } atomic.StoreInt64(&this.rowCopyCompleteFlag, 1) this.migrationContext.MarkRowCopyEndTime() go func() { for err := range this.rowCopyComplete { if err != nil { // Abort synchronously to ensure the error is stored immediately this.abort(err) return } } }() } func (this *Migrator) canStopStreaming() bool { return atomic.LoadInt64(&this.migrationContext.CutOverCompleteFlag) != 0 } // onChangelogEvent is called when a binlog event operation on the changelog table is intercepted. func (this *Migrator) onChangelogEvent(dmlEntry *binlog.BinlogEntry) (err error) { // Hey, I created the changelog table, I know the type of columns it has! switch hint := dmlEntry.DmlEvent.NewColumnValues.StringColumn(2); hint { case "state": return this.onChangelogStateEvent(dmlEntry) case "heartbeat": return this.onChangelogHeartbeatEvent(dmlEntry) default: return nil } } func (this *Migrator) onChangelogStateEvent(dmlEntry *binlog.BinlogEntry) (err error) { changelogStateString := dmlEntry.DmlEvent.NewColumnValues.StringColumn(3) changelogState := ReadChangelogState(changelogStateString) this.migrationContext.Log.Infof("Intercepted changelog state %s", changelogState) switch changelogState { case Migrated, ReadMigrationRangeValues: // no-op event case GhostTableMigrated: // Use helper to prevent deadlock if migration aborts before receiver is ready _ = base.SendWithContext(this.migrationContext.GetContext(), this.ghostTableMigrated, true) case AllEventsUpToLockProcessed: var applyEventFunc tableWriteFunc = func() error { return base.SendWithContext(this.migrationContext.GetContext(), this.allEventsUpToLockProcessed, &lockProcessedStruct{ state: changelogStateString, coords: dmlEntry.Coordinates.Clone(), }) } // at this point we know all events up to lock have been read from the streamer, // because the streamer works sequentially. So those events are either already handled, // or have event functions in applyEventsQueue. // So as not to create a potential deadlock, we write this func to applyEventsQueue // asynchronously, understanding it doesn't really matter. go func() { // Use helper to prevent deadlock if buffer fills and executeWriteFuncs exits _ = base.SendWithContext(this.migrationContext.GetContext(), this.applyEventsQueue, newApplyEventStructByFunc(&applyEventFunc)) }() default: return fmt.Errorf("Unknown changelog state: %+v", changelogState) } this.migrationContext.Log.Infof("Handled changelog state %s", changelogState) return nil } func (this *Migrator) onChangelogHeartbeatEvent(dmlEntry *binlog.BinlogEntry) (err error) { changelogHeartbeatString := dmlEntry.DmlEvent.NewColumnValues.StringColumn(3) heartbeatTime, err := time.Parse(time.RFC3339Nano, changelogHeartbeatString) if err != nil { return this.migrationContext.Log.Errore(err) } else { this.migrationContext.SetLastHeartbeatOnChangelogTime(heartbeatTime) this.applier.CurrentCoordinatesMutex.Lock() this.applier.CurrentCoordinates = dmlEntry.Coordinates this.applier.CurrentCoordinatesMutex.Unlock() return nil } } // abort stores the error, cancels the context, and logs the abort. // This is the common abort logic used by both listenOnPanicAbort and // consumeRowCopyComplete to ensure consistent error handling. func (this *Migrator) abort(err error) { // Store the error for Migrate() to return this.migrationContext.SetAbortError(err) // Cancel the context to signal all goroutines to stop this.migrationContext.CancelContext() // Log the error (but don't panic or exit) this.migrationContext.Log.Errorf("Migration aborted: %v", err) } // listenOnPanicAbort listens for fatal errors and initiates graceful shutdown func (this *Migrator) listenOnPanicAbort() { err := <-this.migrationContext.PanicAbort this.abort(err) } // validateAlterStatement validates the `alter` statement meets criteria. // At this time this means: // - column renames are approved // - no table rename allowed func (this *Migrator) validateAlterStatement() (err error) { if this.parser.IsRenameTable() { return ErrMigratorUnsupportedRenameAlter } if this.parser.HasNonTrivialRenames() && !this.migrationContext.SkipRenamedColumns { this.migrationContext.ColumnRenameMap = this.parser.GetNonTrivialRenames() if !this.migrationContext.ApproveRenamedColumns { return fmt.Errorf("gh-ost believes the ALTER statement renames columns, as follows: %v; as precaution, you are asked to confirm gh-ost is correct, and provide with `--approve-renamed-columns`, and we're all happy. Or you can skip renamed columns via `--skip-renamed-columns`, in which case column data may be lost", this.parser.GetNonTrivialRenames()) } this.migrationContext.Log.Infof("Alter statement has column(s) renamed. gh-ost finds the following renames: %v; --approve-renamed-columns is given and so migration proceeds.", this.parser.GetNonTrivialRenames()) } this.migrationContext.DroppedColumnsMap = this.parser.DroppedColumnsMap() return nil } func (this *Migrator) countTableRows() (err error) { if !this.migrationContext.CountTableRows { // Not counting; we stay with an estimate return nil } if this.migrationContext.Noop { this.migrationContext.Log.Debugf("Noop operation; not really counting table rows") return nil } countRowsFunc := func(ctx context.Context) error { if err := this.inspector.CountTableRows(ctx); err != nil { return err } if err := this.hooksExecutor.onRowCountComplete(); err != nil { return err } return nil } if this.migrationContext.ConcurrentCountTableRows { // store a cancel func so we can stop this query before a cut over rowCountContext, rowCountCancel := context.WithCancel(context.Background()) this.migrationContext.SetCountTableRowsCancelFunc(rowCountCancel) this.migrationContext.Log.Infof("As instructed, counting rows in the background; meanwhile I will use an estimated count, and will update it later on") go countRowsFunc(rowCountContext) // and we ignore errors, because this turns to be a background job return nil } return countRowsFunc(context.Background()) } func (this *Migrator) createFlagFiles() (err error) { if this.migrationContext.PostponeCutOverFlagFile != "" { if !base.FileExists(this.migrationContext.PostponeCutOverFlagFile) { if err := base.TouchFile(this.migrationContext.PostponeCutOverFlagFile); err != nil { return this.migrationContext.Log.Errorf("--postpone-cut-over-flag-file indicated by gh-ost is unable to create said file: %s", err.Error()) } this.migrationContext.Log.Infof("Created postpone-cut-over-flag-file: %s", this.migrationContext.PostponeCutOverFlagFile) } } return nil } // checkAbort returns abort error if migration was aborted func (this *Migrator) checkAbort() error { if abortErr := this.migrationContext.GetAbortError(); abortErr != nil { return abortErr } ctx := this.migrationContext.GetContext() if ctx != nil { select { case <-ctx.Done(): // Context cancelled but no abort error stored yet if abortErr := this.migrationContext.GetAbortError(); abortErr != nil { return abortErr } return ctx.Err() default: // Not cancelled } } return nil } // Migrate executes the complete migration logic. This is *the* major gh-ost function. func (this *Migrator) Migrate() (err error) { this.migrationContext.Log.Infof("Migrating %s.%s", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName)) this.migrationContext.StartTime = time.Now() // Ensure context is cancelled on exit (cleanup) defer this.migrationContext.CancelContext() if this.migrationContext.Hostname, err = os.Hostname(); err != nil { return err } go this.listenOnPanicAbort() if err := this.hooksExecutor.onStartup(); err != nil { return err } if err := this.parser.ParseAlterStatement(this.migrationContext.AlterStatement); err != nil { return err } if err := this.validateAlterStatement(); err != nil { return err } // After this point, we'll need to teardown anything that's been started // so we don't leave things hanging around defer this.teardown() if err := this.initiateInspector(); err != nil { return err } if err := this.checkAbort(); err != nil { return err } // If we are resuming, we will initiateStreaming later when we know // the binlog coordinates to resume streaming from. // If not resuming, the streamer must be initiated before the applier, // so that the "GhostTableMigrated" event gets processed. if !this.migrationContext.Resume { if err := this.initiateStreaming(); err != nil { return err } if err := this.checkAbort(); err != nil { return err } } if err := this.initiateApplier(); err != nil { return err } if err := this.checkAbort(); err != nil { return err } if err := this.createFlagFiles(); err != nil { return err } // In MySQL 8.0 (and possibly earlier) some DDL statements can be applied instantly. // Attempt to do this if AttemptInstantDDL is set. if this.migrationContext.AttemptInstantDDL { if this.migrationContext.Noop { this.migrationContext.Log.Debugf("Noop operation; not really attempting instant DDL") } else { this.migrationContext.Log.Infof("Attempting to execute alter with ALGORITHM=INSTANT") if err := this.applier.AttemptInstantDDL(); err == nil { if err := this.finalCleanup(); err != nil { return nil } if err := this.hooksExecutor.onSuccess(); err != nil { return err } this.migrationContext.Log.Infof("Success! table %s.%s migrated instantly", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName)) return nil } else { this.migrationContext.Log.Infof("ALGORITHM=INSTANT not supported for this operation, proceeding with original algorithm: %s", err) } } } initialLag, _ := this.inspector.getReplicationLag() if !this.migrationContext.Resume { this.migrationContext.Log.Infof("Waiting for ghost table to be migrated. Current lag is %+v", initialLag) <-this.ghostTableMigrated this.migrationContext.Log.Debugf("ghost table migrated") } // Yay! We now know the Ghost and Changelog tables are good to examine! // When running on replica, this means the replica has those tables. When running // on master this is always true, of course, and yet it also implies this knowledge // is in the binlogs. if err := this.inspector.inspectOriginalAndGhostTables(); err != nil { return err } // We can prepare some of the queries on the applier if err := this.applier.prepareQueries(); err != nil { return err } // inspectOriginalAndGhostTables must be called before creating checkpoint table. if this.migrationContext.Checkpoint && !this.migrationContext.Resume { if err := this.applier.CreateCheckpointTable(); err != nil { this.migrationContext.Log.Errorf("Unable to create checkpoint table, see further error details.") } } if this.migrationContext.Resume { lastCheckpoint, err := this.applier.ReadLastCheckpoint() if err != nil { return this.migrationContext.Log.Errorf("No checkpoint found, unable to resume: %+v", err) } this.migrationContext.Log.Infof("Resuming from checkpoint coords=%+v range_min=%+v range_max=%+v iteration=%d", lastCheckpoint.LastTrxCoords, lastCheckpoint.IterationRangeMin.String(), lastCheckpoint.IterationRangeMax.String(), lastCheckpoint.Iteration) this.migrationContext.MigrationIterationRangeMinValues = lastCheckpoint.IterationRangeMin this.migrationContext.MigrationIterationRangeMaxValues = lastCheckpoint.IterationRangeMax this.migrationContext.Iteration = lastCheckpoint.Iteration this.migrationContext.TotalRowsCopied = lastCheckpoint.RowsCopied this.migrationContext.TotalDMLEventsApplied = lastCheckpoint.DMLApplied this.migrationContext.InitialStreamerCoords = lastCheckpoint.LastTrxCoords if err := this.initiateStreaming(); err != nil { return err } } // Validation complete! We're good to execute this migration if err := this.hooksExecutor.onValidated(); err != nil { return err } if err := this.initiateServer(); err != nil { return err } defer this.server.RemoveSocketFile() if err := this.countTableRows(); err != nil { return err } if err := this.addDMLEventsListener(); err != nil { return err } if err := this.applier.ReadMigrationRangeValues(); err != nil { return err } this.initiateThrottler() if err := this.hooksExecutor.onBeforeRowCopy(); err != nil { return err } go func() { if err := this.executeWriteFuncs(); err != nil { // Send error to PanicAbort to trigger abort _ = base.SendWithContext(this.migrationContext.GetContext(), this.migrationContext.PanicAbort, err) } }() go this.iterateChunks() this.migrationContext.MarkRowCopyStartTime() go this.initiateStatus() if this.migrationContext.Checkpoint { go this.checkpointLoop() } this.migrationContext.Log.Debugf("Operating until row copy is complete") this.consumeRowCopyComplete() this.migrationContext.Log.Infof("Row copy complete") // Check if row copy was aborted due to error if err := this.checkAbort(); err != nil { return err } if err := this.hooksExecutor.onRowCopyComplete(); err != nil { return err } this.printStatus(ForcePrintStatusRule) if this.migrationContext.IsCountingTableRows() { this.migrationContext.Log.Info("stopping query for exact row count, because that can accidentally lock out the cut over") this.migrationContext.CancelTableRowsCount() } if err := this.hooksExecutor.onBeforeCutOver(); err != nil { return err } var retrier func(func() error, ...bool) error if this.migrationContext.CutOverExponentialBackoff { retrier = this.retryOperationWithExponentialBackoff } else { retrier = this.retryOperation } if err := retrier(this.cutOver); err != nil { return err } atomic.StoreInt64(&this.migrationContext.CutOverCompleteFlag, 1) if this.migrationContext.Checkpoint && !this.migrationContext.Noop { cutoverChk, err := this.CheckpointAfterCutOver() if err != nil { this.migrationContext.Log.Warningf("failed to checkpoint after cutover: %+v", err) } else { this.migrationContext.Log.Infof("checkpoint success after cutover at coords=%+v", cutoverChk.LastTrxCoords.DisplayString()) } } if err := this.finalCleanup(); err != nil { return nil } if err := this.hooksExecutor.onSuccess(); err != nil { return err } this.migrationContext.Log.Infof("Done migrating %s.%s", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName)) // Final check for abort before declaring success if err := this.checkAbort(); err != nil { return err } return nil } // Revert reverts a migration that previously completed by applying all DML events that happened // after the original cutover, then doing another cutover to swap the tables back. // The steps are similar to Migrate(), but without row copying. func (this *Migrator) Revert() error { this.migrationContext.Log.Infof("Reverting %s.%s from %s.%s", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName), sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OldTableName)) this.migrationContext.StartTime = time.Now() // Ensure context is cancelled on exit (cleanup) defer this.migrationContext.CancelContext() var err error if this.migrationContext.Hostname, err = os.Hostname(); err != nil { return err } go this.listenOnPanicAbort() if err := this.hooksExecutor.onStartup(); err != nil { return err } if err := this.validateAlterStatement(); err != nil { return err } defer this.teardown() if err := this.initiateInspector(); err != nil { return err } if err := this.checkAbort(); err != nil { return err } if err := this.initiateApplier(); err != nil { return err } if err := this.checkAbort(); err != nil { return err } if err := this.createFlagFiles(); err != nil { return err } if err := this.inspector.inspectOriginalAndGhostTables(); err != nil { return err } if err := this.applier.prepareQueries(); err != nil { return err } lastCheckpoint, err := this.applier.ReadLastCheckpoint() if err != nil { return this.migrationContext.Log.Errorf("No checkpoint found, unable to revert: %+v", err) } if !lastCheckpoint.IsCutover { return this.migrationContext.Log.Errorf("Last checkpoint is not after cutover, unable to revert: coords=%+v time=%+v", lastCheckpoint.LastTrxCoords, lastCheckpoint.Timestamp) } this.migrationContext.InitialStreamerCoords = lastCheckpoint.LastTrxCoords this.migrationContext.TotalRowsCopied = lastCheckpoint.RowsCopied this.migrationContext.MigrationIterationRangeMinValues = lastCheckpoint.IterationRangeMin this.migrationContext.MigrationIterationRangeMaxValues = lastCheckpoint.IterationRangeMax if err := this.initiateStreaming(); err != nil { return err } if err := this.checkAbort(); err != nil { return err } if err := this.hooksExecutor.onValidated(); err != nil { return err } if err := this.initiateServer(); err != nil { return err } defer this.server.RemoveSocketFile() if err := this.addDMLEventsListener(); err != nil { return err } this.initiateThrottler() go this.initiateStatus() go func() { if err := this.executeDMLWriteFuncs(); err != nil { // Send error to PanicAbort to trigger abort _ = base.SendWithContext(this.migrationContext.GetContext(), this.migrationContext.PanicAbort, err) } }() this.printStatus(ForcePrintStatusRule) var retrier func(func() error, ...bool) error if this.migrationContext.CutOverExponentialBackoff { retrier = this.retryOperationWithExponentialBackoff } else { retrier = this.retryOperation } if err := this.hooksExecutor.onBeforeCutOver(); err != nil { return err } if err := retrier(this.cutOver); err != nil { return err } atomic.StoreInt64(&this.migrationContext.CutOverCompleteFlag, 1) if err := this.finalCleanup(); err != nil { return nil } if err := this.hooksExecutor.onSuccess(); err != nil { return err } this.migrationContext.Log.Infof("Done reverting %s.%s", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName)) return nil } // ExecOnFailureHook executes the onFailure hook, and this method is provided as the only external // hook access point func (this *Migrator) ExecOnFailureHook() (err error) { return this.hooksExecutor.onFailure() } func (this *Migrator) handleCutOverResult(cutOverError error) (err error) { if this.migrationContext.TestOnReplica { // We're merely testing, we don't want to keep this state. Rollback the renames as possible this.applier.RenameTablesRollback() } if cutOverError == nil { return nil } // Only on error: if this.migrationContext.TestOnReplica { // With `--test-on-replica` we stop replication thread, and then proceed to use // the same cut-over phase as the master would use. That means we take locks // and swap the tables. // The difference is that we will later swap the tables back. if err := this.hooksExecutor.onStartReplication(); err != nil { return this.migrationContext.Log.Errore(err) } if this.migrationContext.TestOnReplicaSkipReplicaStop { this.migrationContext.Log.Warningf("--test-on-replica-skip-replica-stop enabled, we are not starting replication.") } else { this.migrationContext.Log.Debugf("testing on replica. Starting replication IO thread after cut-over failure") if err := this.retryOperation(this.applier.StartReplication); err != nil { return this.migrationContext.Log.Errore(err) } } } return nil } // cutOver performs the final step of migration, based on migration // type (on replica? atomic? safe?) func (this *Migrator) cutOver() (err error) { if this.migrationContext.Noop { this.migrationContext.Log.Debugf("Noop operation; not really swapping tables") return nil } this.migrationContext.MarkPointOfInterest() this.throttler.throttle(func() { this.migrationContext.Log.Debugf("throttling before swapping tables") }) this.migrationContext.MarkPointOfInterest() this.migrationContext.Log.Debugf("checking for cut-over postpone") if err := this.sleepWhileTrue( func() (bool, error) { heartbeatLag := this.migrationContext.TimeSinceLastHeartbeatOnChangelog() maxLagMillisecondsThrottle := time.Duration(atomic.LoadInt64(&this.migrationContext.MaxLagMillisecondsThrottleThreshold)) * time.Millisecond cutOverLockTimeout := time.Duration(this.migrationContext.CutOverLockTimeoutSeconds) * time.Second if heartbeatLag > maxLagMillisecondsThrottle || heartbeatLag > cutOverLockTimeout { this.migrationContext.Log.Debugf("current HeartbeatLag (%.2fs) is too high, it needs to be less than both --max-lag-millis (%.2fs) and --cut-over-lock-timeout-seconds (%.2fs) to continue", heartbeatLag.Seconds(), maxLagMillisecondsThrottle.Seconds(), cutOverLockTimeout.Seconds()) return true, nil } if this.migrationContext.PostponeCutOverFlagFile == "" { return false, nil } if atomic.LoadInt64(&this.migrationContext.UserCommandedUnpostponeFlag) > 0 { atomic.StoreInt64(&this.migrationContext.UserCommandedUnpostponeFlag, 0) return false, nil } if base.FileExists(this.migrationContext.PostponeCutOverFlagFile) { // Postpone file defined and exists! if atomic.LoadInt64(&this.migrationContext.IsPostponingCutOver) == 0 { if err := this.hooksExecutor.onBeginPostponed(); err != nil { return true, err } } atomic.StoreInt64(&this.migrationContext.IsPostponingCutOver, 1) return true, nil } return false, nil }, ); err != nil { return err } atomic.StoreInt64(&this.migrationContext.IsPostponingCutOver, 0) this.migrationContext.MarkPointOfInterest() this.migrationContext.Log.Debugf("checking for cut-over postpone: complete") if this.migrationContext.TestOnReplica { // With `--test-on-replica` we stop replication thread, and then proceed to use // the same cut-over phase as the master would use. That means we take locks // and swap the tables. // The difference is that we will later swap the tables back. if err := this.hooksExecutor.onStopReplication(); err != nil { return err } if this.migrationContext.TestOnReplicaSkipReplicaStop { this.migrationContext.Log.Warningf("--test-on-replica-skip-replica-stop enabled, we are not stopping replication.") } else { this.migrationContext.Log.Debugf("testing on replica. Stopping replication IO thread") if err := this.retryOperation(this.applier.StopReplication); err != nil { return err } } } switch this.migrationContext.CutOverType { case base.CutOverAtomic: // Atomic solution: we use low timeout and multiple attempts. But for // each failed attempt, we throttle until replication lag is back to normal err = this.atomicCutOver() case base.CutOverTwoStep: err = this.cutOverTwoStep() default: return this.migrationContext.Log.Fatalf("Unknown cut-over type: %d; should never get here!", this.migrationContext.CutOverType) } this.handleCutOverResult(err) return err } // Inject the "AllEventsUpToLockProcessed" state hint, wait for it to appear in the binary logs, // make sure the queue is drained. func (this *Migrator) waitForEventsUpToLock() error { timeout := time.NewTimer(time.Second * time.Duration(this.migrationContext.CutOverLockTimeoutSeconds)) this.migrationContext.MarkPointOfInterest() waitForEventsUpToLockStartTime := time.Now() allEventsUpToLockProcessedChallenge := fmt.Sprintf("%s:%d", string(AllEventsUpToLockProcessed), waitForEventsUpToLockStartTime.UnixNano()) this.migrationContext.Log.Infof("Writing changelog state: %+v", allEventsUpToLockProcessedChallenge) if _, err := this.applier.WriteChangelogState(allEventsUpToLockProcessedChallenge); err != nil { return err } this.migrationContext.Log.Infof("Waiting for events up to lock") atomic.StoreInt64(&this.migrationContext.AllEventsUpToLockProcessedInjectedFlag, 1) var lockProcessed *lockProcessedStruct for found := false; !found; { select { case <-timeout.C: { return this.migrationContext.Log.Errorf("Timeout while waiting for events up to lock") } case lockProcessed = <-this.allEventsUpToLockProcessed: { if lockProcessed.state == allEventsUpToLockProcessedChallenge { this.migrationContext.Log.Infof("Waiting for events up to lock: got %s", lockProcessed.state) found = true this.lastLockProcessed = lockProcessed } else { this.migrationContext.Log.Infof("Waiting for events up to lock: skipping %s", lockProcessed.state) } } } } waitForEventsUpToLockDuration := time.Since(waitForEventsUpToLockStartTime) this.migrationContext.Log.Infof("Done waiting for events up to lock; duration=%+v", waitForEventsUpToLockDuration) this.printStatus(ForcePrintStatusAndHintRule) return nil } // cutOverTwoStep will lock down the original table, execute // what's left of last DML entries, and **non-atomically** swap original->old, then new->original. // There is a point in time where the "original" table does not exist and queries are non-blocked // and failing. func (this *Migrator) cutOverTwoStep() (err error) { atomic.StoreInt64(&this.migrationContext.InCutOverCriticalSectionFlag, 1) defer atomic.StoreInt64(&this.migrationContext.InCutOverCriticalSectionFlag, 0) atomic.StoreInt64(&this.migrationContext.AllEventsUpToLockProcessedInjectedFlag, 0) if err := this.retryOperation(this.applier.LockOriginalTable); err != nil { return err } if err := this.retryOperation(this.waitForEventsUpToLock); err != nil { return err } // If we need to create triggers we need to do it here (only create part) if this.migrationContext.IncludeTriggers && len(this.migrationContext.Triggers) > 0 { if err := this.retryOperation(this.applier.CreateTriggersOnGhost); err != nil { return err } } if err := this.retryOperation(this.applier.SwapTablesQuickAndBumpy); err != nil { return err } if err := this.retryOperation(this.applier.UnlockTables); err != nil { return err } lockAndRenameDuration := this.migrationContext.RenameTablesEndTime.Sub(this.migrationContext.LockTablesStartTime) renameDuration := this.migrationContext.RenameTablesEndTime.Sub(this.migrationContext.RenameTablesStartTime) this.migrationContext.Log.Debugf("Lock & rename duration: %s (rename only: %s). During this time, queries on %s were locked or failing", lockAndRenameDuration, renameDuration, sql.EscapeName(this.migrationContext.OriginalTableName)) return nil } // atomicCutOver func (this *Migrator) atomicCutOver() (err error) { atomic.StoreInt64(&this.migrationContext.InCutOverCriticalSectionFlag, 1) defer atomic.StoreInt64(&this.migrationContext.InCutOverCriticalSectionFlag, 0) okToUnlockTable := make(chan bool, 4) defer func() { okToUnlockTable <- true }() atomic.StoreInt64(&this.migrationContext.AllEventsUpToLockProcessedInjectedFlag, 0) lockOriginalSessionIdChan := make(chan int64, 2) tableLocked := make(chan error, 2) tableUnlocked := make(chan error, 2) var renameLockSessionId int64 go func() { if err := this.applier.AtomicCutOverMagicLock(lockOriginalSessionIdChan, tableLocked, okToUnlockTable, tableUnlocked, &renameLockSessionId); err != nil { this.migrationContext.Log.Errore(err) } }() if err := <-tableLocked; err != nil { return this.migrationContext.Log.Errore(err) } lockOriginalSessionId := <-lockOriginalSessionIdChan this.migrationContext.Log.Infof("Session locking original & magic tables is %+v", lockOriginalSessionId) // At this point we know the original table is locked. // We know any newly incoming DML on original table is blocked. if err := this.waitForEventsUpToLock(); err != nil { return this.migrationContext.Log.Errore(err) } // If we need to create triggers we need to do it here (only create part) if this.migrationContext.IncludeTriggers && len(this.migrationContext.Triggers) > 0 { if err := this.applier.CreateTriggersOnGhost(); err != nil { return this.migrationContext.Log.Errore(err) } } // Step 2 // We now attempt an atomic RENAME on original & ghost tables, and expect it to block. this.migrationContext.RenameTablesStartTime = time.Now() var tableRenameKnownToHaveFailed int64 renameSessionIdChan := make(chan int64, 2) tablesRenamed := make(chan error, 2) go func() { if err := this.applier.AtomicCutoverRename(renameSessionIdChan, tablesRenamed); err != nil { // Abort! Release the lock atomic.StoreInt64(&tableRenameKnownToHaveFailed, 1) okToUnlockTable <- true } }() renameSessionId := <-renameSessionIdChan this.migrationContext.Log.Infof("Session renaming tables is %+v", renameSessionId) waitForRename := func() error { if atomic.LoadInt64(&tableRenameKnownToHaveFailed) == 1 { // We return `nil` here so as to avoid the `retry`. The RENAME has failed, // it won't show up in PROCESSLIST, no point in waiting return nil } return this.applier.ExpectProcess(renameSessionId, "metadata lock", "rename") } // Wait for the RENAME to appear in PROCESSLIST if err := this.retryOperation(waitForRename, true); err != nil { // Abort! Release the lock okToUnlockTable <- true return err } if atomic.LoadInt64(&tableRenameKnownToHaveFailed) == 0 { this.migrationContext.Log.Infof("Found atomic RENAME to be blocking, as expected. Double checking the lock is still in place (though I don't strictly have to)") } if err := this.applier.ExpectUsedLock(lockOriginalSessionId); err != nil { // Abort operation. Just make sure to drop the magic table. return this.migrationContext.Log.Errore(err) } this.migrationContext.Log.Infof("Connection holding lock on original table still exists") // Now that we've found the RENAME blocking, AND the locking connection still alive, // we know it is safe to proceed to release the lock renameLockSessionId = renameSessionId okToUnlockTable <- true // BAM! magic table dropped, original table lock is released // -> RENAME released -> queries on original are unblocked. if err := <-tableUnlocked; err != nil { return this.migrationContext.Log.Errore(err) } if err := <-tablesRenamed; err != nil { return this.migrationContext.Log.Errore(err) } this.migrationContext.RenameTablesEndTime = time.Now() // ooh nice! We're actually truly and thankfully done lockAndRenameDuration := this.migrationContext.RenameTablesEndTime.Sub(this.migrationContext.LockTablesStartTime) this.migrationContext.Log.Infof("Lock & rename duration: %s. During this time, queries on %s were blocked", lockAndRenameDuration, sql.EscapeName(this.migrationContext.OriginalTableName)) return nil } // initiateServer begins listening on unix socket/tcp for incoming interactive commands func (this *Migrator) initiateServer() (err error) { var f printStatusFunc = func(rule PrintStatusRule, writer io.Writer) { this.printStatus(rule, writer) } this.server = NewServer(this.migrationContext, this.hooksExecutor, f) if err := this.server.BindSocketFile(); err != nil { return err } if err := this.server.BindTCPPort(); err != nil { return err } go this.server.Serve() return nil } // initiateInspector connects, validates and inspects the "inspector" server. // The "inspector" server is typically a replica; it is where we issue some // queries such as: // - table row count // - schema validation // - heartbeat // When `--allow-on-master` is supplied, the inspector is actually the master. func (this *Migrator) initiateInspector() (err error) { this.inspector = NewInspector(this.migrationContext) if err := this.inspector.InitDBConnections(); err != nil { return err } if err := this.inspector.ValidateOriginalTable(); err != nil { return err } if err := this.inspector.InspectOriginalTable(); err != nil { return err } // So far so good, table is accessible and valid. // Let's get master connection config if this.migrationContext.AssumeMasterHostname == "" { // No forced master host; detect master if this.migrationContext.ApplierConnectionConfig, err = this.inspector.getMasterConnectionConfig(); err != nil { return err } this.migrationContext.Log.Infof("Master found to be %+v", *this.migrationContext.ApplierConnectionConfig.ImpliedKey) } else { // Forced master host. key, err := mysql.ParseInstanceKey(this.migrationContext.AssumeMasterHostname) if err != nil { return err } this.migrationContext.ApplierConnectionConfig = this.migrationContext.InspectorConnectionConfig.DuplicateCredentials(*key) if this.migrationContext.CliMasterUser != "" { this.migrationContext.ApplierConnectionConfig.User = this.migrationContext.CliMasterUser } if this.migrationContext.CliMasterPassword != "" { this.migrationContext.ApplierConnectionConfig.Password = this.migrationContext.CliMasterPassword } if err := this.migrationContext.ApplierConnectionConfig.RegisterTLSConfig(); err != nil { return err } this.migrationContext.Log.Infof("Master forced to be %+v", *this.migrationContext.ApplierConnectionConfig.ImpliedKey) } // validate configs if this.migrationContext.TestOnReplica || this.migrationContext.MigrateOnReplica { if this.migrationContext.InspectorIsAlsoApplier() { return fmt.Errorf("Instructed to --test-on-replica or --migrate-on-replica, but the server we connect to doesn't seem to be a replica") } this.migrationContext.Log.Infof("--test-on-replica or --migrate-on-replica given. Will not execute on master %+v but rather on replica %+v itself", *this.migrationContext.ApplierConnectionConfig.ImpliedKey, *this.migrationContext.InspectorConnectionConfig.ImpliedKey, ) this.migrationContext.ApplierConnectionConfig = this.migrationContext.InspectorConnectionConfig.Duplicate() if this.migrationContext.GetThrottleControlReplicaKeys().Len() == 0 { this.migrationContext.AddThrottleControlReplicaKey(this.migrationContext.InspectorConnectionConfig.Key) } } else if this.migrationContext.InspectorIsAlsoApplier() && !this.migrationContext.AllowedRunningOnMaster { return ErrMigrationNotAllowedOnMaster } if err := this.inspector.validateLogSlaveUpdates(); err != nil { return err } return nil } // initiateStatus sets and activates the printStatus() ticker func (this *Migrator) initiateStatus() { this.printStatus(ForcePrintStatusAndHintRule) ticker := time.NewTicker(time.Second) defer ticker.Stop() var previousCount int64 for range ticker.C { if atomic.LoadInt64(&this.finishedMigrating) > 0 { return } go this.printStatus(HeuristicPrintStatusRule) totalCopied := atomic.LoadInt64(&this.migrationContext.TotalRowsCopied) if previousCount > 0 { copiedThisLoop := totalCopied - previousCount atomic.StoreInt64(&this.migrationContext.EtaRowsPerSecond, copiedThisLoop) } previousCount = totalCopied } } // printMigrationStatusHint prints a detailed configuration dump, that is useful // to keep in mind; such as the name of migrated table, throttle params etc. // This gets printed at beginning and end of migration, every 10 minutes throughout // migration, and as response to the "status" interactive command. func (this *Migrator) printMigrationStatusHint(writers ...io.Writer) { w := io.MultiWriter(writers...) fmt.Fprintf(w, "# Migrating %s.%s; Ghost table is %s.%s\n", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName), sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetGhostTableName()), ) fmt.Fprintf(w, "# Migrating %+v; inspecting %+v; executing on %+v\n", *this.applier.connectionConfig.ImpliedKey, *this.inspector.connectionConfig.ImpliedKey, this.migrationContext.Hostname, ) fmt.Fprintf(w, "# Migration started at %+v\n", this.migrationContext.StartTime.Format(time.RubyDate), ) maxLoad := this.migrationContext.GetMaxLoad() criticalLoad := this.migrationContext.GetCriticalLoad() fmt.Fprintf(w, "# chunk-size: %+v; max-lag-millis: %+vms; dml-batch-size: %+v; max-load: %s; critical-load: %s; nice-ratio: %f\n", atomic.LoadInt64(&this.migrationContext.ChunkSize), atomic.LoadInt64(&this.migrationContext.MaxLagMillisecondsThrottleThreshold), atomic.LoadInt64(&this.migrationContext.DMLBatchSize), maxLoad.String(), criticalLoad.String(), this.migrationContext.GetNiceRatio(), ) if this.migrationContext.ThrottleFlagFile != "" { setIndicator := "" if base.FileExists(this.migrationContext.ThrottleFlagFile) { setIndicator = "[set]" } fmt.Fprintf(w, "# throttle-flag-file: %+v %+v\n", this.migrationContext.ThrottleFlagFile, setIndicator, ) } if this.migrationContext.ThrottleAdditionalFlagFile != "" { setIndicator := "" if base.FileExists(this.migrationContext.ThrottleAdditionalFlagFile) { setIndicator = "[set]" } fmt.Fprintf(w, "# throttle-additional-flag-file: %+v %+v\n", this.migrationContext.ThrottleAdditionalFlagFile, setIndicator, ) } if throttleQuery := this.migrationContext.GetThrottleQuery(); throttleQuery != "" { fmt.Fprintf(w, "# throttle-query: %+v\n", throttleQuery, ) } if throttleControlReplicaKeys := this.migrationContext.GetThrottleControlReplicaKeys(); throttleControlReplicaKeys.Len() > 0 { fmt.Fprintf(w, "# throttle-control-replicas count: %+v\n", throttleControlReplicaKeys.Len(), ) } if this.migrationContext.PostponeCutOverFlagFile != "" { setIndicator := "" if base.FileExists(this.migrationContext.PostponeCutOverFlagFile) { setIndicator = "[set]" } fmt.Fprintf(w, "# postpone-cut-over-flag-file: %+v %+v\n", this.migrationContext.PostponeCutOverFlagFile, setIndicator, ) } if this.migrationContext.PanicFlagFile != "" { fmt.Fprintf(w, "# panic-flag-file: %+v\n", this.migrationContext.PanicFlagFile, ) } fmt.Fprintf(w, "# Serving on unix socket: %+v\n", this.migrationContext.ServeSocketFile, ) if this.migrationContext.ServeTCPPort != 0 { fmt.Fprintf(w, "# Serving on TCP port: %+v\n", this.migrationContext.ServeTCPPort) } } // getProgressPercent returns an estimate of migration progess as a percent. func (this *Migrator) getProgressPercent(rowsEstimate int64) (progressPct float64) { progressPct = 100.0 if rowsEstimate > 0 { progressPct *= float64(this.migrationContext.GetTotalRowsCopied()) / float64(rowsEstimate) } return progressPct } // getMigrationETA returns the estimated duration of the migration func (this *Migrator) getMigrationETA(rowsEstimate int64) (eta string, duration time.Duration) { duration = time.Duration(base.ETAUnknown) progressPct := this.getProgressPercent(rowsEstimate) if progressPct >= 100.0 { duration = 0 } else if progressPct >= 0.1 { totalRowsCopied := this.migrationContext.GetTotalRowsCopied() etaRowsPerSecond := atomic.LoadInt64(&this.migrationContext.EtaRowsPerSecond) var etaSeconds float64 // If there is data available on our current row-copies-per-second rate, use it. // Otherwise we can fallback to the total elapsed time and extrapolate. // This is going to be less accurate on a longer copy as the insert rate // will tend to slow down. if etaRowsPerSecond > 0 { remainingRows := float64(rowsEstimate) - float64(totalRowsCopied) etaSeconds = remainingRows / float64(etaRowsPerSecond) } else { elapsedRowCopySeconds := this.migrationContext.ElapsedRowCopyTime().Seconds() totalExpectedSeconds := elapsedRowCopySeconds * float64(rowsEstimate) / float64(totalRowsCopied) etaSeconds = totalExpectedSeconds - elapsedRowCopySeconds } if etaSeconds >= 0 { duration = time.Duration(etaSeconds) * time.Second } else { duration = 0 } } switch duration { case 0: eta = "due" case time.Duration(base.ETAUnknown): eta = "N/A" default: eta = base.PrettifyDurationOutput(duration) } return eta, duration } // getMigrationStateAndETA returns the state and eta of the migration. func (this *Migrator) getMigrationStateAndETA(rowsEstimate int64) (state, eta string, etaDuration time.Duration) { eta, etaDuration = this.getMigrationETA(rowsEstimate) state = "migrating" if atomic.LoadInt64(&this.migrationContext.CountingRowsFlag) > 0 && !this.migrationContext.ConcurrentCountTableRows { state = "counting rows" } else if atomic.LoadInt64(&this.migrationContext.IsPostponingCutOver) > 0 { eta = "due" state = "postponing cut-over" } else if isThrottled, throttleReason, _ := this.migrationContext.IsThrottled(); isThrottled { state = fmt.Sprintf("throttled, %s", throttleReason) } return state, eta, etaDuration } // shouldPrintStatus returns true when the migrator is due to print status info. func (this *Migrator) shouldPrintStatus(rule PrintStatusRule, elapsedSeconds int64, etaDuration time.Duration) (shouldPrint bool) { if rule != HeuristicPrintStatusRule { return true } etaSeconds := etaDuration.Seconds() if elapsedSeconds <= 60 { shouldPrint = true } else if etaSeconds <= 60 { shouldPrint = true } else if etaSeconds <= 180 { shouldPrint = (elapsedSeconds%5 == 0) } else if elapsedSeconds <= 180 { shouldPrint = (elapsedSeconds%5 == 0) } else if this.migrationContext.TimeSincePointOfInterest().Seconds() <= 60 { shouldPrint = (elapsedSeconds%5 == 0) } else { shouldPrint = (elapsedSeconds%30 == 0) } return shouldPrint } // shouldPrintMigrationStatus returns true when the migrator is due to print the migration status hint func (this *Migrator) shouldPrintMigrationStatusHint(rule PrintStatusRule, elapsedSeconds int64) (shouldPrint bool) { if elapsedSeconds%600 == 0 { shouldPrint = true } else if rule == ForcePrintStatusAndHintRule { shouldPrint = true } return shouldPrint } // printStatus prints the progress status, and optionally additionally detailed // dump of configuration. // `rule` indicates the type of output expected. // By default the status is written to standard output, but other writers can // be used as well. func (this *Migrator) printStatus(rule PrintStatusRule, writers ...io.Writer) { if rule == NoPrintStatusRule { return } writers = append(writers, os.Stdout) elapsedTime := this.migrationContext.ElapsedTime() elapsedSeconds := int64(elapsedTime.Seconds()) totalRowsCopied := this.migrationContext.GetTotalRowsCopied() rowsEstimate := atomic.LoadInt64(&this.migrationContext.RowsEstimate) + atomic.LoadInt64(&this.migrationContext.RowsDeltaEstimate) if atomic.LoadInt64(&this.rowCopyCompleteFlag) == 1 { // Done copying rows. The totalRowsCopied value is the de-facto number of rows, // and there is no further need to keep updating the value. rowsEstimate = totalRowsCopied } // we take the opportunity to update migration context with progressPct progressPct := this.getProgressPercent(rowsEstimate) this.migrationContext.SetProgressPct(progressPct) // Before status, let's see if we should print a nice reminder for what exactly we're doing here. if this.shouldPrintMigrationStatusHint(rule, elapsedSeconds) { this.printMigrationStatusHint(writers...) } // Get state + ETA state, eta, etaDuration := this.getMigrationStateAndETA(rowsEstimate) this.migrationContext.SetETADuration(etaDuration) if !this.shouldPrintStatus(rule, elapsedSeconds, etaDuration) { return } currentBinlogCoordinates := this.eventsStreamer.GetCurrentBinlogCoordinates() status := fmt.Sprintf("Copy: %d/%d %.1f%%; Applied: %d; Backlog: %d/%d; Time: %+v(total), %+v(copy); streamer: %+v; Lag: %.2fs, HeartbeatLag: %.2fs, State: %s; ETA: %s", totalRowsCopied, rowsEstimate, progressPct, atomic.LoadInt64(&this.migrationContext.TotalDMLEventsApplied), len(this.applyEventsQueue), cap(this.applyEventsQueue), base.PrettifyDurationOutput(elapsedTime), base.PrettifyDurationOutput(this.migrationContext.ElapsedRowCopyTime()), currentBinlogCoordinates.DisplayString(), this.migrationContext.GetCurrentLagDuration().Seconds(), this.migrationContext.TimeSinceLastHeartbeatOnChangelog().Seconds(), state, eta, ) this.applier.WriteChangelog( fmt.Sprintf("copy iteration %d at %d", this.migrationContext.GetIteration(), time.Now().Unix()), state, ) w := io.MultiWriter(writers...) fmt.Fprintln(w, status) // This "hack" is required here because the underlying logging library // github.com/outbrain/golib/log provides two functions Info and Infof; but the arguments of // both these functions are eventually redirected to the same function, which internally calls // fmt.Sprintf. So, the argument of every function called on the DefaultLogger object // migrationContext.Log will eventually pass through fmt.Sprintf, and thus the '%' character // needs to be escaped. this.migrationContext.Log.Info(strings.Replace(status, "%", "%%", 1)) hooksStatusIntervalSec := this.migrationContext.HooksStatusIntervalSec if hooksStatusIntervalSec > 0 && elapsedSeconds%hooksStatusIntervalSec == 0 { this.hooksExecutor.onStatus(status) } } // initiateStreaming begins streaming of binary log events and registers listeners for such events func (this *Migrator) initiateStreaming() error { this.eventsStreamer = NewEventsStreamer(this.migrationContext) if err := this.eventsStreamer.InitDBConnections(); err != nil { return err } this.eventsStreamer.AddListener( false, this.migrationContext.DatabaseName, this.migrationContext.GetChangelogTableName(), func(dmlEntry *binlog.BinlogEntry) error { return this.onChangelogEvent(dmlEntry) }, ) go func() { this.migrationContext.Log.Debugf("Beginning streaming") err := this.eventsStreamer.StreamEvents(this.canStopStreaming) if err != nil { // Use helper to prevent deadlock if listenOnPanicAbort already exited _ = base.SendWithContext(this.migrationContext.GetContext(), this.migrationContext.PanicAbort, err) } this.migrationContext.Log.Debugf("Done streaming") }() go func() { ticker := time.NewTicker(time.Second) defer ticker.Stop() for range ticker.C { if atomic.LoadInt64(&this.finishedMigrating) > 0 { return } this.migrationContext.SetRecentBinlogCoordinates(this.eventsStreamer.GetCurrentBinlogCoordinates()) } }() return nil } // addDMLEventsListener begins listening for binlog events on the original table, // and creates & enqueues a write task per such event. func (this *Migrator) addDMLEventsListener() error { err := this.eventsStreamer.AddListener( false, this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName, func(dmlEntry *binlog.BinlogEntry) error { // Use helper to prevent deadlock if buffer fills and executeWriteFuncs exits // This is critical because this callback blocks the event streamer return base.SendWithContext(this.migrationContext.GetContext(), this.applyEventsQueue, newApplyEventStructByDML(dmlEntry)) }, ) return err } // initiateThrottler kicks in the throttling collection and the throttling checks. func (this *Migrator) initiateThrottler() { this.throttler = NewThrottler(this.migrationContext, this.applier, this.inspector, this.appVersion) go this.throttler.initiateThrottlerCollection(this.firstThrottlingCollected) this.migrationContext.Log.Infof("Waiting for first throttle metrics to be collected") <-this.firstThrottlingCollected // replication lag <-this.firstThrottlingCollected // HTTP status <-this.firstThrottlingCollected // other, general metrics this.migrationContext.Log.Infof("First throttle metrics collected") go this.throttler.initiateThrottlerChecks() } func (this *Migrator) initiateApplier() error { this.applier = NewApplier(this.migrationContext) if err := this.applier.InitDBConnections(); err != nil { return err } if this.migrationContext.Revert { if err := this.applier.CreateChangelogTable(); err != nil { this.migrationContext.Log.Errorf("Unable to create changelog table, see further error details. Perhaps a previous migration failed without dropping the table? OR is there a running migration? Bailing out") return err } } else if !this.migrationContext.Resume { if err := this.applier.ValidateOrDropExistingTables(); err != nil { return err } if err := this.applier.CreateChangelogTable(); err != nil { this.migrationContext.Log.Errorf("Unable to create changelog table, see further error details. Perhaps a previous migration failed without dropping the table? OR is there a running migration? Bailing out") return err } if err := this.applier.CreateGhostTable(); err != nil { this.migrationContext.Log.Errorf("Unable to create ghost table, see further error details. Perhaps a previous migration failed without dropping the table? Bailing out") return err } if err := this.applier.AlterGhost(); err != nil { this.migrationContext.Log.Errorf("Unable to ALTER ghost table, see further error details. Bailing out") return err } if this.migrationContext.OriginalTableAutoIncrement > 0 && !this.parser.IsAutoIncrementDefined() { // Original table has AUTO_INCREMENT value and the -alter statement does not indicate any override, // so we should copy AUTO_INCREMENT value onto our ghost table. if err := this.applier.AlterGhostAutoIncrement(); err != nil { this.migrationContext.Log.Errorf("Unable to ALTER ghost table AUTO_INCREMENT value, see further error details. Bailing out") return err } } if _, err := this.applier.WriteChangelogState(string(GhostTableMigrated)); err != nil { return err } } // ensure performance_schema.metadata_locks is available. if err := this.applier.StateMetadataLockInstrument(); err != nil { this.migrationContext.Log.Warning("Unable to enable metadata lock instrument, see further error details.") } if !this.migrationContext.IsOpenMetadataLockInstruments { if !this.migrationContext.SkipMetadataLockCheck { return this.migrationContext.Log.Errorf("Bailing out because metadata lock instrument not enabled. Use --skip-metadata-lock-check if you wish to proceed without. See https://github.com/github/gh-ost/pull/1536 for details.") } this.migrationContext.Log.Warning("Proceeding without metadata lock check. There is a small chance of data loss if another session accesses the ghost table during cut-over. See https://github.com/github/gh-ost/pull/1536 for details.") } go this.applier.InitiateHeartbeat() return nil } // iterateChunks iterates the existing table rows, and generates a copy task of // a chunk of rows onto the ghost table. func (this *Migrator) iterateChunks() error { terminateRowIteration := func(err error) error { _ = base.SendWithContext(this.migrationContext.GetContext(), this.rowCopyComplete, err) return this.migrationContext.Log.Errore(err) } if this.migrationContext.Noop { this.migrationContext.Log.Debugf("Noop operation; not really copying data") return terminateRowIteration(nil) } if this.migrationContext.MigrationRangeMinValues == nil { this.migrationContext.Log.Debugf("No rows found in table. Rowcopy will be implicitly empty") return terminateRowIteration(nil) } var hasNoFurtherRangeFlag int64 // Iterate per chunk: for { if err := this.checkAbort(); err != nil { return terminateRowIteration(err) } if atomic.LoadInt64(&this.rowCopyCompleteFlag) == 1 || atomic.LoadInt64(&hasNoFurtherRangeFlag) == 1 { // Done // There's another such check down the line return nil } copyRowsFunc := func() error { this.migrationContext.SetNextIterationRangeMinValues() // Copy task: applyCopyRowsFunc := func() error { if atomic.LoadInt64(&this.rowCopyCompleteFlag) == 1 || atomic.LoadInt64(&hasNoFurtherRangeFlag) == 1 { // Done. // There's another such check down the line return nil } // When hasFurtherRange is false, original table might be write locked and CalculateNextIterationRangeEndValues would hangs forever hasFurtherRange, err := this.applier.CalculateNextIterationRangeEndValues() if err != nil { return err // wrapping call will retry } if !hasFurtherRange { atomic.StoreInt64(&hasNoFurtherRangeFlag, 1) return terminateRowIteration(nil) } if atomic.LoadInt64(&this.rowCopyCompleteFlag) == 1 { // No need for more writes. // This is the de-facto place where we avoid writing in the event of completed cut-over. // There could _still_ be a race condition, but that's as close as we can get. // What about the race condition? Well, there's actually no data integrity issue. // when rowCopyCompleteFlag==1 that means **guaranteed** all necessary rows have been copied. // But some are still then collected at the binary log, and these are the ones we're trying to // not apply here. If the race condition wins over us, then we just attempt to apply onto the // _ghost_ table, which no longer exists. So, bothering error messages and all, but no damage. return nil } _, rowsAffected, _, err := this.applier.ApplyIterationInsertQuery() if err != nil { return err // wrapping call will retry } if this.migrationContext.PanicOnWarnings { if len(this.migrationContext.MigrationLastInsertSQLWarnings) > 0 { for _, warning := range this.migrationContext.MigrationLastInsertSQLWarnings { this.migrationContext.Log.Infof("ApplyIterationInsertQuery has SQL warnings! %s", warning) } joinedWarnings := strings.Join(this.migrationContext.MigrationLastInsertSQLWarnings, "; ") return terminateRowIteration(fmt.Errorf("ApplyIterationInsertQuery failed because of SQL warnings: [%s]", joinedWarnings)) } } atomic.AddInt64(&this.migrationContext.TotalRowsCopied, rowsAffected) atomic.AddInt64(&this.migrationContext.Iteration, 1) return nil } if err := this.retryBatchCopyWithHooks(applyCopyRowsFunc); err != nil { return terminateRowIteration(err) } // record last successfully copied range this.applier.LastIterationRangeMutex.Lock() if this.migrationContext.MigrationIterationRangeMinValues != nil && this.migrationContext.MigrationIterationRangeMaxValues != nil { this.applier.LastIterationRangeMinValues = this.migrationContext.MigrationIterationRangeMinValues.Clone() this.applier.LastIterationRangeMaxValues = this.migrationContext.MigrationIterationRangeMaxValues.Clone() } this.applier.LastIterationRangeMutex.Unlock() return nil } // Enqueue copy operation; to be executed by executeWriteFuncs() // Use helper to prevent deadlock if executeWriteFuncs exits if err := base.SendWithContext(this.migrationContext.GetContext(), this.copyRowsQueue, copyRowsFunc); err != nil { // Context cancelled, check for abort and exit if abortErr := this.checkAbort(); abortErr != nil { return terminateRowIteration(abortErr) } return terminateRowIteration(err) } } } func (this *Migrator) onApplyEventStruct(eventStruct *applyEventStruct) error { handleNonDMLEventStruct := func(eventStruct *applyEventStruct) error { if eventStruct.writeFunc != nil { if err := this.retryOperation(*eventStruct.writeFunc); err != nil { return this.migrationContext.Log.Errore(err) } } return nil } if eventStruct.dmlEvent == nil { return handleNonDMLEventStruct(eventStruct) } if eventStruct.dmlEvent != nil { dmlEvents := [](*binlog.BinlogDMLEvent){} dmlEvents = append(dmlEvents, eventStruct.dmlEvent) var nonDmlStructToApply *applyEventStruct availableEvents := len(this.applyEventsQueue) batchSize := int(atomic.LoadInt64(&this.migrationContext.DMLBatchSize)) if availableEvents > batchSize-1 { // The "- 1" is because we already consumed one event: the original event that led to this function getting called. // So, if DMLBatchSize==1 we wish to not process any further events availableEvents = batchSize - 1 } for i := 0; i < availableEvents; i++ { additionalStruct := <-this.applyEventsQueue if additionalStruct.dmlEvent == nil { // Not a DML. We don't group this, and we don't batch any further nonDmlStructToApply = additionalStruct break } dmlEvents = append(dmlEvents, additionalStruct.dmlEvent) } // Create a task to apply the DML event; this will be execute by executeWriteFuncs() var applyEventFunc tableWriteFunc = func() error { return this.applier.ApplyDMLEventQueries(dmlEvents) } if err := this.retryOperation(applyEventFunc); err != nil { return this.migrationContext.Log.Errore(err) } // update applier coordinates this.applier.CurrentCoordinatesMutex.Lock() this.applier.CurrentCoordinates = eventStruct.coords this.applier.CurrentCoordinatesMutex.Unlock() if nonDmlStructToApply != nil { // We pulled DML events from the queue, and then we hit a non-DML event. Wait! // We need to handle it! if err := handleNonDMLEventStruct(nonDmlStructToApply); err != nil { return this.migrationContext.Log.Errore(err) } } } return nil } // Checkpoint attempts to write a checkpoint of the Migrator's current state. // It gets the binlog coordinates of the last received trx and waits until the // applier reaches that trx. At that point it's safe to resume from these coordinates. func (this *Migrator) Checkpoint(ctx context.Context) (*Checkpoint, error) { coords := this.eventsStreamer.GetCurrentBinlogCoordinates() this.applier.LastIterationRangeMutex.Lock() if this.applier.LastIterationRangeMaxValues == nil || this.applier.LastIterationRangeMinValues == nil { this.applier.LastIterationRangeMutex.Unlock() return nil, errors.New("iteration range is empty, not checkpointing...") } chk := &Checkpoint{ Iteration: this.migrationContext.GetIteration(), IterationRangeMin: this.applier.LastIterationRangeMinValues.Clone(), IterationRangeMax: this.applier.LastIterationRangeMaxValues.Clone(), LastTrxCoords: coords, RowsCopied: atomic.LoadInt64(&this.migrationContext.TotalRowsCopied), DMLApplied: atomic.LoadInt64(&this.migrationContext.TotalDMLEventsApplied), } this.applier.LastIterationRangeMutex.Unlock() for { if err := ctx.Err(); err != nil { return nil, err } this.applier.CurrentCoordinatesMutex.Lock() if coords.SmallerThanOrEquals(this.applier.CurrentCoordinates) { id, err := this.applier.WriteCheckpoint(chk) chk.Id = id this.applier.CurrentCoordinatesMutex.Unlock() return chk, err } this.applier.CurrentCoordinatesMutex.Unlock() time.Sleep(500 * time.Millisecond) } } // CheckpointAfterCutOver writes a final checkpoint after the cutover completes successfully. func (this *Migrator) CheckpointAfterCutOver() (*Checkpoint, error) { if this.lastLockProcessed == nil || this.lastLockProcessed.coords.IsEmpty() { return nil, this.migrationContext.Log.Errorf("lastLockProcessed coords are empty") } chk := &Checkpoint{ IsCutover: true, LastTrxCoords: this.lastLockProcessed.coords, IterationRangeMin: sql.NewColumnValues(this.migrationContext.UniqueKey.Len()), IterationRangeMax: sql.NewColumnValues(this.migrationContext.UniqueKey.Len()), Iteration: this.migrationContext.GetIteration(), RowsCopied: atomic.LoadInt64(&this.migrationContext.TotalRowsCopied), DMLApplied: atomic.LoadInt64(&this.migrationContext.TotalDMLEventsApplied), } this.applier.LastIterationRangeMutex.Lock() if this.applier.LastIterationRangeMinValues != nil { chk.IterationRangeMin = this.applier.LastIterationRangeMinValues.Clone() } if this.applier.LastIterationRangeMaxValues != nil { chk.IterationRangeMax = this.applier.LastIterationRangeMaxValues.Clone() } this.applier.LastIterationRangeMutex.Unlock() id, err := this.applier.WriteCheckpoint(chk) chk.Id = id return chk, err } func (this *Migrator) checkpointLoop() { if this.migrationContext.Noop { this.migrationContext.Log.Debugf("Noop operation; not really checkpointing") return } checkpointInterval := time.Duration(this.migrationContext.CheckpointIntervalSeconds) * time.Second ticker := time.NewTicker(checkpointInterval) for t := range ticker.C { if atomic.LoadInt64(&this.finishedMigrating) > 0 || atomic.LoadInt64(&this.migrationContext.CutOverCompleteFlag) > 0 { return } if atomic.LoadInt64(&this.migrationContext.InCutOverCriticalSectionFlag) > 0 { continue } this.migrationContext.Log.Infof("starting checkpoint at %+v", t) ctx, cancel := context.WithTimeout(context.Background(), checkpointTimeout) chk, err := this.Checkpoint(ctx) if err != nil { if errors.Is(err, context.DeadlineExceeded) { this.migrationContext.Log.Errorf("checkpoint attempt timed out after %+v", checkpointTimeout) } else { this.migrationContext.Log.Errorf("error attempting checkpoint: %+v", err) } } else { this.migrationContext.Log.Infof("checkpoint success at coords=%+v range_min=%+v range_max=%+v iteration=%d", chk.LastTrxCoords.DisplayString(), chk.IterationRangeMin.String(), chk.IterationRangeMax.String(), chk.Iteration) } cancel() } } // executeWriteFuncs writes data via applier: both the rowcopy and the events backlog. // This is where the ghost table gets the data. The function fills the data single-threaded. // Both event backlog and rowcopy events are polled; the backlog events have precedence. func (this *Migrator) executeWriteFuncs() error { if this.migrationContext.Noop { this.migrationContext.Log.Debugf("Noop operation; not really executing write funcs") return nil } for { if err := this.checkAbort(); err != nil { return err } if atomic.LoadInt64(&this.finishedMigrating) > 0 { return nil } this.throttler.throttle(nil) // We give higher priority to event processing, then secondary priority to // rowcopy select { case eventStruct := <-this.applyEventsQueue: { if err := this.onApplyEventStruct(eventStruct); err != nil { return err } } default: { select { case copyRowsFunc := <-this.copyRowsQueue: { copyRowsStartTime := time.Now() // Retries are handled within the copyRowsFunc if err := copyRowsFunc(); err != nil { return this.migrationContext.Log.Errore(err) } if niceRatio := this.migrationContext.GetNiceRatio(); niceRatio > 0 { copyRowsDuration := time.Since(copyRowsStartTime) sleepTimeNanosecondFloat64 := niceRatio * float64(copyRowsDuration.Nanoseconds()) sleepTime := time.Duration(int64(sleepTimeNanosecondFloat64)) * time.Nanosecond time.Sleep(sleepTime) } } default: { // Hmmmmm... nothing in the queue; no events, but also no row copy. // This is possible upon load. Let's just sleep it over. this.migrationContext.Log.Debugf("Getting nothing in the write queue. Sleeping...") time.Sleep(time.Second) } } } } } } func (this *Migrator) executeDMLWriteFuncs() error { if this.migrationContext.Noop { this.migrationContext.Log.Debugf("Noop operation; not really executing DML write funcs") return nil } for { if atomic.LoadInt64(&this.finishedMigrating) > 0 { return nil } this.throttler.throttle(nil) select { case eventStruct := <-this.applyEventsQueue: { if err := this.onApplyEventStruct(eventStruct); err != nil { return err } } case <-time.After(time.Second): continue } } } // finalCleanup takes actions at very end of migration, dropping tables etc. func (this *Migrator) finalCleanup() error { atomic.StoreInt64(&this.migrationContext.CleanupImminentFlag, 1) this.migrationContext.Log.Infof("Writing changelog state: %+v", Migrated) if _, err := this.applier.WriteChangelogState(string(Migrated)); err != nil { return err } if this.migrationContext.Noop { if createTableStatement, err := this.inspector.showCreateTable(this.migrationContext.GetGhostTableName()); err == nil { this.migrationContext.Log.Infof("New table structure follows") fmt.Println(createTableStatement) } else { this.migrationContext.Log.Errore(err) } } if err := this.eventsStreamer.Close(); err != nil { this.migrationContext.Log.Errore(err) } if err := this.retryOperation(this.applier.DropChangelogTable); err != nil { return err } if this.migrationContext.OkToDropTable && !this.migrationContext.TestOnReplica { if err := this.retryOperation(this.applier.DropOldTable); err != nil { return err } if err := this.retryOperation(this.applier.DropCheckpointTable); err != nil { return err } } else if !this.migrationContext.Noop { this.migrationContext.Log.Infof("Am not dropping old table because I want this operation to be as live as possible. If you insist I should do it, please add `--ok-to-drop-table` next time. But I prefer you do not. To drop the old table, issue:") this.migrationContext.Log.Infof("-- drop table %s.%s", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetOldTableName())) if this.migrationContext.Checkpoint { this.migrationContext.Log.Infof("Am not dropping checkpoint table without `--ok-to-drop-table`. To drop the checkpoint table, issue:") this.migrationContext.Log.Infof("-- drop table %s.%s", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetCheckpointTableName())) } } if this.migrationContext.Noop { if err := this.retryOperation(this.applier.DropGhostTable); err != nil { return err } } return nil } func (this *Migrator) teardown() { atomic.StoreInt64(&this.finishedMigrating, 1) if this.inspector != nil { this.migrationContext.Log.Infof("Tearing down inspector") this.inspector.Teardown() } if this.applier != nil { this.migrationContext.Log.Infof("Tearing down applier") this.applier.Teardown() } if this.eventsStreamer != nil { this.migrationContext.Log.Infof("Tearing down streamer") this.eventsStreamer.Teardown() } if this.throttler != nil { this.migrationContext.Log.Infof("Tearing down throttler") this.throttler.Teardown() } } ================================================ FILE: go/logic/migrator_test.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package logic import ( "bytes" "context" gosql "database/sql" "errors" "fmt" "io" "os" "path/filepath" "strings" "sync" "sync/atomic" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" testmysql "github.com/testcontainers/testcontainers-go/modules/mysql" "runtime" "github.com/github/gh-ost/go/base" "github.com/github/gh-ost/go/binlog" "github.com/github/gh-ost/go/mysql" "github.com/github/gh-ost/go/sql" "github.com/testcontainers/testcontainers-go" ) func TestMigratorOnChangelogEvent(t *testing.T) { migrationContext := base.NewMigrationContext() migrator := NewMigrator(migrationContext, "1.2.3") migrator.applier = NewApplier(migrationContext) t.Run("heartbeat", func(t *testing.T) { columnValues := sql.ToColumnValues([]interface{}{ 123, time.Now().Unix(), "heartbeat", "2022-08-16T00:45:10.52Z", }) require.Nil(t, migrator.onChangelogEvent(&binlog.BinlogEntry{ DmlEvent: &binlog.BinlogDMLEvent{ DatabaseName: "test", DML: binlog.InsertDML, NewColumnValues: columnValues}, Coordinates: mysql.NewFileBinlogCoordinates("mysql-bin.000004", int64(4)), })) }) t.Run("state-AllEventsUpToLockProcessed", func(t *testing.T) { var wg sync.WaitGroup wg.Add(1) go func(wg *sync.WaitGroup) { defer wg.Done() es := <-migrator.applyEventsQueue require.NotNil(t, es) require.NotNil(t, es.writeFunc) }(&wg) columnValues := sql.ToColumnValues([]interface{}{ 123, time.Now().Unix(), "state", AllEventsUpToLockProcessed, }) require.Nil(t, migrator.onChangelogEvent(&binlog.BinlogEntry{ DmlEvent: &binlog.BinlogDMLEvent{ DatabaseName: "test", DML: binlog.InsertDML, NewColumnValues: columnValues}, Coordinates: mysql.NewFileBinlogCoordinates("mysql-bin.000004", int64(4)), })) wg.Wait() }) t.Run("state-GhostTableMigrated", func(t *testing.T) { go func() { require.True(t, <-migrator.ghostTableMigrated) }() columnValues := sql.ToColumnValues([]interface{}{ 123, time.Now().Unix(), "state", GhostTableMigrated, }) require.Nil(t, migrator.onChangelogEvent(&binlog.BinlogEntry{ DmlEvent: &binlog.BinlogDMLEvent{ DatabaseName: "test", DML: binlog.InsertDML, NewColumnValues: columnValues}, Coordinates: mysql.NewFileBinlogCoordinates("mysql-bin.000004", int64(4)), })) }) t.Run("state-Migrated", func(t *testing.T) { columnValues := sql.ToColumnValues([]interface{}{ 123, time.Now().Unix(), "state", Migrated, }) require.Nil(t, migrator.onChangelogEvent(&binlog.BinlogEntry{ DmlEvent: &binlog.BinlogDMLEvent{ DatabaseName: "test", DML: binlog.InsertDML, NewColumnValues: columnValues}, Coordinates: mysql.NewFileBinlogCoordinates("mysql-bin.000004", int64(4)), })) }) t.Run("state-ReadMigrationRangeValues", func(t *testing.T) { columnValues := sql.ToColumnValues([]interface{}{ 123, time.Now().Unix(), "state", ReadMigrationRangeValues, }) require.Nil(t, migrator.onChangelogEvent(&binlog.BinlogEntry{ DmlEvent: &binlog.BinlogDMLEvent{ DatabaseName: "test", DML: binlog.InsertDML, NewColumnValues: columnValues}, Coordinates: mysql.NewFileBinlogCoordinates("mysql-bin.000004", int64(4)), })) }) } func TestMigratorValidateStatement(t *testing.T) { t.Run("add-column", func(t *testing.T) { migrationContext := base.NewMigrationContext() migrator := NewMigrator(migrationContext, "1.2.3") require.Nil(t, migrator.parser.ParseAlterStatement(`ALTER TABLE test ADD test_new VARCHAR(64) NOT NULL`)) require.Nil(t, migrator.validateAlterStatement()) require.Len(t, migrator.migrationContext.DroppedColumnsMap, 0) }) t.Run("drop-column", func(t *testing.T) { migrationContext := base.NewMigrationContext() migrator := NewMigrator(migrationContext, "1.2.3") require.Nil(t, migrator.parser.ParseAlterStatement(`ALTER TABLE test DROP abc`)) require.Nil(t, migrator.validateAlterStatement()) require.Len(t, migrator.migrationContext.DroppedColumnsMap, 1) _, exists := migrator.migrationContext.DroppedColumnsMap["abc"] require.True(t, exists) }) t.Run("rename-column", func(t *testing.T) { migrationContext := base.NewMigrationContext() migrator := NewMigrator(migrationContext, "1.2.3") require.Nil(t, migrator.parser.ParseAlterStatement(`ALTER TABLE test CHANGE test123 test1234 bigint unsigned`)) err := migrator.validateAlterStatement() require.Error(t, err) require.True(t, strings.HasPrefix(err.Error(), "gh-ost believes the ALTER statement renames columns")) require.Len(t, migrator.migrationContext.DroppedColumnsMap, 0) }) t.Run("rename-column-approved", func(t *testing.T) { migrationContext := base.NewMigrationContext() migrator := NewMigrator(migrationContext, "1.2.3") migrator.migrationContext.ApproveRenamedColumns = true require.Nil(t, migrator.parser.ParseAlterStatement(`ALTER TABLE test CHANGE test123 test1234 bigint unsigned`)) require.Nil(t, migrator.validateAlterStatement()) require.Len(t, migrator.migrationContext.DroppedColumnsMap, 0) }) t.Run("rename-table", func(t *testing.T) { migrationContext := base.NewMigrationContext() migrator := NewMigrator(migrationContext, "1.2.3") require.Nil(t, migrator.parser.ParseAlterStatement(`ALTER TABLE test RENAME TO test_new`)) err := migrator.validateAlterStatement() require.Error(t, err) require.True(t, errors.Is(err, ErrMigratorUnsupportedRenameAlter)) require.Len(t, migrator.migrationContext.DroppedColumnsMap, 0) }) } func TestMigratorCreateFlagFiles(t *testing.T) { tmpdir, err := os.MkdirTemp("", t.Name()) if err != nil { panic(err) } defer os.RemoveAll(tmpdir) migrationContext := base.NewMigrationContext() migrationContext.PostponeCutOverFlagFile = filepath.Join(tmpdir, "cut-over.flag") migrator := NewMigrator(migrationContext, "1.2.3") require.Nil(t, migrator.createFlagFiles()) require.Nil(t, migrator.createFlagFiles()) // twice to test already-exists _, err = os.Stat(migrationContext.PostponeCutOverFlagFile) require.NoError(t, err) } func TestMigratorGetProgressPercent(t *testing.T) { migrationContext := base.NewMigrationContext() migrator := NewMigrator(migrationContext, "1.2.3") { require.Equal(t, float64(100.0), migrator.getProgressPercent(0)) } { migrationContext.TotalRowsCopied = 250 require.Equal(t, float64(25.0), migrator.getProgressPercent(1000)) } } func TestMigratorGetMigrationStateAndETA(t *testing.T) { migrationContext := base.NewMigrationContext() migrator := NewMigrator(migrationContext, "1.2.3") now := time.Now() migrationContext.RowCopyStartTime = now.Add(-time.Minute) migrationContext.RowCopyEndTime = now { migrationContext.TotalRowsCopied = 456 state, eta, etaDuration := migrator.getMigrationStateAndETA(123456) require.Equal(t, "migrating", state) require.Equal(t, "4h29m44s", eta) require.Equal(t, "4h29m44s", etaDuration.String()) } { // Test using rows-per-second added data. migrationContext.TotalRowsCopied = 456 migrationContext.EtaRowsPerSecond = 100 state, eta, etaDuration := migrator.getMigrationStateAndETA(123456) require.Equal(t, "migrating", state) require.Equal(t, "20m30s", eta) require.Equal(t, "20m30s", etaDuration.String()) } { migrationContext.TotalRowsCopied = 456 state, eta, etaDuration := migrator.getMigrationStateAndETA(456) require.Equal(t, "migrating", state) require.Equal(t, "due", eta) require.Equal(t, "0s", etaDuration.String()) } { migrationContext.TotalRowsCopied = 123456 state, eta, etaDuration := migrator.getMigrationStateAndETA(456) require.Equal(t, "migrating", state) require.Equal(t, "due", eta) require.Equal(t, "0s", etaDuration.String()) } { atomic.StoreInt64(&migrationContext.CountingRowsFlag, 1) state, eta, etaDuration := migrator.getMigrationStateAndETA(123456) require.Equal(t, "counting rows", state) require.Equal(t, "due", eta) require.Equal(t, "0s", etaDuration.String()) } { atomic.StoreInt64(&migrationContext.CountingRowsFlag, 0) atomic.StoreInt64(&migrationContext.IsPostponingCutOver, 1) state, eta, etaDuration := migrator.getMigrationStateAndETA(123456) require.Equal(t, "postponing cut-over", state) require.Equal(t, "due", eta) require.Equal(t, "0s", etaDuration.String()) } } func TestMigratorShouldPrintStatus(t *testing.T) { migrationContext := base.NewMigrationContext() migrator := NewMigrator(migrationContext, "1.2.3") require.True(t, migrator.shouldPrintStatus(NoPrintStatusRule, 10, time.Second)) // test 'rule != HeuristicPrintStatusRule' return require.True(t, migrator.shouldPrintStatus(HeuristicPrintStatusRule, 10, time.Second)) // test 'etaDuration.Seconds() <= 60' require.True(t, migrator.shouldPrintStatus(HeuristicPrintStatusRule, 90, time.Second)) // test 'etaDuration.Seconds() <= 60' again require.True(t, migrator.shouldPrintStatus(HeuristicPrintStatusRule, 90, time.Minute)) // test 'etaDuration.Seconds() <= 180' require.True(t, migrator.shouldPrintStatus(HeuristicPrintStatusRule, 60, 90*time.Second)) // test 'elapsedSeconds <= 180' require.False(t, migrator.shouldPrintStatus(HeuristicPrintStatusRule, 61, 90*time.Second)) // test 'elapsedSeconds <= 180' require.False(t, migrator.shouldPrintStatus(HeuristicPrintStatusRule, 99, 210*time.Second)) // test 'elapsedSeconds <= 180' require.False(t, migrator.shouldPrintStatus(HeuristicPrintStatusRule, 12345, 86400*time.Second)) // test 'else' require.True(t, migrator.shouldPrintStatus(HeuristicPrintStatusRule, 30030, 86400*time.Second)) // test 'else' again } type MigratorTestSuite struct { suite.Suite mysqlContainer testcontainers.Container db *gosql.DB } func (suite *MigratorTestSuite) SetupSuite() { ctx := context.Background() mysqlContainer, err := testmysql.Run(ctx, testMysqlContainerImage, testmysql.WithDatabase(testMysqlDatabase), testmysql.WithUsername(testMysqlUser), testmysql.WithPassword(testMysqlPass), testmysql.WithConfigFile("my.cnf.test"), ) suite.Require().NoError(err) suite.mysqlContainer = mysqlContainer dsn, err := mysqlContainer.ConnectionString(ctx) suite.Require().NoError(err) db, err := gosql.Open("mysql", dsn) suite.Require().NoError(err) suite.db = db } func (suite *MigratorTestSuite) TeardownSuite() { suite.Assert().NoError(suite.db.Close()) suite.Assert().NoError(testcontainers.TerminateContainer(suite.mysqlContainer)) } func (suite *MigratorTestSuite) SetupTest() { ctx := context.Background() _, err := suite.db.ExecContext(ctx, "CREATE DATABASE IF NOT EXISTS "+testMysqlDatabase) suite.Require().NoError(err) os.Remove("/tmp/gh-ost.sock") } func (suite *MigratorTestSuite) TearDownTest() { ctx := context.Background() _, err := suite.db.ExecContext(ctx, "DROP TABLE IF EXISTS "+getTestTableName()) suite.Require().NoError(err) _, err = suite.db.ExecContext(ctx, "DROP TABLE IF EXISTS "+getTestGhostTableName()) suite.Require().NoError(err) _, err = suite.db.ExecContext(ctx, "DROP TABLE IF EXISTS "+getTestRevertedTableName()) suite.Require().NoError(err) _, err = suite.db.ExecContext(ctx, "DROP TABLE IF EXISTS "+getTestOldTableName()) suite.Require().NoError(err) } func (suite *MigratorTestSuite) TestMigrateEmpty() { ctx := context.Background() _, err := suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, name VARCHAR(64))", getTestTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.InspectorConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.InitiallyDropOldTable = true migrationContext.AlterStatementOptions = "ADD COLUMN foobar varchar(255), ENGINE=InnoDB" migrator := NewMigrator(migrationContext, "0.0.0") err = migrator.Migrate() suite.Require().NoError(err) // Verify the new column was added var tableName, createTableSQL string //nolint:execinquery err = suite.db.QueryRow("SHOW CREATE TABLE "+getTestTableName()).Scan(&tableName, &createTableSQL) suite.Require().NoError(err) suite.Require().Equal("testing", tableName) suite.Require().Equal("CREATE TABLE `testing` (\n `id` int NOT NULL,\n `name` varchar(64) DEFAULT NULL,\n `foobar` varchar(255) DEFAULT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", createTableSQL) // Verify the changelog table was claned up //nolint:execinquery err = suite.db.QueryRow("SHOW TABLES IN test LIKE '_testing_ghc'").Scan(&tableName) suite.Require().Error(err) suite.Require().Equal(gosql.ErrNoRows, err) // Verify the old table was renamed //nolint:execinquery err = suite.db.QueryRow("SHOW TABLES IN test LIKE '_testing_del'").Scan(&tableName) suite.Require().NoError(err) suite.Require().Equal("_testing_del", tableName) } func (suite *MigratorTestSuite) TestRetryBatchCopyWithHooks() { ctx := context.Background() _, err := suite.db.ExecContext(ctx, "CREATE TABLE test.test_retry_batch (id INT PRIMARY KEY AUTO_INCREMENT, name TEXT)") suite.Require().NoError(err) const initStride = 1000 const totalBatches = 3 for i := 0; i < totalBatches; i++ { dataSize := 50 * i for j := 0; j < initStride; j++ { _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO test.test_retry_batch (name) VALUES ('%s')", strings.Repeat("a", dataSize))) suite.Require().NoError(err) } } _, err = suite.db.ExecContext(ctx, fmt.Sprintf("SET GLOBAL max_binlog_cache_size = %d", 1024*8)) suite.Require().NoError(err) defer func() { _, err = suite.db.ExecContext(ctx, fmt.Sprintf("SET GLOBAL max_binlog_cache_size = %d", 1024*1024*1024)) suite.Require().NoError(err) }() tmpDir, err := os.MkdirTemp("", "gh-ost-hooks") suite.Require().NoError(err) defer os.RemoveAll(tmpDir) hookScript := filepath.Join(tmpDir, "gh-ost-on-batch-copy-retry") hookContent := `#!/bin/bash # Mock hook that reduces chunk size on binlog cache error ERROR_MSG="$GH_OST_LAST_BATCH_COPY_ERROR" SOCKET_PATH="/tmp/gh-ost.sock" if ! [[ "$ERROR_MSG" =~ "max_binlog_cache_size" ]]; then echo "Nothing to do for error: $ERROR_MSG" exit 0 fi CHUNK_SIZE=$(echo "chunk-size=?" | nc -U $SOCKET_PATH | tr -d '\n') MIN_CHUNK_SIZE=10 NEW_CHUNK_SIZE=$(( CHUNK_SIZE * 8 / 10 )) if [ $NEW_CHUNK_SIZE -lt $MIN_CHUNK_SIZE ]; then NEW_CHUNK_SIZE=$MIN_CHUNK_SIZE fi if [ $CHUNK_SIZE -eq $NEW_CHUNK_SIZE ]; then echo "Chunk size unchanged: $CHUNK_SIZE" exit 0 fi echo "[gh-ost-on-batch-copy-retry]: Changing chunk size from $CHUNK_SIZE to $NEW_CHUNK_SIZE" echo "chunk-size=$NEW_CHUNK_SIZE" | nc -U $SOCKET_PATH echo "[gh-ost-on-batch-copy-retry]: Done, exiting..." ` err = os.WriteFile(hookScript, []byte(hookContent), 0755) suite.Require().NoError(err) origStdout := os.Stdout origStderr := os.Stderr rOut, wOut, _ := os.Pipe() rErr, wErr, _ := os.Pipe() os.Stdout = wOut os.Stderr = wErr connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := base.NewMigrationContext() migrationContext.AllowedRunningOnMaster = true migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.InspectorConnectionConfig = connectionConfig migrationContext.DatabaseName = "test" migrationContext.SkipPortValidation = true migrationContext.OriginalTableName = "test_retry_batch" migrationContext.SetConnectionConfig("innodb") migrationContext.AlterStatementOptions = "MODIFY name LONGTEXT, ENGINE=InnoDB" migrationContext.ReplicaServerId = 99999 migrationContext.HeartbeatIntervalMilliseconds = 100 migrationContext.ThrottleHTTPIntervalMillis = 100 migrationContext.ThrottleHTTPTimeoutMillis = 1000 migrationContext.HooksPath = tmpDir migrationContext.ChunkSize = 1000 migrationContext.SetDefaultNumRetries(10) migrationContext.ServeSocketFile = "/tmp/gh-ost.sock" migrator := NewMigrator(migrationContext, "0.0.0") err = migrator.Migrate() suite.Require().NoError(err) wOut.Close() wErr.Close() os.Stdout = origStdout os.Stderr = origStderr var bufOut, bufErr bytes.Buffer io.Copy(&bufOut, rOut) io.Copy(&bufErr, rErr) outStr := bufOut.String() errStr := bufErr.String() suite.Assert().Contains(outStr, "chunk-size: 1000") suite.Assert().Contains(errStr, "[gh-ost-on-batch-copy-retry]: Changing chunk size from 1000 to 800") suite.Assert().Contains(outStr, "chunk-size: 800") suite.Assert().Contains(errStr, "[gh-ost-on-batch-copy-retry]: Changing chunk size from 800 to 640") suite.Assert().Contains(outStr, "chunk-size: 640") suite.Assert().Contains(errStr, "[gh-ost-on-batch-copy-retry]: Changing chunk size from 640 to 512") suite.Assert().Contains(outStr, "chunk-size: 512") var count int err = suite.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM test.test_retry_batch").Scan(&count) suite.Require().NoError(err) suite.Assert().Equal(3000, count) } func (suite *MigratorTestSuite) TestCopierIntPK() { ctx := context.Background() _, err := suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, name VARCHAR(64), age INT);", getTestTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.InspectorConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.AlterStatementOptions = "ENGINE=InnoDB" migrationContext.OriginalTableColumns = sql.NewColumnList([]string{"id", "name", "age"}) migrationContext.SharedColumns = sql.NewColumnList([]string{"id", "name", "age"}) migrationContext.MappedSharedColumns = sql.NewColumnList([]string{"id", "name", "age"}) migrationContext.UniqueKey = &sql.UniqueKey{ Name: "PRIMARY", NameInGhostTable: "PRIMARY", Columns: *sql.NewColumnList([]string{"id"}), } chunkSize := int64(73) migrationContext.ChunkSize = chunkSize // fill with some rows numRows := int64(3421) for i := range numRows { _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, name, age) VALUES (%d, 'user-%d', %d)", getTestTableName(), i, i, i%99)) suite.Require().NoError(err) } migrator := NewMigrator(migrationContext, "0.0.0") suite.Require().NoError(migrator.initiateApplier()) suite.Require().NoError(migrator.applier.prepareQueries()) suite.Require().NoError(migrator.applier.ReadMigrationRangeValues()) go migrator.iterateChunks() go func() { if err := <-migrator.rowCopyComplete; err != nil { migrator.migrationContext.PanicAbort <- err } atomic.StoreInt64(&migrator.rowCopyCompleteFlag, 1) }() for { if atomic.LoadInt64(&migrator.rowCopyCompleteFlag) == 1 { suite.Assert().Equal((numRows/chunkSize)+1, migrator.migrationContext.GetIteration()) return } select { case copyRowsFunc := <-migrator.copyRowsQueue: { suite.Require().NoError(copyRowsFunc()) // check ghost table has expected number of rows var ghostRows int64 suite.db.QueryRowContext(ctx, fmt.Sprintf(`SELECT COUNT(*) FROM %s`, getTestGhostTableName()), ).Scan(&ghostRows) suite.Assert().Equal(migrator.migrationContext.TotalRowsCopied, ghostRows) } default: time.Sleep(time.Second) } } } func (suite *MigratorTestSuite) TestCopierCompositePK() { ctx := context.Background() _, err := suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT UNSIGNED, t CHAR(32), PRIMARY KEY (t, id));", getTestTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.InspectorConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.AlterStatementOptions = "ENGINE=InnoDB" migrationContext.OriginalTableColumns = sql.NewColumnList([]string{"id", "t"}) migrationContext.SharedColumns = sql.NewColumnList([]string{"id", "t"}) migrationContext.MappedSharedColumns = sql.NewColumnList([]string{"id", "t"}) migrationContext.UniqueKey = &sql.UniqueKey{ Name: "PRIMARY", NameInGhostTable: "PRIMARY", Columns: *sql.NewColumnList([]string{"t", "id"}), } chunkSize := int64(100) migrationContext.ChunkSize = chunkSize // fill with some rows numRows := int64(2049) for i := range numRows { query := fmt.Sprintf(`INSERT INTO %s (id, t) VALUES (FLOOR(100000000 * RAND(%d)), MD5(RAND(%d)))`, getTestTableName(), i, i) _, err = suite.db.ExecContext(ctx, query) suite.Require().NoError(err) } migrator := NewMigrator(migrationContext, "0.0.0") suite.Require().NoError(migrator.initiateApplier()) suite.Require().NoError(migrator.applier.prepareQueries()) suite.Require().NoError(migrator.applier.ReadMigrationRangeValues()) go migrator.iterateChunks() go func() { if err := <-migrator.rowCopyComplete; err != nil { migrator.migrationContext.PanicAbort <- err } atomic.StoreInt64(&migrator.rowCopyCompleteFlag, 1) }() for { if atomic.LoadInt64(&migrator.rowCopyCompleteFlag) == 1 { suite.Assert().Equal((numRows/chunkSize)+1, migrator.migrationContext.GetIteration()) return } select { case copyRowsFunc := <-migrator.copyRowsQueue: { suite.Require().NoError(copyRowsFunc()) // check ghost table has expected number of rows var ghostRows int64 suite.db.QueryRowContext(ctx, fmt.Sprintf(`SELECT COUNT(*) FROM %s`, getTestGhostTableName()), ).Scan(&ghostRows) suite.Assert().Equal(migrator.migrationContext.TotalRowsCopied, ghostRows) } default: time.Sleep(time.Second) } } } func TestMigratorRetry(t *testing.T) { oldRetrySleepFn := RetrySleepFn defer func() { RetrySleepFn = oldRetrySleepFn }() migrationContext := base.NewMigrationContext() migrationContext.SetDefaultNumRetries(100) migrator := NewMigrator(migrationContext, "1.2.3") var sleeps = 0 RetrySleepFn = func(duration time.Duration) { assert.Equal(t, 1*time.Second, duration) sleeps++ } var tries = 0 retryable := func() error { tries++ if tries < int(migrationContext.MaxRetries()) { return errors.New("Backoff") } return nil } result := migrator.retryOperation(retryable, false) assert.NoError(t, result) assert.Equal(t, sleeps, 99) assert.Equal(t, tries, 100) } func TestMigratorRetryWithExponentialBackoff(t *testing.T) { oldRetrySleepFn := RetrySleepFn defer func() { RetrySleepFn = oldRetrySleepFn }() migrationContext := base.NewMigrationContext() migrationContext.SetDefaultNumRetries(100) migrationContext.SetExponentialBackoffMaxInterval(42) migrator := NewMigrator(migrationContext, "1.2.3") var sleeps = 0 expected := []int{ 1, 2, 4, 8, 16, 32, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, } RetrySleepFn = func(duration time.Duration) { assert.Equal(t, time.Duration(expected[sleeps])*time.Second, duration) sleeps++ } var tries = 0 retryable := func() error { tries++ if tries < int(migrationContext.MaxRetries()) { return errors.New("Backoff") } return nil } result := migrator.retryOperationWithExponentialBackoff(retryable, false) assert.NoError(t, result) assert.Equal(t, sleeps, 99) assert.Equal(t, tries, 100) } func (suite *MigratorTestSuite) TestCutOverLossDataCaseLockGhostBeforeRename() { ctx := context.Background() _, err := suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, name VARCHAR(64))", getTestTableName())) suite.Require().NoError(err) _, err = suite.db.ExecContext(ctx, fmt.Sprintf("insert into %s values(1,'a')", getTestTableName())) suite.Require().NoError(err) done := make(chan error, 1) go func() { connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) if err != nil { done <- err return } migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.InspectorConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.AllowSetupMetadataLockInstruments = true migrationContext.AlterStatementOptions = "ADD COLUMN foobar varchar(255)" migrationContext.HeartbeatIntervalMilliseconds = 100 migrationContext.CutOverLockTimeoutSeconds = 4 _, filename, _, _ := runtime.Caller(0) migrationContext.PostponeCutOverFlagFile = filepath.Join(filepath.Dir(filename), "../../tmp/ghost.postpone.flag") migrator := NewMigrator(migrationContext, "0.0.0") //nolint:contextcheck done <- migrator.Migrate() }() time.Sleep(2 * time.Second) //nolint:dogsled _, filename, _, _ := runtime.Caller(0) err = os.Remove(filepath.Join(filepath.Dir(filename), "../../tmp/ghost.postpone.flag")) if err != nil { suite.Require().NoError(err) } time.Sleep(1 * time.Second) go func() { holdConn, err := suite.db.Conn(ctx) suite.Require().NoError(err) _, err = holdConn.ExecContext(ctx, "SELECT *, sleep(2) FROM test._testing_gho WHERE id = 1") suite.Require().NoError(err) }() dmlConn, err := suite.db.Conn(ctx) suite.Require().NoError(err) _, err = dmlConn.ExecContext(ctx, fmt.Sprintf("insert into %s (id, name) values(2,'b')", getTestTableName())) fmt.Println("insert into table original table") suite.Require().NoError(err) migrateErr := <-done suite.Require().NoError(migrateErr) // Verify the new column was added var delValue, OriginalValue int64 err = suite.db.QueryRow( fmt.Sprintf("select count(*) from %s._%s_del", testMysqlDatabase, testMysqlTableName), ).Scan(&delValue) suite.Require().NoError(err) err = suite.db.QueryRow("select count(*) from " + getTestTableName()).Scan(&OriginalValue) suite.Require().NoError(err) suite.Require().LessOrEqual(delValue, OriginalValue) var tableName, createTableSQL string //nolint:execinquery err = suite.db.QueryRow("SHOW CREATE TABLE "+getTestTableName()).Scan(&tableName, &createTableSQL) suite.Require().NoError(err) suite.Require().Equal(testMysqlTableName, tableName) suite.Require().Equal("CREATE TABLE `testing` (\n `id` int NOT NULL,\n `name` varchar(64) DEFAULT NULL,\n `foobar` varchar(255) DEFAULT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", createTableSQL) } func (suite *MigratorTestSuite) TestRevertEmpty() { ctx := context.Background() _, err := suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, s CHAR(32))", getTestTableName())) suite.Require().NoError(err) var oldTableName string // perform original migration connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) { migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.InspectorConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.AlterStatement = "ADD COLUMN newcol CHAR(32)" migrationContext.Checkpoint = true migrationContext.CheckpointIntervalSeconds = 10 migrationContext.DropServeSocket = true migrationContext.InitiallyDropOldTable = true migrationContext.UseGTIDs = true migrator := NewMigrator(migrationContext, "0.0.0") err = migrator.Migrate() oldTableName = migrationContext.GetOldTableName() suite.Require().NoError(err) } // revert the original migration { migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.InspectorConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.DropServeSocket = true migrationContext.UseGTIDs = true migrationContext.Revert = true migrationContext.OkToDropTable = true migrationContext.OldTableName = oldTableName migrator := NewMigrator(migrationContext, "0.0.0") err = migrator.Revert() suite.Require().NoError(err) } } func (suite *MigratorTestSuite) TestRevert() { ctx := context.Background() _, err := suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, s CHAR(32))", getTestTableName())) suite.Require().NoError(err) numRows := 0 for range 100 { _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, s) VALUES (%d, MD5('%d'))", getTestTableName(), numRows, numRows)) suite.Require().NoError(err) numRows += 1 } var oldTableName string connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) // perform original migration { migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.InspectorConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.AlterStatement = "ADD INDEX idx1 (s)" migrationContext.Checkpoint = true migrationContext.CheckpointIntervalSeconds = 10 migrationContext.DropServeSocket = true migrationContext.InitiallyDropOldTable = true migrationContext.UseGTIDs = true migrator := NewMigrator(migrationContext, "0.0.0") err = migrator.Migrate() oldTableName = migrationContext.GetOldTableName() suite.Require().NoError(err) } // do some writes for range 100 { _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, s) VALUES (%d, MD5('%d'))", getTestTableName(), numRows, numRows)) suite.Require().NoError(err) numRows += 1 } for i := 0; i < numRows; i += 7 { _, err = suite.db.ExecContext(ctx, fmt.Sprintf("UPDATE %s SET s=MD5('%d') where id=%d", getTestTableName(), 2*i, i)) suite.Require().NoError(err) } // revert the original migration { migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.InspectorConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") migrationContext.DropServeSocket = true migrationContext.UseGTIDs = true migrationContext.Revert = true migrationContext.OldTableName = oldTableName migrator := NewMigrator(migrationContext, "0.0.0") err = migrator.Revert() oldTableName = migrationContext.GetOldTableName() suite.Require().NoError(err) } // checksum original and reverted table var _tableName, checksum1, checksum2 string rows, err := suite.db.Query(fmt.Sprintf("CHECKSUM TABLE %s, %s", testMysqlTableName, oldTableName)) suite.Require().NoError(err) defer rows.Close() suite.Require().True(rows.Next()) suite.Require().NoError(rows.Scan(&_tableName, &checksum1)) suite.Require().True(rows.Next()) suite.Require().NoError(rows.Scan(&_tableName, &checksum2)) suite.Require().NoError(rows.Err()) suite.Require().Equal(checksum1, checksum2) } func TestMigrator(t *testing.T) { suite.Run(t, new(MigratorTestSuite)) } func TestPanicAbort_PropagatesError(t *testing.T) { migrationContext := base.NewMigrationContext() migrator := NewMigrator(migrationContext, "1.0.0") // Start listenOnPanicAbort go migrator.listenOnPanicAbort() // Send an error to PanicAbort testErr := errors.New("test abort error") go func() { migrationContext.PanicAbort <- testErr }() // Wait a bit for error to be processed time.Sleep(100 * time.Millisecond) // Verify error was stored got := migrationContext.GetAbortError() if got != testErr { //nolint:errorlint // Testing pointer equality for sentinel error t.Errorf("Expected error %v, got %v", testErr, got) } // Verify context was cancelled ctx := migrationContext.GetContext() select { case <-ctx.Done(): // Success - context was cancelled default: t.Error("Expected context to be cancelled") } } func TestPanicAbort_FirstErrorWins(t *testing.T) { migrationContext := base.NewMigrationContext() migrator := NewMigrator(migrationContext, "1.0.0") // Start listenOnPanicAbort go migrator.listenOnPanicAbort() // Send first error err1 := errors.New("first error") go func() { migrationContext.PanicAbort <- err1 }() // Wait for first error to be processed time.Sleep(50 * time.Millisecond) // Try to send second error (should be ignored) err2 := errors.New("second error") migrationContext.SetAbortError(err2) // Verify only first error is stored got := migrationContext.GetAbortError() if got != err1 { //nolint:errorlint // Testing pointer equality for sentinel error t.Errorf("Expected first error %v, got %v", err1, got) } } func TestAbort_AfterRowCopy(t *testing.T) { migrationContext := base.NewMigrationContext() migrator := NewMigrator(migrationContext, "1.0.0") // Start listenOnPanicAbort go migrator.listenOnPanicAbort() // Give listenOnPanicAbort time to start time.Sleep(20 * time.Millisecond) // Simulate row copy error by sending to rowCopyComplete in a goroutine // (unbuffered channel, so send must be async) testErr := errors.New("row copy failed") go func() { migrator.rowCopyComplete <- testErr }() // Consume the error (simulating what Migrate() does) // This is a blocking call that waits for the error migrator.consumeRowCopyComplete() // Wait for the error to be processed by listenOnPanicAbort time.Sleep(50 * time.Millisecond) // Check that error was stored if got := migrationContext.GetAbortError(); got == nil { t.Fatal("Expected abort error to be stored after row copy error") } else if got.Error() != "row copy failed" { t.Errorf("Expected 'row copy failed', got %v", got) } // Verify context was cancelled ctx := migrationContext.GetContext() select { case <-ctx.Done(): // Success case <-time.After(1 * time.Second): t.Error("Expected context to be cancelled after row copy error") } } func TestAbort_DuringInspection(t *testing.T) { migrationContext := base.NewMigrationContext() migrator := NewMigrator(migrationContext, "1.0.0") // Start listenOnPanicAbort go migrator.listenOnPanicAbort() // Simulate error during inspection phase testErr := errors.New("inspection failed") go func() { time.Sleep(10 * time.Millisecond) select { case migrationContext.PanicAbort <- testErr: case <-migrationContext.GetContext().Done(): } }() // Wait for abort to be processed time.Sleep(50 * time.Millisecond) // Call checkAbort (simulating what Migrate() does after initiateInspector) err := migrator.checkAbort() if err == nil { t.Fatal("Expected checkAbort to return error after abort during inspection") } if err.Error() != "inspection failed" { t.Errorf("Expected 'inspection failed', got %v", err) } } func TestAbort_DuringStreaming(t *testing.T) { migrationContext := base.NewMigrationContext() migrator := NewMigrator(migrationContext, "1.0.0") // Start listenOnPanicAbort go migrator.listenOnPanicAbort() // Simulate error from streaming goroutine testErr := errors.New("streaming error") go func() { time.Sleep(10 * time.Millisecond) // Use select pattern like actual code does select { case migrationContext.PanicAbort <- testErr: case <-migrationContext.GetContext().Done(): } }() // Wait for abort to be processed time.Sleep(50 * time.Millisecond) // Verify error stored and context cancelled if got := migrationContext.GetAbortError(); got == nil { t.Fatal("Expected abort error to be stored") } else if got.Error() != "streaming error" { t.Errorf("Expected 'streaming error', got %v", got) } // Verify checkAbort catches it err := migrator.checkAbort() if err == nil { t.Fatal("Expected checkAbort to return error after streaming abort") } } func TestRetryExhaustion_TriggersAbort(t *testing.T) { migrationContext := base.NewMigrationContext() migrationContext.SetDefaultNumRetries(2) // Only 2 retries migrator := NewMigrator(migrationContext, "1.0.0") // Start listenOnPanicAbort go migrator.listenOnPanicAbort() // Operation that always fails callCount := 0 operation := func() error { callCount++ return errors.New("persistent failure") } // Call retryOperation (with notFatalHint=false so it sends to PanicAbort) err := migrator.retryOperation(operation) // Should have called operation MaxRetries times if callCount != 2 { t.Errorf("Expected 2 retry attempts, got %d", callCount) } // Should return the error if err == nil { t.Fatal("Expected retryOperation to return error") } // Wait for abort to be processed time.Sleep(100 * time.Millisecond) // Verify error was sent to PanicAbort and stored if got := migrationContext.GetAbortError(); got == nil { t.Error("Expected abort error to be stored after retry exhaustion") } // Verify context was cancelled ctx := migrationContext.GetContext() select { case <-ctx.Done(): // Success default: t.Error("Expected context to be cancelled after retry exhaustion") } } func TestRevert_AbortsOnError(t *testing.T) { migrationContext := base.NewMigrationContext() migrationContext.Revert = true migrationContext.OldTableName = "_test_del" migrationContext.OriginalTableName = "test" migrationContext.DatabaseName = "testdb" migrator := NewMigrator(migrationContext, "1.0.0") // Start listenOnPanicAbort go migrator.listenOnPanicAbort() // Simulate error during revert testErr := errors.New("revert failed") go func() { time.Sleep(10 * time.Millisecond) select { case migrationContext.PanicAbort <- testErr: case <-migrationContext.GetContext().Done(): } }() // Wait for abort to be processed time.Sleep(50 * time.Millisecond) // Verify checkAbort catches it err := migrator.checkAbort() if err == nil { t.Fatal("Expected checkAbort to return error during revert") } if err.Error() != "revert failed" { t.Errorf("Expected 'revert failed', got %v", err) } // Verify context was cancelled ctx := migrationContext.GetContext() select { case <-ctx.Done(): // Success default: t.Error("Expected context to be cancelled during revert abort") } } func TestCheckAbort_ReturnsNilWhenNoError(t *testing.T) { migrationContext := base.NewMigrationContext() migrator := NewMigrator(migrationContext, "1.0.0") // No error has occurred err := migrator.checkAbort() if err != nil { t.Errorf("Expected no error, got %v", err) } } func TestCheckAbort_DetectsContextCancellation(t *testing.T) { migrationContext := base.NewMigrationContext() migrator := NewMigrator(migrationContext, "1.0.0") // Cancel context directly (without going through PanicAbort) migrationContext.CancelContext() // checkAbort should detect the cancellation err := migrator.checkAbort() if err == nil { t.Fatal("Expected checkAbort to return error when context is cancelled") } } ================================================ FILE: go/logic/my.cnf.test ================================================ # mysql server configuration for testcontainer [mysqld] max_connections = 200 innodb_log_file_size = 64M innodb_flush_log_at_trx_commit = 2 innodb_flush_method = O_DIRECT skip-name-resolve skip-ssl character-set-server = utf8mb4 collation-server = utf8mb4_0900_ai_ci default-time-zone = '+00:00' sql_mode = STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION general_log = 0 general_log_file = /var/log/mysql/general.log slow_query_log = 0 slow_query_log_file = /var/log/mysql/slow.log long_query_time = 2 gtid_mode=ON enforce_gtid_consistency=ON [client] default-character-set = utf8mb4 ================================================ FILE: go/logic/server.go ================================================ /* Copyright 2021 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package logic import ( "bufio" "bytes" "compress/gzip" "encoding/base64" "errors" "fmt" "io" "net" "os" "runtime" "runtime/pprof" "strconv" "strings" "sync/atomic" "time" "github.com/github/gh-ost/go/base" ) var ( ErrCPUProfilingBadOption = errors.New("unrecognized cpu profiling option") ErrCPUProfilingInProgress = errors.New("cpu profiling already in progress") defaultCPUProfileDuration = time.Second * 30 ) type printStatusFunc func(PrintStatusRule, io.Writer) // Server listens for requests on a socket file or via TCP type Server struct { migrationContext *base.MigrationContext unixListener net.Listener tcpListener net.Listener hooksExecutor *HooksExecutor printStatus printStatusFunc isCPUProfiling int64 } func NewServer(migrationContext *base.MigrationContext, hooksExecutor *HooksExecutor, printStatus printStatusFunc) *Server { return &Server{ migrationContext: migrationContext, hooksExecutor: hooksExecutor, printStatus: printStatus, } } func (this *Server) runCPUProfile(args string) (io.Reader, error) { duration := defaultCPUProfileDuration var err error var blockProfile, useGzip bool if args != "" { s := strings.Split(args, ",") // a duration string must be the 1st field, if any if duration, err = time.ParseDuration(s[0]); err != nil { return nil, err } for _, arg := range s[1:] { switch arg { case "block", "blocked", "blocking": blockProfile = true case "gzip": useGzip = true default: return nil, ErrCPUProfilingBadOption } } } if atomic.LoadInt64(&this.isCPUProfiling) > 0 { return nil, ErrCPUProfilingInProgress } atomic.StoreInt64(&this.isCPUProfiling, 1) defer atomic.StoreInt64(&this.isCPUProfiling, 0) var buf bytes.Buffer var writer io.Writer = &buf if blockProfile { runtime.SetBlockProfileRate(1) defer runtime.SetBlockProfileRate(0) } if useGzip { writer = gzip.NewWriter(writer) } if err = pprof.StartCPUProfile(writer); err != nil { return nil, err } time.Sleep(duration) pprof.StopCPUProfile() this.migrationContext.Log.Infof("Captured %d byte runtime/pprof CPU profile (gzip=%v)", buf.Len(), useGzip) return &buf, nil } func (this *Server) createPostponeCutOverFlagFile(filePath string) (err error) { if !base.FileExists(filePath) { if err := base.TouchFile(filePath); err != nil { return fmt.Errorf("Failed to create postpone cut-over flag file %s: %w", filePath, err) } this.migrationContext.Log.Infof("Created postpone-cut-over-flag-file: %s", filePath) } return nil } func (this *Server) BindSocketFile() (err error) { if this.migrationContext.ServeSocketFile == "" { return nil } if this.migrationContext.DropServeSocket && base.FileExists(this.migrationContext.ServeSocketFile) { os.Remove(this.migrationContext.ServeSocketFile) } this.unixListener, err = net.Listen("unix", this.migrationContext.ServeSocketFile) if err != nil { return err } this.migrationContext.Log.Infof("Listening on unix socket file: %s", this.migrationContext.ServeSocketFile) return nil } func (this *Server) RemoveSocketFile() (err error) { this.migrationContext.Log.Infof("Removing socket file: %s", this.migrationContext.ServeSocketFile) return os.Remove(this.migrationContext.ServeSocketFile) } func (this *Server) BindTCPPort() (err error) { if this.migrationContext.ServeTCPPort == 0 { return nil } this.tcpListener, err = net.Listen("tcp", fmt.Sprintf(":%d", this.migrationContext.ServeTCPPort)) if err != nil { return err } this.migrationContext.Log.Infof("Listening on tcp port: %d", this.migrationContext.ServeTCPPort) return nil } // Serve begins listening & serving on whichever device was configured func (this *Server) Serve() (err error) { go func() { for { conn, err := this.unixListener.Accept() if err != nil { this.migrationContext.Log.Errore(err) } go this.handleConnection(conn) } }() go func() { if this.tcpListener == nil { return } for { conn, err := this.tcpListener.Accept() if err != nil { this.migrationContext.Log.Errore(err) } go this.handleConnection(conn) } }() return nil } func (this *Server) handleConnection(conn net.Conn) (err error) { if conn != nil { defer conn.Close() } command, _, err := bufio.NewReader(conn).ReadLine() if err != nil { return err } return this.onServerCommand(string(command), bufio.NewWriter(conn)) } // onServerCommand responds to a user's interactive command func (this *Server) onServerCommand(command string, writer *bufio.Writer) (err error) { defer writer.Flush() printStatusRule, err := this.applyServerCommand(command, writer) if err == nil { this.printStatus(printStatusRule, writer) } else { fmt.Fprintf(writer, "%s\n", err.Error()) } return this.migrationContext.Log.Errore(err) } // applyServerCommand parses and executes commands by user func (this *Server) applyServerCommand(command string, writer *bufio.Writer) (printStatusRule PrintStatusRule, err error) { tokens := strings.SplitN(command, "=", 2) command = strings.TrimSpace(tokens[0]) arg := "" if len(tokens) > 1 { arg = strings.TrimSpace(tokens[1]) if unquoted, err := strconv.Unquote(arg); err == nil { arg = unquoted } } argIsQuestion := (arg == "?") throttleHint := "# Note: you may only throttle for as long as your binary logs are not purged" if err := this.hooksExecutor.onInteractiveCommand(command); err != nil { return NoPrintStatusRule, err } switch command { case "help": { fmt.Fprint(writer, `available commands: status # Print a detailed status message sup # Print a short status message cpu-profile= # Print a base64-encoded runtime/pprof CPU profile using a duration, default: 30s. Comma-separated options 'gzip' and/or 'block' (blocked profile) may follow the profile duration coordinates # Print the currently inspected coordinates applier # Print the hostname of the applier inspector # Print the hostname of the inspector chunk-size= # Set a new chunk-size dml-batch-size= # Set a new dml-batch-size nice-ratio= # Set a new nice-ratio, immediate sleep after each row-copy operation, float (examples: 0 is aggressive, 0.7 adds 70% runtime, 1.0 doubles runtime, 2.0 triples runtime, ...) critical-load= # Set a new set of max-load thresholds max-lag-millis= # Set a new replication lag threshold replication-lag-query= # Set a new query that determines replication lag (no quotes) max-load= # Set a new set of max-load thresholds throttle-query= # Set a new throttle-query (no quotes) throttle-http= # Set a new throttle URL throttle-control-replicas= # Set a new comma delimited list of throttle control replicas throttle # Force throttling no-throttle # End forced throttling (other throttling may still apply) postpone-cut-over-flag-file= # Postpone the cut-over phase, writing a cut over flag file to the given path unpostpone # Bail out a cut-over postpone; proceed to cut-over panic # panic and quit without cleanup help # This message - use '?' (question mark) as argument to get info rather than set. e.g. "max-load=?" will just print out current max-load. `) } case "sup": return ForcePrintStatusOnlyRule, nil case "info", "status": return ForcePrintStatusAndHintRule, nil case "cpu-profile": cpuProfile, err := this.runCPUProfile(arg) if err == nil { fmt.Fprint(base64.NewEncoder(base64.StdEncoding, writer), cpuProfile) } return NoPrintStatusRule, err case "coordinates": { if argIsQuestion || arg == "" { fmt.Fprintf(writer, "%+v\n", this.migrationContext.GetRecentBinlogCoordinates()) return NoPrintStatusRule, nil } return NoPrintStatusRule, fmt.Errorf("coordinates are read-only") } case "applier": if this.migrationContext.ApplierConnectionConfig != nil && this.migrationContext.ApplierConnectionConfig.ImpliedKey != nil { fmt.Fprintf(writer, "Host: %s, Version: %s\n", this.migrationContext.ApplierConnectionConfig.ImpliedKey.String(), this.migrationContext.ApplierMySQLVersion, ) } return NoPrintStatusRule, nil case "inspector": if this.migrationContext.InspectorConnectionConfig != nil && this.migrationContext.InspectorConnectionConfig.ImpliedKey != nil { fmt.Fprintf(writer, "Host: %s, Version: %s\n", this.migrationContext.InspectorConnectionConfig.ImpliedKey.String(), this.migrationContext.InspectorMySQLVersion, ) } return NoPrintStatusRule, nil case "chunk-size": { if argIsQuestion { fmt.Fprintf(writer, "%+v\n", atomic.LoadInt64(&this.migrationContext.ChunkSize)) return NoPrintStatusRule, nil } if chunkSize, err := strconv.Atoi(arg); err != nil { return NoPrintStatusRule, err } else { this.migrationContext.SetChunkSize(int64(chunkSize)) return ForcePrintStatusAndHintRule, nil } } case "dml-batch-size": { if argIsQuestion { fmt.Fprintf(writer, "%+v\n", atomic.LoadInt64(&this.migrationContext.DMLBatchSize)) return NoPrintStatusRule, nil } if dmlBatchSize, err := strconv.Atoi(arg); err != nil { return NoPrintStatusRule, err } else { this.migrationContext.SetDMLBatchSize(int64(dmlBatchSize)) return ForcePrintStatusAndHintRule, nil } } case "max-lag-millis": { if argIsQuestion { fmt.Fprintf(writer, "%+v\n", atomic.LoadInt64(&this.migrationContext.MaxLagMillisecondsThrottleThreshold)) return NoPrintStatusRule, nil } if maxLagMillis, err := strconv.Atoi(arg); err != nil { return NoPrintStatusRule, err } else { this.migrationContext.SetMaxLagMillisecondsThrottleThreshold(int64(maxLagMillis)) return ForcePrintStatusAndHintRule, nil } } case "replication-lag-query": { return NoPrintStatusRule, fmt.Errorf("replication-lag-query is deprecated. gh-ost uses an internal, subsecond resolution query") } case "nice-ratio": { if argIsQuestion { fmt.Fprintf(writer, "%+v\n", this.migrationContext.GetNiceRatio()) return NoPrintStatusRule, nil } if niceRatio, err := strconv.ParseFloat(arg, 64); err != nil { return NoPrintStatusRule, err } else { this.migrationContext.SetNiceRatio(niceRatio) return ForcePrintStatusAndHintRule, nil } } case "max-load": { if argIsQuestion { maxLoad := this.migrationContext.GetMaxLoad() fmt.Fprintf(writer, "%s\n", maxLoad.String()) return NoPrintStatusRule, nil } if err := this.migrationContext.ReadMaxLoad(arg); err != nil { return NoPrintStatusRule, err } return ForcePrintStatusAndHintRule, nil } case "critical-load": { if argIsQuestion { criticalLoad := this.migrationContext.GetCriticalLoad() fmt.Fprintf(writer, "%s\n", criticalLoad.String()) return NoPrintStatusRule, nil } if err := this.migrationContext.ReadCriticalLoad(arg); err != nil { return NoPrintStatusRule, err } return ForcePrintStatusAndHintRule, nil } case "throttle-query": { if argIsQuestion { fmt.Fprintf(writer, "%+v\n", this.migrationContext.GetThrottleQuery()) return NoPrintStatusRule, nil } this.migrationContext.SetThrottleQuery(arg) fmt.Fprintln(writer, throttleHint) return ForcePrintStatusAndHintRule, nil } case "throttle-http": { if argIsQuestion { fmt.Fprintf(writer, "%+v\n", this.migrationContext.GetThrottleHTTP()) return NoPrintStatusRule, nil } this.migrationContext.SetThrottleHTTP(arg) fmt.Fprintln(writer, throttleHint) return ForcePrintStatusAndHintRule, nil } case "throttle-control-replicas": { if argIsQuestion { fmt.Fprintf(writer, "%s\n", this.migrationContext.GetThrottleControlReplicaKeys().ToCommaDelimitedList()) return NoPrintStatusRule, nil } if err := this.migrationContext.ReadThrottleControlReplicaKeys(arg); err != nil { return NoPrintStatusRule, err } fmt.Fprintf(writer, "%s\n", this.migrationContext.GetThrottleControlReplicaKeys().ToCommaDelimitedList()) return ForcePrintStatusAndHintRule, nil } case "throttle", "pause", "suspend": { if arg != "" && arg != this.migrationContext.OriginalTableName { // User explicitly provided table name. This is a courtesy protection mechanism err := fmt.Errorf("User commanded 'throttle' on %s, but migrated table is %s; ignoring request.", arg, this.migrationContext.OriginalTableName) return NoPrintStatusRule, err } atomic.StoreInt64(&this.migrationContext.ThrottleCommandedByUser, 1) fmt.Fprintln(writer, throttleHint) return ForcePrintStatusAndHintRule, nil } case "no-throttle", "unthrottle", "resume", "continue": { if arg != "" && arg != this.migrationContext.OriginalTableName { // User explicitly provided table name. This is a courtesy protection mechanism err := fmt.Errorf("User commanded 'no-throttle' on %s, but migrated table is %s; ignoring request.", arg, this.migrationContext.OriginalTableName) return NoPrintStatusRule, err } atomic.StoreInt64(&this.migrationContext.ThrottleCommandedByUser, 0) return ForcePrintStatusAndHintRule, nil } case "postpone-cut-over-flag-file": { if arg == "" { err := fmt.Errorf("User commanded 'postpone-cut-over-flag-file' without specifying file path") return NoPrintStatusRule, err } if err := this.createPostponeCutOverFlagFile(arg); err != nil { return NoPrintStatusRule, err } this.migrationContext.PostponeCutOverFlagFile = arg fmt.Fprintf(writer, "Postponed\n") return ForcePrintStatusAndHintRule, nil } case "unpostpone", "no-postpone", "cut-over": { if arg == "" && this.migrationContext.ForceNamedCutOverCommand { err := fmt.Errorf("User commanded 'unpostpone' without specifying table name, but --force-named-cut-over is set") return NoPrintStatusRule, err } if arg != "" && arg != this.migrationContext.OriginalTableName { // User explicitly provided table name. This is a courtesy protection mechanism err := fmt.Errorf("User commanded 'unpostpone' on %s, but migrated table is %s; ignoring request.", arg, this.migrationContext.OriginalTableName) return NoPrintStatusRule, err } if atomic.LoadInt64(&this.migrationContext.IsPostponingCutOver) > 0 { atomic.StoreInt64(&this.migrationContext.UserCommandedUnpostponeFlag, 1) fmt.Fprintf(writer, "Unpostponed\n") return ForcePrintStatusAndHintRule, nil } fmt.Fprintf(writer, "You may only invoke this when gh-ost is actively postponing migration. At this time it is not.\n") return NoPrintStatusRule, nil } case "panic": { if arg == "" && this.migrationContext.ForceNamedPanicCommand { err := fmt.Errorf("User commanded 'panic' without specifying table name, but --force-named-panic is set") return NoPrintStatusRule, err } if arg != "" && arg != this.migrationContext.OriginalTableName { // User explicitly provided table name. This is a courtesy protection mechanism err := fmt.Errorf("User commanded 'panic' on %s, but migrated table is %s; ignoring request.", arg, this.migrationContext.OriginalTableName) return NoPrintStatusRule, err } err := fmt.Errorf("User commanded 'panic'. The migration will be aborted without cleanup. Please drop the gh-ost tables before trying again.") // Use helper to prevent deadlock if listenOnPanicAbort already exited _ = base.SendWithContext(this.migrationContext.GetContext(), this.migrationContext.PanicAbort, err) return NoPrintStatusRule, err } default: err = fmt.Errorf("Unknown command: %s", command) return NoPrintStatusRule, err } return NoPrintStatusRule, nil } ================================================ FILE: go/logic/server_test.go ================================================ package logic import ( "os" "path" "testing" "time" "github.com/github/gh-ost/go/base" "github.com/stretchr/testify/require" ) func TestServerRunCPUProfile(t *testing.T) { t.Parallel() t.Run("failed already running", func(t *testing.T) { s := &Server{isCPUProfiling: 1} profile, err := s.runCPUProfile("15ms") require.Equal(t, err, ErrCPUProfilingInProgress) require.Nil(t, profile) }) t.Run("failed bad duration", func(t *testing.T) { s := &Server{isCPUProfiling: 0} profile, err := s.runCPUProfile("should-fail") require.Error(t, err) require.Nil(t, profile) }) t.Run("failed bad option", func(t *testing.T) { s := &Server{isCPUProfiling: 0} profile, err := s.runCPUProfile("10ms,badoption") require.Equal(t, err, ErrCPUProfilingBadOption) require.Nil(t, profile) }) t.Run("success", func(t *testing.T) { s := &Server{ isCPUProfiling: 0, migrationContext: base.NewMigrationContext(), } defaultCPUProfileDuration = time.Millisecond * 10 profile, err := s.runCPUProfile("") require.NoError(t, err) require.NotNil(t, profile) require.Equal(t, int64(0), s.isCPUProfiling) }) t.Run("success with block", func(t *testing.T) { s := &Server{ isCPUProfiling: 0, migrationContext: base.NewMigrationContext(), } profile, err := s.runCPUProfile("10ms,block") require.NoError(t, err) require.NotNil(t, profile) require.Equal(t, int64(0), s.isCPUProfiling) }) t.Run("success with block and gzip", func(t *testing.T) { s := &Server{ isCPUProfiling: 0, migrationContext: base.NewMigrationContext(), } profile, err := s.runCPUProfile("10ms,block,gzip") require.NoError(t, err) require.NotNil(t, profile) require.Equal(t, int64(0), s.isCPUProfiling) }) } func TestServerCreatePostponeCutOverFlagFile(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { s := &Server{ migrationContext: base.NewMigrationContext(), } dir, err := os.MkdirTemp("", "gh-ost-test-") require.NoError(t, err) defer os.RemoveAll(dir) filePath := path.Join(dir, "postpone-cut-over.flag") err = s.createPostponeCutOverFlagFile(filePath) require.NoError(t, err) require.FileExists(t, filePath) }) t.Run("file already exists", func(t *testing.T) { s := &Server{ migrationContext: base.NewMigrationContext(), } dir, err := os.MkdirTemp("", "gh-ost-test-") require.NoError(t, err) filePath := path.Join(dir, "postpone-cut-over.flag") err = base.TouchFile(filePath) require.NoError(t, err) err = s.createPostponeCutOverFlagFile(filePath) require.NoError(t, err) require.FileExists(t, filePath) }) } ================================================ FILE: go/logic/streamer.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package logic import ( gosql "database/sql" "fmt" "strings" "sync" "time" "github.com/github/gh-ost/go/base" "github.com/github/gh-ost/go/binlog" "github.com/github/gh-ost/go/mysql" gomysql "github.com/go-mysql-org/go-mysql/mysql" "github.com/openark/golib/sqlutils" ) type BinlogEventListener struct { async bool databaseName string tableName string onDmlEvent func(event *binlog.BinlogEntry) error } const ( EventsChannelBufferSize = 1 ReconnectStreamerSleepSeconds = 1 ) // EventsStreamer reads data from binary logs and streams it on. It acts as a publisher, // and interested parties may subscribe for per-table events. type EventsStreamer struct { connectionConfig *mysql.ConnectionConfig db *gosql.DB dbVersion string migrationContext *base.MigrationContext initialBinlogCoordinates mysql.BinlogCoordinates listeners [](*BinlogEventListener) listenersMutex *sync.Mutex eventsChannel chan *binlog.BinlogEntry binlogReader *binlog.GoMySQLReader name string } func NewEventsStreamer(migrationContext *base.MigrationContext) *EventsStreamer { return &EventsStreamer{ connectionConfig: migrationContext.InspectorConnectionConfig, migrationContext: migrationContext, listeners: [](*BinlogEventListener){}, listenersMutex: &sync.Mutex{}, eventsChannel: make(chan *binlog.BinlogEntry, EventsChannelBufferSize), name: "streamer", initialBinlogCoordinates: migrationContext.InitialStreamerCoords, } } // AddListener registers a new listener for binlog events, on a per-table basis func (this *EventsStreamer) AddListener( async bool, databaseName string, tableName string, onDmlEvent func(event *binlog.BinlogEntry) error) (err error) { this.listenersMutex.Lock() defer this.listenersMutex.Unlock() if databaseName == "" { return fmt.Errorf("Empty database name in AddListener") } if tableName == "" { return fmt.Errorf("Empty table name in AddListener") } listener := &BinlogEventListener{ async: async, databaseName: databaseName, tableName: tableName, onDmlEvent: onDmlEvent, } this.listeners = append(this.listeners, listener) return nil } // notifyListeners will notify relevant listeners with given DML event. Only // listeners registered for changes on the table on which the DML operates are notified. func (this *EventsStreamer) notifyListeners(binlogEntry *binlog.BinlogEntry) { this.listenersMutex.Lock() defer this.listenersMutex.Unlock() for _, listener := range this.listeners { listener := listener if !strings.EqualFold(listener.databaseName, binlogEntry.DmlEvent.DatabaseName) { continue } if !strings.EqualFold(listener.tableName, binlogEntry.DmlEvent.TableName) { continue } if listener.async { go func() { listener.onDmlEvent(binlogEntry) }() } else { listener.onDmlEvent(binlogEntry) } } } func (this *EventsStreamer) InitDBConnections() (err error) { EventsStreamerUri := this.connectionConfig.GetDBUri(this.migrationContext.DatabaseName) if this.db, _, err = mysql.GetDB(this.migrationContext.Uuid, EventsStreamerUri); err != nil { return err } version, err := base.ValidateConnection(this.db, this.connectionConfig, this.migrationContext, this.name) if err != nil { return err } this.dbVersion = version if this.initialBinlogCoordinates == nil || this.initialBinlogCoordinates.IsEmpty() { if err := this.readCurrentBinlogCoordinates(); err != nil { return err } } if err := this.initBinlogReader(this.initialBinlogCoordinates); err != nil { return err } return nil } // initBinlogReader creates and connects the reader: we hook up to a MySQL server as a replica func (this *EventsStreamer) initBinlogReader(binlogCoordinates mysql.BinlogCoordinates) error { goMySQLReader := binlog.NewGoMySQLReader(this.migrationContext) if err := goMySQLReader.ConnectBinlogStreamer(binlogCoordinates); err != nil { return err } this.binlogReader = goMySQLReader return nil } func (this *EventsStreamer) GetCurrentBinlogCoordinates() mysql.BinlogCoordinates { return this.binlogReader.GetCurrentBinlogCoordinates() } // readCurrentBinlogCoordinates reads master status from hooked server func (this *EventsStreamer) readCurrentBinlogCoordinates() error { binaryLogStatusTerm := mysql.ReplicaTermFor(this.dbVersion, "master status") query := fmt.Sprintf("show /* gh-ost readCurrentBinlogCoordinates */ %s", binaryLogStatusTerm) foundMasterStatus := false err := sqlutils.QueryRowsMap(this.db, query, func(m sqlutils.RowMap) error { if this.migrationContext.UseGTIDs { execGtidSet := m.GetString("Executed_Gtid_Set") gtidSet, err := gomysql.ParseMysqlGTIDSet(execGtidSet) if err != nil { return err } this.initialBinlogCoordinates = &mysql.GTIDBinlogCoordinates{GTIDSet: gtidSet.(*gomysql.MysqlGTIDSet)} } else { this.initialBinlogCoordinates = &mysql.FileBinlogCoordinates{ LogFile: m.GetString("File"), LogPos: m.GetInt64("Position"), } } foundMasterStatus = true return nil }) if err != nil { return err } if !foundMasterStatus { return fmt.Errorf("Got no results from SHOW %s. Bailing out", strings.ToUpper(binaryLogStatusTerm)) } this.migrationContext.Log.Debugf("Streamer binlog coordinates: %+v", this.initialBinlogCoordinates) return nil } // StreamEvents will begin streaming events. It will be blocking, so should be // executed by a goroutine func (this *EventsStreamer) StreamEvents(canStopStreaming func() bool) error { go func() { for binlogEntry := range this.eventsChannel { if binlogEntry.DmlEvent != nil { this.notifyListeners(binlogEntry) } } }() // The next should block and execute forever, unless there's a serious error. var successiveFailures int var reconnectCoords mysql.BinlogCoordinates ctx := this.migrationContext.GetContext() for { // Check for context cancellation each iteration if err := ctx.Err(); err != nil { return err } if canStopStreaming() { return nil } // We will reconnect the binlog streamer at the coordinates // of the last trx that was read completely from the streamer. // Since row event application is idempotent, it's OK if we reapply some events. if err := this.binlogReader.StreamEvents(canStopStreaming, this.eventsChannel); err != nil { if canStopStreaming() { return nil } this.migrationContext.Log.Infof("StreamEvents encountered unexpected error: %+v", err) this.migrationContext.MarkPointOfInterest() time.Sleep(ReconnectStreamerSleepSeconds * time.Second) // See if there's retry overflow if this.migrationContext.BinlogSyncerMaxReconnectAttempts > 0 && successiveFailures >= this.migrationContext.BinlogSyncerMaxReconnectAttempts { return fmt.Errorf("%d successive failures in streamer reconnect at coordinates %+v", successiveFailures, reconnectCoords) } // Reposition at same coordinates if this.binlogReader.LastTrxCoords != nil { reconnectCoords = this.binlogReader.LastTrxCoords.Clone() } else { reconnectCoords = this.initialBinlogCoordinates.Clone() } if !reconnectCoords.SmallerThan(this.GetCurrentBinlogCoordinates()) { successiveFailures += 1 } else { successiveFailures = 0 } this.migrationContext.Log.Infof("Reconnecting EventsStreamer... Will resume at %+v", reconnectCoords) _ = this.binlogReader.Close() if err := this.initBinlogReader(reconnectCoords); err != nil { return err } } } } func (this *EventsStreamer) Close() (err error) { err = this.binlogReader.Close() this.migrationContext.Log.Infof("Closed streamer connection. err=%+v", err) return err } func (this *EventsStreamer) Teardown() { this.db.Close() } ================================================ FILE: go/logic/streamer_test.go ================================================ package logic import ( "context" "database/sql" gosql "database/sql" "fmt" "testing" "time" "github.com/github/gh-ost/go/binlog" "github.com/stretchr/testify/suite" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/mysql" "golang.org/x/sync/errgroup" ) type EventsStreamerTestSuite struct { suite.Suite mysqlContainer testcontainers.Container db *gosql.DB } func (suite *EventsStreamerTestSuite) SetupSuite() { ctx := context.Background() mysqlContainer, err := mysql.Run(ctx, testMysqlContainerImage, mysql.WithDatabase(testMysqlDatabase), mysql.WithUsername(testMysqlUser), mysql.WithPassword(testMysqlPass), ) suite.Require().NoError(err) suite.mysqlContainer = mysqlContainer dsn, err := mysqlContainer.ConnectionString(ctx) suite.Require().NoError(err) db, err := gosql.Open("mysql", dsn) suite.Require().NoError(err) suite.db = db } func (suite *EventsStreamerTestSuite) TeardownSuite() { suite.Assert().NoError(suite.db.Close()) suite.Assert().NoError(testcontainers.TerminateContainer(suite.mysqlContainer)) } func (suite *EventsStreamerTestSuite) SetupTest() { ctx := context.Background() _, err := suite.db.ExecContext(ctx, "CREATE DATABASE IF NOT EXISTS "+testMysqlDatabase) suite.Require().NoError(err) } func (suite *EventsStreamerTestSuite) TearDownTest() { ctx := context.Background() _, err := suite.db.ExecContext(ctx, "DROP TABLE IF EXISTS "+getTestTableName()) suite.Require().NoError(err) _, err = suite.db.ExecContext(ctx, "DROP TABLE IF EXISTS "+getTestGhostTableName()) suite.Require().NoError(err) } func (suite *EventsStreamerTestSuite) TestStreamEvents() { ctx := context.Background() _, err := suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, name VARCHAR(255))", getTestTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.InspectorConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") streamer := NewEventsStreamer(migrationContext) err = streamer.InitDBConnections() suite.Require().NoError(err) defer streamer.Close() defer streamer.Teardown() streamCtx, cancel := context.WithCancel(context.Background()) dmlEvents := make([]*binlog.BinlogDMLEvent, 0) err = streamer.AddListener(false, testMysqlDatabase, testMysqlTableName, func(event *binlog.BinlogEntry) error { dmlEvents = append(dmlEvents, event.DmlEvent) // Stop once we've collected three events if len(dmlEvents) == 3 { cancel() } return nil }) suite.Require().NoError(err) group := errgroup.Group{} group.Go(func() error { return streamer.StreamEvents(func() bool { return streamCtx.Err() != nil }) }) group.Go(func() error { var err error _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, name) VALUES (1, 'foo')", getTestTableName())) if err != nil { return err } _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, name) VALUES (2, 'bar')", getTestTableName())) if err != nil { return err } _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, name) VALUES (3, 'baz')", getTestTableName())) if err != nil { return err } // Bug: Need to write fourth event to hit the canStopStreaming function again _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, name) VALUES (4, 'qux')", getTestTableName())) if err != nil { return err } return nil }) err = group.Wait() suite.Require().NoError(err) suite.Require().Len(dmlEvents, 3) } func (suite *EventsStreamerTestSuite) TestStreamEventsAutomaticallyReconnects() { ctx := context.Background() _, err := suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY, name VARCHAR(255))", getTestTableName())) suite.Require().NoError(err) connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer) suite.Require().NoError(err) migrationContext := newTestMigrationContext() migrationContext.ApplierConnectionConfig = connectionConfig migrationContext.InspectorConnectionConfig = connectionConfig migrationContext.SetConnectionConfig("innodb") streamer := NewEventsStreamer(migrationContext) err = streamer.InitDBConnections() suite.Require().NoError(err) defer streamer.Close() defer streamer.Teardown() streamCtx, cancel := context.WithCancel(context.Background()) dmlEvents := make([]*binlog.BinlogDMLEvent, 0) err = streamer.AddListener(false, testMysqlDatabase, testMysqlTableName, func(event *binlog.BinlogEntry) error { dmlEvents = append(dmlEvents, event.DmlEvent) // Stop once we've collected three events if len(dmlEvents) == 3 { cancel() } return nil }) suite.Require().NoError(err) group := errgroup.Group{} group.Go(func() error { return streamer.StreamEvents(func() bool { return streamCtx.Err() != nil }) }) group.Go(func() error { var err error _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, name) VALUES (1, 'foo')", getTestTableName())) if err != nil { return err } _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, name) VALUES (2, 'bar')", getTestTableName())) if err != nil { return err } var currentConnectionId int err = suite.db.QueryRowContext(ctx, "SELECT CONNECTION_ID()").Scan(¤tConnectionId) if err != nil { return err } //nolint:execinquery rows, err := suite.db.Query("SHOW FULL PROCESSLIST") if err != nil { return err } defer rows.Close() connectionIdsToKill := make([]int, 0) var id, stateTime int var user, host, dbName, command, state, info sql.NullString for rows.Next() { err = rows.Scan(&id, &user, &host, &dbName, &command, &stateTime, &state, &info) if err != nil { return err } fmt.Printf("id: %d, user: %s, host: %s, dbName: %s, command: %s, time: %d, state: %s, info: %s\n", id, user.String, host.String, dbName.String, command.String, stateTime, state.String, info.String) if id != currentConnectionId && user.String == testMysqlUser { connectionIdsToKill = append(connectionIdsToKill, id) } } if err := rows.Err(); err != nil { return err } for _, connectionIdToKill := range connectionIdsToKill { _, err = suite.db.ExecContext(ctx, "KILL ?", connectionIdToKill) if err != nil { return err } } // Bug: We need to wait here for the streamer to reconnect time.Sleep(time.Second * 2) _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, name) VALUES (3, 'baz')", getTestTableName())) if err != nil { return err } // Bug: Need to write fourth event to hit the canStopStreaming function again _, err = suite.db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, name) VALUES (4, 'qux')", getTestTableName())) if err != nil { return err } return nil }) err = group.Wait() suite.Require().NoError(err) suite.Require().Len(dmlEvents, 3) } func TestEventsStreamer(t *testing.T) { suite.Run(t, new(EventsStreamerTestSuite)) } ================================================ FILE: go/logic/test_utils.go ================================================ package logic import ( "context" "fmt" "path/filepath" "runtime" "github.com/github/gh-ost/go/base" "github.com/github/gh-ost/go/mysql" "github.com/testcontainers/testcontainers-go" ) var ( testMysqlContainerImage = "mysql:8.0.42" testMysqlUser = "root" testMysqlPass = "root-password" testMysqlDatabase = "test" testMysqlTableName = "testing" ) func getTestTableName() string { return fmt.Sprintf("`%s`.`%s`", testMysqlDatabase, testMysqlTableName) } func getTestGhostTableName() string { return fmt.Sprintf("`%s`.`_%s_gho`", testMysqlDatabase, testMysqlTableName) } func getTestRevertedTableName() string { return fmt.Sprintf("`%s`.`_%s_rev_del`", testMysqlDatabase, testMysqlTableName) } func getTestOldTableName() string { return fmt.Sprintf("`%s`.`_%s_del`", testMysqlDatabase, testMysqlTableName) } func getTestConnectionConfig(ctx context.Context, container testcontainers.Container) (*mysql.ConnectionConfig, error) { host, err := container.Host(ctx) if err != nil { return nil, err } port, err := container.MappedPort(ctx, "3306") if err != nil { return nil, err } connectionConfig := mysql.NewConnectionConfig() connectionConfig.Key.Hostname = host connectionConfig.Key.Port = port.Int() connectionConfig.User = testMysqlUser connectionConfig.Password = testMysqlPass return connectionConfig, nil } func newTestMigrationContext() *base.MigrationContext { migrationContext := base.NewMigrationContext() migrationContext.ReplicaServerId = 99999 migrationContext.HeartbeatIntervalMilliseconds = 100 migrationContext.ThrottleHTTPIntervalMillis = 100 migrationContext.ThrottleHTTPTimeoutMillis = 1000 migrationContext.DatabaseName = testMysqlDatabase migrationContext.OriginalTableName = testMysqlTableName migrationContext.SkipPortValidation = true migrationContext.PanicOnWarnings = true migrationContext.AllowedRunningOnMaster = true //nolint:dogsled _, filename, _, _ := runtime.Caller(0) migrationContext.ServeSocketFile = filepath.Join(filepath.Dir(filename), "../../tmp/gh-ost.sock") return migrationContext } ================================================ FILE: go/logic/throttler.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package logic import ( "context" "fmt" "net/http" "strings" "sync/atomic" "time" "github.com/github/gh-ost/go/base" "github.com/github/gh-ost/go/mysql" "github.com/github/gh-ost/go/sql" ) var ( httpStatusMessages = map[int]string{ 200: "OK", 404: "Not found", 417: "Expectation failed", 429: "Too many requests", 500: "Internal server error", -1: "Connection error", } // See https://github.com/github/freno/blob/master/doc/http.md httpStatusFrenoMessages = map[int]string{ 200: "OK", 404: "freno: unknown metric", 417: "freno: access forbidden", 429: "freno: threshold exceeded", 500: "freno: internal error", -1: "freno: connection error", } ) const frenoMagicHint = "freno" // Throttler collects metrics related to throttling and makes informed decision // whether throttling should take place. type Throttler struct { appVersion string migrationContext *base.MigrationContext applier *Applier httpClient *http.Client httpClientTimeout time.Duration inspector *Inspector finishedMigrating int64 } func NewThrottler(migrationContext *base.MigrationContext, applier *Applier, inspector *Inspector, appVersion string) *Throttler { return &Throttler{ appVersion: appVersion, migrationContext: migrationContext, applier: applier, httpClient: &http.Client{}, httpClientTimeout: time.Duration(migrationContext.ThrottleHTTPTimeoutMillis) * time.Millisecond, inspector: inspector, finishedMigrating: 0, } } func (this *Throttler) throttleHttpMessage(statusCode int) string { statusCodesMap := httpStatusMessages if throttleHttp := this.migrationContext.GetThrottleHTTP(); strings.Contains(throttleHttp, frenoMagicHint) { statusCodesMap = httpStatusFrenoMessages } if message, ok := statusCodesMap[statusCode]; ok { return fmt.Sprintf("%s (http=%d)", message, statusCode) } return fmt.Sprintf("http=%d", statusCode) } // shouldThrottle performs checks to see whether we should currently be throttling. // It merely observes the metrics collected by other components, it does not issue // its own metric collection. func (this *Throttler) shouldThrottle() (result bool, reason string, reasonHint base.ThrottleReasonHint) { if hibernateUntil := atomic.LoadInt64(&this.migrationContext.HibernateUntil); hibernateUntil > 0 { hibernateUntilTime := time.Unix(0, hibernateUntil) return true, fmt.Sprintf("critical-load-hibernate until %+v", hibernateUntilTime), base.NoThrottleReasonHint } generalCheckResult := this.migrationContext.GetThrottleGeneralCheckResult() if generalCheckResult.ShouldThrottle { return generalCheckResult.ShouldThrottle, generalCheckResult.Reason, generalCheckResult.ReasonHint } // HTTP throttle statusCode := atomic.LoadInt64(&this.migrationContext.ThrottleHTTPStatusCode) if statusCode != 0 && statusCode != http.StatusOK { return true, this.throttleHttpMessage(int(statusCode)), base.NoThrottleReasonHint } // Replication lag throttle maxLagMillisecondsThrottleThreshold := atomic.LoadInt64(&this.migrationContext.MaxLagMillisecondsThrottleThreshold) lag := atomic.LoadInt64(&this.migrationContext.CurrentLag) if time.Duration(lag) > time.Duration(maxLagMillisecondsThrottleThreshold)*time.Millisecond { return true, fmt.Sprintf("lag=%fs", time.Duration(lag).Seconds()), base.NoThrottleReasonHint } checkThrottleControlReplicas := true if (this.migrationContext.TestOnReplica || this.migrationContext.MigrateOnReplica) && (atomic.LoadInt64(&this.migrationContext.AllEventsUpToLockProcessedInjectedFlag) > 0) { checkThrottleControlReplicas = false } if checkThrottleControlReplicas { lagResult := this.migrationContext.GetControlReplicasLagResult() if lagResult.Err != nil { return true, fmt.Sprintf("%+v %+v", lagResult.Key, lagResult.Err), base.NoThrottleReasonHint } if lagResult.Lag > time.Duration(maxLagMillisecondsThrottleThreshold)*time.Millisecond { return true, fmt.Sprintf("%+v replica-lag=%fs", lagResult.Key, lagResult.Lag.Seconds()), base.NoThrottleReasonHint } } // Got here? No metrics indicates we need throttling. return false, "", base.NoThrottleReasonHint } // parseChangelogHeartbeat parses a string timestamp and deduces replication lag func parseChangelogHeartbeat(heartbeatValue string) (lag time.Duration, err error) { heartbeatTime, err := time.Parse(time.RFC3339Nano, heartbeatValue) if err != nil { return lag, err } lag = time.Since(heartbeatTime) return lag, nil } // parseChangelogHeartbeat parses a string timestamp and deduces replication lag func (this *Throttler) parseChangelogHeartbeat(heartbeatValue string) (err error) { if lag, err := parseChangelogHeartbeat(heartbeatValue); err != nil { return this.migrationContext.Log.Errore(err) } else { atomic.StoreInt64(&this.migrationContext.CurrentLag, int64(lag)) return nil } } // collectReplicationLag reads the latest changelog heartbeat value func (this *Throttler) collectReplicationLag(firstThrottlingCollected chan<- bool) { collectFunc := func() error { if atomic.LoadInt64(&this.migrationContext.CleanupImminentFlag) > 0 { return nil } if atomic.LoadInt64(&this.migrationContext.HibernateUntil) > 0 { return nil } if this.migrationContext.TestOnReplica || this.migrationContext.MigrateOnReplica { // when running on replica, the heartbeat injection is also done on the replica. // This means we will always get a good heartbeat value. // When running on replica, we should instead check the `SHOW SLAVE STATUS` output. if lag, err := mysql.GetReplicationLagFromSlaveStatus(this.inspector.dbVersion, this.inspector.informationSchemaDb); err != nil { return this.migrationContext.Log.Errore(err) } else { atomic.StoreInt64(&this.migrationContext.CurrentLag, int64(lag)) } } else { if heartbeatValue, err := this.inspector.readChangelogState("heartbeat"); err != nil { return this.migrationContext.Log.Errore(err) } else { this.parseChangelogHeartbeat(heartbeatValue) } } return nil } collectFunc() firstThrottlingCollected <- true ticker := time.NewTicker(time.Duration(this.migrationContext.HeartbeatIntervalMilliseconds) * time.Millisecond) defer ticker.Stop() for range ticker.C { if atomic.LoadInt64(&this.finishedMigrating) > 0 { return } go collectFunc() } } // collectControlReplicasLag polls all the control replicas to get maximum lag value func (this *Throttler) collectControlReplicasLag() { if atomic.LoadInt64(&this.migrationContext.HibernateUntil) > 0 { return } replicationLagQuery := fmt.Sprintf(` select value from %s.%s where hint = 'heartbeat' and id <= 255 `, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.GetChangelogTableName()), ) readReplicaLag := func(connectionConfig *mysql.ConnectionConfig) (lag time.Duration, err error) { dbUri := connectionConfig.GetDBUri("information_schema") var heartbeatValue string db, _, err := mysql.GetDB(this.migrationContext.Uuid, dbUri) if err != nil { return lag, err } if err := db.QueryRow(replicationLagQuery).Scan(&heartbeatValue); err != nil { return lag, err } lag, err = parseChangelogHeartbeat(heartbeatValue) return lag, err } readControlReplicasLag := func() (result *mysql.ReplicationLagResult) { instanceKeyMap := this.migrationContext.GetThrottleControlReplicaKeys() if instanceKeyMap.Len() == 0 { return result } lagResults := make(chan *mysql.ReplicationLagResult, instanceKeyMap.Len()) for replicaKey := range *instanceKeyMap { connectionConfig := this.migrationContext.InspectorConnectionConfig.DuplicateCredentials(replicaKey) if err := connectionConfig.RegisterTLSConfig(); err != nil { return &mysql.ReplicationLagResult{Err: err} } lagResult := &mysql.ReplicationLagResult{Key: connectionConfig.Key} go func() { lagResult.Lag, lagResult.Err = readReplicaLag(connectionConfig) lagResults <- lagResult }() } for range *instanceKeyMap { lagResult := <-lagResults if result == nil { result = lagResult } else if lagResult.Err != nil { result = lagResult } else if lagResult.Lag.Nanoseconds() > result.Lag.Nanoseconds() { result = lagResult } } return result } checkControlReplicasLag := func() { if (this.migrationContext.TestOnReplica || this.migrationContext.MigrateOnReplica) && (atomic.LoadInt64(&this.migrationContext.AllEventsUpToLockProcessedInjectedFlag) > 0) { // No need to read lag return } this.migrationContext.SetControlReplicasLagResult(readControlReplicasLag()) } relaxedFactor := 10 counter := 0 shouldReadLagAggressively := false ticker := time.NewTicker(100 * time.Millisecond) defer ticker.Stop() for range ticker.C { if atomic.LoadInt64(&this.finishedMigrating) > 0 { return } if counter%relaxedFactor == 0 { // we only check if we wish to be aggressive once per second. The parameters for being aggressive // do not typically change at all throughout the migration, but nonetheless we check them. counter = 0 maxLagMillisecondsThrottleThreshold := atomic.LoadInt64(&this.migrationContext.MaxLagMillisecondsThrottleThreshold) shouldReadLagAggressively = (maxLagMillisecondsThrottleThreshold < 1000) } if counter == 0 || shouldReadLagAggressively { // We check replication lag every so often, or if we wish to be aggressive checkControlReplicasLag() } counter++ } } func (this *Throttler) criticalLoadIsMet() (met bool, variableName string, value int64, threshold int64, err error) { criticalLoad := this.migrationContext.GetCriticalLoad() for variableName, threshold = range criticalLoad { value, err = this.applier.ShowStatusVariable(variableName) if err != nil { return false, variableName, value, threshold, err } if value >= threshold { return true, variableName, value, threshold, nil } } return false, variableName, value, threshold, nil } // collectThrottleHTTPStatus reads the latest changelog heartbeat value func (this *Throttler) collectThrottleHTTPStatus(firstThrottlingCollected chan<- bool) { collectFunc := func() (sleep bool, err error) { if atomic.LoadInt64(&this.migrationContext.HibernateUntil) > 0 { return true, nil } url := this.migrationContext.GetThrottleHTTP() if url == "" { return true, nil } ctx, cancel := context.WithTimeout(context.Background(), this.httpClientTimeout) defer cancel() req, err := http.NewRequestWithContext(ctx, http.MethodHead, url, nil) if err != nil { return false, err } req.Header.Set("User-Agent", fmt.Sprintf("gh-ost/%s", this.appVersion)) resp, err := this.httpClient.Do(req) if err != nil { return false, err } defer resp.Body.Close() atomic.StoreInt64(&this.migrationContext.ThrottleHTTPStatusCode, int64(resp.StatusCode)) return false, nil } _, err := collectFunc() if err != nil { // If not told to ignore errors, we'll throttle on HTTP connection issues if !this.migrationContext.IgnoreHTTPErrors { atomic.StoreInt64(&this.migrationContext.ThrottleHTTPStatusCode, int64(-1)) } } firstThrottlingCollected <- true collectInterval := time.Duration(this.migrationContext.ThrottleHTTPIntervalMillis) * time.Millisecond ticker := time.NewTicker(collectInterval) defer ticker.Stop() for range ticker.C { if atomic.LoadInt64(&this.finishedMigrating) > 0 { return } sleep, err := collectFunc() if err != nil { // If not told to ignore errors, we'll throttle on HTTP connection issues if !this.migrationContext.IgnoreHTTPErrors { atomic.StoreInt64(&this.migrationContext.ThrottleHTTPStatusCode, int64(-1)) } } if sleep { time.Sleep(1 * time.Second) } } } // collectGeneralThrottleMetrics reads the once-per-sec metrics, and stores them onto this.migrationContext func (this *Throttler) collectGeneralThrottleMetrics() error { if atomic.LoadInt64(&this.migrationContext.HibernateUntil) > 0 { return nil } setThrottle := func(throttle bool, reason string, reasonHint base.ThrottleReasonHint) error { this.migrationContext.SetThrottleGeneralCheckResult(base.NewThrottleCheckResult(throttle, reason, reasonHint)) return nil } // Regardless of throttle, we take opportunity to check for panic-abort if this.migrationContext.PanicFlagFile != "" { if base.FileExists(this.migrationContext.PanicFlagFile) { // Use helper to prevent deadlock if listenOnPanicAbort already exited _ = base.SendWithContext(this.migrationContext.GetContext(), this.migrationContext.PanicAbort, fmt.Errorf("Found panic-file %s. Aborting without cleanup", this.migrationContext.PanicFlagFile)) return nil } } criticalLoadMet, variableName, value, threshold, err := this.criticalLoadIsMet() if err != nil { return setThrottle(true, fmt.Sprintf("%s %s", variableName, err), base.NoThrottleReasonHint) } if criticalLoadMet && this.migrationContext.CriticalLoadHibernateSeconds > 0 { hibernateDuration := time.Duration(this.migrationContext.CriticalLoadHibernateSeconds) * time.Second hibernateUntilTime := time.Now().Add(hibernateDuration) atomic.StoreInt64(&this.migrationContext.HibernateUntil, hibernateUntilTime.UnixNano()) this.migrationContext.Log.Errorf("critical-load met: %s=%d, >=%d. Will hibernate for the duration of %+v, until %+v", variableName, value, threshold, hibernateDuration, hibernateUntilTime) go func() { time.Sleep(hibernateDuration) this.migrationContext.SetThrottleGeneralCheckResult(base.NewThrottleCheckResult(true, "leaving hibernation", base.LeavingHibernationThrottleReasonHint)) atomic.StoreInt64(&this.migrationContext.HibernateUntil, 0) }() return nil } if criticalLoadMet && this.migrationContext.CriticalLoadIntervalMilliseconds == 0 { // Use helper to prevent deadlock if listenOnPanicAbort already exited _ = base.SendWithContext(this.migrationContext.GetContext(), this.migrationContext.PanicAbort, fmt.Errorf("critical-load met: %s=%d, >=%d", variableName, value, threshold)) return nil } if criticalLoadMet && this.migrationContext.CriticalLoadIntervalMilliseconds > 0 { this.migrationContext.Log.Errorf("critical-load met once: %s=%d, >=%d. Will check again in %d millis", variableName, value, threshold, this.migrationContext.CriticalLoadIntervalMilliseconds) go func() { timer := time.NewTimer(time.Millisecond * time.Duration(this.migrationContext.CriticalLoadIntervalMilliseconds)) <-timer.C if criticalLoadMetAgain, variableName, value, threshold, _ := this.criticalLoadIsMet(); criticalLoadMetAgain { // Use helper to prevent deadlock if listenOnPanicAbort already exited _ = base.SendWithContext(this.migrationContext.GetContext(), this.migrationContext.PanicAbort, fmt.Errorf("critical-load met again after %d millis: %s=%d, >=%d", this.migrationContext.CriticalLoadIntervalMilliseconds, variableName, value, threshold)) } }() } // Back to throttle considerations // User-based throttle if atomic.LoadInt64(&this.migrationContext.ThrottleCommandedByUser) > 0 { return setThrottle(true, "commanded by user", base.UserCommandThrottleReasonHint) } if this.migrationContext.ThrottleFlagFile != "" { if base.FileExists(this.migrationContext.ThrottleFlagFile) { // Throttle file defined and exists! return setThrottle(true, "flag-file", base.NoThrottleReasonHint) } } if this.migrationContext.ThrottleAdditionalFlagFile != "" { if base.FileExists(this.migrationContext.ThrottleAdditionalFlagFile) { // 2nd Throttle file defined and exists! return setThrottle(true, "flag-file", base.NoThrottleReasonHint) } } maxLoad := this.migrationContext.GetMaxLoad() for variableName, threshold := range maxLoad { value, err := this.applier.ShowStatusVariable(variableName) if err != nil { return setThrottle(true, fmt.Sprintf("%s %s", variableName, err), base.NoThrottleReasonHint) } if value >= threshold { return setThrottle(true, fmt.Sprintf("max-load %s=%d >= %d", variableName, value, threshold), base.NoThrottleReasonHint) } } if this.migrationContext.GetThrottleQuery() != "" { if res, _ := this.applier.ExecuteThrottleQuery(); res > 0 { return setThrottle(true, "throttle-query", base.NoThrottleReasonHint) } } return setThrottle(false, "", base.NoThrottleReasonHint) } // initiateThrottlerCollection initiates the various processes that collect measurements // that may affect throttling. There are several components, all running independently, // that collect such metrics. func (this *Throttler) initiateThrottlerCollection(firstThrottlingCollected chan<- bool) { go this.collectReplicationLag(firstThrottlingCollected) go this.collectControlReplicasLag() go this.collectThrottleHTTPStatus(firstThrottlingCollected) go func() { this.collectGeneralThrottleMetrics() firstThrottlingCollected <- true ticker := time.NewTicker(time.Second) defer ticker.Stop() for range ticker.C { if atomic.LoadInt64(&this.finishedMigrating) > 0 { return } this.collectGeneralThrottleMetrics() } }() } // initiateThrottlerChecks initiates the throttle ticker and sets the basic behavior of throttling. func (this *Throttler) initiateThrottlerChecks() { throttlerFunction := func() { alreadyThrottling, currentReason, _ := this.migrationContext.IsThrottled() shouldThrottle, throttleReason, throttleReasonHint := this.shouldThrottle() if shouldThrottle && !alreadyThrottling { // New throttling this.applier.WriteAndLogChangelog("throttle", throttleReason) } else if shouldThrottle && alreadyThrottling && (currentReason != throttleReason) { // Change of reason this.applier.WriteAndLogChangelog("throttle", throttleReason) } else if alreadyThrottling && !shouldThrottle { // End of throttling this.applier.WriteAndLogChangelog("throttle", "done throttling") } this.migrationContext.SetThrottled(shouldThrottle, throttleReason, throttleReasonHint) } throttlerFunction() ticker := time.NewTicker(100 * time.Millisecond) defer ticker.Stop() for { // Check for context cancellation each iteration ctx := this.migrationContext.GetContext() select { case <-ctx.Done(): return case <-ticker.C: // Process throttle check } if atomic.LoadInt64(&this.finishedMigrating) > 0 { return } throttlerFunction() } } // throttle sees if throttling needs take place, and if so, continuously sleeps (blocks) // until throttling reasons are gone func (this *Throttler) throttle(onThrottled func()) { for { // IsThrottled() is non-blocking; the throttling decision making takes place asynchronously. // Therefore calling IsThrottled() is cheap if shouldThrottle, _, _ := this.migrationContext.IsThrottled(); !shouldThrottle { return } if onThrottled != nil { onThrottled() } time.Sleep(250 * time.Millisecond) } } func (this *Throttler) Teardown() { this.migrationContext.Log.Debugf("Tearing down...") atomic.StoreInt64(&this.finishedMigrating, 1) } ================================================ FILE: go/mysql/binlog.go ================================================ /* Copyright 2015 Shlomi Noach, courtesy Booking.com Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package mysql type BinlogCoordinates interface { String() string DisplayString() string IsEmpty() bool Equals(other BinlogCoordinates) bool SmallerThan(other BinlogCoordinates) bool SmallerThanOrEquals(other BinlogCoordinates) bool Clone() BinlogCoordinates } ================================================ FILE: go/mysql/binlog_file.go ================================================ /* Copyright 2015 Shlomi Noach, courtesy Booking.com Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package mysql import ( "errors" "fmt" "regexp" "strconv" "strings" ) var detachPattern *regexp.Regexp func init() { detachPattern, _ = regexp.Compile(`//([^/:]+):([\d]+)`) // e.g. `//binlog.01234:567890` } // FileBinlogCoordinates described binary log coordinates in the form of a binlog file & log position. type FileBinlogCoordinates struct { LogFile string LogPos int64 EventSize int64 } func NewFileBinlogCoordinates(logFile string, logPos int64) *FileBinlogCoordinates { return &FileBinlogCoordinates{ LogFile: logFile, LogPos: logPos, } } // ParseFileBinlogCoordinates parses a log file/position string into a *BinlogCoordinates struct. func ParseFileBinlogCoordinates(logFileLogPos string) (*FileBinlogCoordinates, error) { tokens := strings.SplitN(logFileLogPos, ":", 2) if len(tokens) != 2 { return nil, fmt.Errorf("ParseFileBinlogCoordinates: Cannot parse BinlogCoordinates from %s. Expected format is file:pos", logFileLogPos) } if logPos, err := strconv.ParseInt(tokens[1], 10, 0); err != nil { return nil, fmt.Errorf("ParseFileBinlogCoordinates: invalid pos: %s", tokens[1]) } else { return &FileBinlogCoordinates{LogFile: tokens[0], LogPos: logPos}, nil } } // DisplayString returns a user-friendly string representation of these coordinates func (this *FileBinlogCoordinates) DisplayString() string { return fmt.Sprintf("%s:%d", this.LogFile, this.LogPos) } // String returns a user-friendly string representation of these coordinates func (this FileBinlogCoordinates) String() string { return this.DisplayString() } // Equals tests equality of this coordinate and another one. func (this *FileBinlogCoordinates) Equals(other BinlogCoordinates) bool { coord, ok := other.(*FileBinlogCoordinates) if !ok || other == nil { return false } return this.LogFile == coord.LogFile && this.LogPos == coord.LogPos } // IsEmpty returns true if the log file is empty, unnamed func (this *FileBinlogCoordinates) IsEmpty() bool { return this.LogFile == "" } // SmallerThan returns true if this coordinate is strictly smaller than the other. func (this *FileBinlogCoordinates) SmallerThan(other BinlogCoordinates) bool { coord, ok := other.(*FileBinlogCoordinates) if !ok || other == nil { return false } fileNumberDist := this.FileNumberDistance(coord) if fileNumberDist == 0 { return this.LogPos < coord.LogPos } return fileNumberDist > 0 } // SmallerThanOrEquals returns true if this coordinate is the same or equal to the other one. // We do NOT compare the type so we can not use this.Equals() func (this *FileBinlogCoordinates) SmallerThanOrEquals(other BinlogCoordinates) bool { coord, ok := other.(*FileBinlogCoordinates) if !ok || other == nil { return false } if this.SmallerThan(other) { return true } return this.LogFile == coord.LogFile && this.LogPos == coord.LogPos // No Type comparison } // FileNumberDistance returns the numeric distance between this coordinate's file number and the other's. // Effectively it means "how many rotates/FLUSHes would make these coordinates's file reach the other's" func (this *FileBinlogCoordinates) FileNumberDistance(other *FileBinlogCoordinates) int { thisNumber, _ := this.FileNumber() otherNumber, _ := other.FileNumber() return otherNumber - thisNumber } // FileNumber returns the numeric value of the file, and the length in characters representing the number in the filename. // Example: FileNumber() of mysqld.log.000789 is (789, 6) func (this *FileBinlogCoordinates) FileNumber() (int, int) { tokens := strings.Split(this.LogFile, ".") numPart := tokens[len(tokens)-1] numLen := len(numPart) fileNum, err := strconv.Atoi(numPart) if err != nil { return 0, 0 } return fileNum, numLen } // PreviousFileCoordinatesBy guesses the filename of the previous binlog/relaylog, by given offset (number of files back) func (this *FileBinlogCoordinates) PreviousFileCoordinatesBy(offset int) (BinlogCoordinates, error) { result := &FileBinlogCoordinates{} fileNum, numLen := this.FileNumber() if fileNum == 0 { return result, errors.New("Log file number is zero, cannot detect previous file") } newNumStr := fmt.Sprintf("%d", (fileNum - offset)) newNumStr = strings.Repeat("0", numLen-len(newNumStr)) + newNumStr tokens := strings.Split(this.LogFile, ".") tokens[len(tokens)-1] = newNumStr result.LogFile = strings.Join(tokens, ".") return result, nil } // PreviousFileCoordinates guesses the filename of the previous binlog/relaylog func (this *FileBinlogCoordinates) PreviousFileCoordinates() (BinlogCoordinates, error) { return this.PreviousFileCoordinatesBy(1) } // PreviousFileCoordinates guesses the filename of the previous binlog/relaylog func (this *FileBinlogCoordinates) NextFileCoordinates() (BinlogCoordinates, error) { result := &FileBinlogCoordinates{} fileNum, numLen := this.FileNumber() newNumStr := fmt.Sprintf("%d", (fileNum + 1)) newNumStr = strings.Repeat("0", numLen-len(newNumStr)) + newNumStr tokens := strings.Split(this.LogFile, ".") tokens[len(tokens)-1] = newNumStr result.LogFile = strings.Join(tokens, ".") return result, nil } // FileSmallerThan returns true if this coordinate's file is strictly smaller than the other's. func (this *FileBinlogCoordinates) DetachedCoordinates() (isDetached bool, detachedLogFile string, detachedLogPos string) { detachedCoordinatesSubmatch := detachPattern.FindStringSubmatch(this.LogFile) if len(detachedCoordinatesSubmatch) == 0 { return false, "", "" } return true, detachedCoordinatesSubmatch[1], detachedCoordinatesSubmatch[2] } func (this *FileBinlogCoordinates) Clone() BinlogCoordinates { return &FileBinlogCoordinates{ LogPos: this.LogPos, LogFile: this.LogFile, EventSize: this.EventSize, } } // IsLogPosOverflowBeyond4Bytes returns true if the coordinate endpos is overflow beyond 4 bytes. // The binlog event end_log_pos field type is defined as uint32, 4 bytes. // https://github.com/go-mysql-org/go-mysql/blob/master/replication/event.go // https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_replication_binlog_event.html#sect_protocol_replication_binlog_event_header // Issue: https://github.com/github/gh-ost/issues/1366 func (this *FileBinlogCoordinates) IsLogPosOverflowBeyond4Bytes(preCoordinate *FileBinlogCoordinates) bool { if preCoordinate == nil { return false } if preCoordinate.IsEmpty() { return false } if this.LogFile != preCoordinate.LogFile { return false } if preCoordinate.LogPos+this.EventSize >= 1<<32 { // Unexpected rows event, the previous binlog log_pos + current binlog event_size is overflow 4 bytes return true } return false } ================================================ FILE: go/mysql/binlog_file_test.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package mysql import ( "math" "testing" gomysql "github.com/go-mysql-org/go-mysql/mysql" "github.com/openark/golib/log" "github.com/stretchr/testify/require" ) func init() { log.SetLevel(log.ERROR) } func TestBinlogCoordinates(t *testing.T) { c1 := FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: 104} c2 := FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: 104} c3 := FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: 5000} c4 := FileBinlogCoordinates{LogFile: "mysql-bin.00112", LogPos: 104} gtidSet1, _ := gomysql.ParseMysqlGTIDSet("3E11FA47-71CA-11E1-9E33-C80AA9429562:23") gtidSet2, _ := gomysql.ParseMysqlGTIDSet("3E11FA47-71CA-11E1-9E33-C80AA9429562:100") gtidSet3, _ := gomysql.ParseMysqlGTIDSet("7F80FA47-FF33-71A1-AE01-B80CC7823548:100") gtidSetBig1, _ := gomysql.ParseMysqlGTIDSet(`08dc06d7-c27c-11ea-b204-e4434b77a5ce:1-1497873603, 0b4ff540-a712-11ea-9857-e4434b2a1c98:1-4315312982, 19636248-246d-11e9-ab0d-0263df733a8e:1, 1c8cd5dd-8c79-11eb-ae94-e4434b27ee9c:1-18850436, 3342d1ad-bda0-11ea-ba96-e4434b28e6e0:1-475232304, 3bcd300c-c811-11e9-9970-e4434b714c24:1-6209943929, 418b92ed-d6f6-11e8-b18f-246e961e5ed0:1-3299395227, 4465ebe1-2bcc-11e9-8913-e4434b21c560:1-4724945648, 48e2bc1d-d66d-11e8-bf56-a0369f9437b8:1, 492e2980-4518-11e9-92c6-e4434b3eca94:1-4926754392`) gtidSetBig2, _ := gomysql.ParseMysqlGTIDSet(`08dc06d7-c27c-11ea-b204-e4434b77a5ce:1-1497873603, 0b4ff540-a712-11ea-9857-e4434b2a1c98:1-4315312982, 19636248-246d-11e9-ab0d-0263df733a8e:1, 1c8cd5dd-8c79-11eb-ae94-e4434b27ee9c:1-18850436, 3342d1ad-bda0-11ea-ba96-e4434b28e6e0:1-475232304, 3bcd300c-c811-11e9-9970-e4434b714c24:1-6209943929, 418b92ed-d6f6-11e8-b18f-246e961e5ed0:1-3299395227, 4465ebe1-2bcc-11e9-8913-e4434b21c560:1-4724945648, 48e2bc1d-d66d-11e8-bf56-a0369f9437b8:1, 492e2980-4518-11e9-92c6-e4434b3eca94:1-4926754399`) c5 := GTIDBinlogCoordinates{GTIDSet: gtidSet1.(*gomysql.MysqlGTIDSet)} c6 := GTIDBinlogCoordinates{GTIDSet: gtidSet1.(*gomysql.MysqlGTIDSet)} c7 := GTIDBinlogCoordinates{GTIDSet: gtidSet2.(*gomysql.MysqlGTIDSet)} c8 := GTIDBinlogCoordinates{GTIDSet: gtidSet3.(*gomysql.MysqlGTIDSet)} c9 := GTIDBinlogCoordinates{GTIDSet: gtidSetBig1.(*gomysql.MysqlGTIDSet)} c10 := GTIDBinlogCoordinates{GTIDSet: gtidSetBig2.(*gomysql.MysqlGTIDSet)} require.True(t, c5.Equals(&c6)) require.True(t, c1.Equals(&c2)) require.False(t, c1.Equals(&c3)) require.False(t, c1.Equals(&c4)) require.False(t, c1.SmallerThan(&c2)) require.True(t, c1.SmallerThan(&c3)) require.True(t, c1.SmallerThan(&c4)) require.True(t, c3.SmallerThan(&c4)) require.False(t, c3.SmallerThan(&c2)) require.False(t, c4.SmallerThan(&c2)) require.False(t, c4.SmallerThan(&c3)) require.True(t, c1.SmallerThanOrEquals(&c2)) require.True(t, c1.SmallerThanOrEquals(&c3)) require.True(t, c1.SmallerThanOrEquals(&c2)) require.True(t, c1.SmallerThanOrEquals(&c3)) require.True(t, c6.SmallerThanOrEquals(&c7)) require.True(t, c7.SmallerThanOrEquals(&c8)) require.True(t, c9.SmallerThanOrEquals(&c9)) require.True(t, c9.SmallerThanOrEquals(&c10)) } func TestBinlogCoordinatesAsKey(t *testing.T) { m := make(map[BinlogCoordinates]bool) c1 := &FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: 104} c2 := &FileBinlogCoordinates{LogFile: "mysql-bin.00022", LogPos: 104} c3 := &FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: 104} c4 := &FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: 222} m[c1] = true m[c2] = true m[c3] = true m[c4] = true require.Len(t, m, 4) } func TestIsLogPosOverflowBeyond4Bytes(t *testing.T) { { var preCoordinates *FileBinlogCoordinates curCoordinates := &FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: 10321, EventSize: 1100} require.False(t, curCoordinates.IsLogPosOverflowBeyond4Bytes(preCoordinates)) } { preCoordinates := &FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: 1100, EventSize: 1100} curCoordinates := &FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: int64(uint32(preCoordinates.LogPos + 1100)), EventSize: 1100} require.False(t, curCoordinates.IsLogPosOverflowBeyond4Bytes(preCoordinates)) } { preCoordinates := &FileBinlogCoordinates{LogFile: "mysql-bin.00016", LogPos: 1100, EventSize: 1100} curCoordinates := &FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: int64(uint32(preCoordinates.LogPos + 1100)), EventSize: 1100} require.False(t, curCoordinates.IsLogPosOverflowBeyond4Bytes(preCoordinates)) } { preCoordinates := &FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: math.MaxUint32 - 1001, EventSize: 1000} curCoordinates := &FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: int64(uint32(preCoordinates.LogPos + 1000)), EventSize: 1000} require.False(t, curCoordinates.IsLogPosOverflowBeyond4Bytes(preCoordinates)) } { preCoordinates := &FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: math.MaxUint32 - 1000, EventSize: 1000} curCoordinates := &FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: int64(uint32(preCoordinates.LogPos + 1000)), EventSize: 1000} require.False(t, curCoordinates.IsLogPosOverflowBeyond4Bytes(preCoordinates)) } { preCoordinates := &FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: math.MaxUint32 - 999, EventSize: 1000} curCoordinates := &FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: int64(uint32(preCoordinates.LogPos + 1000)), EventSize: 1000} require.True(t, curCoordinates.IsLogPosOverflowBeyond4Bytes(preCoordinates)) } { preCoordinates := &FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: int64(uint32(math.MaxUint32 - 500)), EventSize: 1000} curCoordinates := &FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: int64(uint32(preCoordinates.LogPos + 1000)), EventSize: 1000} require.True(t, curCoordinates.IsLogPosOverflowBeyond4Bytes(preCoordinates)) } { preCoordinates := &FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: math.MaxUint32, EventSize: 1000} curCoordinates := &FileBinlogCoordinates{LogFile: "mysql-bin.00017", LogPos: int64(uint32(preCoordinates.LogPos + 1000)), EventSize: 1000} require.True(t, curCoordinates.IsLogPosOverflowBeyond4Bytes(preCoordinates)) } } func TestBinlogCoordinates_LogFileZeroPaddedTransition(t *testing.T) { c1 := FileBinlogCoordinates{LogFile: "mysql-bin.999999", LogPos: 100} c2 := FileBinlogCoordinates{LogFile: "mysql-bin.1000000", LogPos: 100} require.True(t, c1.SmallerThan(&c2)) } func TestBinlogCoordinates_SameLogFileDifferentPosition(t *testing.T) { c1 := FileBinlogCoordinates{LogFile: "binlog.000001", LogPos: 100} c2 := FileBinlogCoordinates{LogFile: "binlog.000001", LogPos: 200} require.True(t, c1.SmallerThan(&c2)) require.False(t, c2.SmallerThan(&c1)) require.False(t, c1.SmallerThan(&c1)) } ================================================ FILE: go/mysql/binlog_gtid.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package mysql import ( gomysql "github.com/go-mysql-org/go-mysql/mysql" ) // GTIDBinlogCoordinates describe binary log coordinates in MySQL GTID format. type GTIDBinlogCoordinates struct { GTIDSet *gomysql.MysqlGTIDSet UUIDSet *gomysql.UUIDSet } // NewGTIDBinlogCoordinates parses a MySQL GTID set into a *GTIDBinlogCoordinates struct. func NewGTIDBinlogCoordinates(gtidSet string) (*GTIDBinlogCoordinates, error) { set, err := gomysql.ParseMysqlGTIDSet(gtidSet) return >IDBinlogCoordinates{ GTIDSet: set.(*gomysql.MysqlGTIDSet), }, err } // DisplayString returns a user-friendly string representation of these current UUID set or the full GTID set. func (this *GTIDBinlogCoordinates) DisplayString() string { if this.UUIDSet != nil { return this.UUIDSet.String() } return this.String() } // String returns a user-friendly string representation of these full GTID set. func (this GTIDBinlogCoordinates) String() string { return this.GTIDSet.String() } // Equals tests equality of this coordinate and another one. func (this *GTIDBinlogCoordinates) Equals(other BinlogCoordinates) bool { if other == nil || this.IsEmpty() || other.IsEmpty() { return false } otherCoords, ok := other.(*GTIDBinlogCoordinates) if !ok { return false } return this.GTIDSet.Equal(otherCoords.GTIDSet) } // IsEmpty returns true if the GTID set is empty. func (this *GTIDBinlogCoordinates) IsEmpty() bool { return this.GTIDSet == nil } // SmallerThan returns true if this coordinate is strictly smaller than the other. func (this *GTIDBinlogCoordinates) SmallerThan(other BinlogCoordinates) bool { if other == nil || this.IsEmpty() || other.IsEmpty() { return false } otherCoords, ok := other.(*GTIDBinlogCoordinates) if !ok { return false } // if 'this' does not contain the same sets we assume we are behind 'other'. // there are probably edge cases where this isn't true return !this.GTIDSet.Contain(otherCoords.GTIDSet) } // SmallerThanOrEquals returns true if this coordinate is the same or equal to the other one. func (this *GTIDBinlogCoordinates) SmallerThanOrEquals(other BinlogCoordinates) bool { return this.Equals(other) || this.SmallerThan(other) } func (this *GTIDBinlogCoordinates) Clone() BinlogCoordinates { out := >IDBinlogCoordinates{} if this.GTIDSet != nil { out.GTIDSet = this.GTIDSet.Clone().(*gomysql.MysqlGTIDSet) } if this.UUIDSet != nil { out.UUIDSet = this.UUIDSet.Clone() } return out } ================================================ FILE: go/mysql/connection.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package mysql import ( "crypto/tls" "crypto/x509" "errors" "fmt" "net" "os" "strings" "github.com/go-sql-driver/mysql" ) const ( TLS_CONFIG_KEY = "ghost" ) // ConnectionConfig is the minimal configuration required to connect to a MySQL server type ConnectionConfig struct { Key InstanceKey User string Password string ImpliedKey *InstanceKey tlsConfig *tls.Config Timeout float64 TransactionIsolation string Charset string } func NewConnectionConfig() *ConnectionConfig { config := &ConnectionConfig{ Key: InstanceKey{}, } config.ImpliedKey = &config.Key return config } // DuplicateCredentials creates a new connection config with given key and with same credentials as this config func (this *ConnectionConfig) DuplicateCredentials(key InstanceKey) *ConnectionConfig { config := &ConnectionConfig{ Key: key, User: this.User, Password: this.Password, tlsConfig: this.tlsConfig, Timeout: this.Timeout, TransactionIsolation: this.TransactionIsolation, Charset: this.Charset, } if this.tlsConfig != nil { config.tlsConfig = &tls.Config{ ServerName: key.Hostname, Certificates: this.tlsConfig.Certificates, RootCAs: this.tlsConfig.RootCAs, InsecureSkipVerify: this.tlsConfig.InsecureSkipVerify, } } config.ImpliedKey = &config.Key return config } func (this *ConnectionConfig) Duplicate() *ConnectionConfig { return this.DuplicateCredentials(this.Key) } func (this *ConnectionConfig) String() string { return fmt.Sprintf("%s, user=%s, usingTLS=%t", this.Key.DisplayString(), this.User, this.tlsConfig != nil) } func (this *ConnectionConfig) Equals(other *ConnectionConfig) bool { return this.Key.Equals(&other.Key) || this.ImpliedKey.Equals(other.ImpliedKey) } func (this *ConnectionConfig) UseTLS(caCertificatePath, clientCertificate, clientKey string, allowInsecure bool) error { var rootCertPool *x509.CertPool var certs []tls.Certificate var err error if caCertificatePath == "" { rootCertPool, err = x509.SystemCertPool() if err != nil { return err } } else { rootCertPool = x509.NewCertPool() pem, err := os.ReadFile(caCertificatePath) if err != nil { return err } if ok := rootCertPool.AppendCertsFromPEM(pem); !ok { return errors.New("could not add ca certificate to cert pool") } } if clientCertificate != "" || clientKey != "" { cert, err := tls.LoadX509KeyPair(clientCertificate, clientKey) if err != nil { return err } certs = []tls.Certificate{cert} } this.tlsConfig = &tls.Config{ ServerName: this.Key.Hostname, Certificates: certs, RootCAs: rootCertPool, InsecureSkipVerify: allowInsecure, } return this.RegisterTLSConfig() } func (this *ConnectionConfig) RegisterTLSConfig() error { if this.tlsConfig == nil { return nil } if this.tlsConfig.ServerName == "" { return errors.New("tlsConfig.ServerName cannot be empty") } var tlsOption = GetDBTLSConfigKey(this.tlsConfig.ServerName) return mysql.RegisterTLSConfig(tlsOption, this.tlsConfig) } func (this *ConnectionConfig) TLSConfig() *tls.Config { return this.tlsConfig } func (this *ConnectionConfig) GetDBUri(databaseName string) string { hostname := this.Key.Hostname var ip = net.ParseIP(hostname) if (ip != nil) && (ip.To4() == nil) { // Wrap IPv6 literals in square brackets hostname = fmt.Sprintf("[%s]", hostname) } // go-mysql-driver defaults to false if tls param is not provided; explicitly setting here to // simplify construction of the DSN below. tlsOption := "false" if this.tlsConfig != nil { tlsOption = GetDBTLSConfigKey(this.tlsConfig.ServerName) } if this.Charset == "" { this.Charset = "utf8mb4,utf8,latin1" } connectionParams := []string{ "autocommit=true", "interpolateParams=true", fmt.Sprintf("charset=%s", this.Charset), fmt.Sprintf("tls=%s", tlsOption), fmt.Sprintf("transaction_isolation=%q", this.TransactionIsolation), fmt.Sprintf("timeout=%fs", this.Timeout), fmt.Sprintf("readTimeout=%fs", this.Timeout), fmt.Sprintf("writeTimeout=%fs", this.Timeout), } return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?%s", this.User, this.Password, hostname, this.Key.Port, databaseName, strings.Join(connectionParams, "&")) } func GetDBTLSConfigKey(tlsServerName string) string { return fmt.Sprintf("%s-%s", TLS_CONFIG_KEY, tlsServerName) } ================================================ FILE: go/mysql/connection_test.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package mysql import ( "crypto/tls" "testing" "github.com/openark/golib/log" "github.com/stretchr/testify/require" ) const ( transactionIsolation = "REPEATABLE-READ" ) func init() { log.SetLevel(log.ERROR) } func TestNewConnectionConfig(t *testing.T) { c := NewConnectionConfig() require.Equal(t, "", c.Key.Hostname) require.Equal(t, 0, c.Key.Port) require.Equal(t, "", c.ImpliedKey.Hostname) require.Equal(t, 0, c.ImpliedKey.Port) require.Equal(t, "", c.User) require.Equal(t, "", c.Password) require.Equal(t, "", c.TransactionIsolation) require.Equal(t, "", c.Charset) } func TestDuplicateCredentials(t *testing.T) { c := NewConnectionConfig() c.Key = InstanceKey{Hostname: "myhost", Port: 3306} c.User = "gromit" c.Password = "penguin" c.tlsConfig = &tls.Config{ InsecureSkipVerify: true, ServerName: "feathers", } c.TransactionIsolation = transactionIsolation c.Charset = "utf8mb4" dup := c.DuplicateCredentials(InstanceKey{Hostname: "otherhost", Port: 3310}) require.Equal(t, "otherhost", dup.Key.Hostname) require.Equal(t, 3310, dup.Key.Port) require.Equal(t, "otherhost", dup.ImpliedKey.Hostname) require.Equal(t, 3310, dup.ImpliedKey.Port) require.Equal(t, "gromit", dup.User) require.Equal(t, "penguin", dup.Password) require.Equal(t, "otherhost", dup.tlsConfig.ServerName) require.Equal(t, c.tlsConfig.Certificates, dup.tlsConfig.Certificates) require.Equal(t, c.tlsConfig.RootCAs, dup.tlsConfig.RootCAs) require.Equal(t, c.tlsConfig.InsecureSkipVerify, dup.tlsConfig.InsecureSkipVerify) require.Equal(t, c.TransactionIsolation, dup.TransactionIsolation) require.Equal(t, c.Charset, dup.Charset) } func TestDuplicate(t *testing.T) { c := NewConnectionConfig() c.Key = InstanceKey{Hostname: "myhost", Port: 3306} c.User = "gromit" c.Password = "penguin" c.TransactionIsolation = transactionIsolation c.Charset = "utf8mb4" dup := c.Duplicate() require.Equal(t, "myhost", dup.Key.Hostname) require.Equal(t, 3306, dup.Key.Port) require.Equal(t, "myhost", dup.ImpliedKey.Hostname) require.Equal(t, 3306, dup.ImpliedKey.Port) require.Equal(t, "gromit", dup.User) require.Equal(t, "penguin", dup.Password) require.Equal(t, c.tlsConfig, dup.tlsConfig) require.Equal(t, transactionIsolation, dup.TransactionIsolation) require.Equal(t, "utf8mb4", dup.Charset) } func TestGetDBUri(t *testing.T) { c := NewConnectionConfig() c.Key = InstanceKey{Hostname: "myhost", Port: 3306} c.User = "gromit" c.Password = "penguin" c.Timeout = 1.2345 c.TransactionIsolation = transactionIsolation c.Charset = "utf8mb4,utf8,latin1" uri := c.GetDBUri("test") require.Equal(t, `gromit:penguin@tcp(myhost:3306)/test?autocommit=true&interpolateParams=true&charset=utf8mb4,utf8,latin1&tls=false&transaction_isolation="REPEATABLE-READ"&timeout=1.234500s&readTimeout=1.234500s&writeTimeout=1.234500s`, uri) } func TestGetDBUriWithTLSSetup(t *testing.T) { c := NewConnectionConfig() c.Key = InstanceKey{Hostname: "myhost", Port: 3306} c.User = "gromit" c.Password = "penguin" c.Timeout = 1.2345 c.tlsConfig = &tls.Config{ ServerName: c.Key.Hostname, } c.TransactionIsolation = transactionIsolation c.Charset = "utf8mb4_general_ci,utf8_general_ci,latin1" uri := c.GetDBUri("test") require.Equal(t, `gromit:penguin@tcp(myhost:3306)/test?autocommit=true&interpolateParams=true&charset=utf8mb4_general_ci,utf8_general_ci,latin1&tls=ghost-myhost&transaction_isolation="REPEATABLE-READ"&timeout=1.234500s&readTimeout=1.234500s&writeTimeout=1.234500s`, uri) } func TestGetDBTLSConfigKey(t *testing.T) { configKey := GetDBTLSConfigKey("myhost") require.Equal(t, "ghost-myhost", configKey) } ================================================ FILE: go/mysql/instance_key.go ================================================ /* Copyright 2015 Shlomi Noach, courtesy Booking.com Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package mysql import ( "fmt" "regexp" "strconv" "strings" ) const DefaultInstancePort = 3306 var ( ipv4HostPortRegexp = regexp.MustCompile("^([^:]+):([0-9]+)$") ipv4HostRegexp = regexp.MustCompile("^([^:]+)$") // e.g. [2001:db8:1f70::999:de8:7648:6e8]:3308 ipv6HostPortRegexp = regexp.MustCompile("^\\[([:0-9a-fA-F]+)\\]:([0-9]+)$") //nolint:gosimple // e.g. 2001:db8:1f70::999:de8:7648:6e8 ipv6HostRegexp = regexp.MustCompile("^([:0-9a-fA-F]+)$") ) // InstanceKey is an instance indicator, identified by hostname and port type InstanceKey struct { Hostname string Port int } const detachHint = "//" // ParseInstanceKey will parse an InstanceKey from a string representation such as 127.0.0.1:3306 func NewRawInstanceKey(hostPort string) (*InstanceKey, error) { var hostname, port string if submatch := ipv4HostPortRegexp.FindStringSubmatch(hostPort); len(submatch) > 0 { hostname = submatch[1] port = submatch[2] } else if submatch := ipv4HostRegexp.FindStringSubmatch(hostPort); len(submatch) > 0 { hostname = submatch[1] } else if submatch := ipv6HostPortRegexp.FindStringSubmatch(hostPort); len(submatch) > 0 { hostname = submatch[1] port = submatch[2] } else if submatch := ipv6HostRegexp.FindStringSubmatch(hostPort); len(submatch) > 0 { hostname = submatch[1] } else { return nil, fmt.Errorf("Cannot parse address: %s", hostPort) } instanceKey := &InstanceKey{Hostname: hostname, Port: DefaultInstancePort} if port != "" { var err error if instanceKey.Port, err = strconv.Atoi(port); err != nil { return instanceKey, fmt.Errorf("Invalid port: %s", port) } } return instanceKey, nil } // ParseInstanceKey will parse an InstanceKey from a string representation such as 127.0.0.1:3306. // The port part is optional; there will be no name resolve func ParseInstanceKey(hostPort string) (*InstanceKey, error) { return NewRawInstanceKey(hostPort) } // Equals tests equality between this key and another key func (this *InstanceKey) Equals(other *InstanceKey) bool { if other == nil { return false } return this.Hostname == other.Hostname && this.Port == other.Port } // SmallerThan returns true if this key is dictionary-smaller than another. // This is used for consistent sorting/ordering; there's nothing magical about it. func (this *InstanceKey) SmallerThan(other *InstanceKey) bool { if this.Hostname < other.Hostname { return true } if this.Hostname == other.Hostname && this.Port < other.Port { return true } return false } // IsDetached returns 'true' when this hostname is logically "detached" func (this *InstanceKey) IsDetached() bool { return strings.HasPrefix(this.Hostname, detachHint) } // IsValid uses simple heuristics to see whether this key represents an actual instance func (this *InstanceKey) IsValid() bool { if this.Hostname == "_" { return false } if this.IsDetached() { return false } return len(this.Hostname) > 0 && this.Port > 0 } // DetachedKey returns an instance key whose hostname is detached: invalid, but recoverable func (this *InstanceKey) DetachedKey() *InstanceKey { if this.IsDetached() { return this } return &InstanceKey{Hostname: fmt.Sprintf("%s%s", detachHint, this.Hostname), Port: this.Port} } // ReattachedKey returns an instance key whose hostname is detached: invalid, but recoverable func (this *InstanceKey) ReattachedKey() *InstanceKey { if !this.IsDetached() { return this } return &InstanceKey{Hostname: this.Hostname[len(detachHint):], Port: this.Port} } // StringCode returns an official string representation of this key func (this *InstanceKey) StringCode() string { return fmt.Sprintf("%s:%d", this.Hostname, this.Port) } // DisplayString returns a user-friendly string representation of this key func (this *InstanceKey) DisplayString() string { return this.StringCode() } // String returns a user-friendly string representation of this key func (this InstanceKey) String() string { return this.StringCode() } ================================================ FILE: go/mysql/instance_key_map.go ================================================ /* Copyright 2015 Shlomi Noach, courtesy Booking.com See https://github.com/github/gh-ost/blob/master/LICENSE */ package mysql import ( "encoding/json" "strings" ) // InstanceKeyMap is a convenience struct for listing InstanceKey-s type InstanceKeyMap map[InstanceKey]bool func NewInstanceKeyMap() *InstanceKeyMap { return &InstanceKeyMap{} } func (this *InstanceKeyMap) Len() int { return len(*this) } // AddKey adds a single key to this map func (this *InstanceKeyMap) AddKey(key InstanceKey) { (*this)[key] = true } // AddKeys adds all given keys to this map func (this *InstanceKeyMap) AddKeys(keys []InstanceKey) { for _, key := range keys { this.AddKey(key) } } // HasKey checks if given key is within the map func (this *InstanceKeyMap) HasKey(key InstanceKey) bool { _, ok := (*this)[key] return ok } // GetInstanceKeys returns keys in this map in the form of an array func (this *InstanceKeyMap) GetInstanceKeys() []InstanceKey { res := []InstanceKey{} for key := range *this { res = append(res, key) } return res } // MarshalJSON will marshal this map as JSON func (this *InstanceKeyMap) MarshalJSON() ([]byte, error) { return json.Marshal(this.GetInstanceKeys()) } // ToJSON will marshal this map as JSON func (this *InstanceKeyMap) ToJSON() (string, error) { bytes, err := this.MarshalJSON() return string(bytes), err } // ToJSONString will marshal this map as JSON func (this *InstanceKeyMap) ToJSONString() string { s, _ := this.ToJSON() return s } // ToCommaDelimitedList will export this map in comma delimited format func (this *InstanceKeyMap) ToCommaDelimitedList() string { keyDisplays := []string{} for key := range *this { keyDisplays = append(keyDisplays, key.DisplayString()) } return strings.Join(keyDisplays, ",") } // ReadJson unmarshalls a json into this map func (this *InstanceKeyMap) ReadJson(jsonString string) error { var keys []InstanceKey err := json.Unmarshal([]byte(jsonString), &keys) if err != nil { return err } this.AddKeys(keys) return err } // ReadJson unmarshalls a json into this map func (this *InstanceKeyMap) ReadCommaDelimitedList(list string) error { if list == "" { return nil } tokens := strings.Split(list, ",") for _, token := range tokens { key, err := ParseInstanceKey(token) if err != nil { return err } this.AddKey(*key) } return nil } ================================================ FILE: go/mysql/instance_key_test.go ================================================ /* Copyright 2016 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package mysql import ( "testing" "github.com/openark/golib/log" "github.com/stretchr/testify/require" ) func init() { log.SetLevel(log.ERROR) } func TestParseInstanceKey(t *testing.T) { { key, err := ParseInstanceKey("myhost:1234") require.NoError(t, err) require.Equal(t, "myhost", key.Hostname) require.Equal(t, 1234, key.Port) } { key, err := ParseInstanceKey("myhost") require.NoError(t, err) require.Equal(t, "myhost", key.Hostname) require.Equal(t, 3306, key.Port) } { key, err := ParseInstanceKey("10.0.0.3:3307") require.NoError(t, err) require.Equal(t, "10.0.0.3", key.Hostname) require.Equal(t, 3307, key.Port) } { key, err := ParseInstanceKey("10.0.0.3") require.NoError(t, err) require.Equal(t, "10.0.0.3", key.Hostname) require.Equal(t, 3306, key.Port) } { key, err := ParseInstanceKey("[2001:db8:1f70::999:de8:7648:6e8]:3308") require.NoError(t, err) require.Equal(t, "2001:db8:1f70::999:de8:7648:6e8", key.Hostname) require.Equal(t, 3308, key.Port) } { key, err := ParseInstanceKey("::1") require.NoError(t, err) require.Equal(t, "::1", key.Hostname) require.Equal(t, 3306, key.Port) } { key, err := ParseInstanceKey("0:0:0:0:0:0:0:0") require.NoError(t, err) require.Equal(t, "0:0:0:0:0:0:0:0", key.Hostname) require.Equal(t, 3306, key.Port) } { _, err := ParseInstanceKey("[2001:xxxx:1f70::999:de8:7648:6e8]:3308") require.Error(t, err) } { _, err := ParseInstanceKey("10.0.0.4:") require.Error(t, err) } { _, err := ParseInstanceKey("10.0.0.4:5.6.7") require.Error(t, err) } } ================================================ FILE: go/mysql/replica_terminology_map.go ================================================ package mysql import ( version "github.com/hashicorp/go-version" ) const ( MysqlVersionCutoff = "8.4" ) var MysqlReplicaTermMap = map[string]string{ "Seconds_Behind_Master": "Seconds_Behind_Source", "Master_Log_File": "Source_Log_File", "Master_Host": "Source_Host", "Master_Port": "Source_Port", "Exec_Master_Log_Pos": "Exec_Source_Log_Pos", "Read_Master_Log_Pos": "Read_Source_Log_Pos", "Relay_Master_Log_File": "Relay_Source_Log_File", "Slave_IO_Running": "Replica_IO_Running", "Slave_SQL_Running": "Replica_SQL_Running", "master status": "binary log status", "slave hosts": "replicas", "slave status": "replica status", "slave": "replica", } func ReplicaTermFor(mysqlVersion string, term string) string { vs, err := version.NewVersion(mysqlVersion) if err != nil { // default to returning the same term if we cannot determine the version return term } mysqlVersionCutoff, _ := version.NewVersion(MysqlVersionCutoff) if vs.GreaterThanOrEqual(mysqlVersionCutoff) { return MysqlReplicaTermMap[term] } return term } ================================================ FILE: go/mysql/utils.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package mysql import ( gosql "database/sql" "fmt" "strings" "sync" "time" "github.com/github/gh-ost/go/sql" "github.com/openark/golib/log" "github.com/openark/golib/sqlutils" ) const ( MaxTableNameLength = 64 MaxDBPoolConnections = 3 ) type ReplicationLagResult struct { Key InstanceKey Lag time.Duration Err error } type Trigger struct { Name string Event string Statement string Timing string } func NewNoReplicationLagResult() *ReplicationLagResult { return &ReplicationLagResult{Lag: 0, Err: nil} } func (this *ReplicationLagResult) HasLag() bool { return this.Lag > 0 } // knownDBs is a DB cache by uri var knownDBs map[string]*gosql.DB = make(map[string]*gosql.DB) var knownDBsMutex = &sync.Mutex{} func GetDB(migrationUuid string, mysql_uri string) (db *gosql.DB, exists bool, err error) { cacheKey := migrationUuid + ":" + mysql_uri knownDBsMutex.Lock() defer knownDBsMutex.Unlock() if db, exists = knownDBs[cacheKey]; !exists { db, err = gosql.Open("mysql", mysql_uri) if err != nil { return nil, false, err } db.SetMaxOpenConns(MaxDBPoolConnections) db.SetMaxIdleConns(MaxDBPoolConnections) knownDBs[cacheKey] = db } return db, exists, nil } // GetReplicationLagFromSlaveStatus returns replication lag for a given db; via SHOW SLAVE STATUS func GetReplicationLagFromSlaveStatus(dbVersion string, informationSchemaDb *gosql.DB) (replicationLag time.Duration, err error) { showReplicaStatusQuery := fmt.Sprintf("show %s", ReplicaTermFor(dbVersion, `slave status`)) err = sqlutils.QueryRowsMap(informationSchemaDb, showReplicaStatusQuery, func(m sqlutils.RowMap) error { ioRunningTerm := ReplicaTermFor(dbVersion, "Slave_IO_Running") sqlRunningTerm := ReplicaTermFor(dbVersion, "Slave_SQL_Running") slaveIORunning := m.GetString(ioRunningTerm) slaveSQLRunning := m.GetString(sqlRunningTerm) secondsBehindMaster := m.GetNullInt64(ReplicaTermFor(dbVersion, "Seconds_Behind_Master")) if !secondsBehindMaster.Valid { return fmt.Errorf("replication not running; %s=%+v, %s=%+v", ioRunningTerm, slaveIORunning, sqlRunningTerm, slaveSQLRunning) } replicationLag = time.Duration(secondsBehindMaster.Int64) * time.Second return nil }) return replicationLag, err } func GetMasterKeyFromSlaveStatus(dbVersion string, connectionConfig *ConnectionConfig) (masterKey *InstanceKey, err error) { currentUri := connectionConfig.GetDBUri("information_schema") // This function is only called once, okay to not have a cached connection pool db, err := gosql.Open("mysql", currentUri) if err != nil { return nil, err } defer db.Close() showReplicaStatusQuery := fmt.Sprintf("show %s", ReplicaTermFor(dbVersion, `slave status`)) err = sqlutils.QueryRowsMap(db, showReplicaStatusQuery, func(rowMap sqlutils.RowMap) error { // We wish to recognize the case where the topology's master actually has replication configuration. // This can happen when a DBA issues a `RESET SLAVE` instead of `RESET SLAVE ALL`. // An empty log file indicates this is a master: if rowMap.GetString(ReplicaTermFor(dbVersion, "Master_Log_File")) == "" { return nil } ioRunningTerm := ReplicaTermFor(dbVersion, "Slave_IO_Running") sqlRunningTerm := ReplicaTermFor(dbVersion, "Slave_SQL_Running") slaveIORunning := rowMap.GetString(ioRunningTerm) slaveSQLRunning := rowMap.GetString(sqlRunningTerm) if slaveIORunning != "Yes" || slaveSQLRunning != "Yes" { return fmt.Errorf("Replication on %+v is broken: %s: %s, %s: %s. Please make sure replication runs before using gh-ost.", connectionConfig.Key, ioRunningTerm, slaveIORunning, sqlRunningTerm, slaveSQLRunning, ) } masterKey = &InstanceKey{ Hostname: rowMap.GetString(ReplicaTermFor(dbVersion, "Master_Host")), Port: rowMap.GetInt(ReplicaTermFor(dbVersion, "Master_Port")), } return nil }) return masterKey, err } func GetMasterConnectionConfigSafe(dbVersion string, connectionConfig *ConnectionConfig, visitedKeys *InstanceKeyMap, allowMasterMaster bool) (masterConfig *ConnectionConfig, err error) { log.Debugf("Looking for %s on %+v", ReplicaTermFor(dbVersion, "master"), connectionConfig.Key) masterKey, err := GetMasterKeyFromSlaveStatus(dbVersion, connectionConfig) if err != nil { return nil, err } if masterKey == nil { return connectionConfig, nil } if !masterKey.IsValid() { return connectionConfig, nil } masterConfig = connectionConfig.DuplicateCredentials(*masterKey) if err := masterConfig.RegisterTLSConfig(); err != nil { return nil, err } log.Debugf("%s of %+v is %+v", ReplicaTermFor(dbVersion, "master"), connectionConfig.Key, masterConfig.Key) if visitedKeys.HasKey(masterConfig.Key) { if allowMasterMaster { return connectionConfig, nil } return nil, fmt.Errorf("There seems to be a master-master setup at %+v. This is unsupported. Bailing out", masterConfig.Key) } visitedKeys.AddKey(masterConfig.Key) return GetMasterConnectionConfigSafe(dbVersion, masterConfig, visitedKeys, allowMasterMaster) } func GetReplicationBinlogCoordinates(dbVersion string, db *gosql.DB, gtid bool) (readBinlogCoordinates, executeBinlogCoordinates BinlogCoordinates, err error) { showReplicaStatusQuery := fmt.Sprintf("show %s", ReplicaTermFor(dbVersion, `slave status`)) err = sqlutils.QueryRowsMap(db, showReplicaStatusQuery, func(m sqlutils.RowMap) error { if gtid { executeBinlogCoordinates, err = NewGTIDBinlogCoordinates(m.GetString("Executed_Gtid_Set")) if err != nil { return err } readBinlogCoordinates, err = NewGTIDBinlogCoordinates(m.GetString("Retrieved_Gtid_Set")) if err != nil { return err } } else { readBinlogCoordinates = NewFileBinlogCoordinates( m.GetString(ReplicaTermFor(dbVersion, "Master_Log_File")), m.GetInt64(ReplicaTermFor(dbVersion, "Read_Master_Log_Pos")), ) executeBinlogCoordinates = NewFileBinlogCoordinates( m.GetString(ReplicaTermFor(dbVersion, "Relay_Master_Log_File")), m.GetInt64(ReplicaTermFor(dbVersion, "Exec_Master_Log_Pos")), ) } return nil }) return readBinlogCoordinates, executeBinlogCoordinates, err } func GetSelfBinlogCoordinates(dbVersion string, db *gosql.DB, gtid bool) (selfBinlogCoordinates BinlogCoordinates, err error) { binaryLogStatusTerm := ReplicaTermFor(dbVersion, "master status") err = sqlutils.QueryRowsMap(db, fmt.Sprintf("show %s", binaryLogStatusTerm), func(m sqlutils.RowMap) error { if gtid { selfBinlogCoordinates, err = NewGTIDBinlogCoordinates(m.GetString("Executed_Gtid_Set")) } else { selfBinlogCoordinates = NewFileBinlogCoordinates( m.GetString("File"), m.GetInt64("Position"), ) } return nil }) return selfBinlogCoordinates, err } // GetInstanceKey reads hostname and port on given DB func GetInstanceKey(db *gosql.DB) (instanceKey *InstanceKey, err error) { instanceKey = &InstanceKey{} err = db.QueryRow(`select @@global.hostname, @@global.port`).Scan(&instanceKey.Hostname, &instanceKey.Port) return instanceKey, err } // GetTableColumns reads column list from given table func GetTableColumns(db *gosql.DB, databaseName, tableName string) (*sql.ColumnList, *sql.ColumnList, error) { query := fmt.Sprintf(` show columns from %s.%s `, sql.EscapeName(databaseName), sql.EscapeName(tableName), ) columnNames := []string{} virtualColumnNames := []string{} err := sqlutils.QueryRowsMap(db, query, func(rowMap sqlutils.RowMap) error { columnName := rowMap.GetString("Field") columnNames = append(columnNames, columnName) if strings.Contains(rowMap.GetString("Extra"), " GENERATED") { log.Debugf("%s is a generated column", columnName) virtualColumnNames = append(virtualColumnNames, columnName) } return nil }) if err != nil { return nil, nil, err } if len(columnNames) == 0 { return nil, nil, log.Errorf("Found 0 columns on %s.%s. Bailing out", sql.EscapeName(databaseName), sql.EscapeName(tableName), ) } return sql.NewColumnList(columnNames), sql.NewColumnList(virtualColumnNames), nil } // Kill executes a KILL QUERY by connection id func Kill(db *gosql.DB, connectionID string) error { _, err := db.Exec(`KILL QUERY %s`, connectionID) return err } // GetTriggers reads trigger list from given table func GetTriggers(db *gosql.DB, databaseName, tableName string) (triggers []Trigger, err error) { query := `select trigger_name as name, event_manipulation as event, action_statement as statement, action_timing as timing from information_schema.triggers where trigger_schema = ? and event_object_table = ?` err = sqlutils.QueryRowsMap(db, query, func(rowMap sqlutils.RowMap) error { triggers = append(triggers, Trigger{ Name: rowMap.GetString("name"), Event: rowMap.GetString("event"), Statement: rowMap.GetString("statement"), Timing: rowMap.GetString("timing"), }) return nil }, databaseName, tableName) if err != nil { return nil, err } return triggers, nil } ================================================ FILE: go/sql/builder.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package sql import ( "fmt" "strconv" "strings" ) type ValueComparisonSign string const ( LessThanComparisonSign ValueComparisonSign = "<" LessThanOrEqualsComparisonSign ValueComparisonSign = "<=" EqualsComparisonSign ValueComparisonSign = "=" GreaterThanOrEqualsComparisonSign ValueComparisonSign = ">=" GreaterThanComparisonSign ValueComparisonSign = ">" NotEqualsComparisonSign ValueComparisonSign = "!=" MaxColumnNameLength = 64 ) // EscapeName will escape a db/table/column/... name by wrapping with backticks. // It is not fool proof. I'm just trying to do the right thing here, not solving // SQL injection issues, which should be irrelevant for this tool. func EscapeName(name string) string { if unquoted, err := strconv.Unquote(name); err == nil { name = unquoted } return fmt.Sprintf("`%s`", name) } // TruncateColumnName truncates a name so it can be used as a MySQL // column name, taking into account UTF-8 characters. func TruncateColumnName(name string, limit int) string { truncatedName := name chars := 0 for byteIdx := range name { if chars >= limit { truncatedName = name[:byteIdx] break } chars++ } return truncatedName } func buildColumnsPreparedValues(columns *ColumnList) []string { values := make([]string, columns.Len()) for i, column := range columns.Columns() { var token string if column.timezoneConversion != nil { token = fmt.Sprintf("convert_tz(?, '%s', '%s')", column.timezoneConversion.ToTimezone, "+00:00") } else if column.enumToTextConversion { token = fmt.Sprintf("ELT(?, %s)", column.EnumValues) } else if column.Type == JSONColumnType { token = "convert(? using utf8mb4)" } else { token = "?" } values[i] = token } return values } func buildPreparedValues(length int) []string { values := make([]string, length) for i := 0; i < length; i++ { values[i] = "?" } return values } func duplicateNames(names []string) []string { duplicate := make([]string, len(names)) copy(duplicate, names) return duplicate } func BuildValueComparison(column string, value string, comparisonSign ValueComparisonSign) (result string, err error) { if column == "" { return "", fmt.Errorf("Empty column in GetValueComparison") } if value == "" { return "", fmt.Errorf("Empty value in GetValueComparison") } comparison := fmt.Sprintf("(%s %s %s)", EscapeName(column), string(comparisonSign), value) return comparison, err } func BuildEqualsComparison(columns []string, values []string) (result string, err error) { if len(columns) == 0 { return "", fmt.Errorf("Got 0 columns in GetEqualsComparison") } if len(columns) != len(values) { return "", fmt.Errorf("Got %d columns but %d values in GetEqualsComparison", len(columns), len(values)) } comparisons := []string{} for i, column := range columns { value := values[i] comparison, err := BuildValueComparison(column, value, EqualsComparisonSign) if err != nil { return "", err } comparisons = append(comparisons, comparison) } result = strings.Join(comparisons, " and ") result = fmt.Sprintf("(%s)", result) return result, nil } func BuildEqualsPreparedComparison(columns []string) (result string, err error) { values := buildPreparedValues(len(columns)) return BuildEqualsComparison(columns, values) } // It holds the prepared query statement so it doesn't need to be recreated every time. type CheckpointInsertQueryBuilder struct { uniqueKeyColumns *ColumnList preparedStatement string } func NewCheckpointQueryBuilder(databaseName, tableName string, uniqueKeyColumns *ColumnList) (*CheckpointInsertQueryBuilder, error) { if uniqueKeyColumns.Len() == 0 { return nil, fmt.Errorf("Got 0 columns in BuildSetCheckpointInsertQuery") } values := buildColumnsPreparedValues(uniqueKeyColumns) minUniqueColNames := []string{} maxUniqueColNames := []string{} for _, name := range uniqueKeyColumns.Names() { minColName := TruncateColumnName(name, MaxColumnNameLength-4) + "_min" maxColName := TruncateColumnName(name, MaxColumnNameLength-4) + "_max" minUniqueColNames = append(minUniqueColNames, minColName) maxUniqueColNames = append(maxUniqueColNames, maxColName) } databaseName = EscapeName(databaseName) tableName = EscapeName(tableName) stmt := fmt.Sprintf(` insert /* gh-ost */ into %s.%s (gh_ost_chk_timestamp, gh_ost_chk_coords, gh_ost_chk_iteration, gh_ost_rows_copied, gh_ost_dml_applied, gh_ost_is_cutover, %s, %s) values (unix_timestamp(now()), ?, ?, ?, ?, ?, %s, %s)`, databaseName, tableName, strings.Join(minUniqueColNames, ", "), strings.Join(maxUniqueColNames, ", "), strings.Join(values, ", "), strings.Join(values, ", "), ) b := &CheckpointInsertQueryBuilder{ uniqueKeyColumns: uniqueKeyColumns, preparedStatement: stmt, } return b, nil } // BuildQuery builds the insert query. func (b *CheckpointInsertQueryBuilder) BuildQuery(uniqueKeyArgs []interface{}) (string, []interface{}, error) { if len(uniqueKeyArgs) != 2*b.uniqueKeyColumns.Len() { return "", nil, fmt.Errorf("args count differs from 2 x unique key column count") } convertedArgs := make([]interface{}, 0, 2*b.uniqueKeyColumns.Len()) for i, column := range b.uniqueKeyColumns.Columns() { minArg := column.convertArg(uniqueKeyArgs[i]) convertedArgs = append(convertedArgs, minArg) } for i, column := range b.uniqueKeyColumns.Columns() { minArg := column.convertArg(uniqueKeyArgs[i+b.uniqueKeyColumns.Len()]) convertedArgs = append(convertedArgs, minArg) } return b.preparedStatement, convertedArgs, nil } func BuildSetPreparedClause(columns *ColumnList) (result string, err error) { if columns.Len() == 0 { return "", fmt.Errorf("Got 0 columns in BuildSetPreparedClause") } setTokens := []string{} for _, column := range columns.Columns() { var setToken string if column.timezoneConversion != nil { setToken = fmt.Sprintf("%s=convert_tz(?, '%s', '%s')", EscapeName(column.Name), column.timezoneConversion.ToTimezone, "+00:00") } else if column.enumToTextConversion { setToken = fmt.Sprintf("%s=ELT(?, %s)", EscapeName(column.Name), column.EnumValues) } else if column.Type == JSONColumnType { setToken = fmt.Sprintf("%s=convert(? using utf8mb4)", EscapeName(column.Name)) } else { setToken = fmt.Sprintf("%s=?", EscapeName(column.Name)) } setTokens = append(setTokens, setToken) } return strings.Join(setTokens, ", "), nil } func BuildRangeComparison(columns []string, values []string, args []interface{}, comparisonSign ValueComparisonSign) (result string, explodedArgs []interface{}, err error) { if len(columns) == 0 { return "", explodedArgs, fmt.Errorf("Got 0 columns in GetRangeComparison") } if len(columns) != len(values) { return "", explodedArgs, fmt.Errorf("Got %d columns but %d values in GetEqualsComparison", len(columns), len(values)) } if len(columns) != len(args) { return "", explodedArgs, fmt.Errorf("Got %d columns but %d args in GetEqualsComparison", len(columns), len(args)) } includeEquals := false if comparisonSign == LessThanOrEqualsComparisonSign { comparisonSign = LessThanComparisonSign includeEquals = true } if comparisonSign == GreaterThanOrEqualsComparisonSign { comparisonSign = GreaterThanComparisonSign includeEquals = true } comparisons := []string{} for i, column := range columns { value := values[i] rangeComparison, err := BuildValueComparison(column, value, comparisonSign) if err != nil { return "", explodedArgs, err } if i > 0 { equalitiesComparison, err := BuildEqualsComparison(columns[0:i], values[0:i]) if err != nil { return "", explodedArgs, err } comparison := fmt.Sprintf("(%s AND %s)", equalitiesComparison, rangeComparison) comparisons = append(comparisons, comparison) explodedArgs = append(explodedArgs, args[0:i]...) explodedArgs = append(explodedArgs, args[i]) } else { comparisons = append(comparisons, rangeComparison) explodedArgs = append(explodedArgs, args[i]) } } if includeEquals { comparison, err := BuildEqualsComparison(columns, values) if err != nil { return "", explodedArgs, err } comparisons = append(comparisons, comparison) explodedArgs = append(explodedArgs, args...) } result = strings.Join(comparisons, " or ") result = fmt.Sprintf("(%s)", result) return result, explodedArgs, nil } func BuildRangePreparedComparison(columns *ColumnList, args []interface{}, comparisonSign ValueComparisonSign) (result string, explodedArgs []interface{}, err error) { values := buildColumnsPreparedValues(columns) return BuildRangeComparison(columns.Names(), values, args, comparisonSign) } func BuildRangeInsertQuery(databaseName, originalTableName, ghostTableName string, sharedColumns []string, mappedSharedColumns []string, uniqueKey string, uniqueKeyColumns *ColumnList, rangeStartValues, rangeEndValues []string, rangeStartArgs, rangeEndArgs []interface{}, includeRangeStartValues bool, transactionalTable bool, noWait bool) (result string, explodedArgs []interface{}, err error) { if len(sharedColumns) == 0 { return "", explodedArgs, fmt.Errorf("Got 0 shared columns in BuildRangeInsertQuery") } databaseName = EscapeName(databaseName) originalTableName = EscapeName(originalTableName) ghostTableName = EscapeName(ghostTableName) mappedSharedColumns = duplicateNames(mappedSharedColumns) for i := range mappedSharedColumns { mappedSharedColumns[i] = EscapeName(mappedSharedColumns[i]) } mappedSharedColumnsListing := strings.Join(mappedSharedColumns, ", ") sharedColumns = duplicateNames(sharedColumns) for i := range sharedColumns { sharedColumns[i] = EscapeName(sharedColumns[i]) } sharedColumnsListing := strings.Join(sharedColumns, ", ") uniqueKey = EscapeName(uniqueKey) var minRangeComparisonSign ValueComparisonSign = GreaterThanComparisonSign if includeRangeStartValues { minRangeComparisonSign = GreaterThanOrEqualsComparisonSign } rangeStartComparison, rangeExplodedArgs, err := BuildRangeComparison(uniqueKeyColumns.Names(), rangeStartValues, rangeStartArgs, minRangeComparisonSign) if err != nil { return "", explodedArgs, err } explodedArgs = append(explodedArgs, rangeExplodedArgs...) transactionalClause := "" if transactionalTable { if noWait { transactionalClause = "for share nowait" } else { transactionalClause = "lock in share mode" } } rangeEndComparison, rangeExplodedArgs, err := BuildRangeComparison(uniqueKeyColumns.Names(), rangeEndValues, rangeEndArgs, LessThanOrEqualsComparisonSign) if err != nil { return "", explodedArgs, err } explodedArgs = append(explodedArgs, rangeExplodedArgs...) result = fmt.Sprintf(` insert /* gh-ost %s.%s */ ignore into %s.%s (%s) ( select %s from %s.%s force index (%s) where (%s and %s) %s )`, databaseName, originalTableName, databaseName, ghostTableName, mappedSharedColumnsListing, sharedColumnsListing, databaseName, originalTableName, uniqueKey, rangeStartComparison, rangeEndComparison, transactionalClause) return result, explodedArgs, nil } func BuildRangeInsertPreparedQuery(databaseName, originalTableName, ghostTableName string, sharedColumns []string, mappedSharedColumns []string, uniqueKey string, uniqueKeyColumns *ColumnList, rangeStartArgs, rangeEndArgs []interface{}, includeRangeStartValues bool, transactionalTable bool, noWait bool) (result string, explodedArgs []interface{}, err error) { rangeStartValues := buildColumnsPreparedValues(uniqueKeyColumns) rangeEndValues := buildColumnsPreparedValues(uniqueKeyColumns) return BuildRangeInsertQuery(databaseName, originalTableName, ghostTableName, sharedColumns, mappedSharedColumns, uniqueKey, uniqueKeyColumns, rangeStartValues, rangeEndValues, rangeStartArgs, rangeEndArgs, includeRangeStartValues, transactionalTable, noWait) } func BuildUniqueKeyRangeEndPreparedQueryViaOffset(databaseName, tableName string, uniqueKeyColumns *ColumnList, rangeStartArgs, rangeEndArgs []interface{}, chunkSize int64, includeRangeStartValues bool, hint string) (result string, explodedArgs []interface{}, err error) { if uniqueKeyColumns.Len() == 0 { return "", explodedArgs, fmt.Errorf("Got 0 columns in BuildUniqueKeyRangeEndPreparedQuery") } databaseName = EscapeName(databaseName) tableName = EscapeName(tableName) var startRangeComparisonSign ValueComparisonSign = GreaterThanComparisonSign if includeRangeStartValues { startRangeComparisonSign = GreaterThanOrEqualsComparisonSign } rangeStartComparison, rangeExplodedArgs, err := BuildRangePreparedComparison(uniqueKeyColumns, rangeStartArgs, startRangeComparisonSign) if err != nil { return "", explodedArgs, err } explodedArgs = append(explodedArgs, rangeExplodedArgs...) rangeEndComparison, rangeExplodedArgs, err := BuildRangePreparedComparison(uniqueKeyColumns, rangeEndArgs, LessThanOrEqualsComparisonSign) if err != nil { return "", explodedArgs, err } explodedArgs = append(explodedArgs, rangeExplodedArgs...) uniqueKeyColumnNames := duplicateNames(uniqueKeyColumns.Names()) uniqueKeyColumnAscending := make([]string, len(uniqueKeyColumnNames)) for i, column := range uniqueKeyColumns.Columns() { uniqueKeyColumnNames[i] = EscapeName(uniqueKeyColumnNames[i]) if column.Type == EnumColumnType { uniqueKeyColumnAscending[i] = fmt.Sprintf("concat(%s) asc", uniqueKeyColumnNames[i]) } else { uniqueKeyColumnAscending[i] = fmt.Sprintf("%s asc", uniqueKeyColumnNames[i]) } } result = fmt.Sprintf(` select /* gh-ost %s.%s %s */ %s from %s.%s where %s and %s order by %s limit 1 offset %d`, databaseName, tableName, hint, strings.Join(uniqueKeyColumnNames, ", "), databaseName, tableName, rangeStartComparison, rangeEndComparison, strings.Join(uniqueKeyColumnAscending, ", "), (chunkSize - 1), ) return result, explodedArgs, nil } func BuildUniqueKeyRangeEndPreparedQueryViaTemptable(databaseName, tableName string, uniqueKeyColumns *ColumnList, rangeStartArgs, rangeEndArgs []interface{}, chunkSize int64, includeRangeStartValues bool, hint string) (result string, explodedArgs []interface{}, err error) { if uniqueKeyColumns.Len() == 0 { return "", explodedArgs, fmt.Errorf("Got 0 columns in BuildUniqueKeyRangeEndPreparedQuery") } databaseName = EscapeName(databaseName) tableName = EscapeName(tableName) var startRangeComparisonSign ValueComparisonSign = GreaterThanComparisonSign if includeRangeStartValues { startRangeComparisonSign = GreaterThanOrEqualsComparisonSign } rangeStartComparison, rangeExplodedArgs, err := BuildRangePreparedComparison(uniqueKeyColumns, rangeStartArgs, startRangeComparisonSign) if err != nil { return "", explodedArgs, err } explodedArgs = append(explodedArgs, rangeExplodedArgs...) rangeEndComparison, rangeExplodedArgs, err := BuildRangePreparedComparison(uniqueKeyColumns, rangeEndArgs, LessThanOrEqualsComparisonSign) if err != nil { return "", explodedArgs, err } explodedArgs = append(explodedArgs, rangeExplodedArgs...) uniqueKeyColumnNames := duplicateNames(uniqueKeyColumns.Names()) uniqueKeyColumnAscending := make([]string, len(uniqueKeyColumnNames)) uniqueKeyColumnDescending := make([]string, len(uniqueKeyColumnNames)) for i, column := range uniqueKeyColumns.Columns() { uniqueKeyColumnNames[i] = EscapeName(uniqueKeyColumnNames[i]) if column.Type == EnumColumnType { uniqueKeyColumnAscending[i] = fmt.Sprintf("concat(%s) asc", uniqueKeyColumnNames[i]) uniqueKeyColumnDescending[i] = fmt.Sprintf("concat(%s) desc", uniqueKeyColumnNames[i]) } else { uniqueKeyColumnAscending[i] = fmt.Sprintf("%s asc", uniqueKeyColumnNames[i]) uniqueKeyColumnDescending[i] = fmt.Sprintf("%s desc", uniqueKeyColumnNames[i]) } } result = fmt.Sprintf(` select /* gh-ost %s.%s %s */ %s from ( select %s from %s.%s where %s and %s order by %s limit %d ) select_osc_chunk order by %s limit 1`, databaseName, tableName, hint, strings.Join(uniqueKeyColumnNames, ", "), strings.Join(uniqueKeyColumnNames, ", "), databaseName, tableName, rangeStartComparison, rangeEndComparison, strings.Join(uniqueKeyColumnAscending, ", "), chunkSize, strings.Join(uniqueKeyColumnDescending, ", "), ) return result, explodedArgs, nil } func BuildUniqueKeyMinValuesPreparedQuery(databaseName, tableName string, uniqueKey *UniqueKey) (string, error) { return buildUniqueKeyMinMaxValuesPreparedQuery(databaseName, tableName, uniqueKey, "asc") } func BuildUniqueKeyMaxValuesPreparedQuery(databaseName, tableName string, uniqueKey *UniqueKey) (string, error) { return buildUniqueKeyMinMaxValuesPreparedQuery(databaseName, tableName, uniqueKey, "desc") } func buildUniqueKeyMinMaxValuesPreparedQuery(databaseName, tableName string, uniqueKey *UniqueKey, order string) (string, error) { if uniqueKey.Columns.Len() == 0 { return "", fmt.Errorf("Got 0 columns in BuildUniqueKeyMinMaxValuesPreparedQuery") } databaseName = EscapeName(databaseName) tableName = EscapeName(tableName) uniqueKeyColumnNames := duplicateNames(uniqueKey.Columns.Names()) uniqueKeyColumnOrder := make([]string, len(uniqueKeyColumnNames)) for i, column := range uniqueKey.Columns.Columns() { uniqueKeyColumnNames[i] = EscapeName(uniqueKeyColumnNames[i]) if column.Type == EnumColumnType { uniqueKeyColumnOrder[i] = fmt.Sprintf("concat(%s) %s", uniqueKeyColumnNames[i], order) } else { uniqueKeyColumnOrder[i] = fmt.Sprintf("%s %s", uniqueKeyColumnNames[i], order) } } query := fmt.Sprintf(` select /* gh-ost %s.%s */ %s from %s.%s force index (%s) order by %s limit 1`, databaseName, tableName, strings.Join(uniqueKeyColumnNames, ", "), databaseName, tableName, uniqueKey.Name, strings.Join(uniqueKeyColumnOrder, ", "), ) return query, nil } // DMLDeleteQueryBuilder can build DELETE queries for DML events. // It holds the prepared query statement so it doesn't need to be recreated every time. type DMLDeleteQueryBuilder struct { tableColumns, uniqueKeyColumns *ColumnList preparedStatement string } // NewDMLDeleteQueryBuilder creates a new DMLDeleteQueryBuilder. // It prepares the DELETE query statement. // Returns an error if no unique key columns are given // or the prepared statement cannot be built. func NewDMLDeleteQueryBuilder(databaseName, tableName string, tableColumns, uniqueKeyColumns *ColumnList) (*DMLDeleteQueryBuilder, error) { if uniqueKeyColumns.Len() == 0 { return nil, fmt.Errorf("no unique key columns found in NewDMLDeleteQueryBuilder") } databaseName = EscapeName(databaseName) tableName = EscapeName(tableName) equalsComparison, err := BuildEqualsPreparedComparison(uniqueKeyColumns.Names()) if err != nil { return nil, err } stmt := fmt.Sprintf(` delete /* gh-ost %s.%s */ from %s.%s where %s`, databaseName, tableName, databaseName, tableName, equalsComparison, ) b := &DMLDeleteQueryBuilder{ tableColumns: tableColumns, uniqueKeyColumns: uniqueKeyColumns, preparedStatement: stmt, } return b, nil } // BuildQuery builds the arguments array for a DML event DELETE query. // It returns the query string and the unique key arguments array. // Returns an error if the number of arguments is not equal to the number of table columns. func (b *DMLDeleteQueryBuilder) BuildQuery(args []interface{}) (string, []interface{}, error) { if len(args) != b.tableColumns.Len() { return "", nil, fmt.Errorf("args count differs from table column count in BuildDMLDeleteQuery") } uniqueKeyArgs := make([]interface{}, 0, b.uniqueKeyColumns.Len()) for _, column := range b.uniqueKeyColumns.Columns() { tableOrdinal := b.tableColumns.Ordinals[column.Name] arg := column.convertArg(args[tableOrdinal]) uniqueKeyArgs = append(uniqueKeyArgs, arg) } return b.preparedStatement, uniqueKeyArgs, nil } // DMLInsertQueryBuilder can build INSERT queries for DML events. // It holds the prepared query statement so it doesn't need to be recreated every time. type DMLInsertQueryBuilder struct { tableColumns, sharedColumns *ColumnList preparedStatement string } // NewDMLInsertQueryBuilder creates a new DMLInsertQueryBuilder. // It prepares the INSERT query statement. // Returns an error if no shared columns are given, the shared columns are not a subset of the table columns, // or the prepared statement cannot be built. func NewDMLInsertQueryBuilder(databaseName, tableName string, tableColumns, sharedColumns, mappedSharedColumns *ColumnList) (*DMLInsertQueryBuilder, error) { if !sharedColumns.IsSubsetOf(tableColumns) { return nil, fmt.Errorf("shared columns is not a subset of table columns in NewDMLInsertQueryBuilder") } if sharedColumns.Len() == 0 { return nil, fmt.Errorf("no shared columns found in NewDMLInsertQueryBuilder") } databaseName = EscapeName(databaseName) tableName = EscapeName(tableName) mappedSharedColumnNames := duplicateNames(mappedSharedColumns.Names()) for i := range mappedSharedColumnNames { mappedSharedColumnNames[i] = EscapeName(mappedSharedColumnNames[i]) } preparedValues := buildColumnsPreparedValues(mappedSharedColumns) stmt := fmt.Sprintf(` insert /* gh-ost %s.%s */ ignore into %s.%s (%s) values (%s)`, databaseName, tableName, databaseName, tableName, strings.Join(mappedSharedColumnNames, ", "), strings.Join(preparedValues, ", "), ) return &DMLInsertQueryBuilder{ tableColumns: tableColumns, sharedColumns: sharedColumns, preparedStatement: stmt, }, nil } // BuildQuery builds the arguments array for a DML event INSERT query. // It returns the query string and the shared arguments array. // Returns an error if the number of arguments differs from the number of table columns. func (b *DMLInsertQueryBuilder) BuildQuery(args []interface{}) (string, []interface{}, error) { if len(args) != b.tableColumns.Len() { return "", nil, fmt.Errorf("args count differs from table column count in BuildDMLInsertQuery") } sharedArgs := make([]interface{}, 0, b.sharedColumns.Len()) for _, column := range b.sharedColumns.Columns() { tableOrdinal := b.tableColumns.Ordinals[column.Name] arg := column.convertArg(args[tableOrdinal]) sharedArgs = append(sharedArgs, arg) } return b.preparedStatement, sharedArgs, nil } // DMLUpdateQueryBuilder can build UPDATE queries for DML events. // It holds the prepared query statement so it doesn't need to be recreated every time. type DMLUpdateQueryBuilder struct { tableColumns, sharedColumns, uniqueKeyColumns *ColumnList preparedStatement string } // NewDMLUpdateQueryBuilder creates a new DMLUpdateQueryBuilder. // It prepares the UPDATE query statement. // Returns an error if no shared columns are given, the shared columns are not a subset of the table columns, // no unique key columns are given or the prepared statement cannot be built. func NewDMLUpdateQueryBuilder(databaseName, tableName string, tableColumns, sharedColumns, mappedSharedColumns, uniqueKeyColumns *ColumnList) (*DMLUpdateQueryBuilder, error) { if !sharedColumns.IsSubsetOf(tableColumns) { return nil, fmt.Errorf("shared columns is not a subset of table columns in NewDMLUpdateQueryBuilder") } if sharedColumns.Len() == 0 { return nil, fmt.Errorf("no shared columns found in NewDMLUpdateQueryBuilder") } if uniqueKeyColumns.Len() == 0 { return nil, fmt.Errorf("no unique key columns found in NewDMLUpdateQueryBuilder") } // If unique key contains virtual columns, those column won't be in sharedColumns // which only contains non-virtual columns nonVirtualUniqueKeyColumns := uniqueKeyColumns.FilterBy(func(column Column) bool { return !column.IsVirtual }) if !nonVirtualUniqueKeyColumns.IsSubsetOf(sharedColumns) { return nil, fmt.Errorf("unique key columns is not a subset of shared columns in NewDMLUpdateQueryBuilder") } databaseName = EscapeName(databaseName) tableName = EscapeName(tableName) setClause, err := BuildSetPreparedClause(mappedSharedColumns) if err != nil { return nil, err } equalsComparison, err := BuildEqualsPreparedComparison(uniqueKeyColumns.Names()) if err != nil { return nil, err } stmt := fmt.Sprintf(` update /* gh-ost %s.%s */ %s.%s set %s where %s`, databaseName, tableName, databaseName, tableName, setClause, equalsComparison, ) return &DMLUpdateQueryBuilder{ tableColumns: tableColumns, sharedColumns: sharedColumns, uniqueKeyColumns: uniqueKeyColumns, preparedStatement: stmt, }, nil } // BuildQuery builds the arguments array for a DML event UPDATE query. // It returns the query string, the shared arguments array, and the unique key arguments array. func (b *DMLUpdateQueryBuilder) BuildQuery(valueArgs, whereArgs []interface{}) (string, []interface{}, error) { args := make([]interface{}, 0, b.sharedColumns.Len()+b.uniqueKeyColumns.Len()) for _, column := range b.sharedColumns.Columns() { tableOrdinal := b.tableColumns.Ordinals[column.Name] arg := column.convertArg(valueArgs[tableOrdinal]) args = append(args, arg) } for _, column := range b.uniqueKeyColumns.Columns() { tableOrdinal := b.tableColumns.Ordinals[column.Name] arg := column.convertArg(whereArgs[tableOrdinal]) args = append(args, arg) } return b.preparedStatement, args, nil } ================================================ FILE: go/sql/builder_test.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package sql import ( "testing" "regexp" "strings" "github.com/openark/golib/log" "github.com/stretchr/testify/require" ) var ( spacesRegexp = regexp.MustCompile(`[ \t\n\r]+`) ) func init() { log.SetLevel(log.ERROR) } func normalizeQuery(name string) string { name = strings.Replace(name, "`", "", -1) name = spacesRegexp.ReplaceAllString(name, " ") name = strings.TrimSpace(name) return name } func TestEscapeName(t *testing.T) { names := []string{"my_table", `"my_table"`, "`my_table`"} for _, name := range names { escaped := EscapeName(name) require.Equal(t, "`my_table`", escaped) } } func TestBuildEqualsComparison(t *testing.T) { { columns := []string{"c1"} values := []string{"@v1"} comparison, err := BuildEqualsComparison(columns, values) require.NoError(t, err) require.Equal(t, "((`c1` = @v1))", comparison) } { columns := []string{"c1", "c2"} values := []string{"@v1", "@v2"} comparison, err := BuildEqualsComparison(columns, values) require.NoError(t, err) require.Equal(t, "((`c1` = @v1) and (`c2` = @v2))", comparison) } { columns := []string{"c1"} values := []string{"@v1", "@v2"} _, err := BuildEqualsComparison(columns, values) require.Error(t, err) } { columns := []string{} values := []string{} _, err := BuildEqualsComparison(columns, values) require.Error(t, err) } } func TestBuildEqualsPreparedComparison(t *testing.T) { { columns := []string{"c1", "c2"} comparison, err := BuildEqualsPreparedComparison(columns) require.NoError(t, err) require.Equal(t, "((`c1` = ?) and (`c2` = ?))", comparison) } } func TestBuildSetPreparedClause(t *testing.T) { { columns := NewColumnList([]string{"c1"}) clause, err := BuildSetPreparedClause(columns) require.NoError(t, err) require.Equal(t, "`c1`=?", clause) } { columns := NewColumnList([]string{"c1", "c2"}) clause, err := BuildSetPreparedClause(columns) require.NoError(t, err) require.Equal(t, "`c1`=?, `c2`=?", clause) } { columns := NewColumnList([]string{}) _, err := BuildSetPreparedClause(columns) require.Error(t, err) } } func TestBuildRangeComparison(t *testing.T) { { columns := []string{"c1"} values := []string{"@v1"} args := []interface{}{3} comparison, explodedArgs, err := BuildRangeComparison(columns, values, args, LessThanComparisonSign) require.NoError(t, err) require.Equal(t, "((`c1` < @v1))", comparison) require.Equal(t, []interface{}{3}, explodedArgs) } { columns := []string{"c1"} values := []string{"@v1"} args := []interface{}{3} comparison, explodedArgs, err := BuildRangeComparison(columns, values, args, LessThanOrEqualsComparisonSign) require.NoError(t, err) require.Equal(t, "((`c1` < @v1) or ((`c1` = @v1)))", comparison) require.Equal(t, []interface{}{3, 3}, explodedArgs) } { columns := []string{"c1", "c2"} values := []string{"@v1", "@v2"} args := []interface{}{3, 17} comparison, explodedArgs, err := BuildRangeComparison(columns, values, args, LessThanComparisonSign) require.NoError(t, err) require.Equal(t, "((`c1` < @v1) or (((`c1` = @v1)) AND (`c2` < @v2)))", comparison) require.Equal(t, []interface{}{3, 3, 17}, explodedArgs) } { columns := []string{"c1", "c2"} values := []string{"@v1", "@v2"} args := []interface{}{3, 17} comparison, explodedArgs, err := BuildRangeComparison(columns, values, args, LessThanOrEqualsComparisonSign) require.NoError(t, err) require.Equal(t, "((`c1` < @v1) or (((`c1` = @v1)) AND (`c2` < @v2)) or ((`c1` = @v1) and (`c2` = @v2)))", comparison) require.Equal(t, []interface{}{3, 3, 17, 3, 17}, explodedArgs) } { columns := []string{"c1", "c2", "c3"} values := []string{"@v1", "@v2", "@v3"} args := []interface{}{3, 17, 22} comparison, explodedArgs, err := BuildRangeComparison(columns, values, args, LessThanOrEqualsComparisonSign) require.NoError(t, err) require.Equal(t, "((`c1` < @v1) or (((`c1` = @v1)) AND (`c2` < @v2)) or (((`c1` = @v1) and (`c2` = @v2)) AND (`c3` < @v3)) or ((`c1` = @v1) and (`c2` = @v2) and (`c3` = @v3)))", comparison) require.Equal(t, []interface{}{3, 3, 17, 3, 17, 22, 3, 17, 22}, explodedArgs) } { columns := []string{"c1"} values := []string{"@v1", "@v2"} args := []interface{}{3, 17} _, _, err := BuildRangeComparison(columns, values, args, LessThanOrEqualsComparisonSign) require.Error(t, err) } { columns := []string{} values := []string{} args := []interface{}{} _, _, err := BuildRangeComparison(columns, values, args, LessThanOrEqualsComparisonSign) require.Error(t, err) } } func TestBuildRangeInsertQuery(t *testing.T) { databaseName := "mydb" originalTableName := "tbl" ghostTableName := "ghost" sharedColumns := []string{"id", "name", "position"} { uniqueKey := "PRIMARY" uniqueKeyColumns := NewColumnList([]string{"id"}) rangeStartValues := []string{"@v1s"} rangeEndValues := []string{"@v1e"} rangeStartArgs := []interface{}{3} rangeEndArgs := []interface{}{103} query, explodedArgs, err := BuildRangeInsertQuery(databaseName, originalTableName, ghostTableName, sharedColumns, sharedColumns, uniqueKey, uniqueKeyColumns, rangeStartValues, rangeEndValues, rangeStartArgs, rangeEndArgs, true, true, true) require.NoError(t, err) expected := ` insert /* gh-ost mydb.tbl */ ignore into mydb.ghost (id, name, position) ( select id, name, position from mydb.tbl force index (PRIMARY) where (((id > @v1s) or ((id = @v1s))) and ((id < @v1e) or ((id = @v1e)))) for share nowait )` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{3, 3, 103, 103}, explodedArgs) } { uniqueKey := "name_position_uidx" uniqueKeyColumns := NewColumnList([]string{"name", "position"}) rangeStartValues := []string{"@v1s", "@v2s"} rangeEndValues := []string{"@v1e", "@v2e"} rangeStartArgs := []interface{}{3, 17} rangeEndArgs := []interface{}{103, 117} query, explodedArgs, err := BuildRangeInsertQuery(databaseName, originalTableName, ghostTableName, sharedColumns, sharedColumns, uniqueKey, uniqueKeyColumns, rangeStartValues, rangeEndValues, rangeStartArgs, rangeEndArgs, true, true, true) require.NoError(t, err) expected := ` insert /* gh-ost mydb.tbl */ ignore into mydb.ghost (id, name, position) ( select id, name, position from mydb.tbl force index (name_position_uidx) where (((name > @v1s) or (((name = @v1s)) AND (position > @v2s)) or ((name = @v1s) and (position = @v2s))) and ((name < @v1e) or (((name = @v1e)) AND (position < @v2e)) or ((name = @v1e) and (position = @v2e)))) for share nowait )` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{3, 3, 17, 3, 17, 103, 103, 117, 103, 117}, explodedArgs) } } func TestBuildRangeInsertQueryRenameMap(t *testing.T) { databaseName := "mydb" originalTableName := "tbl" ghostTableName := "ghost" sharedColumns := []string{"id", "name", "position"} mappedSharedColumns := []string{"id", "name", "location"} { uniqueKey := "PRIMARY" uniqueKeyColumns := NewColumnList([]string{"id"}) rangeStartValues := []string{"@v1s"} rangeEndValues := []string{"@v1e"} rangeStartArgs := []interface{}{3} rangeEndArgs := []interface{}{103} query, explodedArgs, err := BuildRangeInsertQuery(databaseName, originalTableName, ghostTableName, sharedColumns, mappedSharedColumns, uniqueKey, uniqueKeyColumns, rangeStartValues, rangeEndValues, rangeStartArgs, rangeEndArgs, true, true, true) require.NoError(t, err) expected := ` insert /* gh-ost mydb.tbl */ ignore into mydb.ghost (id, name, location) ( select id, name, position from mydb.tbl force index (PRIMARY) where (((id > @v1s) or ((id = @v1s))) and ((id < @v1e) or ((id = @v1e)))) for share nowait )` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{3, 3, 103, 103}, explodedArgs) } { uniqueKey := "name_position_uidx" uniqueKeyColumns := NewColumnList([]string{"name", "position"}) rangeStartValues := []string{"@v1s", "@v2s"} rangeEndValues := []string{"@v1e", "@v2e"} rangeStartArgs := []interface{}{3, 17} rangeEndArgs := []interface{}{103, 117} query, explodedArgs, err := BuildRangeInsertQuery(databaseName, originalTableName, ghostTableName, sharedColumns, mappedSharedColumns, uniqueKey, uniqueKeyColumns, rangeStartValues, rangeEndValues, rangeStartArgs, rangeEndArgs, true, true, true) require.NoError(t, err) expected := ` insert /* gh-ost mydb.tbl */ ignore into mydb.ghost (id, name, location) ( select id, name, position from mydb.tbl force index (name_position_uidx) where (((name > @v1s) or (((name = @v1s)) AND (position > @v2s)) or ((name = @v1s) and (position = @v2s))) and ((name < @v1e) or (((name = @v1e)) AND (position < @v2e)) or ((name = @v1e) and (position = @v2e)))) for share nowait )` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{3, 3, 17, 3, 17, 103, 103, 117, 103, 117}, explodedArgs) } } func TestBuildRangeInsertPreparedQuery(t *testing.T) { databaseName := "mydb" originalTableName := "tbl" ghostTableName := "ghost" sharedColumns := []string{"id", "name", "position"} { uniqueKey := "name_position_uidx" uniqueKeyColumns := NewColumnList([]string{"name", "position"}) rangeStartArgs := []interface{}{3, 17} rangeEndArgs := []interface{}{103, 117} query, explodedArgs, err := BuildRangeInsertPreparedQuery(databaseName, originalTableName, ghostTableName, sharedColumns, sharedColumns, uniqueKey, uniqueKeyColumns, rangeStartArgs, rangeEndArgs, true, true, true) require.NoError(t, err) expected := ` insert /* gh-ost mydb.tbl */ ignore into mydb.ghost (id, name, position) ( select id, name, position from mydb.tbl force index (name_position_uidx) where (((name > ?) or (((name = ?)) AND (position > ?)) or ((name = ?) and (position = ?))) and ((name < ?) or (((name = ?)) AND (position < ?)) or ((name = ?) and (position = ?)))) for share nowait )` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{3, 3, 17, 3, 17, 103, 103, 117, 103, 117}, explodedArgs) } } func TestBuildUniqueKeyRangeEndPreparedQueryViaOffset(t *testing.T) { databaseName := "mydb" originalTableName := "tbl" var chunkSize int64 = 500 { uniqueKeyColumns := NewColumnList([]string{"name", "position"}) rangeStartArgs := []interface{}{3, 17} rangeEndArgs := []interface{}{103, 117} query, explodedArgs, err := BuildUniqueKeyRangeEndPreparedQueryViaOffset(databaseName, originalTableName, uniqueKeyColumns, rangeStartArgs, rangeEndArgs, chunkSize, false, "test") require.NoError(t, err) expected := ` select /* gh-ost mydb.tbl test */ name, position from mydb.tbl where ((name > ?) or (((name = ?)) AND (position > ?))) and ((name < ?) or (((name = ?)) AND (position < ?)) or ((name = ?) and (position = ?))) order by name asc, position asc limit 1 offset 499` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{3, 3, 17, 103, 103, 117, 103, 117}, explodedArgs) } } func TestBuildUniqueKeyRangeEndPreparedQueryViaTemptable(t *testing.T) { databaseName := "mydb" originalTableName := "tbl" var chunkSize int64 = 500 { uniqueKeyColumns := NewColumnList([]string{"name", "position"}) rangeStartArgs := []interface{}{3, 17} rangeEndArgs := []interface{}{103, 117} query, explodedArgs, err := BuildUniqueKeyRangeEndPreparedQueryViaTemptable(databaseName, originalTableName, uniqueKeyColumns, rangeStartArgs, rangeEndArgs, chunkSize, false, "test") require.NoError(t, err) expected := ` select /* gh-ost mydb.tbl test */ name, position from ( select name, position from mydb.tbl where ((name > ?) or (((name = ?)) AND (position > ?))) and ((name < ?) or (((name = ?)) AND (position < ?)) or ((name = ?) and (position = ?))) order by name asc, position asc limit 500 ) select_osc_chunk order by name desc, position desc limit 1` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{3, 3, 17, 103, 103, 117, 103, 117}, explodedArgs) } } func TestBuildUniqueKeyMinValuesPreparedQuery(t *testing.T) { databaseName := "mydb" originalTableName := "tbl" uniqueKeyColumns := NewColumnList([]string{"name", "position"}) uniqueKey := &UniqueKey{Name: "PRIMARY", Columns: *uniqueKeyColumns} { query, err := BuildUniqueKeyMinValuesPreparedQuery(databaseName, originalTableName, uniqueKey) require.NoError(t, err) expected := ` select /* gh-ost mydb.tbl */ name, position from mydb.tbl force index (PRIMARY) order by name asc, position asc limit 1 ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) } { query, err := BuildUniqueKeyMaxValuesPreparedQuery(databaseName, originalTableName, uniqueKey) require.NoError(t, err) expected := ` select /* gh-ost mydb.tbl */ name, position from mydb.tbl force index (PRIMARY) order by name desc, position desc limit 1 ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) } } func TestBuildDMLDeleteQuery(t *testing.T) { databaseName := "mydb" tableName := "tbl" tableColumns := NewColumnList([]string{"id", "name", "rank", "position", "age"}) args := []interface{}{3, "testname", "first", 17, 23} { uniqueKeyColumns := NewColumnList([]string{"position"}) builder, err := NewDMLDeleteQueryBuilder(databaseName, tableName, tableColumns, uniqueKeyColumns) require.NoError(t, err) query, uniqueKeyArgs, err := builder.BuildQuery(args) require.NoError(t, err) expected := ` delete /* gh-ost mydb.tbl */ from mydb.tbl where ((position = ?)) ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{17}, uniqueKeyArgs) } { uniqueKeyColumns := NewColumnList([]string{"name", "position"}) builder, err := NewDMLDeleteQueryBuilder(databaseName, tableName, tableColumns, uniqueKeyColumns) require.NoError(t, err) query, uniqueKeyArgs, err := builder.BuildQuery(args) require.NoError(t, err) expected := ` delete /* gh-ost mydb.tbl */ from mydb.tbl where ((name = ?) and (position = ?)) ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{"testname", 17}, uniqueKeyArgs) } { uniqueKeyColumns := NewColumnList([]string{"position", "name"}) builder, err := NewDMLDeleteQueryBuilder(databaseName, tableName, tableColumns, uniqueKeyColumns) require.NoError(t, err) query, uniqueKeyArgs, err := builder.BuildQuery(args) require.NoError(t, err) expected := ` delete /* gh-ost mydb.tbl */ from mydb.tbl where ((position = ?) and (name = ?)) ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{17, "testname"}, uniqueKeyArgs) } { uniqueKeyColumns := NewColumnList([]string{"position", "name"}) args := []interface{}{"first", 17} builder, err := NewDMLDeleteQueryBuilder(databaseName, tableName, tableColumns, uniqueKeyColumns) require.NoError(t, err) _, _, err = builder.BuildQuery(args) require.Error(t, err) } } func TestBuildDMLDeleteQuerySignedUnsigned(t *testing.T) { databaseName := "mydb" tableName := "tbl" tableColumns := NewColumnList([]string{"id", "name", "rank", "position", "age"}) uniqueKeyColumns := NewColumnList([]string{"position"}) builder, err := NewDMLDeleteQueryBuilder(databaseName, tableName, tableColumns, uniqueKeyColumns) require.NoError(t, err) { // test signed (expect no change) args := []interface{}{3, "testname", "first", -1, 23} query, uniqueKeyArgs, err := builder.BuildQuery(args) require.NoError(t, err) expected := ` delete /* gh-ost mydb.tbl */ from mydb.tbl where ((position = ?)) ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{-1}, uniqueKeyArgs) } { // test unsigned args := []interface{}{3, "testname", "first", int8(-1), 23} uniqueKeyColumns.SetUnsigned("position") query, uniqueKeyArgs, err := builder.BuildQuery(args) require.NoError(t, err) expected := ` delete /* gh-ost mydb.tbl */ from mydb.tbl where ((position = ?)) ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{uint8(255)}, uniqueKeyArgs) } } func TestBuildDMLInsertQuery(t *testing.T) { databaseName := "mydb" tableName := "tbl" tableColumns := NewColumnList([]string{"id", "name", "rank", "position", "age"}) args := []interface{}{3, "testname", "first", 17, 23} { sharedColumns := NewColumnList([]string{"id", "name", "position", "age"}) builder, err := NewDMLInsertQueryBuilder(databaseName, tableName, tableColumns, sharedColumns, sharedColumns) require.NoError(t, err) query, sharedArgs, err := builder.BuildQuery(args) require.NoError(t, err) expected := ` insert /* gh-ost mydb.tbl */ ignore into mydb.tbl (id, name, position, age) values (?, ?, ?, ?) ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{3, "testname", 17, 23}, sharedArgs) } { sharedColumns := NewColumnList([]string{"position", "name", "age", "id"}) builder, err := NewDMLInsertQueryBuilder(databaseName, tableName, tableColumns, sharedColumns, sharedColumns) require.NoError(t, err) query, sharedArgs, err := builder.BuildQuery(args) require.NoError(t, err) expected := ` insert /* gh-ost mydb.tbl */ ignore into mydb.tbl (position, name, age, id) values (?, ?, ?, ?) ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{17, "testname", 23, 3}, sharedArgs) } { sharedColumns := NewColumnList([]string{"position", "name", "surprise", "id"}) _, err := NewDMLInsertQueryBuilder(databaseName, tableName, tableColumns, sharedColumns, sharedColumns) require.Error(t, err) } { sharedColumns := NewColumnList([]string{}) _, err := NewDMLInsertQueryBuilder(databaseName, tableName, tableColumns, sharedColumns, sharedColumns) require.Error(t, err) } } func TestBuildDMLInsertQuerySignedUnsigned(t *testing.T) { databaseName := "mydb" tableName := "tbl" tableColumns := NewColumnList([]string{"id", "name", "rank", "position", "age"}) sharedColumns := NewColumnList([]string{"id", "name", "position", "age"}) { // testing signed args := []interface{}{3, "testname", "first", int8(-1), 23} sharedColumns := NewColumnList([]string{"id", "name", "position", "age"}) builder, err := NewDMLInsertQueryBuilder(databaseName, tableName, tableColumns, sharedColumns, sharedColumns) require.NoError(t, err) query, sharedArgs, err := builder.BuildQuery(args) require.NoError(t, err) expected := ` insert /* gh-ost mydb.tbl */ ignore into mydb.tbl (id, name, position, age) values (?, ?, ?, ?) ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{3, "testname", int8(-1), 23}, sharedArgs) } { // testing unsigned args := []interface{}{3, "testname", "first", int8(-1), 23} sharedColumns.SetUnsigned("position") builder, err := NewDMLInsertQueryBuilder(databaseName, tableName, tableColumns, sharedColumns, sharedColumns) require.NoError(t, err) query, sharedArgs, err := builder.BuildQuery(args) require.NoError(t, err) expected := ` insert /* gh-ost mydb.tbl */ ignore into mydb.tbl (id, name, position, age) values (?, ?, ?, ?) ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{3, "testname", uint8(255), 23}, sharedArgs) } { // testing unsigned args := []interface{}{3, "testname", "first", int32(-1), 23} sharedColumns.SetUnsigned("position") builder, err := NewDMLInsertQueryBuilder(databaseName, tableName, tableColumns, sharedColumns, sharedColumns) require.NoError(t, err) query, sharedArgs, err := builder.BuildQuery(args) require.NoError(t, err) expected := ` insert /* gh-ost mydb.tbl */ ignore into mydb.tbl (id, name, position, age) values (?, ?, ?, ?) ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{3, "testname", uint32(4294967295), 23}, sharedArgs) } } func TestBuildDMLUpdateQuery(t *testing.T) { databaseName := "mydb" tableName := "tbl" tableColumns := NewColumnList([]string{"id", "name", "rank", "position", "age"}) valueArgs := []interface{}{3, "testname", "newval", 17, 23} whereArgs := []interface{}{3, "testname", "findme", 17, 56} { sharedColumns := NewColumnList([]string{"id", "name", "position", "age"}) uniqueKeyColumns := NewColumnList([]string{"position"}) builder, err := NewDMLUpdateQueryBuilder(databaseName, tableName, tableColumns, sharedColumns, sharedColumns, uniqueKeyColumns) require.NoError(t, err) query, updateArgs, err := builder.BuildQuery(valueArgs, whereArgs) require.NoError(t, err) expected := ` update /* gh-ost mydb.tbl */ mydb.tbl set id=?, name=?, position=?, age=? where ((position = ?)) ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{3, "testname", 17, 23, 17}, updateArgs) } { sharedColumns := NewColumnList([]string{"id", "name", "position", "age"}) uniqueKeyColumns := NewColumnList([]string{"position", "name"}) builder, err := NewDMLUpdateQueryBuilder(databaseName, tableName, tableColumns, sharedColumns, sharedColumns, uniqueKeyColumns) require.NoError(t, err) query, updateArgs, err := builder.BuildQuery(valueArgs, whereArgs) require.NoError(t, err) expected := ` update /* gh-ost mydb.tbl */ mydb.tbl set id=?, name=?, position=?, age=? where ((position = ?) and (name = ?)) ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{3, "testname", 17, 23, 17, "testname"}, updateArgs) } { sharedColumns := NewColumnList([]string{"id", "name", "position", "age"}) uniqueKeyColumns := NewColumnList([]string{"age"}) builder, err := NewDMLUpdateQueryBuilder(databaseName, tableName, tableColumns, sharedColumns, sharedColumns, uniqueKeyColumns) require.NoError(t, err) query, updateArgs, err := builder.BuildQuery(valueArgs, whereArgs) require.NoError(t, err) expected := ` update /* gh-ost mydb.tbl */ mydb.tbl set id=?, name=?, position=?, age=? where ((age = ?)) ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{3, "testname", 17, 23, 56}, updateArgs) } { sharedColumns := NewColumnList([]string{"id", "name", "position", "age"}) uniqueKeyColumns := NewColumnList([]string{"age", "position", "id", "name"}) builder, err := NewDMLUpdateQueryBuilder(databaseName, tableName, tableColumns, sharedColumns, sharedColumns, uniqueKeyColumns) require.NoError(t, err) query, updateArgs, err := builder.BuildQuery(valueArgs, whereArgs) require.NoError(t, err) expected := ` update /* gh-ost mydb.tbl */ mydb.tbl set id=?, name=?, position=?, age=? where ((age = ?) and (position = ?) and (id = ?) and (name = ?)) ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{3, "testname", 17, 23, 56, 17, 3, "testname"}, updateArgs) } { sharedColumns := NewColumnList([]string{"id", "name", "position", "age"}) uniqueKeyColumns := NewColumnList([]string{"age", "surprise"}) _, err := NewDMLUpdateQueryBuilder(databaseName, tableName, tableColumns, sharedColumns, sharedColumns, uniqueKeyColumns) require.Error(t, err) } { sharedColumns := NewColumnList([]string{"id", "name", "position", "age"}) uniqueKeyColumns := NewColumnList([]string{}) _, err := NewDMLUpdateQueryBuilder(databaseName, tableName, tableColumns, sharedColumns, sharedColumns, uniqueKeyColumns) require.Error(t, err) } { sharedColumns := NewColumnList([]string{"id", "name", "position", "age"}) mappedColumns := NewColumnList([]string{"id", "name", "role", "age"}) uniqueKeyColumns := NewColumnList([]string{"id"}) builder, err := NewDMLUpdateQueryBuilder(databaseName, tableName, tableColumns, sharedColumns, mappedColumns, uniqueKeyColumns) require.NoError(t, err) query, updateArgs, err := builder.BuildQuery(valueArgs, whereArgs) require.NoError(t, err) expected := ` update /* gh-ost mydb.tbl */ mydb.tbl set id=?, name=?, role=?, age=? where ((id = ?)) ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{3, "testname", 17, 23, 3}, updateArgs) } } func TestBuildDMLUpdateQuerySignedUnsigned(t *testing.T) { databaseName := "mydb" tableName := "tbl" tableColumns := NewColumnList([]string{"id", "name", "rank", "position", "age"}) valueArgs := []interface{}{3, "testname", "newval", int8(-17), int8(-2)} whereArgs := []interface{}{3, "testname", "findme", int8(-3), 56} sharedColumns := NewColumnList([]string{"id", "name", "position", "age"}) uniqueKeyColumns := NewColumnList([]string{"position"}) builder, err := NewDMLUpdateQueryBuilder(databaseName, tableName, tableColumns, sharedColumns, sharedColumns, uniqueKeyColumns) require.NoError(t, err) { // test signed query, updateArgs, err := builder.BuildQuery(valueArgs, whereArgs) require.NoError(t, err) expected := ` update /* gh-ost mydb.tbl */ mydb.tbl set id=?, name=?, position=?, age=? where ((position = ?)) ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{3, "testname", int8(-17), int8(-2), int8(-3)}, updateArgs) } { // test unsigned sharedColumns.SetUnsigned("age") uniqueKeyColumns.SetUnsigned("position") query, updateArgs, err := builder.BuildQuery(valueArgs, whereArgs) require.NoError(t, err) expected := ` update /* gh-ost mydb.tbl */ mydb.tbl set id=?, name=?, position=?, age=? where ((position = ?)) ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{3, "testname", int8(-17), uint8(254), uint8(253)}, updateArgs) } } func TestCheckpointQueryBuilder(t *testing.T) { databaseName := "mydb" tableName := "_tbl_ghk" valueArgs := []interface{}{"mona", "mascot", int8(-17), "anothername", "anotherposition", int8(-2)} uniqueKeyColumns := NewColumnList([]string{"name", "position", "my_very_long_column_that_is_64_utf8_characters_long_很长很长很长很长很长很长"}) builder, err := NewCheckpointQueryBuilder(databaseName, tableName, uniqueKeyColumns) require.NoError(t, err) query, uniqueKeyArgs, err := builder.BuildQuery(valueArgs) require.NoError(t, err) expected := ` insert /* gh-ost */ into mydb._tbl_ghk (gh_ost_chk_timestamp, gh_ost_chk_coords, gh_ost_chk_iteration, gh_ost_rows_copied, gh_ost_dml_applied, gh_ost_is_cutover, name_min, position_min, my_very_long_column_that_is_64_utf8_characters_long_很长很长很长很长_min, name_max, position_max, my_very_long_column_that_is_64_utf8_characters_long_很长很长很长很长_max) values (unix_timestamp(now()), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ` require.Equal(t, normalizeQuery(expected), normalizeQuery(query)) require.Equal(t, []interface{}{"mona", "mascot", int8(-17), "anothername", "anotherposition", int8(-2)}, uniqueKeyArgs) } ================================================ FILE: go/sql/encoding.go ================================================ /* Copyright 2016 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package sql import ( "golang.org/x/text/encoding" "golang.org/x/text/encoding/charmap" "golang.org/x/text/encoding/simplifiedchinese" ) type charsetEncoding map[string]encoding.Encoding var charsetEncodingMap charsetEncoding func init() { charsetEncodingMap = make(map[string]encoding.Encoding) // Begin mappings charsetEncodingMap["latin1"] = charmap.Windows1252 charsetEncodingMap["gbk"] = simplifiedchinese.GBK } ================================================ FILE: go/sql/parser.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package sql import ( "regexp" "strconv" "strings" ) var ( sanitizeQuotesRegexp = regexp.MustCompile("('[^']*')") renameColumnRegexp = regexp.MustCompile(`(?i)\bchange\s+(column\s+|)([\S]+)\s+([\S]+)\s+`) dropColumnRegexp = regexp.MustCompile(`(?i)\bdrop\s+(column\s+|)([\S]+)$`) renameTableRegexp = regexp.MustCompile(`(?i)\brename\s+(to|as)\s+`) autoIncrementRegexp = regexp.MustCompile(`(?i)\bauto_increment[\s]*=[\s]*([0-9]+)`) alterTableExplicitSchemaTableRegexps = []*regexp.Regexp{ // ALTER TABLE `scm`.`tbl` something regexp.MustCompile(`(?i)\balter\s+table\s+` + "`" + `([^` + "`" + `]+)` + "`" + `[.]` + "`" + `([^` + "`" + `]+)` + "`" + `\s+(.*$)`), // ALTER TABLE `scm`.tbl something regexp.MustCompile(`(?i)\balter\s+table\s+` + "`" + `([^` + "`" + `]+)` + "`" + `[.]([\S]+)\s+(.*$)`), // ALTER TABLE scm.`tbl` something regexp.MustCompile(`(?i)\balter\s+table\s+([\S]+)[.]` + "`" + `([^` + "`" + `]+)` + "`" + `\s+(.*$)`), // ALTER TABLE scm.tbl something regexp.MustCompile(`(?i)\balter\s+table\s+([\S]+)[.]([\S]+)\s+(.*$)`), } alterTableExplicitTableRegexps = []*regexp.Regexp{ // ALTER TABLE `tbl` something regexp.MustCompile(`(?i)\balter\s+table\s+` + "`" + `([^` + "`" + `]+)` + "`" + `\s+(.*$)`), // ALTER TABLE tbl something regexp.MustCompile(`(?i)\balter\s+table\s+([\S]+)\s+(.*$)`), } enumValuesRegexp = regexp.MustCompile("^enum[(](.*)[)]$") ) type AlterTableParser struct { columnRenameMap map[string]string droppedColumns map[string]bool isRenameTable bool isAutoIncrementDefined bool alterStatementOptions string alterTokens []string explicitSchema string explicitTable string } func NewAlterTableParser() *AlterTableParser { return &AlterTableParser{ columnRenameMap: make(map[string]string), droppedColumns: make(map[string]bool), } } func NewParserFromAlterStatement(alterStatement string) *AlterTableParser { parser := NewAlterTableParser() parser.ParseAlterStatement(alterStatement) return parser } func (this *AlterTableParser) tokenizeAlterStatement(alterStatement string) (tokens []string) { terminatingQuote := rune(0) f := func(c rune) bool { switch { case c == terminatingQuote: terminatingQuote = rune(0) return false case terminatingQuote != rune(0): return false case c == '\'': terminatingQuote = c return false case c == '(': terminatingQuote = ')' return false default: return c == ',' } } tokens = strings.FieldsFunc(alterStatement, f) for i := range tokens { tokens[i] = strings.TrimSpace(tokens[i]) } return tokens } func (this *AlterTableParser) sanitizeQuotesFromAlterStatement(alterStatement string) (strippedStatement string) { strippedStatement = alterStatement strippedStatement = sanitizeQuotesRegexp.ReplaceAllString(strippedStatement, "''") return strippedStatement } func (this *AlterTableParser) parseAlterToken(alterToken string) { { // rename allStringSubmatch := renameColumnRegexp.FindAllStringSubmatch(alterToken, -1) for _, submatch := range allStringSubmatch { if unquoted, err := strconv.Unquote(submatch[2]); err == nil { submatch[2] = unquoted } if unquoted, err := strconv.Unquote(submatch[3]); err == nil { submatch[3] = unquoted } this.columnRenameMap[submatch[2]] = submatch[3] } } { // drop allStringSubmatch := dropColumnRegexp.FindAllStringSubmatch(alterToken, -1) for _, submatch := range allStringSubmatch { if unquoted, err := strconv.Unquote(submatch[2]); err == nil { submatch[2] = unquoted } this.droppedColumns[submatch[2]] = true } } { // rename table if renameTableRegexp.MatchString(alterToken) { this.isRenameTable = true } } { // auto_increment if autoIncrementRegexp.MatchString(alterToken) { this.isAutoIncrementDefined = true } } } func (this *AlterTableParser) ParseAlterStatement(alterStatement string) (err error) { this.alterStatementOptions = alterStatement for _, alterTableRegexp := range alterTableExplicitSchemaTableRegexps { if submatch := alterTableRegexp.FindStringSubmatch(this.alterStatementOptions); len(submatch) > 0 { this.explicitSchema = submatch[1] this.explicitTable = submatch[2] this.alterStatementOptions = submatch[3] break } } for _, alterTableRegexp := range alterTableExplicitTableRegexps { if submatch := alterTableRegexp.FindStringSubmatch(this.alterStatementOptions); len(submatch) > 0 { this.explicitTable = submatch[1] this.alterStatementOptions = submatch[2] break } } for _, alterToken := range this.tokenizeAlterStatement(this.alterStatementOptions) { alterToken = this.sanitizeQuotesFromAlterStatement(alterToken) this.parseAlterToken(alterToken) this.alterTokens = append(this.alterTokens, alterToken) } return nil } func (this *AlterTableParser) GetNonTrivialRenames() map[string]string { result := make(map[string]string) for column, renamed := range this.columnRenameMap { if column != renamed { result[column] = renamed } } return result } func (this *AlterTableParser) HasNonTrivialRenames() bool { return len(this.GetNonTrivialRenames()) > 0 } func (this *AlterTableParser) DroppedColumnsMap() map[string]bool { return this.droppedColumns } func (this *AlterTableParser) IsRenameTable() bool { return this.isRenameTable } func (this *AlterTableParser) IsAutoIncrementDefined() bool { return this.isAutoIncrementDefined } func (this *AlterTableParser) GetExplicitSchema() string { return this.explicitSchema } func (this *AlterTableParser) HasExplicitSchema() bool { return this.GetExplicitSchema() != "" } func (this *AlterTableParser) GetExplicitTable() string { return this.explicitTable } func (this *AlterTableParser) HasExplicitTable() bool { return this.GetExplicitTable() != "" } func (this *AlterTableParser) GetAlterStatementOptions() string { return this.alterStatementOptions } func ParseEnumValues(enumColumnType string) string { if submatch := enumValuesRegexp.FindStringSubmatch(enumColumnType); len(submatch) > 0 { return submatch[1] } return enumColumnType } ================================================ FILE: go/sql/parser_test.go ================================================ /* Copyright 2022 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package sql import ( "testing" "github.com/openark/golib/log" "github.com/stretchr/testify/require" ) func init() { log.SetLevel(log.ERROR) } func TestParseAlterStatement(t *testing.T) { statement := "add column t int, engine=innodb" parser := NewAlterTableParser() err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.Equal(t, statement, parser.alterStatementOptions) require.False(t, parser.HasNonTrivialRenames()) require.False(t, parser.IsAutoIncrementDefined()) } func TestParseAlterStatementrivialRename(t *testing.T) { statement := "add column t int, change ts ts timestamp, engine=innodb" parser := NewAlterTableParser() err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.Equal(t, statement, parser.alterStatementOptions) require.False(t, parser.HasNonTrivialRenames()) require.False(t, parser.IsAutoIncrementDefined()) require.Len(t, parser.columnRenameMap, 1) require.Equal(t, "ts", parser.columnRenameMap["ts"]) } func TestParseAlterStatementWithAutoIncrement(t *testing.T) { statements := []string{ "auto_increment=7", "auto_increment = 7", "AUTO_INCREMENT = 71", "add column t int, change ts ts timestamp, auto_increment=7 engine=innodb", "add column t int, change ts ts timestamp, auto_increment =7 engine=innodb", "add column t int, change ts ts timestamp, AUTO_INCREMENT = 7 engine=innodb", "add column t int, change ts ts timestamp, engine=innodb auto_increment=73425", } for _, statement := range statements { parser := NewAlterTableParser() err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.Equal(t, statement, parser.alterStatementOptions) require.True(t, parser.IsAutoIncrementDefined()) } } func TestParseAlterStatementrivialRenames(t *testing.T) { statement := "add column t int, change ts ts timestamp, CHANGE f `f` float, engine=innodb" parser := NewAlterTableParser() err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.Equal(t, statement, parser.alterStatementOptions) require.False(t, parser.HasNonTrivialRenames()) require.False(t, parser.IsAutoIncrementDefined()) require.Len(t, parser.columnRenameMap, 2) require.Equal(t, "ts", parser.columnRenameMap["ts"]) require.Equal(t, "f", parser.columnRenameMap["f"]) } func TestParseAlterStatementNonTrivial(t *testing.T) { statements := []string{ `add column b bigint, change f fl float, change i count int, engine=innodb`, "add column b bigint, change column `f` fl float, change `i` `count` int, engine=innodb", "add column b bigint, change column `f` fl float, change `i` `count` int, change ts ts timestamp, engine=innodb", `change f fl float, CHANGE COLUMN i count int, engine=innodb`, } for _, statement := range statements { parser := NewAlterTableParser() err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.False(t, parser.IsAutoIncrementDefined()) require.Equal(t, statement, parser.alterStatementOptions) renames := parser.GetNonTrivialRenames() require.Len(t, renames, 2) require.Equal(t, "count", renames["i"]) require.Equal(t, "fl", renames["f"]) } } func TestTokenizeAlterStatement(t *testing.T) { parser := NewAlterTableParser() { alterStatement := "add column t int" tokens := parser.tokenizeAlterStatement(alterStatement) require.Equal(t, []string{"add column t int"}, tokens) } { alterStatement := "add column t int, change column i int" tokens := parser.tokenizeAlterStatement(alterStatement) require.Equal(t, []string{"add column t int", "change column i int"}, tokens) } { alterStatement := "add column t int, change column i int 'some comment'" tokens := parser.tokenizeAlterStatement(alterStatement) require.Equal(t, []string{"add column t int", "change column i int 'some comment'"}, tokens) } { alterStatement := "add column t int, change column i int 'some comment, with comma'" tokens := parser.tokenizeAlterStatement(alterStatement) require.Equal(t, []string{"add column t int", "change column i int 'some comment, with comma'"}, tokens) } { alterStatement := "add column t int, add column d decimal(10,2)" tokens := parser.tokenizeAlterStatement(alterStatement) require.Equal(t, []string{"add column t int", "add column d decimal(10,2)"}, tokens) } { alterStatement := "add column t int, add column e enum('a','b','c')" tokens := parser.tokenizeAlterStatement(alterStatement) require.Equal(t, []string{"add column t int", "add column e enum('a','b','c')"}, tokens) } { alterStatement := "add column t int(11), add column e enum('a','b','c')" tokens := parser.tokenizeAlterStatement(alterStatement) require.Equal(t, []string{"add column t int(11)", "add column e enum('a','b','c')"}, tokens) } } func TestSanitizeQuotesFromAlterStatement(t *testing.T) { parser := NewAlterTableParser() { alterStatement := "add column e enum('a','b','c')" strippedStatement := parser.sanitizeQuotesFromAlterStatement(alterStatement) require.Equal(t, "add column e enum('','','')", strippedStatement) } { alterStatement := "change column i int 'some comment, with comma'" strippedStatement := parser.sanitizeQuotesFromAlterStatement(alterStatement) require.Equal(t, "change column i int ''", strippedStatement) } } func TestParseAlterStatementDroppedColumns(t *testing.T) { { parser := NewAlterTableParser() statement := "drop column b" err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.Len(t, parser.droppedColumns, 1) require.True(t, parser.droppedColumns["b"]) } { parser := NewAlterTableParser() statement := "drop column b, drop key c_idx, drop column `d`" err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.Equal(t, statement, parser.alterStatementOptions) require.Len(t, parser.droppedColumns, 2) require.True(t, parser.droppedColumns["b"]) require.True(t, parser.droppedColumns["d"]) } { parser := NewAlterTableParser() statement := "drop column b, drop key c_idx, drop column `d`, drop `e`, drop primary key, drop foreign key fk_1" err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.Len(t, parser.droppedColumns, 3) require.True(t, parser.droppedColumns["b"]) require.True(t, parser.droppedColumns["d"]) require.True(t, parser.droppedColumns["e"]) } { parser := NewAlterTableParser() statement := "drop column b, drop bad statement, add column i int" err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.Len(t, parser.droppedColumns, 1) require.True(t, parser.droppedColumns["b"]) } } func TestParseAlterStatementRenameTable(t *testing.T) { { parser := NewAlterTableParser() statement := "drop column b" err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.False(t, parser.isRenameTable) } { parser := NewAlterTableParser() statement := "rename as something_else" err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.True(t, parser.isRenameTable) } { parser := NewAlterTableParser() statement := "drop column b, rename as something_else" err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.Equal(t, statement, parser.alterStatementOptions) require.True(t, parser.isRenameTable) } { parser := NewAlterTableParser() statement := "engine=innodb rename as something_else" err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.True(t, parser.isRenameTable) } { parser := NewAlterTableParser() statement := "rename as something_else, engine=innodb" err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.True(t, parser.isRenameTable) } } func TestParseAlterStatementExplicitTable(t *testing.T) { { parser := NewAlterTableParser() statement := "drop column b" err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.Equal(t, "", parser.explicitSchema) require.Equal(t, "", parser.explicitTable) require.Equal(t, "drop column b", parser.alterStatementOptions) require.Equal(t, []string{"drop column b"}, parser.alterTokens) } { parser := NewAlterTableParser() statement := "alter table tbl drop column b" err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.Equal(t, "", parser.explicitSchema) require.Equal(t, "tbl", parser.explicitTable) require.Equal(t, "drop column b", parser.alterStatementOptions) require.Equal(t, []string{"drop column b"}, parser.alterTokens) } { parser := NewAlterTableParser() statement := "alter table `tbl` drop column b" err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.Equal(t, parser.explicitSchema, "") require.Equal(t, parser.explicitTable, "tbl") require.Equal(t, parser.alterStatementOptions, "drop column b") require.Equal(t, parser.alterTokens, []string{"drop column b"}) } { parser := NewAlterTableParser() statement := "alter table `scm with spaces`.`tbl` drop column b" err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.Equal(t, parser.explicitSchema, "scm with spaces") require.Equal(t, parser.explicitTable, "tbl") require.Equal(t, parser.alterStatementOptions, "drop column b") require.Equal(t, parser.alterTokens, []string{"drop column b"}) } { parser := NewAlterTableParser() statement := "alter table `scm`.`tbl with spaces` drop column b" err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.Equal(t, parser.explicitSchema, "scm") require.Equal(t, parser.explicitTable, "tbl with spaces") require.Equal(t, parser.alterStatementOptions, "drop column b") require.Equal(t, parser.alterTokens, []string{"drop column b"}) } { parser := NewAlterTableParser() statement := "alter table `scm`.tbl drop column b" err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.Equal(t, parser.explicitSchema, "scm") require.Equal(t, parser.explicitTable, "tbl") require.Equal(t, parser.alterStatementOptions, "drop column b") require.Equal(t, parser.alterTokens, []string{"drop column b"}) } { parser := NewAlterTableParser() statement := "alter table scm.`tbl` drop column b" err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.Equal(t, parser.explicitSchema, "scm") require.Equal(t, parser.explicitTable, "tbl") require.Equal(t, parser.alterStatementOptions, "drop column b") require.Equal(t, parser.alterTokens, []string{"drop column b"}) } { parser := NewAlterTableParser() statement := "alter table scm.tbl drop column b" err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.Equal(t, parser.explicitSchema, "scm") require.Equal(t, parser.explicitTable, "tbl") require.Equal(t, parser.alterStatementOptions, "drop column b") require.Equal(t, parser.alterTokens, []string{"drop column b"}) } { parser := NewAlterTableParser() statement := "alter table scm.tbl drop column b, add index idx(i)" err := parser.ParseAlterStatement(statement) require.NoError(t, err) require.Equal(t, parser.explicitSchema, "scm") require.Equal(t, parser.explicitTable, "tbl") require.Equal(t, parser.alterStatementOptions, "drop column b, add index idx(i)") require.Equal(t, parser.alterTokens, []string{"drop column b", "add index idx(i)"}) } } func TestParseEnumValues(t *testing.T) { { s := "enum('red','green','blue','orange')" values := ParseEnumValues(s) require.Equal(t, values, "'red','green','blue','orange'") } { s := "('red','green','blue','orange')" values := ParseEnumValues(s) require.Equal(t, values, "('red','green','blue','orange')") } { s := "zzz" values := ParseEnumValues(s) require.Equal(t, values, "zzz") } } ================================================ FILE: go/sql/types.go ================================================ /* Copyright 2016 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package sql import ( "bytes" "fmt" "reflect" "strconv" "strings" ) type ColumnType int const ( UnknownColumnType ColumnType = iota TimestampColumnType DateTimeColumnType EnumColumnType MediumIntColumnType JSONColumnType FloatColumnType BinaryColumnType ) const maxMediumintUnsigned int32 = 16777215 type TimezoneConversion struct { ToTimezone string } type CharacterSetConversion struct { ToCharset string FromCharset string } type Column struct { Name string IsUnsigned bool IsVirtual bool Charset string // Type represents a subset of MySQL types // used for mapping columns to golang values. Type ColumnType EnumValues string timezoneConversion *TimezoneConversion enumToTextConversion bool // add Octet length for binary type, fix bytes with suffix "00" get clipped in mysql binlog. // https://github.com/github/gh-ost/issues/909 BinaryOctetLength uint charsetConversion *CharacterSetConversion CharacterSetName string Nullable bool MySQLType string } func (this *Column) convertArg(arg interface{}) interface{} { var arg2Bytes []byte if s, ok := arg.(string); ok { arg2Bytes = []byte(s) } else if b, ok := arg.([]uint8); ok { arg2Bytes = b } else { arg2Bytes = nil } if arg2Bytes != nil { if this.Charset != "" && this.charsetConversion == nil { arg = arg2Bytes } else { if encoding, ok := charsetEncodingMap[this.Charset]; ok { decodedBytes, _ := encoding.NewDecoder().Bytes(arg2Bytes) arg = string(decodedBytes) } } if this.Type == BinaryColumnType { size := len(arg2Bytes) if uint(size) < this.BinaryOctetLength { buf := bytes.NewBuffer(arg2Bytes) for i := uint(0); i < (this.BinaryOctetLength - uint(size)); i++ { buf.Write([]byte{0}) } arg = buf.Bytes() } } return arg } if this.IsUnsigned { if i, ok := arg.(int8); ok { return uint8(i) } if i, ok := arg.(int16); ok { return uint16(i) } if i, ok := arg.(int32); ok { if this.Type == MediumIntColumnType { // problem with mediumint is that it's a 3-byte type. There is no compatible golang type to match that. // So to convert from negative to positive we'd need to convert the value manually if i >= 0 { return i } return uint32(maxMediumintUnsigned + i + 1) } return uint32(i) } if i, ok := arg.(int64); ok { return strconv.FormatUint(uint64(i), 10) } if i, ok := arg.(int); ok { return uint(i) } } return arg } func NewColumns(names []string) []Column { result := make([]Column, len(names)) for i := range names { result[i].Name = names[i] } return result } func ParseColumns(names string) []Column { namesArray := strings.Split(names, ",") return NewColumns(namesArray) } // ColumnsMap maps a column name onto its ordinal position type ColumnsMap map[string]int func NewEmptyColumnsMap() ColumnsMap { columnsMap := make(map[string]int) return ColumnsMap(columnsMap) } func NewColumnsMap(orderedColumns []Column) ColumnsMap { columnsMap := NewEmptyColumnsMap() for i, column := range orderedColumns { columnsMap[column.Name] = i } return columnsMap } // ColumnList makes for a named list of columns type ColumnList struct { columns []Column Ordinals ColumnsMap } // NewColumnList creates an object given ordered list of column names func NewColumnList(names []string) *ColumnList { result := &ColumnList{ columns: NewColumns(names), } result.Ordinals = NewColumnsMap(result.columns) return result } // ParseColumnList parses a comma delimited list of column names func ParseColumnList(names string) *ColumnList { result := &ColumnList{ columns: ParseColumns(names), } result.Ordinals = NewColumnsMap(result.columns) return result } func (this *ColumnList) Columns() []Column { return this.columns } func (this *ColumnList) Names() []string { names := make([]string, len(this.columns)) for i := range this.columns { names[i] = this.columns[i].Name } return names } func (this *ColumnList) GetColumn(columnName string) *Column { if ordinal, ok := this.Ordinals[columnName]; ok { return &this.columns[ordinal] } return nil } func (this *ColumnList) SetUnsigned(columnName string) { this.GetColumn(columnName).IsUnsigned = true } func (this *ColumnList) IsUnsigned(columnName string) bool { return this.GetColumn(columnName).IsUnsigned } func (this *ColumnList) SetCharset(columnName string, charset string) { this.GetColumn(columnName).Charset = charset } func (this *ColumnList) GetCharset(columnName string) string { return this.GetColumn(columnName).Charset } func (this *ColumnList) SetColumnType(columnName string, columnType ColumnType) { this.GetColumn(columnName).Type = columnType } func (this *ColumnList) GetColumnType(columnName string) ColumnType { return this.GetColumn(columnName).Type } func (this *ColumnList) SetConvertDatetimeToTimestamp(columnName string, toTimezone string) { this.GetColumn(columnName).timezoneConversion = &TimezoneConversion{ToTimezone: toTimezone} } func (this *ColumnList) HasTimezoneConversion(columnName string) bool { return this.GetColumn(columnName).timezoneConversion != nil } func (this *ColumnList) SetEnumToTextConversion(columnName string) { this.GetColumn(columnName).enumToTextConversion = true } func (this *ColumnList) IsEnumToTextConversion(columnName string) bool { return this.GetColumn(columnName).enumToTextConversion } func (this *ColumnList) SetEnumValues(columnName string, enumValues string) { this.GetColumn(columnName).EnumValues = enumValues } func (this *ColumnList) String() string { return strings.Join(this.Names(), ",") } func (this *ColumnList) Equals(other *ColumnList) bool { return reflect.DeepEqual(this.Columns, other.Columns) } func (this *ColumnList) EqualsByNames(other *ColumnList) bool { return reflect.DeepEqual(this.Names(), other.Names()) } // IsSubsetOf returns 'true' when column names of this list are a subset of // another list, in arbitrary order (order agnostic) func (this *ColumnList) IsSubsetOf(other *ColumnList) bool { for _, column := range this.columns { if _, exists := other.Ordinals[column.Name]; !exists { return false } } return true } func (this *ColumnList) FilterBy(f func(Column) bool) *ColumnList { filteredCols := make([]Column, 0, len(this.columns)) for _, column := range this.columns { if f(column) { filteredCols = append(filteredCols, column) } } return &ColumnList{Ordinals: this.Ordinals, columns: filteredCols} } func (this *ColumnList) Len() int { return len(this.columns) } func (this *ColumnList) SetCharsetConversion(columnName string, fromCharset string, toCharset string) { this.GetColumn(columnName).charsetConversion = &CharacterSetConversion{FromCharset: fromCharset, ToCharset: toCharset} } // UniqueKey is the combination of a key's name and columns type UniqueKey struct { Name string NameInGhostTable string // Name of the corresponding key in the Ghost table in case it is being renamed Columns ColumnList HasNullable bool IsAutoIncrement bool } // IsPrimary checks if this unique key is primary func (this *UniqueKey) IsPrimary() bool { return this.Name == "PRIMARY" } func (this *UniqueKey) Len() int { return this.Columns.Len() } func (this *UniqueKey) String() string { description := this.Name if this.IsAutoIncrement { description = fmt.Sprintf("%s (auto_increment)", description) } return fmt.Sprintf("%s: %s; has nullable: %+v", description, this.Columns.Names(), this.HasNullable) } type ColumnValues struct { abstractValues []interface{} ValuesPointers []interface{} } func NewColumnValues(length int) *ColumnValues { result := &ColumnValues{ abstractValues: make([]interface{}, length), ValuesPointers: make([]interface{}, length), } for i := 0; i < length; i++ { result.ValuesPointers[i] = &result.abstractValues[i] } return result } func ToColumnValues(abstractValues []interface{}) *ColumnValues { result := &ColumnValues{ abstractValues: abstractValues, ValuesPointers: make([]interface{}, len(abstractValues)), } for i := 0; i < len(abstractValues); i++ { result.ValuesPointers[i] = &result.abstractValues[i] } return result } func (this *ColumnValues) AbstractValues() []interface{} { return this.abstractValues } func (this *ColumnValues) StringColumn(index int) string { val := this.AbstractValues()[index] if ints, ok := val.([]uint8); ok { return fmt.Sprintf("%x", ints) } return fmt.Sprintf("%+v", val) } func (this *ColumnValues) String() string { stringValues := []string{} for i := range this.AbstractValues() { stringValues = append(stringValues, this.StringColumn(i)) } return strings.Join(stringValues, ",") } func (this *ColumnValues) Clone() *ColumnValues { cv := NewColumnValues(len(this.abstractValues)) copy(cv.abstractValues, this.abstractValues) return cv } ================================================ FILE: go/sql/types_test.go ================================================ /* Copyright 2016 GitHub Inc. See https://github.com/github/gh-ost/blob/master/LICENSE */ package sql import ( "testing" "github.com/openark/golib/log" "github.com/stretchr/testify/require" ) func init() { log.SetLevel(log.ERROR) } func TestParseColumnList(t *testing.T) { names := "id,category,max_len" columnList := ParseColumnList(names) require.Equal(t, 3, columnList.Len()) require.Equal(t, []string{"id", "category", "max_len"}, columnList.Names()) require.Equal(t, 0, columnList.Ordinals["id"]) require.Equal(t, 1, columnList.Ordinals["category"]) require.Equal(t, 2, columnList.Ordinals["max_len"]) } func TestGetColumn(t *testing.T) { names := "id,category,max_len" columnList := ParseColumnList(names) { column := columnList.GetColumn("category") require.NotNil(t, column) require.Equal(t, column.Name, "category") } { column := columnList.GetColumn("no_such_column") require.Nil(t, column) } } func TestBinaryToString(t *testing.T) { id := []uint8{0x1b, 0x99} col := make([]interface{}, 1) col[0] = id cv := ToColumnValues(col) require.Equal(t, "1b99", cv.StringColumn(0)) } func TestConvertArgCharsetDecoding(t *testing.T) { latin1Bytes := []uint8{0x47, 0x61, 0x72, 0xe7, 0x6f, 0x6e, 0x20, 0x21} col := Column{ Charset: "latin1", charsetConversion: &CharacterSetConversion{ FromCharset: "latin1", ToCharset: "utf8mb4", }, } // Should decode []uint8 str := col.convertArg(latin1Bytes) require.Equal(t, "Garçon !", str) } func TestConvertArgBinaryColumnPadding(t *testing.T) { // Test that binary columns are padded with trailing zeros to their declared length. // This is needed because MySQL's binlog strips trailing 0x00 bytes from binary values. // See https://github.com/github/gh-ost/issues/909 // Simulates a binary(20) column where binlog delivered only 18 bytes // (trailing zeros were stripped) truncatedValue := []uint8{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, // 18 bytes, missing 2 trailing zeros } col := Column{ Name: "bin_col", Type: BinaryColumnType, BinaryOctetLength: 20, } result := col.convertArg(truncatedValue) resultBytes := result.([]byte) require.Equal(t, 20, len(resultBytes), "binary column should be padded to declared length") // First 18 bytes should be unchanged require.Equal(t, truncatedValue, resultBytes[:18]) // Last 2 bytes should be zeros require.Equal(t, []byte{0x00, 0x00}, resultBytes[18:]) } func TestConvertArgBinaryColumnNoPaddingWhenFull(t *testing.T) { // When binary value is already at full length, no padding should occur fullValue := []uint8{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, // 20 bytes } col := Column{ Name: "bin_col", Type: BinaryColumnType, BinaryOctetLength: 20, } result := col.convertArg(fullValue) resultBytes := result.([]byte) require.Equal(t, 20, len(resultBytes)) require.Equal(t, fullValue, resultBytes) } ================================================ FILE: go.mod ================================================ module github.com/github/gh-ost go 1.23.0 require ( github.com/go-ini/ini v1.67.0 github.com/go-mysql-org/go-mysql v1.11.0 github.com/go-sql-driver/mysql v1.8.1 github.com/google/uuid v1.6.0 github.com/hashicorp/go-version v1.7.0 github.com/openark/golib v0.0.0-20210531070646-355f37940af8 github.com/stretchr/testify v1.10.0 github.com/testcontainers/testcontainers-go v0.37.0 github.com/testcontainers/testcontainers-go/modules/mysql v0.37.0 golang.org/x/net v0.38.0 golang.org/x/sync v0.13.0 golang.org/x/term v0.31.0 golang.org/x/text v0.24.0 ) require ( dario.cat/mergo v1.0.1 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v28.0.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/ebitengine/purego v0.8.2 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/klauspost/compress v1.17.8 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.10 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/user v0.3.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb // indirect github.com/pingcap/log v1.1.1-0.20230317032135-a0d097d16e22 // indirect github.com/pingcap/tidb/pkg/parser v0.0.0-20241118164214-4f047be191be // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v4 v4.25.1 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/siddontang/go-log v0.0.0-20180807004314-8d05993dda07 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.35.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/sdk v1.21.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.37.0 // indirect golang.org/x/sys v0.32.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect google.golang.org/protobuf v1.35.2 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) ================================================ FILE: go.sum ================================================ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 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/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0= github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-mysql-org/go-mysql v1.11.0 h1:Y0ooXu2UtbjsgpfjFBXZEvidEl1q8n0ESxej0zZ78Zc= github.com/go-mysql-org/go-mysql v1.11.0/go.mod h1:y/7aggbs+Io8rPVerIjTe1+nMgt8q5tBIxIc+qQnE0k= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 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/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/openark/golib v0.0.0-20210531070646-355f37940af8 h1:9ciIHNuyFqRWi9NpMNw9sVLB6z1ItpP5ZhTY9Q1xVu4= github.com/openark/golib v0.0.0-20210531070646-355f37940af8/go.mod h1:1jj8x1eDVZxgc/Z4VyamX4qTbAdHPUQA6NeVtCd8Sl8= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb h1:3pSi4EDG6hg0orE1ndHkXvX6Qdq2cZn8gAPir8ymKZk= github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg= github.com/pingcap/log v1.1.1-0.20230317032135-a0d097d16e22 h1:2SOzvGvE8beiC1Y4g9Onkvu6UmuBBOeWRGQEjJaT/JY= github.com/pingcap/log v1.1.1-0.20230317032135-a0d097d16e22/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4= github.com/pingcap/tidb/pkg/parser v0.0.0-20241118164214-4f047be191be h1:t5EkCmZpxLCig5GQA0AZG47aqsuL5GTsJeeUD+Qfies= github.com/pingcap/tidb/pkg/parser v0.0.0-20241118164214-4f047be191be/go.mod h1:Hju1TEWZvrctQKbztTRwXH7rd41Yq0Pgmq4PrEKcq7o= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs= github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/siddontang/go-log v0.0.0-20180807004314-8d05993dda07 h1:oI+RNwuC9jF2g2lP0u0cVEEZrc/AYBCuFdvwrLWM/6Q= github.com/siddontang/go-log v0.0.0-20180807004314-8d05993dda07/go.mod h1:yFdBgwXP24JziuRl2NMUahT7nGLNOKi1SIiFxMttVD4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg= github.com/testcontainers/testcontainers-go v0.37.0/go.mod h1:QPzbxZhQ6Bclip9igjLFj6z0hs01bU8lrl2dHQmgFGM= github.com/testcontainers/testcontainers-go/modules/mysql v0.37.0 h1:LqUos1oR5iuuzorFnSvxsHNdYdCHB/DfI82CuT58wbI= github.com/testcontainers/testcontainers-go/modules/mysql v0.37.0/go.mod h1:vHEEHx5Kf+uq5hveaVAMrTzPY8eeRZcKcl23MRw5Tkc= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e h1:Ao9GzfUMPH3zjVfzXG5rlWlk+Q8MXWKwWpwVQE1MXfw= google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= ================================================ FILE: localtests/alter-charset/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, t1 varchar(128) charset latin1 collate latin1_swedish_ci, t2 varchar(128) charset latin1 collate latin1_swedish_ci, tutf8 varchar(128) charset utf8, tutf8mb4 varchar(128) charset utf8mb4, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, md5(rand()), md5(rand()), md5(rand()), md5(rand())); insert into gh_ost_test values (null, 'átesting', 'átesting', 'átesting', 'átesting'); insert into gh_ost_test values (null, 'testátest', 'testátest', 'testátest', '🍻😀'); end ;; ================================================ FILE: localtests/alter-charset/extra_args ================================================ --alter='MODIFY `t1` varchar(128) CHARACTER SET utf8mb4 NOT NULL, MODIFY `t2` varchar(128) CHARACTER SET latin2 NOT NULL, MODIFY `tutf8` varchar(128) CHARACTER SET latin1 NOT NULL' ================================================ FILE: localtests/alter-charset-all-dml/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, t1 varchar(128) charset latin1 collate latin1_swedish_ci, t2 varchar(128) charset latin1 collate latin1_swedish_ci, tutf8 varchar(128) charset utf8, tutf8mb4 varchar(128) charset utf8mb4, random_value varchar(128) charset ascii, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, md5(rand()), md5(rand()), md5(rand()), md5(rand()), md5(rand())); insert into gh_ost_test values (null, 'átesting', 'átesting', 'átesting', 'átesting', md5(rand())); insert into gh_ost_test values (null, 'átesting_del', 'átesting', 'átesting', 'átesting', md5(rand())); insert into gh_ost_test values (null, 'testátest', 'testátest', 'testátest', '🍻😀', md5(rand())); update gh_ost_test set t1='átesting2' where t1='átesting' order by id desc limit 1; delete from gh_ost_test where t1='átesting_del' order by id desc limit 1; end ;; ================================================ FILE: localtests/alter-charset-all-dml/extra_args ================================================ --alter='MODIFY `t1` varchar(128) CHARACTER SET utf8mb4 NOT NULL, MODIFY `t2` varchar(128) CHARACTER SET latin2 NOT NULL, MODIFY `tutf8` varchar(128) CHARACTER SET latin1 NOT NULL' ================================================ FILE: localtests/attempt-instant-ddl/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, color varchar(32), primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; insert into gh_ost_test values (null, 11, 'red'); insert into gh_ost_test values (null, 13, 'green'); insert into gh_ost_test values (null, 17, 'blue'); ================================================ FILE: localtests/attempt-instant-ddl/extra_args ================================================ --attempt-instant-ddl ================================================ FILE: localtests/autoinc-copy-deletes/create.sql ================================================ drop event if exists gh_ost_test; drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, primary key(id) ) auto_increment=1; insert into gh_ost_test values (NULL, 11); insert into gh_ost_test values (NULL, 13); insert into gh_ost_test values (NULL, 17); insert into gh_ost_test values (NULL, 23); insert into gh_ost_test values (NULL, 29); insert into gh_ost_test values (NULL, 31); insert into gh_ost_test values (NULL, 37); delete from gh_ost_test where id>=5; ================================================ FILE: localtests/autoinc-copy-deletes/expect_table_structure ================================================ AUTO_INCREMENT=8 ================================================ FILE: localtests/autoinc-copy-deletes-user-defined/create.sql ================================================ drop event if exists gh_ost_test; drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, primary key(id) ) auto_increment=1; insert into gh_ost_test values (NULL, 11); insert into gh_ost_test values (NULL, 13); insert into gh_ost_test values (NULL, 17); insert into gh_ost_test values (NULL, 23); insert into gh_ost_test values (NULL, 29); insert into gh_ost_test values (NULL, 31); insert into gh_ost_test values (NULL, 37); delete from gh_ost_test where id>=5; ================================================ FILE: localtests/autoinc-copy-deletes-user-defined/expect_table_structure ================================================ AUTO_INCREMENT=7 ================================================ FILE: localtests/autoinc-copy-deletes-user-defined/extra_args ================================================ --alter='AUTO_INCREMENT=7' ================================================ FILE: localtests/autoinc-copy-simple/create.sql ================================================ drop event if exists gh_ost_test; drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, primary key(id) ) auto_increment=1; insert into gh_ost_test values (NULL, 11); insert into gh_ost_test values (NULL, 13); insert into gh_ost_test values (NULL, 17); insert into gh_ost_test values (NULL, 23); ================================================ FILE: localtests/autoinc-copy-simple/expect_table_structure ================================================ AUTO_INCREMENT=5 ================================================ FILE: localtests/autoinc-zero-value/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, primary key(id) ) auto_increment=1; set session sql_mode='NO_AUTO_VALUE_ON_ZERO'; insert into gh_ost_test values (0, 23); ================================================ FILE: localtests/bigint-change-nullable/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id bigint auto_increment, val bigint not null, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 18446744073709551615); insert into gh_ost_test values (null, 18446744073709551614); insert into gh_ost_test values (null, 18446744073709551613); end ;; ================================================ FILE: localtests/bigint-change-nullable/extra_args ================================================ --alter="change val val bigint" ================================================ FILE: localtests/binary-to-varbinary/create.sql ================================================ -- Test for https://github.com/github/gh-ost/issues/909 variant: -- Binary columns with trailing zeros should preserve their values -- when migrating from binary(N) to varbinary(M), even for rows -- modified during migration via binlog events. drop table if exists gh_ost_test; create table gh_ost_test ( id int NOT NULL AUTO_INCREMENT, info varchar(255) NOT NULL, data binary(20) NOT NULL, PRIMARY KEY (id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin -- Insert rows where data has trailing zeros (will be stripped by binlog) INSERT INTO gh_ost_test (info, data) VALUES ('insert-during-1', X'aabbccdd00000000000000000000000000000000'); INSERT INTO gh_ost_test (info, data) VALUES ('insert-during-2', X'11223344556677889900000000000000000000ee'); -- Update existing rows to values with trailing zeros UPDATE gh_ost_test SET data = X'ffeeddcc00000000000000000000000000000000' WHERE info = 'update-target-1'; UPDATE gh_ost_test SET data = X'aabbccdd11111111111111111100000000000000' WHERE info = 'update-target-2'; end ;; -- Pre-existing rows (copied via rowcopy, not binlog - these should work fine) INSERT INTO gh_ost_test (info, data) VALUES ('pre-existing-1', X'01020304050607080910111213141516171819ff'), ('pre-existing-2', X'0102030405060708091011121314151617181900'), ('update-target-1', X'ffffffffffffffffffffffffffffffffffffffff'), ('update-target-2', X'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'); ================================================ FILE: localtests/binary-to-varbinary/extra_args ================================================ --alter="MODIFY data varbinary(32)" ================================================ FILE: localtests/bit-add/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11); insert into gh_ost_test values (null, 13); end ;; ================================================ FILE: localtests/bit-add/extra_args ================================================ --alter="add column is_good bit null default 0" ================================================ FILE: localtests/bit-add/ghost_columns ================================================ id, i ================================================ FILE: localtests/bit-add/orig_columns ================================================ id, i ================================================ FILE: localtests/bit-dml/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, is_good bit null default 0, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, 0); insert into gh_ost_test values (null, 13, 1); insert into gh_ost_test values (null, 17, 1); update gh_ost_test set is_good=0 where i=13 order by id desc limit 1; end ;; ================================================ FILE: localtests/bit-dml/extra_args ================================================ --alter="modify column is_good bit not null default 0" --approve-renamed-columns ================================================ FILE: localtests/compound-pk/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, v varchar(128), updated tinyint unsigned default 0, primary key(id, v) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, 'eleven', 0); update gh_ost_test set updated = 1 where i = 11 order by id desc limit 1; insert into gh_ost_test values (null, 13, 'thirteen', 0); update gh_ost_test set updated = 1 where i = 13 order by id desc limit 1; insert into gh_ost_test values (null, 17, 'seventeen', 0); update gh_ost_test set updated = 1 where i = 17 order by id desc limit 1; insert into gh_ost_test values (null, 19, 'nineteen', 0); update gh_ost_test set updated = 1 where i = 19 order by id desc limit 1; insert into gh_ost_test values (null, 23, 'twenty three', 0); update gh_ost_test set updated = 1 where i = 23 order by id desc limit 1; insert into gh_ost_test values (null, 29, 'twenty nine', 0); insert into gh_ost_test values (null, 31, 'thirty one', 0); insert into gh_ost_test values (null, 37, 'thirty seven', 0); insert into gh_ost_test values (null, 41, 'forty one', 0); delete from gh_ost_test where i = 31 order by id desc limit 1; end ;; ================================================ FILE: localtests/compound-pk-ts/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, ts0 timestamp(6) default current_timestamp(6), updated tinyint unsigned default 0, primary key(id, ts0) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, sysdate(6), 0); update gh_ost_test set updated = 1 where i = 11 order by id desc limit 1; insert into gh_ost_test values (null, 13, sysdate(6), 0); update gh_ost_test set updated = 1 where i = 13 order by id desc limit 1; insert into gh_ost_test values (null, 17, sysdate(6), 0); update gh_ost_test set updated = 1 where i = 17 order by id desc limit 1; insert into gh_ost_test values (null, 19, sysdate(6), 0); update gh_ost_test set updated = 1 where i = 19 order by id desc limit 1; insert into gh_ost_test values (null, 23, sysdate(6), 0); update gh_ost_test set updated = 1 where i = 23 order by id desc limit 1; insert into gh_ost_test values (null, 29, sysdate(6), 0); insert into gh_ost_test values (null, 31, sysdate(6), 0); insert into gh_ost_test values (null, 37, sysdate(6), 0); insert into gh_ost_test values (null, 41, sysdate(6), 0); delete from gh_ost_test where i = 31 order by id desc limit 1; end ;; ================================================ FILE: localtests/convert-utf8mb4/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, t varchar(128) charset utf8 collate utf8_general_ci, tl varchar(128) charset latin1 not null, ta varchar(128) charset ascii not null, primary key(id) ) auto_increment=1; insert into gh_ost_test values (null, 'Hello world, Καλημέρα κόσμε, コンニチハ', 'átesting0', 'initial'); drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, md5(rand()), 'átesting-a', 'a'); insert into gh_ost_test values (null, 'novo proprietário', 'átesting-b', 'b'); insert into gh_ost_test values (null, '2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm', 'átesting-c', 'c'); insert into gh_ost_test values (null, 'usuário', 'átesting-x', 'x'); delete from gh_ost_test where ta='x' order by id desc limit 1; end ;; ================================================ FILE: localtests/convert-utf8mb4/extra_args ================================================ --alter='convert to character set utf8mb4' ================================================ FILE: localtests/copy-retries-exhausted/after.sql ================================================ set global max_binlog_cache_size = 1073741824; -- 1GB ================================================ FILE: localtests/copy-retries-exhausted/before.sql ================================================ set global max_binlog_cache_size = 1024; ================================================ FILE: localtests/copy-retries-exhausted/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, name mediumtext not null, primary key (id) ) auto_increment=1; insert into gh_ost_test (name) select repeat('a', 1500) from information_schema.columns cross join information_schema.tables limit 1000; ================================================ FILE: localtests/copy-retries-exhausted/expect_failure ================================================ Multi-statement transaction required more than 'max_binlog_cache_size' bytes of storage ================================================ FILE: localtests/copy-retries-exhausted/extra_args ================================================ --alter "modify column name mediumtext" --default-retries=1 --chunk-size=1000 ================================================ FILE: localtests/datetime/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, dt0 datetime default current_timestamp, dt1 datetime, dt2 datetime, updated tinyint unsigned default 0, primary key(id), key i_idx(i) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, null, now(), now(), 0); update gh_ost_test set dt2=now() + interval 1 minute, updated = 1 where i = 11 order by id desc limit 1; insert into gh_ost_test values (null, 13, null, now(), now(), 0); update gh_ost_test set dt2=now() + interval 1 minute, updated = 1 where i = 13 order by id desc limit 1; insert into gh_ost_test values (null, 17, null, now(), now(), 0); update gh_ost_test set dt2=now() + interval 1 minute, updated = 1 where i = 17 order by id desc limit 1; insert into gh_ost_test values (null, 19, null, now(), now(), 0); update gh_ost_test set dt2=now() + interval 1 minute, updated = 1 where i = 19 order by id desc limit 1; insert into gh_ost_test values (null, 23, null, now(), now(), 0); update gh_ost_test set dt2=now() + interval 1 minute, updated = 1 where i = 23 order by id desc limit 1; end ;; ================================================ FILE: localtests/datetime-1970/create.sql ================================================ set session time_zone='+00:00'; drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, create_time timestamp NULL DEFAULT '0000-00-00 00:00:00', update_time timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, counter int(10) unsigned DEFAULT NULL, primary key(id) ) auto_increment=1; set session time_zone='+00:00'; insert into gh_ost_test values (1, '0000-00-00 00:00:00', now(), 0); drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin set session time_zone='+00:00'; update gh_ost_test set counter = counter + 1 where id = 1; end ;; ================================================ FILE: localtests/datetime-1970/extra_args ================================================ --alter='add column name varchar(1)' ================================================ FILE: localtests/datetime-1970/ghost_columns ================================================ id, create_time, update_time, counter ================================================ FILE: localtests/datetime-1970/orig_columns ================================================ id, create_time, update_time, counter ================================================ FILE: localtests/datetime-1970/sql_mode ================================================ ================================================ FILE: localtests/datetime-submillis/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, dt0 datetime(6), dt1 datetime(6), ts2 timestamp(6), updated tinyint unsigned default 0, primary key(id), key i_idx(i) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve disable on slave do begin insert into gh_ost_test values (null, 11, now(), now(), now(), 0); update gh_ost_test set dt1='2016-10-31 11:22:33.444', updated = 1 where i = 11 order by id desc limit 1; insert into gh_ost_test values (null, 13, now(), now(), now(), 0); update gh_ost_test set ts1='2016-11-01 11:22:33.444', updated = 1 where i = 13 order by id desc limit 1; end ;; ================================================ FILE: localtests/datetime-submillis-zeroleading/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, dt0 datetime(6), dt1 datetime(6), ts2 timestamp(6), updated tinyint unsigned default 0, primary key(id), key i_idx(i) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, '2016-10-31 11:22:33.0123', now(), '2016-10-31 11:22:33.0369', 0); update gh_ost_test set dt1='2016-10-31 11:22:33.0246', updated = 1 where i = 11 order by id desc limit 1; insert into gh_ost_test values (null, 13, '2016-10-31 11:22:33.0123', '2016-10-31 11:22:33.789', '2016-10-31 11:22:33.0369', 0); end ;; ================================================ FILE: localtests/datetime-to-timestamp/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int unsigned auto_increment, i int not null, ts0 timestamp default current_timestamp, ts1 timestamp null, dt2 datetime, t datetime null, updated tinyint unsigned default 0, primary key(id), key i_idx(i) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 7, null, now(), now(), '2010-10-20 10:20:30', 0); insert into gh_ost_test values (null, 11, null, now(), now(), '2010-10-20 10:20:30', 0); update gh_ost_test set dt2=now() + interval 1 minute, updated = 1 where i = 11 order by id desc limit 1; insert into gh_ost_test values (null, 13, null, now(), now(), '2010-10-20 10:20:30', 0); update gh_ost_test set t=t + interval 1 minute, updated = 1 where i = 13 order by id desc limit 1; end ;; ================================================ FILE: localtests/datetime-to-timestamp/extra_args ================================================ --alter="change column t t timestamp null" ================================================ FILE: localtests/datetime-to-timestamp-pk-fail/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int unsigned auto_increment, i int not null, ts0 timestamp default current_timestamp, ts1 timestamp null, dt2 datetime, t datetime default current_timestamp, updated tinyint unsigned default 0, primary key(id, t), key i_idx(i) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 7, null, now(), now(), '2010-10-20 10:20:30', 0); insert into gh_ost_test values (null, 11, null, now(), now(), '2010-10-20 10:20:30', 0); update gh_ost_test set dt2=now() + interval 1 minute, updated = 1 where i = 11 order by id desc limit 1; insert into gh_ost_test values (null, 13, null, now(), now(), '2010-10-20 10:20:30', 0); update gh_ost_test set t=t + interval 1 minute, updated = 1 where i = 13 order by id desc limit 1; end ;; ================================================ FILE: localtests/datetime-to-timestamp-pk-fail/expect_failure ================================================ No support at this time for converting a column from DATETIME to TIMESTAMP that is also part of the chosen unique key ================================================ FILE: localtests/datetime-to-timestamp-pk-fail/extra_args ================================================ --alter="change column t t timestamp default current_timestamp" ================================================ FILE: localtests/datetime-with-zero/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int unsigned auto_increment, i int not null, dt datetime, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 7, '2010-10-20 10:20:30'); end ;; ================================================ FILE: localtests/datetime-with-zero/extra_args ================================================ --allow-zero-in-date --alter="change column dt dt datetime not null default '1970-00-00 00:00:00'" ================================================ FILE: localtests/decimal/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, dec0 decimal(65,30) unsigned NOT NULL DEFAULT '0.000000000000000000000000000000', dec1 decimal(65,30) unsigned NOT NULL DEFAULT '1.000000000000000000000000000000', primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 0.0, 0.0); insert into gh_ost_test values (null, 2.0, 4.0); insert into gh_ost_test values (null, 99999999999999999999999999999999999.000, 6.0); update gh_ost_test set dec1=4.5 where dec2=4.0 order by id desc limit 1; end ;; ================================================ FILE: localtests/discard-fk/create.sql ================================================ drop table if exists gh_ost_test_child; drop table if exists gh_ost_test; drop table if exists gh_ost_test_fk_parent; create table gh_ost_test_fk_parent ( id int auto_increment, ts timestamp, primary key(id) ); create table gh_ost_test ( id int auto_increment, i int not null, parent_id int not null, primary key(id), constraint test_fk foreign key (parent_id) references gh_ost_test_fk_parent (id) on delete no action ) auto_increment=1; insert into gh_ost_test_fk_parent (id) values (1),(2),(3); drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, 1); insert into gh_ost_test values (null, 13, 2); insert into gh_ost_test values (null, 17, 3); end ;; ================================================ FILE: localtests/discard-fk/extra_args ================================================ --discard-foreign-keys ================================================ FILE: localtests/discard-fk/ignore_versions ================================================ Percona ================================================ FILE: localtests/docker-compose.yml ================================================ services: mysql-primary: image: $TEST_MYSQL_IMAGE container_name: mysql-primary command: --server-id=1 --log-bin=mysql-bin --binlog-format=row --gtid-mode=ON --enforce-gtid-consistency=ON --character-set-server=utf8mb4 $MYSQL_NATIVE_PASSWORD_FLAG environment: MYSQL_ROOT_PASSWORD: opensesame MYSQL_ROOT_HOST: '%' MYSQL_DATABASE: test MYSQL_TCP_PORT: 3307 INIT_ROCKSDB: 1 # for percona-server ports: - '3307:3307' expose: - '3307' mysql-replica: image: $TEST_MYSQL_IMAGE container_name: mysql-replica command: --server-id=2 --log-bin=mysql-bin --binlog-format=row --gtid-mode=ON --enforce-gtid-consistency=ON --log-slave-updates=ON --character-set-server=utf8mb4 $MYSQL_NATIVE_PASSWORD_FLAG environment: MYSQL_ROOT_PASSWORD: opensesame MYSQL_ROOT_HOST: '%' MYSQL_DATABASE: test MYSQL_TCP_PORT: 3308 INIT_ROCKSDB: 1 # for percona-server ports: - '3308:3308' expose: - '3308' ================================================ FILE: localtests/drop-null-add-not-null/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, c1 int null, c2 int not null, primary key (id) ) auto_increment=1; insert into gh_ost_test values (null, null, 17); insert into gh_ost_test values (null, null, 19); drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert ignore into gh_ost_test values (101, 11, 23); insert ignore into gh_ost_test values (102, 13, 23); insert into gh_ost_test values (null, 17, 23); insert into gh_ost_test values (null, null, 29); set @last_insert_id := last_insert_id(); -- update gh_ost_test set c2=c2+@last_insert_id where id=@last_insert_id order by id desc limit 1; delete from gh_ost_test where id=1; delete from gh_ost_test where c1=13; -- id=2 end ;; ================================================ FILE: localtests/drop-null-add-not-null/extra_args ================================================ --alter="drop column c1, add column c1 int not null default 47" ================================================ FILE: localtests/drop-null-add-not-null/ghost_columns ================================================ c2 ================================================ FILE: localtests/drop-null-add-not-null/orig_columns ================================================ c2 ================================================ FILE: localtests/enum/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, e enum('red', 'green', 'blue', 'orange') null default null collate 'utf8_bin', primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, 'red'); insert into gh_ost_test values (null, 13, 'green'); insert into gh_ost_test values (null, 17, 'blue'); set @last_insert_id := last_insert_id(); update gh_ost_test set e='orange' where id = @last_insert_id; insert into gh_ost_test values (null, 23, null); set @last_insert_id := last_insert_id(); update gh_ost_test set i=i+1, e=null where id = @last_insert_id; end ;; ================================================ FILE: localtests/enum/extra_args ================================================ --alter="change e e enum('red', 'green', 'blue', 'orange', 'yellow') null default null collate 'utf8_bin'" ================================================ FILE: localtests/enum-pk/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, e enum('red', 'green', 'blue', 'orange') not null default 'red' collate 'utf8_bin', primary key(id, e) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, 'red'); set @last_insert_id := last_insert_id(); insert into gh_ost_test values (@last_insert_id, 11, 'green'); insert into gh_ost_test values (null, 13, 'green'); insert into gh_ost_test values (null, 17, 'blue'); set @last_insert_id := last_insert_id(); update gh_ost_test set e='orange' where id = @last_insert_id; insert into gh_ost_test values (null, 23, null); set @last_insert_id := last_insert_id(); update gh_ost_test set i=i+1, e=null where id = @last_insert_id; end ;; ================================================ FILE: localtests/enum-to-varchar/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, e enum('red', 'green', 'blue', 'orange') null default null collate 'utf8_bin', primary key(id) ) auto_increment=1; insert into gh_ost_test values (null, 7, 'red'); drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, 'red'); insert into gh_ost_test values (null, 13, 'green'); insert into gh_ost_test values (null, 17, 'blue'); set @last_insert_id := last_insert_id(); update gh_ost_test set e='orange' where id = @last_insert_id; end ;; ================================================ FILE: localtests/enum-to-varchar/extra_args ================================================ --alter="change e e varchar(32) not null default ''" ================================================ FILE: localtests/existing-datetime-with-zero/create.sql ================================================ set session sql_mode=''; drop table if exists gh_ost_test; create table gh_ost_test ( id int unsigned auto_increment, i int not null, dt datetime not null default '1970-00-00 00:00:00', primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 7, '2010-10-20 10:20:30'); end ;; ================================================ FILE: localtests/existing-datetime-with-zero/extra_args ================================================ --allow-zero-in-date --alter="engine=innodb" ================================================ FILE: localtests/fail-datetime-with-zero/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int unsigned auto_increment, i int not null, dt datetime, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 7, '2010-10-20 10:20:30'); end ;; ================================================ FILE: localtests/fail-datetime-with-zero/expect_failure ================================================ Invalid default value for 'dt' ================================================ FILE: localtests/fail-datetime-with-zero/extra_args ================================================ --alter="change column dt dt datetime not null default '1970-00-00 00:00:00'" ================================================ FILE: localtests/fail-drop-pk/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, ts timestamp, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, now()); insert into gh_ost_test values (null, 13, now()); insert into gh_ost_test values (null, 17, now()); end ;; ================================================ FILE: localtests/fail-drop-pk/expect_failure ================================================ No PRIMARY nor UNIQUE key found in table ================================================ FILE: localtests/fail-drop-pk/extra_args ================================================ --alter="change id id int, drop primary key" ================================================ FILE: localtests/fail-existing-datetime-with-zero/create.sql ================================================ set session sql_mode=''; drop table if exists gh_ost_test; create table gh_ost_test ( id int unsigned auto_increment, i int not null, dt datetime not null default '1970-00-00 00:00:00', primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 7, '2010-10-20 10:20:30'); end ;; ================================================ FILE: localtests/fail-existing-datetime-with-zero/expect_failure ================================================ Invalid default value for 'dt' ================================================ FILE: localtests/fail-existing-datetime-with-zero/extra_args ================================================ --alter="engine=innodb" ================================================ FILE: localtests/fail-fk/create.sql ================================================ drop table if exists gh_ost_test_child; drop table if exists gh_ost_test; drop table if exists gh_ost_test_fk_parent; create table gh_ost_test_fk_parent ( id int auto_increment, ts timestamp, primary key(id) ); create table gh_ost_test ( id int auto_increment, i int not null, parent_id int not null, primary key(id), constraint test_fk foreign key (parent_id) references gh_ost_test_fk_parent (id) on delete no action ) auto_increment=1; insert into gh_ost_test_fk_parent (id) values (1),(2),(3); drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, 1); insert into gh_ost_test values (null, 13, 2); insert into gh_ost_test values (null, 17, 3); end ;; ================================================ FILE: localtests/fail-fk/expect_failure ================================================ Child-side foreign keys are not supported. Bailing out ================================================ FILE: localtests/fail-fk/ignore_versions ================================================ Percona ================================================ FILE: localtests/fail-fk-parent/create.sql ================================================ drop table if exists gh_ost_test_child; drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, primary key(id) ) engine=innodb auto_increment=1; create table gh_ost_test_child ( id int auto_increment, i int not null, parent_id int not null, constraint test_fk foreign key (parent_id) references gh_ost_test (id) on delete no action, primary key(id) ) engine=innodb; insert into gh_ost_test (id) values (1),(2),(3); drop event if exists gh_ost_test; drop event if exists gh_ost_test_cleanup; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test_child values (null, 11, 1); insert into gh_ost_test_child values (null, 13, 2); insert into gh_ost_test_child values (null, 17, 3); end ;; create event gh_ost_test_cleanup on schedule at current_timestamp + interval 60 second on completion not preserve enable do begin drop table if exists gh_ost_test_child; end ;; ================================================ FILE: localtests/fail-fk-parent/destroy.sql ================================================ drop table if exists gh_ost_test_child; ================================================ FILE: localtests/fail-fk-parent/expect_failure ================================================ Parent-side foreign keys are not supported ================================================ FILE: localtests/fail-fk-parent/extra_args ================================================ --discard-foreign-keys ================================================ FILE: localtests/fail-fk-parent/ignore_versions ================================================ Percona ================================================ FILE: localtests/fail-float-unique-key/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( f float, i int not null, ts timestamp default current_timestamp, dt datetime, key i_idx(i), unique key f_uidx(f) ) auto_increment=1; drop event if exists gh_ost_test; ================================================ FILE: localtests/fail-float-unique-key/expect_failure ================================================ No shared unique key can be found ================================================ FILE: localtests/fail-float-unique-key/extra_args ================================================ --alter="add column v varchar(32)" ================================================ FILE: localtests/fail-no-shared-uk/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int not null, i int not null, ts timestamp, primary key(id) ); drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, now()); insert into gh_ost_test values (null, 13, now()); insert into gh_ost_test values (null, 17, now()); end ;; ================================================ FILE: localtests/fail-no-shared-uk/expect_failure ================================================ No shared unique key can be found after ALTER ================================================ FILE: localtests/fail-no-shared-uk/extra_args ================================================ --alter="drop primary key, add primary key (i)" ================================================ FILE: localtests/fail-no-unique-key/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( i int not null, ts timestamp default current_timestamp, dt datetime, key i_idx(i) ) auto_increment=1; drop event if exists gh_ost_test; ================================================ FILE: localtests/fail-no-unique-key/expect_failure ================================================ No PRIMARY nor UNIQUE key found in table ================================================ FILE: localtests/fail-no-unique-key/extra_args ================================================ --alter="add column v varchar(32)" ================================================ FILE: localtests/fail-rename-table/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, ts timestamp, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, now()); insert into gh_ost_test values (null, 13, now()); insert into gh_ost_test values (null, 17, now()); end ;; ================================================ FILE: localtests/fail-rename-table/expect_failure ================================================ ALTER statement seems to RENAME the table ================================================ FILE: localtests/fail-rename-table/extra_args ================================================ --alter="rename as something_else" ================================================ FILE: localtests/fail-update-pk-column/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, primary key(id) ) auto_increment=1; insert into gh_ost_test values (null, 101); insert into gh_ost_test values (null, 102); insert into gh_ost_test values (null, 103); insert into gh_ost_test values (null, 104); insert into gh_ost_test values (null, 105); insert into gh_ost_test values (null, 106); insert into gh_ost_test values (null, 107); insert into gh_ost_test values (null, 108); insert into gh_ost_test values (null, 109); insert into gh_ost_test values (null, 110); insert into gh_ost_test values (null, 111); insert into gh_ost_test values (null, 112); insert into gh_ost_test values (null, 113); insert into gh_ost_test values (null, 114); insert into gh_ost_test values (null, 115); insert into gh_ost_test values (null, 116); insert into gh_ost_test values (null, 117); insert into gh_ost_test values (null, 118); insert into gh_ost_test values (null, 119); insert into gh_ost_test values (null, 120); insert into gh_ost_test values (null, 121); insert into gh_ost_test values (null, 122); insert into gh_ost_test values (null, 123); insert into gh_ost_test values (null, 124); insert into gh_ost_test values (null, 125); insert into gh_ost_test values (null, 126); insert into gh_ost_test values (null, 127); insert into gh_ost_test values (null, 128); insert into gh_ost_test values (null, 129); drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp + interval 3 second ends current_timestamp + interval 60 second on completion not preserve enable do begin update gh_ost_test set id=-2 where id=21; update gh_ost_test set id=55 where id=22; update gh_ost_test set id=23 where id=23; update gh_ost_test set i=5024 where id=24; end ;; ================================================ FILE: localtests/gbk-charset/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(512) DEFAULT NULL, v varchar(255) DEFAULT NULL COMMENT '添加普通列测试', PRIMARY KEY (id) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=gbk; insert into gh_ost_test values (null, 'gbk-test-initial', '添加普通列测试-添加普通列测试'); insert into gh_ost_test values (null, 'gbk-test-initial', '添加普通列测试-添加普通列测试'); drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test (name) values ('gbk-test-default'); insert into gh_ost_test values (null, 'gbk-test', '添加普通列测试-添加普通列测试'); update gh_ost_test set v='添加普通列测试' where v='添加普通列测试-添加普通列测试' order by id desc limit 1; end ;; ================================================ FILE: localtests/gbk-charset/extra_args ================================================ ================================================ FILE: localtests/generated-columns/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, a int not null, b int not null, sum_ab int as (a + b) virtual not null, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test (id, a, b) values (null, 2,3); insert into gh_ost_test (id, a, b) values (null, 2,4); insert into gh_ost_test (id, a, b) values (null, 2,5); insert into gh_ost_test (id, a, b) values (null, 2,6); insert into gh_ost_test (id, a, b) values (null, 2,7); insert into gh_ost_test (id, a, b) values (null, 2,8); insert into gh_ost_test (id, a, b) values (null, 2,9); insert into gh_ost_test (id, a, b) values (null, 2,0); insert into gh_ost_test (id, a, b) values (null, 2,1); insert into gh_ost_test (id, a, b) values (null, 2,2); update gh_ost_test set b=b+1 where id < 5; update gh_ost_test set b=b-1 where id >= 5; end ;; ================================================ FILE: localtests/generated-columns/ignore_versions ================================================ Percona ================================================ FILE: localtests/generated-columns-add/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, a int not null, b int not null, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test (id, a, b) values (null, 2,3); insert into gh_ost_test (id, a, b) values (null, 2,4); insert into gh_ost_test (id, a, b) values (null, 2,5); insert into gh_ost_test (id, a, b) values (null, 2,6); insert into gh_ost_test (id, a, b) values (null, 2,7); insert into gh_ost_test (id, a, b) values (null, 2,8); insert into gh_ost_test (id, a, b) values (null, 2,9); insert into gh_ost_test (id, a, b) values (null, 2,0); insert into gh_ost_test (id, a, b) values (null, 2,1); insert into gh_ost_test (id, a, b) values (null, 2,2); end ;; ================================================ FILE: localtests/generated-columns-add/extra_args ================================================ --alter="add column sum_ab int as (a + b) virtual not null" ================================================ FILE: localtests/generated-columns-add/ghost_columns ================================================ id, a, b ================================================ FILE: localtests/generated-columns-add/ignore_versions ================================================ Percona ================================================ FILE: localtests/generated-columns-add/order_by ================================================ id ================================================ FILE: localtests/generated-columns-add/orig_columns ================================================ id, a, b ================================================ FILE: localtests/generated-columns-rename/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, a int not null, b int not null, sum_ab int as (a + b) virtual not null, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test (id, a, b) values (null, 2,3); insert into gh_ost_test (id, a, b) values (null, 2,4); insert into gh_ost_test (id, a, b) values (null, 2,5); insert into gh_ost_test (id, a, b) values (null, 2,6); insert into gh_ost_test (id, a, b) values (null, 2,7); insert into gh_ost_test (id, a, b) values (null, 2,8); insert into gh_ost_test (id, a, b) values (null, 2,9); insert into gh_ost_test (id, a, b) values (null, 2,0); insert into gh_ost_test (id, a, b) values (null, 2,1); insert into gh_ost_test (id, a, b) values (null, 2,2); end ;; ================================================ FILE: localtests/generated-columns-rename/extra_args ================================================ --alter="change sum_ab total_ab int as (a + b) virtual not null" --approve-renamed-columns ================================================ FILE: localtests/generated-columns-rename/ignore_versions ================================================ Percona ================================================ FILE: localtests/generated-columns-unique/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, `idb` varchar(36) CHARACTER SET utf8mb4 GENERATED ALWAYS AS (json_unquote(json_extract(`jsonobj`,_utf8mb4'$._id'))) STORED NOT NULL, `jsonobj` json NOT NULL, updated datetime DEFAULT NULL, PRIMARY KEY (`id`,`idb`) ) auto_increment=1; insert into gh_ost_test (id, jsonobj) values (null, '{"_id":2}'); insert into gh_ost_test (id, jsonobj) values (null, '{"_id":3}'); drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test (id, jsonobj) values (null, '{"_id":5}'); insert into gh_ost_test (id, jsonobj) values (null, '{"_id":7}'); insert into gh_ost_test (id, jsonobj) values (null, '{"_id":11}'); insert into gh_ost_test (id, jsonobj) values (null, '{"_id":13}'); insert into gh_ost_test (id, jsonobj) values (null, '{"_id":17}'); insert into gh_ost_test (id, jsonobj) values (null, '{"_id":19}'); update gh_ost_test set updated=NOW() where idb=5; update gh_ost_test set updated=NOW() where idb=7; update gh_ost_test set updated=NOW() where idb=11; update gh_ost_test set updated=NOW() where idb=13; update gh_ost_test set updated=NOW() where idb=17; update gh_ost_test set updated=NOW() where idb=19; end ;; ================================================ FILE: localtests/generated-columns-unique/ignore_versions ================================================ Percona ================================================ FILE: localtests/geometry/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, g geometry, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, ST_GeomFromText('POINT(1 1)')); insert into gh_ost_test values (null, ST_GeomFromText('POINT(2 2)')); insert into gh_ost_test values (null, ST_GeomFromText('POINT(3 3)')); end ;; ================================================ FILE: localtests/geometry/ignore_versions ================================================ Percona ================================================ FILE: localtests/gtid/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, t varchar(128) charset utf8mb4, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, md5(rand())); end ;; ================================================ FILE: localtests/gtid/extra_args ================================================ --gtid ================================================ FILE: localtests/gtid/gtid_mode ================================================ ON ================================================ FILE: localtests/gtid/ignore_versions ================================================ (5.5) ================================================ FILE: localtests/json/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, j json, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, '"sometext"'); insert into gh_ost_test values (null, '{"key":"val"}'); insert into gh_ost_test values (null, '{"is-it": true, "count": 3, "elements": []}'); end ;; ================================================ FILE: localtests/json-dml/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, updated tinyint not null default 0, j json, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test (id, i, j) values (null, 11, '"sometext"'); insert into gh_ost_test (id, i, j) values (null, 13, '{"key":"val"}'); insert into gh_ost_test (id, i, j) values (null, 17, '{"is-it": true, "count": 3, "elements": []}'); insert into gh_ost_test (id, i, j) values (null, 19, '{"text":"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac"}'); update gh_ost_test set j = '{"updated": 11}', updated = 1 where i = 11 and updated = 0; update gh_ost_test set j = json_set(j, '$.count', 13, '$.id', id), updated = 1 where i = 13 and updated = 0; delete from gh_ost_test where i = 17; end ;; ================================================ FILE: localtests/keyword-column/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, color varchar(32), primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; insert into gh_ost_test values (null, 11, 'red'); insert into gh_ost_test values (null, 13, 'green'); insert into gh_ost_test values (null, 17, 'blue'); ================================================ FILE: localtests/keyword-column/extra_args ================================================ --alter='add column `index` int unsigned' ================================================ FILE: localtests/keyword-column/ghost_columns ================================================ id, i, color ================================================ FILE: localtests/keyword-column/orig_columns ================================================ id, i, color ================================================ FILE: localtests/latin1/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, t varchar(128) charset latin1 collate latin1_swedish_ci, primary key(id) ) auto_increment=1 charset latin1 collate latin1_swedish_ci; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, md5(rand())); insert into gh_ost_test values (null, 'átesting'); insert into gh_ost_test values (null, 'ádelete'); insert into gh_ost_test values (null, 'testátest'); update gh_ost_test set t='áupdated' order by id desc limit 1; update gh_ost_test set t='áupdated1' where t='áupdated' order by id desc limit 1; delete from gh_ost_test where t='ádelete'; end ;; ================================================ FILE: localtests/latin1text/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, t text charset latin1 collate latin1_swedish_ci, primary key(id) ) auto_increment=1 charset latin1 collate latin1_swedish_ci; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, md5(rand())); insert into gh_ost_test values (null, 'átesting'); insert into gh_ost_test values (null, 'ádelete'); insert into gh_ost_test values (null, 'testátest'); update gh_ost_test set t='áupdated' order by id desc limit 1; update gh_ost_test set t='áupdated1' where t='áupdated' order by id desc limit 1; delete from gh_ost_test where t='ádelete'; end ;; ================================================ FILE: localtests/mixed-charset/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, t varchar(128) charset latin1 collate latin1_swedish_ci, tutf8 varchar(128) charset utf8, tutf8mb4 varchar(128) charset utf8mb4, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, md5(rand()), md5(rand()), md5(rand())); insert into gh_ost_test values (null, 'átesting', 'átesting', 'átesting'); insert into gh_ost_test values (null, 'testátest', 'testátest', '🍻😀'); end ;; ================================================ FILE: localtests/modify-change-case/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, c1 int not null default 0, c2 int not null default 0, primary key (id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert ignore into gh_ost_test values (1, 11, 23); insert ignore into gh_ost_test values (2, 13, 23); insert into gh_ost_test values (null, 17, 23); set @last_insert_id := last_insert_id(); update gh_ost_test set c1=c1+@last_insert_id, c2=c2+@last_insert_id where id=@last_insert_id order by id desc limit 1; delete from gh_ost_test where id=1; delete from gh_ost_test where c1=13; -- id=2 end ;; ================================================ FILE: localtests/modify-change-case/extra_args ================================================ --alter="modify C2 int not null default 0" ================================================ FILE: localtests/modify-change-case-pk/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, c1 int not null default 0, c2 int not null default 0, primary key (id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert ignore into gh_ost_test values (1, 11, 23); insert ignore into gh_ost_test values (2, 13, 23); insert into gh_ost_test values (null, 17, 23); set @last_insert_id := last_insert_id(); update gh_ost_test set c1=c1+@last_insert_id, c2=c2+@last_insert_id where id=@last_insert_id order by id desc limit 1; delete from gh_ost_test where id=1; delete from gh_ost_test where c1=13; -- id=2 end ;; ================================================ FILE: localtests/modify-change-case-pk/expect_failure ================================================ No shared unique key can be found after ALTER ================================================ FILE: localtests/modify-change-case-pk/extra_args ================================================ --alter="modify ID int" ================================================ FILE: localtests/panic-on-warnings-duplicate-unique-values-on-column-type-change/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, name varchar(255) not null, primary key (id) ) auto_increment=1; create unique index name_index on gh_ost_test (name); insert into gh_ost_test (`name`) values ('John Smith'); insert into gh_ost_test (`name`) values ('John Travolta'); drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert ignore into gh_ost_test values ('John ' || last_insert_id()); insert ignore into gh_ost_test values ('Adam ' || last_insert_id()); end ;; ================================================ FILE: localtests/panic-on-warnings-duplicate-unique-values-on-column-type-change/expect_failure ================================================ Warning: Duplicate entry 'John' for key Warning: Duplicate entry 'John' for key ================================================ FILE: localtests/panic-on-warnings-duplicate-unique-values-on-column-type-change/extra_args ================================================ --panic-on-warnings --alter "modify column name varchar(4) not null" ================================================ FILE: localtests/panic-on-warnings-duplicate-values-for-unique-index/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, name varchar(255) not null, primary key (id) ) auto_increment=1; insert into gh_ost_test (`name`) values ('John'); insert into gh_ost_test (`name`) values ('John'); drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert ignore into gh_ost_test values ('John ' || last_insert_id()); insert ignore into gh_ost_test values ('Adam ' || last_insert_id()); end ;; ================================================ FILE: localtests/panic-on-warnings-duplicate-values-for-unique-index/expect_failure ================================================ Warning: Duplicate entry 'John' ================================================ FILE: localtests/panic-on-warnings-duplicate-values-for-unique-index/extra_args ================================================ --panic-on-warnings --alter "add unique index name_index(name)" ================================================ FILE: localtests/panic-on-warnings-update-pk-with-duplicate-on-new-unique-index/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, email varchar(100) not null, primary key (id) ) auto_increment=1; insert into gh_ost_test (email) values ('alice@example.com'); insert into gh_ost_test (email) values ('bob@example.com'); insert into gh_ost_test (email) values ('charlie@example.com'); ================================================ FILE: localtests/panic-on-warnings-update-pk-with-duplicate-on-new-unique-index/expect_failure ================================================ Warnings detected during DML event application ================================================ FILE: localtests/panic-on-warnings-update-pk-with-duplicate-on-new-unique-index/extra_args ================================================ --panic-on-warnings --alter "ADD UNIQUE KEY email_unique (email)" --postpone-cut-over-flag-file=/tmp/gh-ost-test.postpone-cutover ================================================ FILE: localtests/panic-on-warnings-update-pk-with-duplicate-on-new-unique-index/test.sh ================================================ #!/bin/bash # Custom test: inject conflicting data AFTER row copy completes # This tests the DML event application code path, not row copy # Create postpone flag file (referenced in extra_args) postpone_flag_file=/tmp/gh-ost-test.postpone-cutover touch $postpone_flag_file # Build gh-ost command using framework function build_ghost_command # Run in background echo_dot # Clear log file before starting gh-ost echo > $test_logfile bash -c "$cmd" >>$test_logfile 2>&1 & ghost_pid=$! # Wait for row copy to complete echo_dot row_copy_complete=false for i in {1..30}; do if grep -q "Row copy complete" $test_logfile; then row_copy_complete=true break fi ps -p $ghost_pid > /dev/null || { echo; echo "ERROR gh-ost exited early"; rm -f $postpone_flag_file; return 1; } sleep 1; echo_dot done if ! $row_copy_complete; then echo; echo "ERROR row copy did not complete within expected time" rm -f $postpone_flag_file return 1 fi # Inject conflicting SQL after row copy (UPDATE with PK change creates DELETE+INSERT in binlog) echo_dot gh-ost-test-mysql-master test -e "update gh_ost_test set id = 200, email = 'alice@example.com' where id = 2" # Wait for binlog event to replicate and be applied sleep 10; echo_dot # Complete cutover by removing postpone flag rm -f $postpone_flag_file # Wait for gh-ost to complete wait $ghost_pid execution_result=$? rm -f $postpone_flag_file # Validate using framework function validate_expected_failure return $? ================================================ FILE: localtests/rename/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, c1 int not null, c2 int not null, primary key (id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert ignore into gh_ost_test values (1, 11, 23); insert ignore into gh_ost_test values (2, 13, 23); insert into gh_ost_test values (null, 17, 23); set @last_insert_id := last_insert_id(); update gh_ost_test set c1=c1+@last_insert_id, c2=c2+@last_insert_id where id=@last_insert_id order by id desc limit 1; delete from gh_ost_test where id=1; delete from gh_ost_test where c1=13; -- id=2 end ;; ================================================ FILE: localtests/rename/extra_args ================================================ --alter="change column c2 c3 int not null" --approve-renamed-columns ================================================ FILE: localtests/rename-inserts-only/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, c1 int not null, c2 int not null, primary key (id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, 23); insert into gh_ost_test values (null, 13, 23); insert into gh_ost_test values (null, floor(rand()*pow(2,32)), floor(rand()*pow(2,32))); end ;; ================================================ FILE: localtests/rename-inserts-only/extra_args ================================================ --alter="change column c2 c3 int not null" --approve-renamed-columns ================================================ FILE: localtests/rename-none-column/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, c1 int not null, primary key (id) ) auto_increment=1; drop event if exists gh_ost_test; ================================================ FILE: localtests/rename-none-column/extra_args ================================================ --alter="add column exchange double comment 'exchange rate used for pay in your own currency'" ================================================ FILE: localtests/rename-none-comment/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, c1 int not null, primary key (id) ) auto_increment=1; drop event if exists gh_ost_test; ================================================ FILE: localtests/rename-none-comment/extra_args ================================================ --alter="add column exchange_rate double comment 'change rate used for pay in your own currency'" ================================================ FILE: localtests/rename-reorder-column/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, c1 int not null, c2 int not null, primary key (id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert ignore into gh_ost_test values (1, 11, 23); insert ignore into gh_ost_test values (2, 13, 23); insert into gh_ost_test values (null, 17, 23); set @last_insert_id := last_insert_id(); update gh_ost_test set c1=c1+@last_insert_id, c2=c2+@last_insert_id where id=@last_insert_id order by id desc limit 1; delete from gh_ost_test where id=1; delete from gh_ost_test where c1=13; -- id=2 end ;; ================================================ FILE: localtests/rename-reorder-column/extra_args ================================================ --alter="change column c2 c2a int not null after id" --approve-renamed-columns ================================================ FILE: localtests/rename-reorder-column/ghost_columns ================================================ id, c1, c2a ================================================ FILE: localtests/rename-reorder-column/orig_columns ================================================ id, c1, c2 ================================================ FILE: localtests/rename-reorder-columns/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, c1 int not null, c2 int not null, c3 int not null, primary key (id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert ignore into gh_ost_test values (1, 11, 23, 97); insert ignore into gh_ost_test values (2, 13, 27, 61); insert into gh_ost_test values (null, 17, 31, 53); set @last_insert_id := last_insert_id(); update gh_ost_test set c1=c1+@last_insert_id, c2=c2+@last_insert_id, c3=c3+@last_insert_id where id=@last_insert_id order by id desc limit 1; delete from gh_ost_test where id=1; delete from gh_ost_test where c1=13; -- id=2 end ;; ================================================ FILE: localtests/rename-reorder-columns/extra_args ================================================ --alter="change column c2 c2a int not null, change column c3 c3 int not null after id" --approve-renamed-columns ================================================ FILE: localtests/rename-reorder-columns/ghost_columns ================================================ id, c1, c2a, c3 ================================================ FILE: localtests/rename-reorder-columns/orig_columns ================================================ id, c1, c2, c3 ================================================ FILE: localtests/reorder-columns/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, c1 int not null, c2 int not null, primary key (id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert ignore into gh_ost_test values (1, 11, 23); insert ignore into gh_ost_test values (2, 13, 29); insert into gh_ost_test values (null, 17, 31); set @last_insert_id := last_insert_id(); update gh_ost_test set c1=c1+@last_insert_id, c2=c2+@last_insert_id where id=@last_insert_id order by id desc limit 1; delete from gh_ost_test where id=1; delete from gh_ost_test where c1=13; end ;; ================================================ FILE: localtests/reorder-columns/extra_args ================================================ --alter="change column c2 c2 int not null after id" ================================================ FILE: localtests/reorder-columns/ghost_columns ================================================ id, c1, c2 ================================================ FILE: localtests/reorder-columns/orig_columns ================================================ id, c1, c2 ================================================ FILE: localtests/shared-uk/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, ts timestamp, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, now()); insert into gh_ost_test values (null, 13, now()); insert into gh_ost_test values (null, 17, now()); end ;; ================================================ FILE: localtests/shared-uk/extra_args ================================================ --alter="drop primary key, add primary key (id, i)" ================================================ FILE: localtests/spatial/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, g geometry, pt point, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, ST_GeomFromText('POINT(1 1)'), POINT(10,10)); insert into gh_ost_test values (null, ST_GeomFromText('POINT(2 2)'), POINT(20,20)); insert into gh_ost_test values (null, ST_GeomFromText('POINT(3 3)'), POINT(30,30)); end ;; ================================================ FILE: localtests/spatial/ignore_versions ================================================ Percona ================================================ FILE: localtests/swap-pk-uk/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id bigint, i int not null, ts timestamp(6), primary key(id), unique key its_uidx(i, ts) ) ; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values ((unix_timestamp() << 2) + 0, 11, now(6)); insert into gh_ost_test values ((unix_timestamp() << 2) + 1, 13, now(6)); insert into gh_ost_test values ((unix_timestamp() << 2) + 2, 17, now(6)); insert into gh_ost_test values ((unix_timestamp() << 2) + 3, 19, now(6)); end ;; ================================================ FILE: localtests/swap-pk-uk/extra_args ================================================ --alter="drop primary key, drop key its_uidx, add primary key (i, ts), add unique key id_uidx(id)" ================================================ FILE: localtests/swap-pk-uk/order_by ================================================ id ================================================ FILE: localtests/swap-uk/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, ts timestamp, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, now()); insert into gh_ost_test values (null, 13, now()); insert into gh_ost_test values (null, 17, now()); end ;; ================================================ FILE: localtests/swap-uk/extra_args ================================================ --alter="drop primary key, add unique key(id)" ================================================ FILE: localtests/swap-uk-uk/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id bigint not null, i int not null, ts timestamp(6) not null, unique key id_uidx(id), unique key its_uidx(i, ts) ) ; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values ((unix_timestamp() << 2) + 0, 11, now(6)); insert into gh_ost_test values ((unix_timestamp() << 2) + 1, 13, now(6)); insert into gh_ost_test values ((unix_timestamp() << 2) + 2, 17, now(6)); insert into gh_ost_test values ((unix_timestamp() << 2) + 3, 19, now(6)); end ;; ================================================ FILE: localtests/swap-uk-uk/extra_args ================================================ --panic-on-warnings --alter="drop key id_uidx, drop key its_uidx, add unique key its2_uidx(i, ts), add unique key id2_uidx(id)" ================================================ FILE: localtests/swap-uk-uk/order_by ================================================ id ================================================ FILE: localtests/sysbench/create.sql ================================================ /* This table will get created by `sysbench prepare` */ /* CREATE TABLE `sbtest1` ( `id` int NOT NULL AUTO_INCREMENT, `k` int NOT NULL DEFAULT '0', `c` char(120) NOT NULL DEFAULT '', `pad` char(60) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `k_1` (`k`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; */ DROP TABLE IF EXISTS `sbtest1`; ================================================ FILE: localtests/test.sh ================================================ #!/bin/bash # Local integration tests. To be used by CI. # See https://github.com/github/gh-ost/tree/doc/local-tests.md # # Usage: localtests/test/sh [filter] # By default, runs all tests. Given filter, will only run tests matching given regep tests_path=$(dirname $0) test_logfile=/tmp/gh-ost-test.log default_ghost_binary=/tmp/gh-ost-test ghost_binary="" docker=false toxiproxy=false gtid=false storage_engine=innodb exec_command_file=/tmp/gh-ost-test.bash ghost_structure_output_file=/tmp/gh-ost-test.ghost.structure.sql orig_content_output_file=/tmp/gh-ost-test.orig.content.csv ghost_content_output_file=/tmp/gh-ost-test.ghost.content.csv throttle_flag_file=/tmp/gh-ost-test.ghost.throttle.flag table_name= ghost_table_name= master_host= master_port= replica_host= replica_port= original_sql_mode= current_gtid_mode= sysbench_pid= test_timeout=120 test_failure_log_tail_lines=50 OPTIND=1 while getopts "b:s:dtg" OPTION; do case $OPTION in b) ghost_binary="$OPTARG" ;; s) storage_engine="$OPTARG" ;; t) toxiproxy=true ;; d) docker=true ;; g) gtid=true ;; esac done shift $((OPTIND - 1)) test_pattern="${1:-.}" verify_master_and_replica() { if [ "$(gh-ost-test-mysql-master -e "select 1" -ss)" != "1" ]; then echo "Cannot verify gh-ost-test-mysql-master" exit 1 fi read master_host master_port <<<$(gh-ost-test-mysql-master -e "select @@hostname, @@port" -ss) [ "$master_host" == "$(hostname)" ] && master_host="127.0.0.1" echo "# master verified at $master_host:$master_port" if ! gh-ost-test-mysql-master -e "set global event_scheduler := 1"; then echo "Cannot enable event_scheduler on master" exit 1 fi original_sql_mode="$(gh-ost-test-mysql-master -e "select @@global.sql_mode" -s -s)" echo "sql_mode on master is ${original_sql_mode}" current_gtid_mode=$(gh-ost-test-mysql-master -s -s -e "select @@global.gtid_mode" 2>/dev/null || echo unsupported) current_enforce_gtid_consistency=$(gh-ost-test-mysql-master -s -s -e "select @@global.enforce_gtid_consistency" 2>/dev/null || echo unsupported) current_master_server_uuid=$(gh-ost-test-mysql-master -s -s -e "select @@global.server_uuid" 2>/dev/null || echo unsupported) current_replica_server_uuid=$(gh-ost-test-mysql-replica -s -s -e "select @@global.server_uuid" 2>/dev/null || echo unsupported) echo "gtid_mode on master is ${current_gtid_mode} with enforce_gtid_consistency=${current_enforce_gtid_consistency}" echo "server_uuid on master is ${current_master_server_uuid}, replica is ${current_replica_server_uuid}" echo "Gracefully sleeping for 3 seconds while replica is setting up..." sleep 3 if [ "$(gh-ost-test-mysql-replica -e "select 1" -ss)" != "1" ]; then echo "Cannot verify gh-ost-test-mysql-replica" exit 1 fi if [ "$(gh-ost-test-mysql-replica -e "select @@global.binlog_format" -ss)" != "ROW" ]; then echo "Expecting test replica to have binlog_format=ROW" exit 1 fi read replica_host replica_port <<<$(gh-ost-test-mysql-replica -e "select @@hostname, @@port" -ss) [ "$replica_host" == "$(hostname)" ] && replica_host="127.0.0.1" echo "# replica verified at $replica_host:$replica_port" if [ "$docker" = true ]; then master_host="0.0.0.0" master_port="3307" echo "# using docker master at $master_host:$master_port" replica_host="0.0.0.0" if [ "$toxiproxy" = true ]; then replica_port="23308" echo "# using toxiproxy replica at $replica_host:$replica_port" else replica_port="3308" echo "# using docker replica at $replica_host:$replica_port" fi fi } echo_dot() { echo -n "." } start_replication() { mysql_version="$(gh-ost-test-mysql-replica -e "select @@version")" if [[ $mysql_version =~ "8.4" ]]; then seconds_behind_source="Seconds_Behind_Source" replica_terminology="replica" else seconds_behind_source="Seconds_Behind_Master" replica_terminology="slave" fi gh-ost-test-mysql-replica -e "stop $replica_terminology; start $replica_terminology;" num_attempts=0 while gh-ost-test-mysql-replica -e "show $replica_terminology status\G" | grep $seconds_behind_source | grep -q NULL; do ((num_attempts = num_attempts + 1)) if [ $num_attempts -gt 10 ]; then echo echo "ERROR replication failure" exit 1 fi echo_dot sleep 1 done } build_ghost_command() { # Build gh-ost command with all standard options # Expects: ghost_binary, replica_host, replica_port, master_host, master_port, # table_name, storage_engine, throttle_flag_file, extra_args cmd="GOTRACEBACK=crash $ghost_binary \ --user=gh-ost \ --password=gh-ost \ --host=$replica_host \ --port=$replica_port \ --assume-master-host=${master_host}:${master_port} \ --database=test \ --table=${table_name} \ --storage-engine=${storage_engine} \ --alter='engine=${storage_engine}' \ --exact-rowcount \ --assume-rbr \ --skip-metadata-lock-check \ --initially-drop-old-table \ --initially-drop-ghost-table \ --throttle-query='select timestampdiff(second, min(last_update), now()) < 5 from _${table_name}_ghc' \ --throttle-flag-file=$throttle_flag_file \ --serve-socket-file=/tmp/gh-ost.test.sock \ --initially-drop-socket-file \ --test-on-replica \ --default-retries=3 \ --chunk-size=10 \ --verbose \ --debug \ --stack \ --checkpoint \ --execute ${extra_args[@]}" } print_log_excerpt() { echo "=== Last $test_failure_log_tail_lines lines of $test_logfile ===" tail -n $test_failure_log_tail_lines $test_logfile echo "=== End log excerpt ===" } validate_expected_failure() { # Check if test expected to fail and validate error message # Expects: tests_path, test_name, execution_result, test_logfile if [ -f $tests_path/$test_name/expect_failure ]; then if [ $execution_result -eq 0 ]; then echo echo "ERROR $test_name execution was expected to exit on error but did not." print_log_excerpt return 1 fi if [ -s $tests_path/$test_name/expect_failure ]; then # 'expect_failure' file has content. We expect to find this content in the log. expected_error_message="$(cat $tests_path/$test_name/expect_failure)" if grep -q "$expected_error_message" $test_logfile; then return 0 fi echo echo "ERROR $test_name execution was expected to exit with error message '${expected_error_message}' but did not." print_log_excerpt return 1 fi # 'expect_failure' file has no content. We generally agree that the failure is correct return 0 fi if [ $execution_result -ne 0 ]; then echo echo "ERROR $test_name execution failure. cat $test_logfile:" cat $test_logfile return 1 fi return 0 } sysbench_prepare() { local mysql_host="$1" local mysql_port="$2" sysbench oltp_write_only \ --mysql-host="$mysql_host" \ --mysql-port="$mysql_port" \ --mysql-user=root \ --mysql-password=opensesame \ --mysql-db=test \ --tables=1 \ --table-size=20000 \ prepare } sysbench_run_cmd() { local mysql_host="$1" local mysql_port="$2" cmd="sysbench oltp_write_only \ --mysql-host="$mysql_host" \ --mysql-port="$mysql_port" \ --mysql-user=root \ --mysql-password=opensesame \ --mysql-db=test \ --rand-seed=163 \ --tables=1 \ --threads=2 \ --time=30 \ --report-interval=10 \ --rate=200 \ run" echo $cmd } cleanup() { if ! [ -z $sysbench_pid ] && ps -p $sysbench_pid >/dev/null; then kill $sysbench_pid fi } test_single() { local test_name test_name="$1" if [ -f $tests_path/$test_name/ignore_versions ]; then ignore_versions=$(cat $tests_path/$test_name/ignore_versions) mysql_version=$(gh-ost-test-mysql-master -s -s -e "select @@version") mysql_version_comment=$(gh-ost-test-mysql-master -s -s -e "select @@version_comment") if echo "$mysql_version" | egrep -q "^${ignore_versions}"; then echo -n "Skipping: $test_name" return 0 elif echo "$mysql_version_comment" | egrep -i -q "^${ignore_versions}"; then echo -n "Skipping: $test_name" return 0 fi fi echo -n "Testing: $test_name" echo_dot start_replication echo_dot if [ -f $tests_path/$test_name/gtid_mode ]; then target_gtid_mode=$(cat $tests_path/$test_name/gtid_mode) if [ "$current_gtid_mode" != "$target_gtid_mode" ]; then echo "gtid_mode is ${current_gtid_mode}, expected ${target_gtid_mode}" exit 1 fi fi if [ -f $tests_path/$test_name/sql_mode ]; then gh-ost-test-mysql-master --default-character-set=utf8mb4 test -e "set @@global.sql_mode='$(cat $tests_path/$test_name/sql_mode)'" gh-ost-test-mysql-replica --default-character-set=utf8mb4 test -e "set @@global.sql_mode='$(cat $tests_path/$test_name/sql_mode)'" fi gh-ost-test-mysql-master --default-character-set=utf8mb4 test <$tests_path/$test_name/create.sql test_create_result=$? if [ $test_create_result -ne 0 ]; then echo echo "ERROR $test_name create failure. cat $tests_path/$test_name/create.sql:" cat $tests_path/$test_name/create.sql return 1 fi if [ -f $tests_path/$test_name/before.sql ]; then gh-ost-test-mysql-master --default-character-set=utf8mb4 test < $tests_path/$test_name/before.sql gh-ost-test-mysql-replica --default-character-set=utf8mb4 test < $tests_path/$test_name/before.sql fi extra_args="" if [ -f $tests_path/$test_name/extra_args ]; then extra_args=$(cat $tests_path/$test_name/extra_args) fi if [ "$gtid" = true ]; then extra_args+=" --gtid" fi if [ "$toxiproxy" = true ]; then extra_args+=" --skip-port-validation" fi orig_columns="*" ghost_columns="*" order_by="" if [ -f $tests_path/$test_name/orig_columns ]; then orig_columns=$(cat $tests_path/$test_name/orig_columns) fi if [ -f $tests_path/$test_name/ghost_columns ]; then ghost_columns=$(cat $tests_path/$test_name/ghost_columns) fi if [ -f $tests_path/$test_name/order_by ]; then order_by="order by $(cat $tests_path/$test_name/order_by)" fi # graceful sleep for replica to catch up echo_dot sleep 1 table_name="gh_ost_test" ghost_table_name="_gh_ost_test_gho" # Check for custom test script if [ -f $tests_path/$test_name/test.sh ]; then # Run the custom test script in a subshell with timeout monitoring # The subshell inherits all functions and variables from the current shell (source $tests_path/$test_name/test.sh) & test_pid=$! # Monitor the test with timeout timeout_counter=0 while kill -0 $test_pid 2>/dev/null; do if [ $timeout_counter -ge $test_timeout ]; then kill -TERM $test_pid 2>/dev/null sleep 1 kill -KILL $test_pid 2>/dev/null wait $test_pid 2>/dev/null echo echo "ERROR $test_name execution timed out" print_log_excerpt return 1 fi sleep 1 ((timeout_counter++)) done # Get the exit code wait $test_pid 2>/dev/null execution_result=$? return $execution_result fi # test with sysbench oltp write load if [[ "$test_name" == "sysbench" ]]; then if ! command -v sysbench &>/dev/null; then echo "skipping" return 0 fi table_name="sbtest1" ghost_table_name="_${table_name}_gho" echo "Preparing sysbench..." sysbench_prepare "$master_host" "$master_port" load_cmd="$(sysbench_run_cmd $master_host $master_port)" eval "$load_cmd" & sysbench_pid=$! echo echo -n "Started sysbench (PID $sysbench_pid): " echo $load_cmd fi trap cleanup SIGINT # Build and execute gh-ost command build_ghost_command echo_dot echo $cmd >$exec_command_file echo_dot timeout $test_timeout bash $exec_command_file >$test_logfile 2>&1 execution_result=$? cleanup # Check for timeout (exit code 124) if [ $execution_result -eq 124 ]; then echo echo "ERROR $test_name execution timed out" print_log_excerpt return 1 fi if [ -f $tests_path/$test_name/sql_mode ]; then gh-ost-test-mysql-master --default-character-set=utf8mb4 test -e "set @@global.sql_mode='${original_sql_mode}'" gh-ost-test-mysql-replica --default-character-set=utf8mb4 test -e "set @@global.sql_mode='${original_sql_mode}'" fi if [ -f $tests_path/$test_name/after.sql ]; then gh-ost-test-mysql-master --default-character-set=utf8mb4 test < $tests_path/$test_name/after.sql gh-ost-test-mysql-replica --default-character-set=utf8mb4 test < $tests_path/$test_name/after.sql fi if [ -f $tests_path/$test_name/destroy.sql ]; then gh-ost-test-mysql-master --default-character-set=utf8mb4 test <$tests_path/$test_name/destroy.sql fi # Validate expected failure or success if ! validate_expected_failure; then return 1 fi # If this was an expected failure test, we're done (no need to validate structure/checksums) if [ -f $tests_path/$test_name/expect_failure ]; then return 0 fi # Test succeeded - now validate structure and checksums gh-ost-test-mysql-replica --default-character-set=utf8mb4 test -e "show create table ${ghost_table_name}\G" -ss >$ghost_structure_output_file if [ -f $tests_path/$test_name/expect_table_structure ]; then expected_table_structure="$(cat $tests_path/$test_name/expect_table_structure)" if ! grep -q "$expected_table_structure" $ghost_structure_output_file; then echo echo "ERROR $test_name: table structure was expected to include ${expected_table_structure} but did not. cat $ghost_structure_output_file:" cat $ghost_structure_output_file return 1 fi fi echo_dot gh-ost-test-mysql-replica --default-character-set=utf8mb4 test -e "select ${orig_columns} from ${table_name} ${order_by}" -ss >$orig_content_output_file gh-ost-test-mysql-replica --default-character-set=utf8mb4 test -e "select ${ghost_columns} from ${ghost_table_name} ${order_by}" -ss >$ghost_content_output_file orig_checksum=$(cat $orig_content_output_file | md5sum) ghost_checksum=$(cat $ghost_content_output_file | md5sum) if [ "$orig_checksum" != "$ghost_checksum" ]; then gh-ost-test-mysql-replica --default-character-set=utf8mb4 test -e "select ${orig_columns} from ${table_name}" -ss >$orig_content_output_file gh-ost-test-mysql-replica --default-character-set=utf8mb4 test -e "select ${ghost_columns} from ${ghost_table_name}" -ss >$ghost_content_output_file echo "ERROR $test_name: checksum mismatch" echo "---" diff $orig_content_output_file $ghost_content_output_file echo "diff $orig_content_output_file $ghost_content_output_file" return 1 fi } build_binary() { echo "Building" rm -f $default_ghost_binary [ "$ghost_binary" == "" ] && ghost_binary="$default_ghost_binary" if [ -f "$ghost_binary" ]; then echo "Using binary: $ghost_binary" return 0 fi go build -o $ghost_binary go/cmd/gh-ost/main.go if [ $? -ne 0 ]; then echo "Build failure" exit 1 fi } test_all() { build_binary test_dirs=$(find "$tests_path" -mindepth 1 -maxdepth 1 ! -path . -type d | grep "$test_pattern" | sort) while read -r test_dir; do test_name=$(basename "$test_dir") local test_start_time=$(date +%s) if ! test_single "$test_name"; then local test_end_time=$(date +%s) local test_duration=$((test_end_time - test_start_time)) create_statement=$(gh-ost-test-mysql-replica test -t -e "show create table ${ghost_table_name} \G") echo "$create_statement" >>$test_logfile echo "+ FAIL (${test_duration}s)" return 1 else local test_end_time=$(date +%s) local test_duration=$((test_end_time - test_start_time)) echo echo "+ pass (${test_duration}s)" fi mysql_version="$(gh-ost-test-mysql-replica -e "select @@version")" replica_terminology="slave" if [[ $mysql_version =~ "8.4" ]]; then replica_terminology="replica" fi gh-ost-test-mysql-replica -e "start $replica_terminology" done <<<"$test_dirs" } verify_master_and_replica test_all ================================================ FILE: localtests/timestamp/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, ts0 timestamp default current_timestamp, ts1 timestamp default current_timestamp, ts2 timestamp default current_timestamp, updated tinyint unsigned default 0, primary key(id), key i_idx(i) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, null, now(), now(), 0); update gh_ost_test set ts2=now() + interval 1 minute, updated = 1 where i = 11 order by id desc limit 1; insert into gh_ost_test values (null, 13, null, now(), now(), 0); update gh_ost_test set ts2=now() + interval 1 minute, updated = 1 where i = 13 order by id desc limit 1; insert into gh_ost_test values (null, 17, null, now(), now(), 0); update gh_ost_test set ts2=now() + interval 1 minute, updated = 1 where i = 17 order by id desc limit 1; insert into gh_ost_test values (null, 19, null, now(), now(), 0); update gh_ost_test set ts2=now() + interval 1 minute, updated = 1 where i = 19 order by id desc limit 1; insert into gh_ost_test values (null, 23, null, now(), now(), 0); update gh_ost_test set ts2=now() + interval 1 minute, updated = 1 where i = 23 order by id desc limit 1; end ;; ================================================ FILE: localtests/timestamp-datetime/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, ts timestamp default current_timestamp, dt datetime, ts2ts timestamp null, ts2dt datetime null, dt2ts timestamp null, dt2dt datetime null, updated tinyint unsigned default 0, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, now(), now(),null, null, null, null, 0); update gh_ost_test set ts2ts=ts, ts2dt=ts, dt2ts=dt, dt2dt=dt where i = 11 order by id desc limit 1; insert into gh_ost_test values (null, 13, null, now(), now(), 0); update gh_ost_test set ts2ts=ts, ts2dt=ts, dt2ts=dt, dt2dt=dt where i = 13 order by id desc limit 1; insert into gh_ost_test values (null, 17, null, '2016-07-06 10:20:30', '2016-07-06 10:20:30', 0); update gh_ost_test set ts2ts=ts, ts2dt=ts, dt2ts=dt, dt2dt=dt where i = 17 order by id desc limit 1; end ;; ================================================ FILE: localtests/timestamp-to-datetime/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, ts0 timestamp default current_timestamp, ts1 timestamp default current_timestamp, dt2 datetime, t datetime, updated tinyint unsigned default 0, primary key(id), key i_idx(i) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 7, null, now(), now(), '2010-10-20 10:20:30', 0); insert into gh_ost_test values (null, 11, null, now(), now(), '2010-10-20 10:20:30', 0); update gh_ost_test set ts2=now() + interval 1 minute, updated = 1 where i = 11 order by id desc limit 1; insert into gh_ost_test values (null, 13, null, now(), now(), '2010-10-20 10:20:30', 0); update gh_ost_test set ts2=now() + interval 1 minute, updated = 1 where i = 13 order by id desc limit 1; end ;; ================================================ FILE: localtests/timestamp-to-datetime/extra_args ================================================ --alter="change column t t datetime not null" ================================================ FILE: localtests/trigger-advanced-features/create.sql ================================================ -- Drop triggers to ensure a clean slate drop trigger if exists gh_ost_test_bi; drop trigger if exists gh_ost_test_ai; drop trigger if exists gh_ost_test_bu; drop trigger if exists gh_ost_test_au; drop trigger if exists gh_ost_test_bd; drop trigger if exists gh_ost_test_ad; -- Drop tables drop table if exists gh_ost_test_log; drop table if exists gh_ost_test_stats; drop table if exists gh_ost_test; -- Create main table create table gh_ost_test ( id int auto_increment, i int not null, color varchar(32), ts timestamp default current_timestamp, modified_count int default 0, primary key(id) ) auto_increment=1; -- Create log table for trigger operations create table gh_ost_test_log ( id int auto_increment, test_id int, trigger_name varchar(128), action varchar(16), ts timestamp default current_timestamp, primary key(id) ); -- Create stats table for complex test create table gh_ost_test_stats ( id int auto_increment, event_type varchar(32), i_sum int not null default 0, count_events int not null default 0, primary key(id), unique key event_type_uidx(event_type) ); -- Create all types of triggers (BEFORE/AFTER with INSERT/UPDATE/DELETE) -- Multiple trigger types from trigger-multiple test -- BEFORE INSERT delimiter ;; create trigger gh_ost_test_bi before insert on gh_ost_test for each row begin insert into gh_ost_test_log (test_id, trigger_name, action) values (NEW.id, 'gh_ost_test_bi', 'BEFORE_INSERT'); end ;; delimiter ; -- AFTER INSERT delimiter ;; create trigger gh_ost_test_ai after insert on gh_ost_test for each row begin insert into gh_ost_test_log (test_id, trigger_name, action) values (NEW.id, 'gh_ost_test_ai', 'AFTER_INSERT'); -- Complex logic from trigger-complex insert into gh_ost_test_stats (event_type, i_sum, count_events) values ('insert', NEW.i, 1) on duplicate key update i_sum=i_sum+NEW.i, count_events=count_events+1; end ;; delimiter ; -- BEFORE UPDATE delimiter ;; create trigger gh_ost_test_bu before update on gh_ost_test for each row begin insert into gh_ost_test_log (test_id, trigger_name, action) values (NEW.id, 'gh_ost_test_bu', 'BEFORE_UPDATE'); end ;; delimiter ; -- AFTER UPDATE delimiter ;; create trigger gh_ost_test_au after update on gh_ost_test for each row begin insert into gh_ost_test_log (test_id, trigger_name, action) values (NEW.id, 'gh_ost_test_au', 'AFTER_UPDATE'); -- Complex logic from trigger-complex insert into gh_ost_test_stats (event_type, i_sum, count_events) values ('update', NEW.i-OLD.i, 1) on duplicate key update i_sum=i_sum+(NEW.i-OLD.i), count_events=count_events+1; if NEW.color != OLD.color then update gh_ost_test set modified_count = modified_count + 1 where id = NEW.id; end if; end ;; delimiter ; -- BEFORE DELETE delimiter ;; create trigger gh_ost_test_bd before delete on gh_ost_test for each row begin insert into gh_ost_test_log (test_id, trigger_name, action) values (OLD.id, 'gh_ost_test_bd', 'BEFORE_DELETE'); end ;; delimiter ; -- AFTER DELETE delimiter ;; create trigger gh_ost_test_ad after delete on gh_ost_test for each row begin insert into gh_ost_test_log (test_id, trigger_name, action) values (OLD.id, 'gh_ost_test_ad', 'AFTER_DELETE'); -- Complex logic from trigger-complex insert into gh_ost_test_stats (event_type, i_sum, count_events) values ('delete', -OLD.i, 1) on duplicate key update i_sum=i_sum-OLD.i, count_events=count_events+1; end ;; delimiter ; -- Insert initial data insert into gh_ost_test values (null, 11, 'red', null, 0); insert into gh_ost_test values (null, 13, 'green', null, 0); insert into gh_ost_test values (null, 17, 'blue', null, 0); -- Create event to test all trigger types with complex operations drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin -- Test INSERT triggers insert into gh_ost_test values (null, 23, 'red', null, 0); -- Test UPDATE triggers with numeric change (for stats) update gh_ost_test set i=i+1 where id=1; -- Test UPDATE triggers with color change update gh_ost_test set color='yellow' where color='red' limit 1; -- Test DELETE triggers delete from gh_ost_test where id=2; end ;; delimiter ; ================================================ FILE: localtests/trigger-advanced-features/extra_args ================================================ --include-triggers --trigger-suffix=_ght --remove-trigger-suffix-if-exists ================================================ FILE: localtests/trigger-ghost-name-conflict/create.sql ================================================ -- Bug #3 regression test: validateGhostTriggersDontExist must check whole schema -- MySQL trigger names are unique per SCHEMA, not per table. -- The validation must detect a trigger with the ghost name on ANY table in the schema. drop trigger if exists gh_ost_test_ai_ght; drop trigger if exists gh_ost_test_ai; drop table if exists gh_ost_test_other; drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, primary key(id) ) auto_increment=1; -- This trigger has the _ght suffix (simulating a previous migration left it). -- Ghost name = "gh_ost_test_ai" (suffix removed). create trigger gh_ost_test_ai_ght after insert on gh_ost_test for each row set @dummy = 1; -- Create ANOTHER table with a trigger named "gh_ost_test_ai" (the ghost name). -- Validation must detect this conflict even though the trigger is on a different table. create table gh_ost_test_other ( id int auto_increment, primary key(id) ); create trigger gh_ost_test_ai after insert on gh_ost_test_other for each row set @dummy = 1; insert into gh_ost_test values (null, 11); insert into gh_ost_test values (null, 13); insert into gh_ost_test values (null, 17); ================================================ FILE: localtests/trigger-ghost-name-conflict/destroy.sql ================================================ drop trigger if exists gh_ost_test_ai; drop table if exists gh_ost_test_other; ================================================ FILE: localtests/trigger-ghost-name-conflict/expect_failure ================================================ Found gh-ost triggers ================================================ FILE: localtests/trigger-ghost-name-conflict/extra_args ================================================ --include-triggers --trigger-suffix=_ght --remove-trigger-suffix-if-exists ================================================ FILE: localtests/trigger-long-name-validation/create.sql ================================================ -- Bug #1: Double-transformation in trigger length validation -- A trigger with a 60-char name should be valid: ghost name = 60 + 4 (_ght) = 64 chars (max allowed). -- But validateGhostTriggersLength() applies GetGhostTriggerName() twice, -- computing 60 + 4 + 4 = 68, which falsely exceeds the 64-char limit. drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, primary key(id) ) auto_increment=1; -- Trigger name is exactly 60 characters (padded to 60). -- Ghost name with _ght suffix = 64 chars = exactly at the MySQL limit. -- 60 chars: trigger_long_name_padding_aaaaaaaaaaaaaaaaaaaaa_60chars_xxxx create trigger trigger_long_name_padding_aaaaaaaaaaaaaaaaaaaaa_60chars_xxxx after insert on gh_ost_test for each row set @dummy = 1; insert into gh_ost_test values (null, 11); insert into gh_ost_test values (null, 13); insert into gh_ost_test values (null, 17); drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 23); update gh_ost_test set i=i+1 where id=1; end ;; delimiter ; ================================================ FILE: localtests/trigger-long-name-validation/extra_args ================================================ --include-triggers --trigger-suffix=_ght --remove-trigger-suffix-if-exists ================================================ FILE: localtests/trivial/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, color varchar(32), primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; insert into gh_ost_test values (null, 11, 'red'); insert into gh_ost_test values (null, 13, 'green'); insert into gh_ost_test values (null, 17, 'blue'); ================================================ FILE: localtests/trivial/extra_args ================================================ --throttle-query='select false' ================================================ FILE: localtests/tz/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, ts0 timestamp default current_timestamp, ts1 timestamp default current_timestamp, ts2 timestamp default current_timestamp, updated tinyint unsigned default 0, primary key(id), key i_idx(i) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, null, now(), now(), 0); update gh_ost_test set ts2=now() + interval 10 minute, updated = 1 where i = 11 order by id desc limit 1; set session time_zone='system'; insert into gh_ost_test values (null, 13, null, now(), now(), 0); update gh_ost_test set ts2=now() + interval 10 minute, updated = 1 where i = 13 order by id desc limit 1; set session time_zone='+00:00'; insert into gh_ost_test values (null, 17, null, now(), now(), 0); update gh_ost_test set ts2=now() + interval 10 minute, updated = 1 where i = 17 order by id desc limit 1; set session time_zone='-03:00'; insert into gh_ost_test values (null, 19, null, now(), now(), 0); update gh_ost_test set ts2=now() + interval 10 minute, updated = 1 where i = 19 order by id desc limit 1; set session time_zone='+05:00'; insert into gh_ost_test values (null, 23, null, now(), now(), 0); update gh_ost_test set ts2=now() + interval 10 minute, updated = 1 where i = 23 order by id desc limit 1; end ;; ================================================ FILE: localtests/tz-datetime/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, ts0 timestamp default current_timestamp, ts1 datetime, ts2 datetime, updated tinyint unsigned default 0, primary key(id), key i_idx(i) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 11, null, now(), now(), 0); update gh_ost_test set ts2=now() + interval 10 minute, updated = 1 where i = 11 order by id desc limit 1; set session time_zone='system'; insert into gh_ost_test values (null, 13, null, now(), now(), 0); update gh_ost_test set ts2=now() + interval 10 minute, updated = 1 where i = 13 order by id desc limit 1; set session time_zone='+00:00'; insert into gh_ost_test values (null, 17, null, now(), now(), 0); update gh_ost_test set ts2=now() + interval 10 minute, updated = 1 where i = 17 order by id desc limit 1; set session time_zone='-03:00'; insert into gh_ost_test values (null, 19, null, now(), now(), 0); update gh_ost_test set ts2=now() + interval 10 minute, updated = 1 where i = 19 order by id desc limit 1; set session time_zone='+05:00'; insert into gh_ost_test values (null, 23, null, now(), now(), 0); update gh_ost_test set ts2=now() + interval 10 minute, updated = 1 where i = 23 order by id desc limit 1; end ;; ================================================ FILE: localtests/tz-datetime-ts/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, ts0 timestamp default current_timestamp, ts1 timestamp default current_timestamp, dt2 datetime, t datetime, updated tinyint unsigned default 0, primary key(id), key i_idx(i) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, 7, null, now(), now(), '2010-10-20 10:20:30', 0); insert into gh_ost_test values (null, 11, null, now(), now(), '2010-10-20 10:20:30', 0); update gh_ost_test set ts2=now() + interval 1 minute, updated = 1 where i = 11 order by id desc limit 1; set session time_zone='system'; insert into gh_ost_test values (null, 13, null, now(), now(), '2010-10-20 10:20:30', 0); update gh_ost_test set ts2=now() + interval 1 minute, updated = 1 where i = 13 order by id desc limit 1; set session time_zone='+00:00'; insert into gh_ost_test values (null, 17, null, now(), now(), '2010-10-20 10:20:30', 0); update gh_ost_test set ts2=now() + interval 1 minute, updated = 1 where i = 17 order by id desc limit 1; set session time_zone='-03:00'; insert into gh_ost_test values (null, 19, null, now(), now(), '2010-10-20 10:20:30', 0); update gh_ost_test set ts2=now() + interval 1 minute, updated = 1 where i = 19 order by id desc limit 1; set session time_zone='+05:00'; insert into gh_ost_test values (null, 23, null, now(), now(), '2010-10-20 10:20:30', 0); update gh_ost_test set ts2=now() + interval 1 minute, updated = 1 where i = 23 order by id desc limit 1; end ;; ================================================ FILE: localtests/tz-datetime-ts/extra_args ================================================ --alter="change column t t timestamp not null default current_timestamp" ================================================ FILE: localtests/unsigned/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, bi bigint not null, iu int unsigned not null, biu bigint unsigned not null, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, -2147483647, -9223372036854775807, 4294967295, 18446744073709551615); set @last_insert_id := cast(last_insert_id() as signed); update gh_ost_test set i=-2147483647+@last_insert_id, bi=-9223372036854775807+@last_insert_id, iu=4294967295-@last_insert_id, biu=18446744073709551615-@last_insert_id where id < @last_insert_id order by id desc limit 1; end ;; ================================================ FILE: localtests/unsigned-modify/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id bigint(20) NOT NULL AUTO_INCREMENT, column1 int(11) NOT NULL, column2 smallint(5) unsigned NOT NULL, column3 mediumint(8) unsigned NOT NULL, column4 tinyint(3) unsigned NOT NULL, column5 int(11) NOT NULL, column6 int(11) NOT NULL, PRIMARY KEY (id), KEY c12_ix (column1, column2) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin -- mediumint maxvalue: 16777215 (unsigned), 8388607 (signed) insert into gh_ost_test values (NULL, 13382498, 536, 8388607, 3, 1483892217, 1483892218); insert into gh_ost_test values (NULL, 13382498, 536, 8388607, 250, 1483892217, 1483892218); insert into gh_ost_test values (NULL, 13382498, 536, 10000000, 3, 1483892217, 1483892218); end ;; ================================================ FILE: localtests/unsigned-rename/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, bi bigint not null, iu int unsigned not null, biu bigint unsigned not null, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, -2147483647, -9223372036854775807, 4294967295, 18446744073709551615); set @last_insert_id := cast(last_insert_id() as signed); update gh_ost_test set i=-2147483647+@last_insert_id, bi=-9223372036854775807+@last_insert_id, iu=4294967295-@last_insert_id, biu=18446744073709551615-@last_insert_id where id < @last_insert_id order by id desc limit 1; end ;; ================================================ FILE: localtests/unsigned-rename/extra_args ================================================ --alter="change column iu iu_renamed int unsigned not null" --approve-renamed-columns ================================================ FILE: localtests/unsigned-rename/ghost_columns ================================================ id, i, bi, iu_renamed, biu ================================================ FILE: localtests/unsigned-rename/orig_columns ================================================ id, i, bi, iu, biu ================================================ FILE: localtests/unsigned-reorder/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, i int not null, bi bigint not null, iu int unsigned not null, biu bigint unsigned not null, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, -2147483647, -9223372036854775807, 4294967295, 18446744073709551615); set @last_insert_id := cast(last_insert_id() as signed); update gh_ost_test set i=-2147483647+@last_insert_id, bi=-9223372036854775807+@last_insert_id, iu=4294967295-@last_insert_id, biu=18446744073709551615-@last_insert_id where id < @last_insert_id order by id desc limit 1; end ;; ================================================ FILE: localtests/unsigned-reorder/extra_args ================================================ --alter="change column iu iu int unsigned not null after id" --approve-renamed-columns ================================================ FILE: localtests/unsigned-reorder/ghost_columns ================================================ id, i, bi, iu, biu ================================================ FILE: localtests/unsigned-reorder/orig_columns ================================================ id, i, bi, iu, biu ================================================ FILE: localtests/utf8/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, t varchar(128) charset utf8 collate utf8_general_ci, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, md5(rand())); insert into gh_ost_test values (null, 'novo proprietário'); insert into gh_ost_test values (null, 'usuário'); end ;; ================================================ FILE: localtests/utf8mb4/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id int auto_increment, t varchar(128) charset utf8mb4, primary key(id) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin insert into gh_ost_test values (null, md5(rand())); insert into gh_ost_test values (null, 'átesting'); insert into gh_ost_test values (null, '🍻😀'); end ;; ================================================ FILE: localtests/varbinary/create.sql ================================================ drop table if exists gh_ost_test; create table gh_ost_test ( id binary(16) NOT NULL, info varchar(255) COLLATE utf8_unicode_ci NOT NULL, data binary(8) NOT NULL, primary key (id), unique key info_uidx (info) ) auto_increment=1; drop event if exists gh_ost_test; delimiter ;; create event gh_ost_test on schedule every 1 second starts current_timestamp ends current_timestamp + interval 60 second on completion not preserve enable do begin replace into gh_ost_test (id, info, data) values (X'12ffffffffffffffffffffffffffff00', 'item 1a', X'12ffffffffffffff'); replace into gh_ost_test (id, info, data) values (X'34ffffffffffffffffffffffffffffff', 'item 3a', X'34ffffffffffffff'); replace into gh_ost_test (id, info, data) values (X'90ffffffffffffffffffffffffffffff', 'item 9a', X'90ffffffffffff00'); DELETE FROM gh_ost_test WHERE id = X'11ffffffffffffffffffffffffffff00'; UPDATE gh_ost_test SET info = 'item 2++' WHERE id = X'22ffffffffffffffffffffffffffff00'; UPDATE gh_ost_test SET info = 'item 3++', data = X'33ffffffffffff00' WHERE id = X'33ffffffffffffffffffffffffffffff'; DELETE FROM gh_ost_test WHERE id = X'44ffffffffffffffffffffffffffffff'; UPDATE gh_ost_test SET info = 'item 5++', data = X'55ffffffffffffee' WHERE id = X'55ffffffffffffffffffffffffffffff'; INSERT INTO gh_ost_test (id, info, data) VALUES (X'66ffffffffffffffffffffffffffff00', 'item 6', X'66ffffffffffffff'); INSERT INTO gh_ost_test (id, info, data) VALUES (X'77ffffffffffffffffffffffffffffff', 'item 7', X'77ffffffffffff00'); INSERT INTO gh_ost_test (id, info, data) VALUES (X'88ffffffffffffffffffffffffffffff', 'item 8', X'88ffffffffffffff'); end ;; INSERT INTO gh_ost_test (id, info, data) VALUES (X'11ffffffffffffffffffffffffffff00', 'item 1', X'11ffffffffffffff'), -- id ends in 00 (X'22ffffffffffffffffffffffffffff00', 'item 2', X'22ffffffffffffff'), -- id ends in 00 (X'33ffffffffffffffffffffffffffffff', 'item 3', X'33ffffffffffffff'), (X'44ffffffffffffffffffffffffffffff', 'item 4', X'44ffffffffffffff'), (X'55ffffffffffffffffffffffffffffff', 'item 5', X'55ffffffffffffff'), (X'99ffffffffffffffffffffffffffffff', 'item 9', X'99ffffffffffff00'); -- data ends in 00 ================================================ FILE: resources/hooks-sample/gh-ost-on-before-cut-over-hook ================================================ #!/bin/bash # Sample hook file for gh-ost-on-before-cut-over echo "$(date) gh-ost-on-before-cut-over $GH_OST_DATABASE_NAME.$GH_OST_TABLE_NAME" >> /tmp/gh-ost.log ================================================ FILE: resources/hooks-sample/gh-ost-on-before-row-copy-hook ================================================ #!/bin/bash # Sample hook file for gh-ost-on-before-row-copy echo "$(date) gh-ost-on-before-row-copy $GH_OST_DATABASE_NAME.$GH_OST_TABLE_NAME" >> /tmp/gh-ost.log ================================================ FILE: resources/hooks-sample/gh-ost-on-begin-postponed-hook ================================================ #!/bin/bash # Sample hook file for gh-ost-on-begin-postponed echo "$(date) gh-ost-on-begin-postponed $GH_OST_DATABASE_NAME.$GH_OST_TABLE_NAME" >> /tmp/gh-ost.log ================================================ FILE: resources/hooks-sample/gh-ost-on-failure-hook ================================================ #!/bin/bash # Sample hook file for gh-ost-on-failure echo "$(date) gh-ost-on-failure $GH_OST_DATABASE_NAME.$GH_OST_TABLE_NAME; ghost: $GH_OST_OLD_TABLE_NAME" >> /tmp/gh-ost.log ================================================ FILE: resources/hooks-sample/gh-ost-on-interactive-command-hook ================================================ #!/bin/bash # Sample hook file for gh-ost-on-interactive-command echo "$(date) gh-ost-on-interactive-command $GH_OST_COMMAND" >> /tmp/gh-ost.log ================================================ FILE: resources/hooks-sample/gh-ost-on-row-copy-complete-hook ================================================ #!/bin/bash # Sample hook file for gh-ost-on-row-copy-complete echo "$(date) gh-ost-on-row-copy-complete $GH_OST_DATABASE_NAME.$GH_OST_TABLE_NAME" >> /tmp/gh-ost.log ================================================ FILE: resources/hooks-sample/gh-ost-on-rowcount-complete-hook ================================================ #!/bin/bash # Sample hook file for gh-ost-on-rowcount-complete echo "$(date) gh-ost-on-rowcount-complete $GH_OST_DATABASE_NAME.$GH_OST_TABLE_NAME" >> /tmp/gh-ost.log ================================================ FILE: resources/hooks-sample/gh-ost-on-start-replication-hook ================================================ #!/bin/bash # Sample hook file for gh-ost-on-start-replication # Useful for RDS/Aurora setups, see https://github.com/github/gh-ost/issues/163 echo "$(date) gh-ost-on-start-replication $GH_OST_DATABASE_NAME.$GH_OST_TABLE_NAME $GH_OST_MIGRATED_HOST" >> /tmp/gh-ost.log ================================================ FILE: resources/hooks-sample/gh-ost-on-startup-hook ================================================ #!/bin/bash # Sample hook file for gh-ost-on-startup echo "$(date) gh-ost-on-startup $GH_OST_DATABASE_NAME.$GH_OST_TABLE_NAME" >> /tmp/gh-ost.log ================================================ FILE: resources/hooks-sample/gh-ost-on-status-hook ================================================ #!/bin/bash # Sample hook file for gh-ost-on-status echo "$(date) gh-ost-on-status; elapsed: ${GH_OST_ELAPSED_SECONDS}; msg: ${GH_OST_STATUS}" >> /tmp/gh-ost.log ================================================ FILE: resources/hooks-sample/gh-ost-on-stop-replication-hook ================================================ #!/bin/bash # Sample hook file for gh-ost-on-stop-replication # Useful for RDS/Aurora setups, see https://github.com/github/gh-ost/issues/163 echo "$(date) gh-ost-on-stop-replication $GH_OST_DATABASE_NAME.$GH_OST_TABLE_NAME $GH_OST_MIGRATED_HOST" >> /tmp/gh-ost.log ================================================ FILE: resources/hooks-sample/gh-ost-on-success-hook ================================================ #!/bin/bash # Sample hook file for gh-ost-on-success echo "$(date) gh-ost-on-success $GH_OST_DATABASE_NAME.$GH_OST_TABLE_NAME" >> /tmp/gh-ost.log ================================================ FILE: resources/hooks-sample/gh-ost-on-success-hook-2 ================================================ #!/bin/bash # Sample hook file for gh-ost-on-success echo "$(date) gh-ost-on-success $GH_OST_DATABASE_NAME.$GH_OST_TABLE_NAME -- this message should show on the gh-ost log" echo "$(date) gh-ost-on-success copied $GH_OST_COPIED_ROWS rows in $GH_OST_ELAPSED_COPY_SECONDS seconds. Total runtime was $GH_OST_ELAPSED_SECONDS seconds" ================================================ FILE: resources/hooks-sample/gh-ost-on-validated-hook ================================================ #!/bin/bash # Sample hook file for gh-ost-on-validated echo "$(date) gh-ost-on-validated $GH_OST_DATABASE_NAME.$GH_OST_TABLE_NAME" >> /tmp/gh-ost.log ================================================ FILE: script/bootstrap ================================================ #!/bin/bash set -e # Make sure we have the version of Go we want to depend on, either from the # system or one we grab ourselves. # If executing from within Dockerfile then this assumption is inherently true, since we use a `golang` docker image. . script/ensure-go-installed # Since we want to be able to build this outside of GOPATH, we set it # up so it points back to us and go is none the wiser set -x rm -rf .gopath mkdir -p .gopath/src/github.com/github ln -s "$PWD" .gopath/src/github.com/github/gh-ost export GOPATH=$PWD/.gopath:$GOPATH ================================================ FILE: script/build ================================================ #!/bin/bash set -e . script/bootstrap mkdir -p bin bindir="$PWD"/bin scriptdir="$PWD"/script # We have a few binaries that we want to build, so let's put them into bin/ version=$(git rev-parse HEAD) describe=$(git describe --tags --always --dirty) export GOPATH="$PWD/.gopath" cd .gopath/src/github.com/github/gh-ost # We put the binaries directly into the bindir, because we have no need for shim wrappers go build -o "$bindir/gh-ost" -ldflags "-X main.AppVersion=${version} -X main.BuildDescribe=${describe}" ./go/cmd/gh-ost/main.go ================================================ FILE: script/build-deploy-tarball ================================================ #!/bin/sh set -e script/build # Get a fresh directory and make sure to delete it afterwards build_dir=tmp/build rm -rf $build_dir mkdir -p $build_dir trap "rm -rf $build_dir" EXIT commit_sha=$(git rev-parse HEAD) if [ $(uname -s) = "Darwin" ]; then build_arch="$(uname -sr | tr -d ' ' | tr '[:upper:]' '[:lower:]')-$(uname -m)" else build_arch="$(lsb_release -sc | tr -d ' ' | tr '[:upper:]' '[:lower:]')-$(uname -m)" fi tarball=$build_dir/${commit_sha}-${build_arch}.tar # Create the tarball tar cvf $tarball --mode="ugo=rx" bin/ # Compress it and copy it to the directory for the CI to upload it gzip $tarball mkdir -p "$BUILD_ARTIFACT_DIR"/gh-ost cp ${tarball}.gz "$BUILD_ARTIFACT_DIR"/gh-ost/ ### HACK HACK HACK HACK ### # blame @carlosmn, @mattr, @timvaillancourt and @rashiq # Allow builds on buster to also be used for focal focal_tarball_name=$(echo $(basename "${tarball}") | sed s/-bullseye-/-focal-/) cp ${tarball}.gz "$BUILD_ARTIFACT_DIR/gh-ost/${focal_tarball_name}.gz" ================================================ FILE: script/cibuild ================================================ #!/bin/bash script/test ================================================ FILE: script/cibuild-gh-ost-build-deploy-tarball ================================================ #!/bin/bash output_fold() { # Exit early if no label provided if [ -z "$1" ]; then echo "output_fold(): requires a label argument." return fi exit_value=0 # exit_value is used to record exit status of the given command label=$1 # human-readable label describing what's being folded up shift 1 # having retrieved the output_fold()-specific arguments, strip them off $@ # Only echo the tags when in CI_MODE if [ "$CI_MODE" ]; then echo "%%%FOLD {$label}%%%" fi # run the remaining arguments. If the command exits non-0, the `||` will # prevent the `-e` flag from seeing the failure exit code, and we'll see # the second echo execute "$@" || exit_value=$? # Only echo the tags when in CI_MODE if [ "$CI_MODE" ]; then echo "%%%END FOLD%%%" fi # preserve the exit code from the subcommand. return $exit_value } function cleanup() { echo echo "%%%FOLD {Shutting down services...}%%%" docker-compose down echo "%%%END FOLD%%%" } trap cleanup EXIT export CI_MODE=true output_fold "Bootstrapping container..." docker-compose build output_fold "Running tests..." docker-compose run --rm app docker-compose run -e BUILD_ARTIFACT_DIR=$BUILD_ARTIFACT_DIR -v $BUILD_ARTIFACT_DIR:$BUILD_ARTIFACT_DIR app script/build-deploy-tarball ================================================ FILE: script/dock ================================================ #!/bin/bash # Usage: # dock [arg] # dock test: build gh-ost & run unit and integration tests # docker pkg [target-path]: build gh-ost release packages and copy to target path (default path: /tmp/gh-ost-release) command="$1" case "$command" in "test") docker_target="gh-ost-test" docker build . -f Dockerfile.test -t "${docker_target}" && docker run --rm -it "${docker_target}:latest" ;; "pkg") packages_path="${2:-/tmp/gh-ost-release}" docker_target="gh-ost-packaging" docker build . -f Dockerfile.packaging -t "${docker_target}" && docker run --rm -it -v "${packages_path}:/tmp/pkg" "${docker_target}:latest" bash -c 'find /tmp/gh-ost-release/ -maxdepth 1 -type f | xargs cp -t /tmp/pkg' echo "packages generated on ${packages_path}:" ls -l "${packages_path}" ;; *) >&2 echo "Usage: dock dock [arg]" exit 1 esac ================================================ FILE: script/docker-gh-ost-replica-tests ================================================ #!/bin/bash # This script starts two MySQL docker containers in a primary-replica setup # which can be used for running the replica tests in localtests/ . # Set the environment var TEST_MYSQL_IMAGE to change the docker image. # # Usage: # docker-gh-ost-replica-tests up [-t] start the containers # docker-gh-ost-replica-tests down remove the containers # docker-gh-ost-replica-tests run [-t] run replica tests on the containers # # Flags: # -t use a toxiproxy for replica connection to simulate dropped connections set -e toxiproxy=false GH_OST_ROOT=$(git rev-parse --show-toplevel) if [[ ":$PATH:" != *":$GH_OST_ROOT:"* ]]; then export PATH="${PATH}:${GH_OST_ROOT}/script" fi poll_mysql() { CTR=0 cmd="gh-ost-test-mysql-$1" while ! $cmd -e "select 1;" >/dev/null 2>&1; do sleep 1 CTR=$((CTR + 1)) if [ $CTR -gt 30 ]; then echo " ❌ MySQL $1 failed to start" return 1 fi done echo " ✔ MySQL $1 OK" return 0 } mysql-source() { if [[ $TEST_MYSQL_IMAGE =~ "mysql:8.4" ]]; then gh-ost-test-mysql-master --ssl-mode=required "$@" else gh-ost-test-mysql-master "$@" fi } mysql-replica() { if [[ $TEST_MYSQL_IMAGE =~ "mysql:8.4" ]]; then gh-ost-test-mysql-replica --ssl-mode=required "$@" else gh-ost-test-mysql-replica "$@" fi } create_toxiproxy() { curl --fail -X POST http://localhost:8474/proxies \ -H "Content-Type: application/json" \ -d '{"name": "mysql_proxy", "listen": "0.0.0.0:23308", "upstream": "host.docker.internal:3308"}' echo curl --fail -X POST http://localhost:8474/proxies/mysql_proxy/toxics \ -H "Content-Type: application/json" \ -d '{"name": "limit_data_downstream", "type": "limit_data", "attributes": {"bytes": 1000000}}' echo } setup() { [ -z "$TEST_MYSQL_IMAGE" ] && TEST_MYSQL_IMAGE="mysql:8.0.41" echo "Starting MySQL $TEST_MYSQL_IMAGE containers..." compose_file="$GH_OST_ROOT/localtests/docker-compose.yml" MYSQL_SHA2_RSA_KEYS_FLAG="" MYSQL_PASSWORD_HASHING_ALGORITHM="mysql_native_password" if [[ $TEST_MYSQL_IMAGE =~ "mysql:8.4" ]]; then MYSQL_PASSWORD_HASHING_ALGORITHM="caching_sha2_password" MYSQL_SHA2_RSA_KEYS_FLAG="--caching-sha2-password-auto-generate-rsa-keys=ON" fi (TEST_MYSQL_IMAGE="$TEST_MYSQL_IMAGE" MYSQL_SHA2_RSA_KEYS_FLAG="$MYSQL_SHA2_RSA_KEYS_FLAG" envsubst <"$compose_file") >"$compose_file.tmp" if [ "$toxiproxy" = true ]; then echo "Starting toxiproxy container..." cat <>"$compose_file.tmp" mysql-toxiproxy: image: "ghcr.io/shopify/toxiproxy:latest" container_name: mysql-toxiproxy ports: - '8474:8474' - '23308:23308' expose: - '23308' - '8474' EOF fi docker compose -f "$compose_file.tmp" up -d --wait echo "Waiting for MySQL..." poll_mysql "master" || exit 1 poll_mysql "replica" || exit 1 echo -n "Setting up replication..." mysql-source -e "create user if not exists 'repl'@'%' identified with $MYSQL_PASSWORD_HASHING_ALGORITHM by 'repl';" mysql-source -e "grant replication slave on *.* to 'repl'@'%'; flush privileges;" mysql-source -e "create user if not exists 'gh-ost'@'%' identified with $MYSQL_PASSWORD_HASHING_ALGORITHM by 'gh-ost';" mysql-source -e "grant all on *.* to 'gh-ost'@'%';" sleep 1 if [[ $TEST_MYSQL_IMAGE =~ "mysql:8.4" ]]; then mysql-replica -e "change replication source to source_host='mysql-primary', source_port=3307, source_user='repl', source_password='repl', source_auto_position=1, source_ssl=1;" mysql-replica -e "start replica;" else mysql-replica -e "change master to master_host='mysql-primary', master_port=3307, master_user='repl', master_password='repl', master_auto_position=1;" mysql-replica -e "start slave;" fi echo "OK" if [ "$toxiproxy" = true ]; then echo "Creating toxiproxy..." create_toxiproxy echo "OK" fi } teardown() { echo "Stopping containers..." docker stop mysql-replica docker stop mysql-primary docker stop mysql-toxiproxy 2>/dev/null || true echo "Removing containers..." docker rm mysql-replica docker rm mysql-primary docker rm mysql-toxiproxy 2>/dev/null || true } main() { local cmd="$1" local tflag= if [[ "$2" == "-t" ]]; then toxiproxy=true fi if [[ "$cmd" == "up" ]]; then setup elif [[ "$cmd" == "down" ]]; then teardown elif [[ "$cmd" == "run" ]]; then shift 1 "$GH_OST_ROOT/localtests/test.sh" -d "$@" fi } main "$@" ================================================ FILE: script/ensure-go-installed ================================================ #!/bin/bash PREFERRED_GO_VERSION=go1.23.0 SUPPORTED_GO_VERSIONS='go1.2[012345]' GO_PKG_DARWIN=${PREFERRED_GO_VERSION}.darwin-amd64.pkg GO_PKG_DARWIN_SHA=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 GO_PKG_LINUX=${PREFERRED_GO_VERSION}.linux-amd64.tar.gz GO_PKG_LINUX_SHA=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 export ROOTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd $ROOTDIR # If Go isn't installed globally, setup environment variables for local install. if [ -z "$(which go)" ] || [ -z "$(go version | grep "$SUPPORTED_GO_VERSIONS")" ]; then GODIR="$ROOTDIR/.vendor/golocal" if [ $(uname -s) = "Darwin" ]; then export GOROOT="$GODIR/usr/local/go" else export GOROOT="$GODIR/go" fi export PATH="$GOROOT/bin:$PATH" fi # Check if local install exists, and install otherwise. if [ -z "$(which go)" ] || [ -z "$(go version | grep "$SUPPORTED_GO_VERSIONS")" ]; then [ -d "$GODIR" ] && rm -rf $GODIR mkdir -p "$GODIR" cd "$GODIR" if [ $(uname -s) = "Darwin" ]; then curl -L -O https://dl.google.com/go/$GO_PKG_DARWIN shasum -a256 $GO_PKG_DARWIN | grep $GO_PKG_DARWIN_SHA xar -xf $GO_PKG_DARWIN cpio -i /dev/null; go test $*; [ $? -ne 0 ] && retval=1 popd > /dev/null; done exit $retval ================================================ FILE: tmp/.gitkeep ================================================ ================================================ FILE: vendor/dario.cat/mergo/.deepsource.toml ================================================ version = 1 test_patterns = [ "*_test.go" ] [[analyzers]] name = "go" enabled = true [analyzers.meta] import_path = "dario.cat/mergo" ================================================ FILE: vendor/dario.cat/mergo/.gitignore ================================================ #### joe made this: http://goel.io/joe #### go #### # Binaries for programs and plugins *.exe *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Golang/Intellij .idea # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ #### vim #### # Swap [._]*.s[a-v][a-z] [._]*.sw[a-p] [._]s[a-v][a-z] [._]sw[a-p] # Session Session.vim # Temporary .netrwhist *~ # Auto-generated tag files tags ================================================ FILE: vendor/dario.cat/mergo/.travis.yml ================================================ language: go arch: - amd64 - ppc64le install: - go get -t - go get golang.org/x/tools/cmd/cover - go get github.com/mattn/goveralls script: - go test -race -v ./... after_script: - $HOME/gopath/bin/goveralls -service=travis-ci -repotoken $COVERALLS_TOKEN ================================================ FILE: vendor/dario.cat/mergo/CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at i@dario.im. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: vendor/dario.cat/mergo/CONTRIBUTING.md ================================================ # Contributing to mergo First off, thanks for taking the time to contribute! ❤️ All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉 > And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about: > - Star the project > - Tweet about it > - Refer this project in your project's readme > - Mention the project at local meetups and tell your friends/colleagues ## Table of Contents - [Code of Conduct](#code-of-conduct) - [I Have a Question](#i-have-a-question) - [I Want To Contribute](#i-want-to-contribute) - [Reporting Bugs](#reporting-bugs) - [Suggesting Enhancements](#suggesting-enhancements) ## Code of Conduct This project and everyone participating in it is governed by the [mergo Code of Conduct](https://github.com/imdario/mergoblob/master/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to <>. ## I Have a Question > If you want to ask a question, we assume that you have read the available [Documentation](https://pkg.go.dev/github.com/imdario/mergo). Before you ask a question, it is best to search for existing [Issues](https://github.com/imdario/mergo/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first. If you then still feel the need to ask a question and need clarification, we recommend the following: - Open an [Issue](https://github.com/imdario/mergo/issues/new). - Provide as much context as you can about what you're running into. - Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant. We will then take care of the issue as soon as possible. ## I Want To Contribute > ### Legal Notice > When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license. ### Reporting Bugs #### Before Submitting a Bug Report A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible. - Make sure that you are using the latest version. - Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](). If you are looking for support, you might want to check [this section](#i-have-a-question)). - To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/imdario/mergoissues?q=label%3Abug). - Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue. - Collect information about the bug: - Stack trace (Traceback) - OS, Platform and Version (Windows, Linux, macOS, x86, ARM) - Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant. - Possibly your input and the output - Can you reliably reproduce the issue? And can you also reproduce it with older versions? #### How Do I Submit a Good Bug Report? > You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to . We use GitHub issues to track bugs and errors. If you run into an issue with the project: - Open an [Issue](https://github.com/imdario/mergo/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.) - Explain the behavior you would expect and the actual behavior. - Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case. - Provide the information you collected in the previous section. Once it's filed: - The project team will label the issue accordingly. - A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced. - If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be implemented by someone. ### Suggesting Enhancements This section guides you through submitting an enhancement suggestion for mergo, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions. #### Before Submitting an Enhancement - Make sure that you are using the latest version. - Read the [documentation]() carefully and find out if the functionality is already covered, maybe by an individual configuration. - Perform a [search](https://github.com/imdario/mergo/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. - Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library. #### How Do I Submit a Good Enhancement Suggestion? Enhancement suggestions are tracked as [GitHub issues](https://github.com/imdario/mergo/issues). - Use a **clear and descriptive title** for the issue to identify the suggestion. - Provide a **step-by-step description of the suggested enhancement** in as many details as possible. - **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you. - You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. - **Explain why this enhancement would be useful** to most mergo users. You may also want to point out the other projects that solved it better and which could serve as inspiration. ## Attribution This guide is based on the **contributing-gen**. [Make your own](https://github.com/bttger/contributing-gen)! ================================================ FILE: vendor/dario.cat/mergo/LICENSE ================================================ Copyright (c) 2013 Dario Castañé. All rights reserved. Copyright (c) 2012 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of Google Inc. 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 OWNER 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. ================================================ FILE: vendor/dario.cat/mergo/README.md ================================================ # Mergo [![GitHub release][5]][6] [![GoCard][7]][8] [![Test status][1]][2] [![OpenSSF Scorecard][21]][22] [![OpenSSF Best Practices][19]][20] [![Coverage status][9]][10] [![Sourcegraph][11]][12] [![FOSSA status][13]][14] [![GoDoc][3]][4] [![Become my sponsor][15]][16] [![Tidelift][17]][18] [1]: https://github.com/imdario/mergo/workflows/tests/badge.svg?branch=master [2]: https://github.com/imdario/mergo/actions/workflows/tests.yml [3]: https://godoc.org/github.com/imdario/mergo?status.svg [4]: https://godoc.org/github.com/imdario/mergo [5]: https://img.shields.io/github/release/imdario/mergo.svg [6]: https://github.com/imdario/mergo/releases [7]: https://goreportcard.com/badge/imdario/mergo [8]: https://goreportcard.com/report/github.com/imdario/mergo [9]: https://coveralls.io/repos/github/imdario/mergo/badge.svg?branch=master [10]: https://coveralls.io/github/imdario/mergo?branch=master [11]: https://sourcegraph.com/github.com/imdario/mergo/-/badge.svg [12]: https://sourcegraph.com/github.com/imdario/mergo?badge [13]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=shield [14]: https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield [15]: https://img.shields.io/github/sponsors/imdario [16]: https://github.com/sponsors/imdario [17]: https://tidelift.com/badges/package/go/github.com%2Fimdario%2Fmergo [18]: https://tidelift.com/subscription/pkg/go-github.com-imdario-mergo [19]: https://bestpractices.coreinfrastructure.org/projects/7177/badge [20]: https://bestpractices.coreinfrastructure.org/projects/7177 [21]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo/badge [22]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements. Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection). Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region of Marche. ## Status Mergo is stable and frozen, ready for production. Check a short list of the projects using at large scale it [here](https://github.com/imdario/mergo#mergo-in-the-wild). No new features are accepted. They will be considered for a future v2 that improves the implementation and fixes bugs for corner cases. ### Important notes #### 1.0.0 In [1.0.0](//github.com/imdario/mergo/releases/tag/1.0.0) Mergo moves to a vanity URL `dario.cat/mergo`. No more v1 versions will be released. If the vanity URL is causing issues in your project due to a dependency pulling Mergo - it isn't a direct dependency in your project - it is recommended to use [replace](https://github.com/golang/go/wiki/Modules#when-should-i-use-the-replace-directive) to pin the version to the last one with the old import URL: ``` replace github.com/imdario/mergo => github.com/imdario/mergo v0.3.16 ``` #### 0.3.9 Please keep in mind that a problematic PR broke [0.3.9](//github.com/imdario/mergo/releases/tag/0.3.9). I reverted it in [0.3.10](//github.com/imdario/mergo/releases/tag/0.3.10), and I consider it stable but not bug-free. Also, this version adds support for go modules. Keep in mind that in [0.3.2](//github.com/imdario/mergo/releases/tag/0.3.2), Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). I added an optional/variadic argument so that it won't break the existing code. If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with ```go get -u dario.cat/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0). ### Donations If Mergo is useful to you, consider buying me a coffee, a beer, or making a monthly donation to allow me to keep building great free software. :heart_eyes: Donate using Liberapay Become my sponsor ### Mergo in the wild Mergo is used by [thousands](https://deps.dev/go/dario.cat%2Fmergo/v1.0.0/dependents) [of](https://deps.dev/go/github.com%2Fimdario%2Fmergo/v0.3.16/dependents) [projects](https://deps.dev/go/github.com%2Fimdario%2Fmergo/v0.3.12), including: * [containerd/containerd](https://github.com/containerd/containerd) * [datadog/datadog-agent](https://github.com/datadog/datadog-agent) * [docker/cli/](https://github.com/docker/cli/) * [goreleaser/goreleaser](https://github.com/goreleaser/goreleaser) * [go-micro/go-micro](https://github.com/go-micro/go-micro) * [grafana/loki](https://github.com/grafana/loki) * [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes) * [masterminds/sprig](github.com/Masterminds/sprig) * [moby/moby](https://github.com/moby/moby) * [slackhq/nebula](https://github.com/slackhq/nebula) * [volcano-sh/volcano](https://github.com/volcano-sh/volcano) ## Install go get dario.cat/mergo // use in your .go code import ( "dario.cat/mergo" ) ## Usage You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as [they are zero values](https://golang.org/ref/spec#The_zero_value) too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection). ```go if err := mergo.Merge(&dst, src); err != nil { // ... } ``` Also, you can merge overwriting values using the transformer `WithOverride`. ```go if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil { // ... } ``` If you need to override pointers, so the source pointer's value is assigned to the destination's pointer, you must use `WithoutDereference`: ```go package main import ( "fmt" "dario.cat/mergo" ) type Foo struct { A *string B int64 } func main() { first := "first" second := "second" src := Foo{ A: &first, B: 2, } dest := Foo{ A: &second, B: 1, } mergo.Merge(&dest, src, mergo.WithOverride, mergo.WithoutDereference) } ``` Additionally, you can map a `map[string]interface{}` to a struct (and otherwise, from struct to map), following the same restrictions as in `Merge()`. Keys are capitalized to find each corresponding exported field. ```go if err := mergo.Map(&dst, srcMap); err != nil { // ... } ``` Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as `map[string]interface{}`. They will be just assigned as values. Here is a nice example: ```go package main import ( "fmt" "dario.cat/mergo" ) type Foo struct { A string B int64 } func main() { src := Foo{ A: "one", B: 2, } dest := Foo{ A: "two", } mergo.Merge(&dest, src) fmt.Println(dest) // Will print // {two 2} } ``` Note: if test are failing due missing package, please execute: go get gopkg.in/yaml.v3 ### Transformers Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, `time.Time` is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero `time.Time`? ```go package main import ( "fmt" "dario.cat/mergo" "reflect" "time" ) type timeTransformer struct { } func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { if typ == reflect.TypeOf(time.Time{}) { return func(dst, src reflect.Value) error { if dst.CanSet() { isZero := dst.MethodByName("IsZero") result := isZero.Call([]reflect.Value{}) if result[0].Bool() { dst.Set(src) } } return nil } } return nil } type Snapshot struct { Time time.Time // ... } func main() { src := Snapshot{time.Now()} dest := Snapshot{} mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{})) fmt.Println(dest) // Will print // { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 } } ``` ## Contact me If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): [@im_dario](https://twitter.com/im_dario) ## About Written by [Dario Castañé](http://dario.im). ## License [BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE). [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_large) ================================================ FILE: vendor/dario.cat/mergo/SECURITY.md ================================================ # Security Policy ## Supported Versions | Version | Supported | | ------- | ------------------ | | 0.3.x | :white_check_mark: | | < 0.3 | :x: | ## Security contact information To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. ================================================ FILE: vendor/dario.cat/mergo/doc.go ================================================ // Copyright 2013 Dario Castañé. All rights reserved. // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. /* A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements. Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection). # Status It is ready for production use. It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc. # Important notes 1.0.0 In 1.0.0 Mergo moves to a vanity URL `dario.cat/mergo`. 0.3.9 Please keep in mind that a problematic PR broke 0.3.9. We reverted it in 0.3.10. We consider 0.3.10 as stable but not bug-free. . Also, this version adds suppot for go modules. Keep in mind that in 0.3.2, Mergo changed Merge() and Map() signatures to support transformers. We added an optional/variadic argument so that it won't break the existing code. If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with go get -u dario.cat/mergo. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0). # Install Do your usual installation procedure: go get dario.cat/mergo // use in your .go code import ( "dario.cat/mergo" ) # Usage You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as they are zero values too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection). if err := mergo.Merge(&dst, src); err != nil { // ... } Also, you can merge overwriting values using the transformer WithOverride. if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil { // ... } Additionally, you can map a map[string]interface{} to a struct (and otherwise, from struct to map), following the same restrictions as in Merge(). Keys are capitalized to find each corresponding exported field. if err := mergo.Map(&dst, srcMap); err != nil { // ... } Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as map[string]interface{}. They will be just assigned as values. Here is a nice example: package main import ( "fmt" "dario.cat/mergo" ) type Foo struct { A string B int64 } func main() { src := Foo{ A: "one", B: 2, } dest := Foo{ A: "two", } mergo.Merge(&dest, src) fmt.Println(dest) // Will print // {two 2} } # Transformers Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, time.Time is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero time.Time? package main import ( "fmt" "dario.cat/mergo" "reflect" "time" ) type timeTransformer struct { } func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { if typ == reflect.TypeOf(time.Time{}) { return func(dst, src reflect.Value) error { if dst.CanSet() { isZero := dst.MethodByName("IsZero") result := isZero.Call([]reflect.Value{}) if result[0].Bool() { dst.Set(src) } } return nil } } return nil } type Snapshot struct { Time time.Time // ... } func main() { src := Snapshot{time.Now()} dest := Snapshot{} mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{})) fmt.Println(dest) // Will print // { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 } } # Contact me If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): https://twitter.com/im_dario # About Written by Dario Castañé: https://da.rio.hn # License BSD 3-Clause license, as Go language. */ package mergo ================================================ FILE: vendor/dario.cat/mergo/map.go ================================================ // Copyright 2014 Dario Castañé. All rights reserved. // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Based on src/pkg/reflect/deepequal.go from official // golang's stdlib. package mergo import ( "fmt" "reflect" "unicode" "unicode/utf8" ) func changeInitialCase(s string, mapper func(rune) rune) string { if s == "" { return s } r, n := utf8.DecodeRuneInString(s) return string(mapper(r)) + s[n:] } func isExported(field reflect.StructField) bool { r, _ := utf8.DecodeRuneInString(field.Name) return r >= 'A' && r <= 'Z' } // Traverses recursively both values, assigning src's fields values to dst. // The map argument tracks comparisons that have already been seen, which allows // short circuiting on recursive types. func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) { overwrite := config.Overwrite if dst.CanAddr() { addr := dst.UnsafeAddr() h := 17 * addr seen := visited[h] typ := dst.Type() for p := seen; p != nil; p = p.next { if p.ptr == addr && p.typ == typ { return nil } } // Remember, remember... visited[h] = &visit{typ, seen, addr} } zeroValue := reflect.Value{} switch dst.Kind() { case reflect.Map: dstMap := dst.Interface().(map[string]interface{}) for i, n := 0, src.NumField(); i < n; i++ { srcType := src.Type() field := srcType.Field(i) if !isExported(field) { continue } fieldName := field.Name fieldName = changeInitialCase(fieldName, unicode.ToLower) if _, ok := dstMap[fieldName]; !ok || (!isEmptyValue(reflect.ValueOf(src.Field(i).Interface()), !config.ShouldNotDereference) && overwrite) || config.overwriteWithEmptyValue { dstMap[fieldName] = src.Field(i).Interface() } } case reflect.Ptr: if dst.IsNil() { v := reflect.New(dst.Type().Elem()) dst.Set(v) } dst = dst.Elem() fallthrough case reflect.Struct: srcMap := src.Interface().(map[string]interface{}) for key := range srcMap { config.overwriteWithEmptyValue = true srcValue := srcMap[key] fieldName := changeInitialCase(key, unicode.ToUpper) dstElement := dst.FieldByName(fieldName) if dstElement == zeroValue { // We discard it because the field doesn't exist. continue } srcElement := reflect.ValueOf(srcValue) dstKind := dstElement.Kind() srcKind := srcElement.Kind() if srcKind == reflect.Ptr && dstKind != reflect.Ptr { srcElement = srcElement.Elem() srcKind = reflect.TypeOf(srcElement.Interface()).Kind() } else if dstKind == reflect.Ptr { // Can this work? I guess it can't. if srcKind != reflect.Ptr && srcElement.CanAddr() { srcPtr := srcElement.Addr() srcElement = reflect.ValueOf(srcPtr) srcKind = reflect.Ptr } } if !srcElement.IsValid() { continue } if srcKind == dstKind { if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { return } } else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface { if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { return } } else if srcKind == reflect.Map { if err = deepMap(dstElement, srcElement, visited, depth+1, config); err != nil { return } } else { return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind) } } } return } // Map sets fields' values in dst from src. // src can be a map with string keys or a struct. dst must be the opposite: // if src is a map, dst must be a valid pointer to struct. If src is a struct, // dst must be map[string]interface{}. // It won't merge unexported (private) fields and will do recursively // any exported field. // If dst is a map, keys will be src fields' names in lower camel case. // Missing key in src that doesn't match a field in dst will be skipped. This // doesn't apply if dst is a map. // This is separated method from Merge because it is cleaner and it keeps sane // semantics: merging equal types, mapping different (restricted) types. func Map(dst, src interface{}, opts ...func(*Config)) error { return _map(dst, src, opts...) } // MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overridden by // non-empty src attribute values. // Deprecated: Use Map(…) with WithOverride func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error { return _map(dst, src, append(opts, WithOverride)...) } func _map(dst, src interface{}, opts ...func(*Config)) error { if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr { return ErrNonPointerArgument } var ( vDst, vSrc reflect.Value err error ) config := &Config{} for _, opt := range opts { opt(config) } if vDst, vSrc, err = resolveValues(dst, src); err != nil { return err } // To be friction-less, we redirect equal-type arguments // to deepMerge. Only because arguments can be anything. if vSrc.Kind() == vDst.Kind() { return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config) } switch vSrc.Kind() { case reflect.Struct: if vDst.Kind() != reflect.Map { return ErrExpectedMapAsDestination } case reflect.Map: if vDst.Kind() != reflect.Struct { return ErrExpectedStructAsDestination } default: return ErrNotSupported } return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, config) } ================================================ FILE: vendor/dario.cat/mergo/merge.go ================================================ // Copyright 2013 Dario Castañé. All rights reserved. // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Based on src/pkg/reflect/deepequal.go from official // golang's stdlib. package mergo import ( "fmt" "reflect" ) func hasMergeableFields(dst reflect.Value) (exported bool) { for i, n := 0, dst.NumField(); i < n; i++ { field := dst.Type().Field(i) if field.Anonymous && dst.Field(i).Kind() == reflect.Struct { exported = exported || hasMergeableFields(dst.Field(i)) } else if isExportedComponent(&field) { exported = exported || len(field.PkgPath) == 0 } } return } func isExportedComponent(field *reflect.StructField) bool { pkgPath := field.PkgPath if len(pkgPath) > 0 { return false } c := field.Name[0] if 'a' <= c && c <= 'z' || c == '_' { return false } return true } type Config struct { Transformers Transformers Overwrite bool ShouldNotDereference bool AppendSlice bool TypeCheck bool overwriteWithEmptyValue bool overwriteSliceWithEmptyValue bool sliceDeepCopy bool debug bool } type Transformers interface { Transformer(reflect.Type) func(dst, src reflect.Value) error } // Traverses recursively both values, assigning src's fields values to dst. // The map argument tracks comparisons that have already been seen, which allows // short circuiting on recursive types. func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) { overwrite := config.Overwrite typeCheck := config.TypeCheck overwriteWithEmptySrc := config.overwriteWithEmptyValue overwriteSliceWithEmptySrc := config.overwriteSliceWithEmptyValue sliceDeepCopy := config.sliceDeepCopy if !src.IsValid() { return } if dst.CanAddr() { addr := dst.UnsafeAddr() h := 17 * addr seen := visited[h] typ := dst.Type() for p := seen; p != nil; p = p.next { if p.ptr == addr && p.typ == typ { return nil } } // Remember, remember... visited[h] = &visit{typ, seen, addr} } if config.Transformers != nil && !isReflectNil(dst) && dst.IsValid() { if fn := config.Transformers.Transformer(dst.Type()); fn != nil { err = fn(dst, src) return } } switch dst.Kind() { case reflect.Struct: if hasMergeableFields(dst) { for i, n := 0, dst.NumField(); i < n; i++ { if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil { return } } } else { if dst.CanSet() && (isReflectNil(dst) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc) { dst.Set(src) } } case reflect.Map: if dst.IsNil() && !src.IsNil() { if dst.CanSet() { dst.Set(reflect.MakeMap(dst.Type())) } else { dst = src return } } if src.Kind() != reflect.Map { if overwrite && dst.CanSet() { dst.Set(src) } return } for _, key := range src.MapKeys() { srcElement := src.MapIndex(key) if !srcElement.IsValid() { continue } dstElement := dst.MapIndex(key) switch srcElement.Kind() { case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice: if srcElement.IsNil() { if overwrite { dst.SetMapIndex(key, srcElement) } continue } fallthrough default: if !srcElement.CanInterface() { continue } switch reflect.TypeOf(srcElement.Interface()).Kind() { case reflect.Struct: fallthrough case reflect.Ptr: fallthrough case reflect.Map: srcMapElm := srcElement dstMapElm := dstElement if srcMapElm.CanInterface() { srcMapElm = reflect.ValueOf(srcMapElm.Interface()) if dstMapElm.IsValid() { dstMapElm = reflect.ValueOf(dstMapElm.Interface()) } } if err = deepMerge(dstMapElm, srcMapElm, visited, depth+1, config); err != nil { return } case reflect.Slice: srcSlice := reflect.ValueOf(srcElement.Interface()) var dstSlice reflect.Value if !dstElement.IsValid() || dstElement.IsNil() { dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len()) } else { dstSlice = reflect.ValueOf(dstElement.Interface()) } if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy { if typeCheck && srcSlice.Type() != dstSlice.Type() { return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type()) } dstSlice = srcSlice } else if config.AppendSlice { if srcSlice.Type() != dstSlice.Type() { return fmt.Errorf("cannot append two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type()) } dstSlice = reflect.AppendSlice(dstSlice, srcSlice) } else if sliceDeepCopy { i := 0 for ; i < srcSlice.Len() && i < dstSlice.Len(); i++ { srcElement := srcSlice.Index(i) dstElement := dstSlice.Index(i) if srcElement.CanInterface() { srcElement = reflect.ValueOf(srcElement.Interface()) } if dstElement.CanInterface() { dstElement = reflect.ValueOf(dstElement.Interface()) } if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { return } } } dst.SetMapIndex(key, dstSlice) } } if dstElement.IsValid() && !isEmptyValue(dstElement, !config.ShouldNotDereference) { if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice { continue } if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map && reflect.TypeOf(dstElement.Interface()).Kind() == reflect.Map { continue } } if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement, !config.ShouldNotDereference)) { if dst.IsNil() { dst.Set(reflect.MakeMap(dst.Type())) } dst.SetMapIndex(key, srcElement) } } // Ensure that all keys in dst are deleted if they are not in src. if overwriteWithEmptySrc { for _, key := range dst.MapKeys() { srcElement := src.MapIndex(key) if !srcElement.IsValid() { dst.SetMapIndex(key, reflect.Value{}) } } } case reflect.Slice: if !dst.CanSet() { break } if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy { dst.Set(src) } else if config.AppendSlice { if src.Type() != dst.Type() { return fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type()) } dst.Set(reflect.AppendSlice(dst, src)) } else if sliceDeepCopy { for i := 0; i < src.Len() && i < dst.Len(); i++ { srcElement := src.Index(i) dstElement := dst.Index(i) if srcElement.CanInterface() { srcElement = reflect.ValueOf(srcElement.Interface()) } if dstElement.CanInterface() { dstElement = reflect.ValueOf(dstElement.Interface()) } if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { return } } } case reflect.Ptr: fallthrough case reflect.Interface: if isReflectNil(src) { if overwriteWithEmptySrc && dst.CanSet() && src.Type().AssignableTo(dst.Type()) { dst.Set(src) } break } if src.Kind() != reflect.Interface { if dst.IsNil() || (src.Kind() != reflect.Ptr && overwrite) { if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) { dst.Set(src) } } else if src.Kind() == reflect.Ptr { if !config.ShouldNotDereference { if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { return } } else if src.Elem().Kind() != reflect.Struct { if overwriteWithEmptySrc || (overwrite && !src.IsNil()) || dst.IsNil() { dst.Set(src) } } } else if dst.Elem().Type() == src.Type() { if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil { return } } else { return ErrDifferentArgumentsTypes } break } if dst.IsNil() || overwrite { if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) { dst.Set(src) } break } if dst.Elem().Kind() == src.Elem().Kind() { if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { return } break } default: mustSet := (isEmptyValue(dst, !config.ShouldNotDereference) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc) if mustSet { if dst.CanSet() { dst.Set(src) } else { dst = src } } } return } // Merge will fill any empty for value type attributes on the dst struct using corresponding // src attributes if they themselves are not empty. dst and src must be valid same-type structs // and dst must be a pointer to struct. // It won't merge unexported (private) fields and will do recursively any exported field. func Merge(dst, src interface{}, opts ...func(*Config)) error { return merge(dst, src, opts...) } // MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overridden by // non-empty src attribute values. // Deprecated: use Merge(…) with WithOverride func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error { return merge(dst, src, append(opts, WithOverride)...) } // WithTransformers adds transformers to merge, allowing to customize the merging of some types. func WithTransformers(transformers Transformers) func(*Config) { return func(config *Config) { config.Transformers = transformers } } // WithOverride will make merge override non-empty dst attributes with non-empty src attributes values. func WithOverride(config *Config) { config.Overwrite = true } // WithOverwriteWithEmptyValue will make merge override non empty dst attributes with empty src attributes values. func WithOverwriteWithEmptyValue(config *Config) { config.Overwrite = true config.overwriteWithEmptyValue = true } // WithOverrideEmptySlice will make merge override empty dst slice with empty src slice. func WithOverrideEmptySlice(config *Config) { config.overwriteSliceWithEmptyValue = true } // WithoutDereference prevents dereferencing pointers when evaluating whether they are empty // (i.e. a non-nil pointer is never considered empty). func WithoutDereference(config *Config) { config.ShouldNotDereference = true } // WithAppendSlice will make merge append slices instead of overwriting it. func WithAppendSlice(config *Config) { config.AppendSlice = true } // WithTypeCheck will make merge check types while overwriting it (must be used with WithOverride). func WithTypeCheck(config *Config) { config.TypeCheck = true } // WithSliceDeepCopy will merge slice element one by one with Overwrite flag. func WithSliceDeepCopy(config *Config) { config.sliceDeepCopy = true config.Overwrite = true } func merge(dst, src interface{}, opts ...func(*Config)) error { if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr { return ErrNonPointerArgument } var ( vDst, vSrc reflect.Value err error ) config := &Config{} for _, opt := range opts { opt(config) } if vDst, vSrc, err = resolveValues(dst, src); err != nil { return err } if vDst.Type() != vSrc.Type() { return ErrDifferentArgumentsTypes } return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config) } // IsReflectNil is the reflect value provided nil func isReflectNil(v reflect.Value) bool { k := v.Kind() switch k { case reflect.Interface, reflect.Slice, reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr: // Both interface and slice are nil if first word is 0. // Both are always bigger than a word; assume flagIndir. return v.IsNil() default: return false } } ================================================ FILE: vendor/dario.cat/mergo/mergo.go ================================================ // Copyright 2013 Dario Castañé. All rights reserved. // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Based on src/pkg/reflect/deepequal.go from official // golang's stdlib. package mergo import ( "errors" "reflect" ) // Errors reported by Mergo when it finds invalid arguments. var ( ErrNilArguments = errors.New("src and dst must not be nil") ErrDifferentArgumentsTypes = errors.New("src and dst must be of same type") ErrNotSupported = errors.New("only structs, maps, and slices are supported") ErrExpectedMapAsDestination = errors.New("dst was expected to be a map") ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct") ErrNonPointerArgument = errors.New("dst must be a pointer") ) // During deepMerge, must keep track of checks that are // in progress. The comparison algorithm assumes that all // checks in progress are true when it reencounters them. // Visited are stored in a map indexed by 17 * a1 + a2; type visit struct { typ reflect.Type next *visit ptr uintptr } // From src/pkg/encoding/json/encode.go. func isEmptyValue(v reflect.Value, shouldDereference bool) bool { switch v.Kind() { case reflect.Array, reflect.Map, reflect.Slice, reflect.String: return v.Len() == 0 case reflect.Bool: return !v.Bool() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v.Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return v.Uint() == 0 case reflect.Float32, reflect.Float64: return v.Float() == 0 case reflect.Interface, reflect.Ptr: if v.IsNil() { return true } if shouldDereference { return isEmptyValue(v.Elem(), shouldDereference) } return false case reflect.Func: return v.IsNil() case reflect.Invalid: return true } return false } func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) { if dst == nil || src == nil { err = ErrNilArguments return } vDst = reflect.ValueOf(dst).Elem() if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map && vDst.Kind() != reflect.Slice { err = ErrNotSupported return } vSrc = reflect.ValueOf(src) // We check if vSrc is a pointer to dereference it. if vSrc.Kind() == reflect.Ptr { vSrc = vSrc.Elem() } return } ================================================ FILE: vendor/filippo.io/edwards25519/LICENSE ================================================ Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of Google Inc. 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 OWNER 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. ================================================ FILE: vendor/filippo.io/edwards25519/README.md ================================================ # filippo.io/edwards25519 ``` import "filippo.io/edwards25519" ``` This library implements the edwards25519 elliptic curve, exposing the necessary APIs to build a wide array of higher-level primitives. Read the docs at [pkg.go.dev/filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519). The code is originally derived from Adam Langley's internal implementation in the Go standard library, and includes George Tankersley's [performance improvements](https://golang.org/cl/71950). It was then further developed by Henry de Valence for use in ristretto255, and was finally [merged back into the Go standard library](https://golang.org/cl/276272) as of Go 1.17. It now tracks the upstream codebase and extends it with additional functionality. Most users don't need this package, and should instead use `crypto/ed25519` for signatures, `golang.org/x/crypto/curve25519` for Diffie-Hellman, or `github.com/gtank/ristretto255` for prime order group logic. However, for anyone currently using a fork of `crypto/internal/edwards25519`/`crypto/ed25519/internal/edwards25519` or `github.com/agl/edwards25519`, this package should be a safer, faster, and more powerful alternative. Since this package is meant to curb proliferation of edwards25519 implementations in the Go ecosystem, it welcomes requests for new APIs or reviewable performance improvements. ================================================ FILE: vendor/filippo.io/edwards25519/doc.go ================================================ // Copyright (c) 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package edwards25519 implements group logic for the twisted Edwards curve // // -x^2 + y^2 = 1 + -(121665/121666)*x^2*y^2 // // This is better known as the Edwards curve equivalent to Curve25519, and is // the curve used by the Ed25519 signature scheme. // // Most users don't need this package, and should instead use crypto/ed25519 for // signatures, golang.org/x/crypto/curve25519 for Diffie-Hellman, or // github.com/gtank/ristretto255 for prime order group logic. // // However, developers who do need to interact with low-level edwards25519 // operations can use this package, which is an extended version of // crypto/internal/edwards25519 from the standard library repackaged as // an importable module. package edwards25519 ================================================ FILE: vendor/filippo.io/edwards25519/edwards25519.go ================================================ // Copyright (c) 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package edwards25519 import ( "errors" "filippo.io/edwards25519/field" ) // Point types. type projP1xP1 struct { X, Y, Z, T field.Element } type projP2 struct { X, Y, Z field.Element } // Point represents a point on the edwards25519 curve. // // This type works similarly to math/big.Int, and all arguments and receivers // are allowed to alias. // // The zero value is NOT valid, and it may be used only as a receiver. type Point struct { // Make the type not comparable (i.e. used with == or as a map key), as // equivalent points can be represented by different Go values. _ incomparable // The point is internally represented in extended coordinates (X, Y, Z, T) // where x = X/Z, y = Y/Z, and xy = T/Z per https://eprint.iacr.org/2008/522. x, y, z, t field.Element } type incomparable [0]func() func checkInitialized(points ...*Point) { for _, p := range points { if p.x == (field.Element{}) && p.y == (field.Element{}) { panic("edwards25519: use of uninitialized Point") } } } type projCached struct { YplusX, YminusX, Z, T2d field.Element } type affineCached struct { YplusX, YminusX, T2d field.Element } // Constructors. func (v *projP2) Zero() *projP2 { v.X.Zero() v.Y.One() v.Z.One() return v } // identity is the point at infinity. var identity, _ = new(Point).SetBytes([]byte{ 1, 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}) // NewIdentityPoint returns a new Point set to the identity. func NewIdentityPoint() *Point { return new(Point).Set(identity) } // generator is the canonical curve basepoint. See TestGenerator for the // correspondence of this encoding with the values in RFC 8032. var generator, _ = new(Point).SetBytes([]byte{ 0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}) // NewGeneratorPoint returns a new Point set to the canonical generator. func NewGeneratorPoint() *Point { return new(Point).Set(generator) } func (v *projCached) Zero() *projCached { v.YplusX.One() v.YminusX.One() v.Z.One() v.T2d.Zero() return v } func (v *affineCached) Zero() *affineCached { v.YplusX.One() v.YminusX.One() v.T2d.Zero() return v } // Assignments. // Set sets v = u, and returns v. func (v *Point) Set(u *Point) *Point { *v = *u return v } // Encoding. // Bytes returns the canonical 32-byte encoding of v, according to RFC 8032, // Section 5.1.2. func (v *Point) Bytes() []byte { // This function is outlined to make the allocations inline in the caller // rather than happen on the heap. var buf [32]byte return v.bytes(&buf) } func (v *Point) bytes(buf *[32]byte) []byte { checkInitialized(v) var zInv, x, y field.Element zInv.Invert(&v.z) // zInv = 1 / Z x.Multiply(&v.x, &zInv) // x = X / Z y.Multiply(&v.y, &zInv) // y = Y / Z out := copyFieldElement(buf, &y) out[31] |= byte(x.IsNegative() << 7) return out } var feOne = new(field.Element).One() // SetBytes sets v = x, where x is a 32-byte encoding of v. If x does not // represent a valid point on the curve, SetBytes returns nil and an error and // the receiver is unchanged. Otherwise, SetBytes returns v. // // Note that SetBytes accepts all non-canonical encodings of valid points. // That is, it follows decoding rules that match most implementations in // the ecosystem rather than RFC 8032. func (v *Point) SetBytes(x []byte) (*Point, error) { // Specifically, the non-canonical encodings that are accepted are // 1) the ones where the field element is not reduced (see the // (*field.Element).SetBytes docs) and // 2) the ones where the x-coordinate is zero and the sign bit is set. // // Read more at https://hdevalence.ca/blog/2020-10-04-its-25519am, // specifically the "Canonical A, R" section. y, err := new(field.Element).SetBytes(x) if err != nil { return nil, errors.New("edwards25519: invalid point encoding length") } // -x² + y² = 1 + dx²y² // x² + dx²y² = x²(dy² + 1) = y² - 1 // x² = (y² - 1) / (dy² + 1) // u = y² - 1 y2 := new(field.Element).Square(y) u := new(field.Element).Subtract(y2, feOne) // v = dy² + 1 vv := new(field.Element).Multiply(y2, d) vv = vv.Add(vv, feOne) // x = +√(u/v) xx, wasSquare := new(field.Element).SqrtRatio(u, vv) if wasSquare == 0 { return nil, errors.New("edwards25519: invalid point encoding") } // Select the negative square root if the sign bit is set. xxNeg := new(field.Element).Negate(xx) xx = xx.Select(xxNeg, xx, int(x[31]>>7)) v.x.Set(xx) v.y.Set(y) v.z.One() v.t.Multiply(xx, y) // xy = T / Z return v, nil } func copyFieldElement(buf *[32]byte, v *field.Element) []byte { copy(buf[:], v.Bytes()) return buf[:] } // Conversions. func (v *projP2) FromP1xP1(p *projP1xP1) *projP2 { v.X.Multiply(&p.X, &p.T) v.Y.Multiply(&p.Y, &p.Z) v.Z.Multiply(&p.Z, &p.T) return v } func (v *projP2) FromP3(p *Point) *projP2 { v.X.Set(&p.x) v.Y.Set(&p.y) v.Z.Set(&p.z) return v } func (v *Point) fromP1xP1(p *projP1xP1) *Point { v.x.Multiply(&p.X, &p.T) v.y.Multiply(&p.Y, &p.Z) v.z.Multiply(&p.Z, &p.T) v.t.Multiply(&p.X, &p.Y) return v } func (v *Point) fromP2(p *projP2) *Point { v.x.Multiply(&p.X, &p.Z) v.y.Multiply(&p.Y, &p.Z) v.z.Square(&p.Z) v.t.Multiply(&p.X, &p.Y) return v } // d is a constant in the curve equation. var d, _ = new(field.Element).SetBytes([]byte{ 0xa3, 0x78, 0x59, 0x13, 0xca, 0x4d, 0xeb, 0x75, 0xab, 0xd8, 0x41, 0x41, 0x4d, 0x0a, 0x70, 0x00, 0x98, 0xe8, 0x79, 0x77, 0x79, 0x40, 0xc7, 0x8c, 0x73, 0xfe, 0x6f, 0x2b, 0xee, 0x6c, 0x03, 0x52}) var d2 = new(field.Element).Add(d, d) func (v *projCached) FromP3(p *Point) *projCached { v.YplusX.Add(&p.y, &p.x) v.YminusX.Subtract(&p.y, &p.x) v.Z.Set(&p.z) v.T2d.Multiply(&p.t, d2) return v } func (v *affineCached) FromP3(p *Point) *affineCached { v.YplusX.Add(&p.y, &p.x) v.YminusX.Subtract(&p.y, &p.x) v.T2d.Multiply(&p.t, d2) var invZ field.Element invZ.Invert(&p.z) v.YplusX.Multiply(&v.YplusX, &invZ) v.YminusX.Multiply(&v.YminusX, &invZ) v.T2d.Multiply(&v.T2d, &invZ) return v } // (Re)addition and subtraction. // Add sets v = p + q, and returns v. func (v *Point) Add(p, q *Point) *Point { checkInitialized(p, q) qCached := new(projCached).FromP3(q) result := new(projP1xP1).Add(p, qCached) return v.fromP1xP1(result) } // Subtract sets v = p - q, and returns v. func (v *Point) Subtract(p, q *Point) *Point { checkInitialized(p, q) qCached := new(projCached).FromP3(q) result := new(projP1xP1).Sub(p, qCached) return v.fromP1xP1(result) } func (v *projP1xP1) Add(p *Point, q *projCached) *projP1xP1 { var YplusX, YminusX, PP, MM, TT2d, ZZ2 field.Element YplusX.Add(&p.y, &p.x) YminusX.Subtract(&p.y, &p.x) PP.Multiply(&YplusX, &q.YplusX) MM.Multiply(&YminusX, &q.YminusX) TT2d.Multiply(&p.t, &q.T2d) ZZ2.Multiply(&p.z, &q.Z) ZZ2.Add(&ZZ2, &ZZ2) v.X.Subtract(&PP, &MM) v.Y.Add(&PP, &MM) v.Z.Add(&ZZ2, &TT2d) v.T.Subtract(&ZZ2, &TT2d) return v } func (v *projP1xP1) Sub(p *Point, q *projCached) *projP1xP1 { var YplusX, YminusX, PP, MM, TT2d, ZZ2 field.Element YplusX.Add(&p.y, &p.x) YminusX.Subtract(&p.y, &p.x) PP.Multiply(&YplusX, &q.YminusX) // flipped sign MM.Multiply(&YminusX, &q.YplusX) // flipped sign TT2d.Multiply(&p.t, &q.T2d) ZZ2.Multiply(&p.z, &q.Z) ZZ2.Add(&ZZ2, &ZZ2) v.X.Subtract(&PP, &MM) v.Y.Add(&PP, &MM) v.Z.Subtract(&ZZ2, &TT2d) // flipped sign v.T.Add(&ZZ2, &TT2d) // flipped sign return v } func (v *projP1xP1) AddAffine(p *Point, q *affineCached) *projP1xP1 { var YplusX, YminusX, PP, MM, TT2d, Z2 field.Element YplusX.Add(&p.y, &p.x) YminusX.Subtract(&p.y, &p.x) PP.Multiply(&YplusX, &q.YplusX) MM.Multiply(&YminusX, &q.YminusX) TT2d.Multiply(&p.t, &q.T2d) Z2.Add(&p.z, &p.z) v.X.Subtract(&PP, &MM) v.Y.Add(&PP, &MM) v.Z.Add(&Z2, &TT2d) v.T.Subtract(&Z2, &TT2d) return v } func (v *projP1xP1) SubAffine(p *Point, q *affineCached) *projP1xP1 { var YplusX, YminusX, PP, MM, TT2d, Z2 field.Element YplusX.Add(&p.y, &p.x) YminusX.Subtract(&p.y, &p.x) PP.Multiply(&YplusX, &q.YminusX) // flipped sign MM.Multiply(&YminusX, &q.YplusX) // flipped sign TT2d.Multiply(&p.t, &q.T2d) Z2.Add(&p.z, &p.z) v.X.Subtract(&PP, &MM) v.Y.Add(&PP, &MM) v.Z.Subtract(&Z2, &TT2d) // flipped sign v.T.Add(&Z2, &TT2d) // flipped sign return v } // Doubling. func (v *projP1xP1) Double(p *projP2) *projP1xP1 { var XX, YY, ZZ2, XplusYsq field.Element XX.Square(&p.X) YY.Square(&p.Y) ZZ2.Square(&p.Z) ZZ2.Add(&ZZ2, &ZZ2) XplusYsq.Add(&p.X, &p.Y) XplusYsq.Square(&XplusYsq) v.Y.Add(&YY, &XX) v.Z.Subtract(&YY, &XX) v.X.Subtract(&XplusYsq, &v.Y) v.T.Subtract(&ZZ2, &v.Z) return v } // Negation. // Negate sets v = -p, and returns v. func (v *Point) Negate(p *Point) *Point { checkInitialized(p) v.x.Negate(&p.x) v.y.Set(&p.y) v.z.Set(&p.z) v.t.Negate(&p.t) return v } // Equal returns 1 if v is equivalent to u, and 0 otherwise. func (v *Point) Equal(u *Point) int { checkInitialized(v, u) var t1, t2, t3, t4 field.Element t1.Multiply(&v.x, &u.z) t2.Multiply(&u.x, &v.z) t3.Multiply(&v.y, &u.z) t4.Multiply(&u.y, &v.z) return t1.Equal(&t2) & t3.Equal(&t4) } // Constant-time operations // Select sets v to a if cond == 1 and to b if cond == 0. func (v *projCached) Select(a, b *projCached, cond int) *projCached { v.YplusX.Select(&a.YplusX, &b.YplusX, cond) v.YminusX.Select(&a.YminusX, &b.YminusX, cond) v.Z.Select(&a.Z, &b.Z, cond) v.T2d.Select(&a.T2d, &b.T2d, cond) return v } // Select sets v to a if cond == 1 and to b if cond == 0. func (v *affineCached) Select(a, b *affineCached, cond int) *affineCached { v.YplusX.Select(&a.YplusX, &b.YplusX, cond) v.YminusX.Select(&a.YminusX, &b.YminusX, cond) v.T2d.Select(&a.T2d, &b.T2d, cond) return v } // CondNeg negates v if cond == 1 and leaves it unchanged if cond == 0. func (v *projCached) CondNeg(cond int) *projCached { v.YplusX.Swap(&v.YminusX, cond) v.T2d.Select(new(field.Element).Negate(&v.T2d), &v.T2d, cond) return v } // CondNeg negates v if cond == 1 and leaves it unchanged if cond == 0. func (v *affineCached) CondNeg(cond int) *affineCached { v.YplusX.Swap(&v.YminusX, cond) v.T2d.Select(new(field.Element).Negate(&v.T2d), &v.T2d, cond) return v } ================================================ FILE: vendor/filippo.io/edwards25519/extra.go ================================================ // Copyright (c) 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package edwards25519 // This file contains additional functionality that is not included in the // upstream crypto/internal/edwards25519 package. import ( "errors" "filippo.io/edwards25519/field" ) // ExtendedCoordinates returns v in extended coordinates (X:Y:Z:T) where // x = X/Z, y = Y/Z, and xy = T/Z as in https://eprint.iacr.org/2008/522. func (v *Point) ExtendedCoordinates() (X, Y, Z, T *field.Element) { // This function is outlined to make the allocations inline in the caller // rather than happen on the heap. Don't change the style without making // sure it doesn't increase the inliner cost. var e [4]field.Element X, Y, Z, T = v.extendedCoordinates(&e) return } func (v *Point) extendedCoordinates(e *[4]field.Element) (X, Y, Z, T *field.Element) { checkInitialized(v) X = e[0].Set(&v.x) Y = e[1].Set(&v.y) Z = e[2].Set(&v.z) T = e[3].Set(&v.t) return } // SetExtendedCoordinates sets v = (X:Y:Z:T) in extended coordinates where // x = X/Z, y = Y/Z, and xy = T/Z as in https://eprint.iacr.org/2008/522. // // If the coordinates are invalid or don't represent a valid point on the curve, // SetExtendedCoordinates returns nil and an error and the receiver is // unchanged. Otherwise, SetExtendedCoordinates returns v. func (v *Point) SetExtendedCoordinates(X, Y, Z, T *field.Element) (*Point, error) { if !isOnCurve(X, Y, Z, T) { return nil, errors.New("edwards25519: invalid point coordinates") } v.x.Set(X) v.y.Set(Y) v.z.Set(Z) v.t.Set(T) return v, nil } func isOnCurve(X, Y, Z, T *field.Element) bool { var lhs, rhs field.Element XX := new(field.Element).Square(X) YY := new(field.Element).Square(Y) ZZ := new(field.Element).Square(Z) TT := new(field.Element).Square(T) // -x² + y² = 1 + dx²y² // -(X/Z)² + (Y/Z)² = 1 + d(T/Z)² // -X² + Y² = Z² + dT² lhs.Subtract(YY, XX) rhs.Multiply(d, TT).Add(&rhs, ZZ) if lhs.Equal(&rhs) != 1 { return false } // xy = T/Z // XY/Z² = T/Z // XY = TZ lhs.Multiply(X, Y) rhs.Multiply(T, Z) return lhs.Equal(&rhs) == 1 } // BytesMontgomery converts v to a point on the birationally-equivalent // Curve25519 Montgomery curve, and returns its canonical 32 bytes encoding // according to RFC 7748. // // Note that BytesMontgomery only encodes the u-coordinate, so v and -v encode // to the same value. If v is the identity point, BytesMontgomery returns 32 // zero bytes, analogously to the X25519 function. // // The lack of an inverse operation (such as SetMontgomeryBytes) is deliberate: // while every valid edwards25519 point has a unique u-coordinate Montgomery // encoding, X25519 accepts inputs on the quadratic twist, which don't correspond // to any edwards25519 point, and every other X25519 input corresponds to two // edwards25519 points. func (v *Point) BytesMontgomery() []byte { // This function is outlined to make the allocations inline in the caller // rather than happen on the heap. var buf [32]byte return v.bytesMontgomery(&buf) } func (v *Point) bytesMontgomery(buf *[32]byte) []byte { checkInitialized(v) // RFC 7748, Section 4.1 provides the bilinear map to calculate the // Montgomery u-coordinate // // u = (1 + y) / (1 - y) // // where y = Y / Z. var y, recip, u field.Element y.Multiply(&v.y, y.Invert(&v.z)) // y = Y / Z recip.Invert(recip.Subtract(feOne, &y)) // r = 1/(1 - y) u.Multiply(u.Add(feOne, &y), &recip) // u = (1 + y)*r return copyFieldElement(buf, &u) } // MultByCofactor sets v = 8 * p, and returns v. func (v *Point) MultByCofactor(p *Point) *Point { checkInitialized(p) result := projP1xP1{} pp := (&projP2{}).FromP3(p) result.Double(pp) pp.FromP1xP1(&result) result.Double(pp) pp.FromP1xP1(&result) result.Double(pp) return v.fromP1xP1(&result) } // Given k > 0, set s = s**(2*i). func (s *Scalar) pow2k(k int) { for i := 0; i < k; i++ { s.Multiply(s, s) } } // Invert sets s to the inverse of a nonzero scalar v, and returns s. // // If t is zero, Invert returns zero. func (s *Scalar) Invert(t *Scalar) *Scalar { // Uses a hardcoded sliding window of width 4. var table [8]Scalar var tt Scalar tt.Multiply(t, t) table[0] = *t for i := 0; i < 7; i++ { table[i+1].Multiply(&table[i], &tt) } // Now table = [t**1, t**3, t**5, t**7, t**9, t**11, t**13, t**15] // so t**k = t[k/2] for odd k // To compute the sliding window digits, use the following Sage script: // sage: import itertools // sage: def sliding_window(w,k): // ....: digits = [] // ....: while k > 0: // ....: if k % 2 == 1: // ....: kmod = k % (2**w) // ....: digits.append(kmod) // ....: k = k - kmod // ....: else: // ....: digits.append(0) // ....: k = k // 2 // ....: return digits // Now we can compute s roughly as follows: // sage: s = 1 // sage: for coeff in reversed(sliding_window(4,l-2)): // ....: s = s*s // ....: if coeff > 0 : // ....: s = s*t**coeff // This works on one bit at a time, with many runs of zeros. // The digits can be collapsed into [(count, coeff)] as follows: // sage: [(len(list(group)),d) for d,group in itertools.groupby(sliding_window(4,l-2))] // Entries of the form (k, 0) turn into pow2k(k) // Entries of the form (1, coeff) turn into a squaring and then a table lookup. // We can fold the squaring into the previous pow2k(k) as pow2k(k+1). *s = table[1/2] s.pow2k(127 + 1) s.Multiply(s, &table[1/2]) s.pow2k(4 + 1) s.Multiply(s, &table[9/2]) s.pow2k(3 + 1) s.Multiply(s, &table[11/2]) s.pow2k(3 + 1) s.Multiply(s, &table[13/2]) s.pow2k(3 + 1) s.Multiply(s, &table[15/2]) s.pow2k(4 + 1) s.Multiply(s, &table[7/2]) s.pow2k(4 + 1) s.Multiply(s, &table[15/2]) s.pow2k(3 + 1) s.Multiply(s, &table[5/2]) s.pow2k(3 + 1) s.Multiply(s, &table[1/2]) s.pow2k(4 + 1) s.Multiply(s, &table[15/2]) s.pow2k(4 + 1) s.Multiply(s, &table[15/2]) s.pow2k(4 + 1) s.Multiply(s, &table[7/2]) s.pow2k(3 + 1) s.Multiply(s, &table[3/2]) s.pow2k(4 + 1) s.Multiply(s, &table[11/2]) s.pow2k(5 + 1) s.Multiply(s, &table[11/2]) s.pow2k(9 + 1) s.Multiply(s, &table[9/2]) s.pow2k(3 + 1) s.Multiply(s, &table[3/2]) s.pow2k(4 + 1) s.Multiply(s, &table[3/2]) s.pow2k(4 + 1) s.Multiply(s, &table[3/2]) s.pow2k(4 + 1) s.Multiply(s, &table[9/2]) s.pow2k(3 + 1) s.Multiply(s, &table[7/2]) s.pow2k(3 + 1) s.Multiply(s, &table[3/2]) s.pow2k(3 + 1) s.Multiply(s, &table[13/2]) s.pow2k(3 + 1) s.Multiply(s, &table[7/2]) s.pow2k(4 + 1) s.Multiply(s, &table[9/2]) s.pow2k(3 + 1) s.Multiply(s, &table[15/2]) s.pow2k(4 + 1) s.Multiply(s, &table[11/2]) return s } // MultiScalarMult sets v = sum(scalars[i] * points[i]), and returns v. // // Execution time depends only on the lengths of the two slices, which must match. func (v *Point) MultiScalarMult(scalars []*Scalar, points []*Point) *Point { if len(scalars) != len(points) { panic("edwards25519: called MultiScalarMult with different size inputs") } checkInitialized(points...) // Proceed as in the single-base case, but share doublings // between each point in the multiscalar equation. // Build lookup tables for each point tables := make([]projLookupTable, len(points)) for i := range tables { tables[i].FromP3(points[i]) } // Compute signed radix-16 digits for each scalar digits := make([][64]int8, len(scalars)) for i := range digits { digits[i] = scalars[i].signedRadix16() } // Unwrap first loop iteration to save computing 16*identity multiple := &projCached{} tmp1 := &projP1xP1{} tmp2 := &projP2{} // Lookup-and-add the appropriate multiple of each input point for j := range tables { tables[j].SelectInto(multiple, digits[j][63]) tmp1.Add(v, multiple) // tmp1 = v + x_(j,63)*Q in P1xP1 coords v.fromP1xP1(tmp1) // update v } tmp2.FromP3(v) // set up tmp2 = v in P2 coords for next iteration for i := 62; i >= 0; i-- { tmp1.Double(tmp2) // tmp1 = 2*(prev) in P1xP1 coords tmp2.FromP1xP1(tmp1) // tmp2 = 2*(prev) in P2 coords tmp1.Double(tmp2) // tmp1 = 4*(prev) in P1xP1 coords tmp2.FromP1xP1(tmp1) // tmp2 = 4*(prev) in P2 coords tmp1.Double(tmp2) // tmp1 = 8*(prev) in P1xP1 coords tmp2.FromP1xP1(tmp1) // tmp2 = 8*(prev) in P2 coords tmp1.Double(tmp2) // tmp1 = 16*(prev) in P1xP1 coords v.fromP1xP1(tmp1) // v = 16*(prev) in P3 coords // Lookup-and-add the appropriate multiple of each input point for j := range tables { tables[j].SelectInto(multiple, digits[j][i]) tmp1.Add(v, multiple) // tmp1 = v + x_(j,i)*Q in P1xP1 coords v.fromP1xP1(tmp1) // update v } tmp2.FromP3(v) // set up tmp2 = v in P2 coords for next iteration } return v } // VarTimeMultiScalarMult sets v = sum(scalars[i] * points[i]), and returns v. // // Execution time depends on the inputs. func (v *Point) VarTimeMultiScalarMult(scalars []*Scalar, points []*Point) *Point { if len(scalars) != len(points) { panic("edwards25519: called VarTimeMultiScalarMult with different size inputs") } checkInitialized(points...) // Generalize double-base NAF computation to arbitrary sizes. // Here all the points are dynamic, so we only use the smaller // tables. // Build lookup tables for each point tables := make([]nafLookupTable5, len(points)) for i := range tables { tables[i].FromP3(points[i]) } // Compute a NAF for each scalar nafs := make([][256]int8, len(scalars)) for i := range nafs { nafs[i] = scalars[i].nonAdjacentForm(5) } multiple := &projCached{} tmp1 := &projP1xP1{} tmp2 := &projP2{} tmp2.Zero() // Move from high to low bits, doubling the accumulator // at each iteration and checking whether there is a nonzero // coefficient to look up a multiple of. // // Skip trying to find the first nonzero coefficent, because // searching might be more work than a few extra doublings. for i := 255; i >= 0; i-- { tmp1.Double(tmp2) for j := range nafs { if nafs[j][i] > 0 { v.fromP1xP1(tmp1) tables[j].SelectInto(multiple, nafs[j][i]) tmp1.Add(v, multiple) } else if nafs[j][i] < 0 { v.fromP1xP1(tmp1) tables[j].SelectInto(multiple, -nafs[j][i]) tmp1.Sub(v, multiple) } } tmp2.FromP1xP1(tmp1) } v.fromP2(tmp2) return v } ================================================ FILE: vendor/filippo.io/edwards25519/field/fe.go ================================================ // Copyright (c) 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package field implements fast arithmetic modulo 2^255-19. package field import ( "crypto/subtle" "encoding/binary" "errors" "math/bits" ) // Element represents an element of the field GF(2^255-19). Note that this // is not a cryptographically secure group, and should only be used to interact // with edwards25519.Point coordinates. // // This type works similarly to math/big.Int, and all arguments and receivers // are allowed to alias. // // The zero value is a valid zero element. type Element struct { // An element t represents the integer // t.l0 + t.l1*2^51 + t.l2*2^102 + t.l3*2^153 + t.l4*2^204 // // Between operations, all limbs are expected to be lower than 2^52. l0 uint64 l1 uint64 l2 uint64 l3 uint64 l4 uint64 } const maskLow51Bits uint64 = (1 << 51) - 1 var feZero = &Element{0, 0, 0, 0, 0} // Zero sets v = 0, and returns v. func (v *Element) Zero() *Element { *v = *feZero return v } var feOne = &Element{1, 0, 0, 0, 0} // One sets v = 1, and returns v. func (v *Element) One() *Element { *v = *feOne return v } // reduce reduces v modulo 2^255 - 19 and returns it. func (v *Element) reduce() *Element { v.carryPropagate() // After the light reduction we now have a field element representation // v < 2^255 + 2^13 * 19, but need v < 2^255 - 19. // If v >= 2^255 - 19, then v + 19 >= 2^255, which would overflow 2^255 - 1, // generating a carry. That is, c will be 0 if v < 2^255 - 19, and 1 otherwise. c := (v.l0 + 19) >> 51 c = (v.l1 + c) >> 51 c = (v.l2 + c) >> 51 c = (v.l3 + c) >> 51 c = (v.l4 + c) >> 51 // If v < 2^255 - 19 and c = 0, this will be a no-op. Otherwise, it's // effectively applying the reduction identity to the carry. v.l0 += 19 * c v.l1 += v.l0 >> 51 v.l0 = v.l0 & maskLow51Bits v.l2 += v.l1 >> 51 v.l1 = v.l1 & maskLow51Bits v.l3 += v.l2 >> 51 v.l2 = v.l2 & maskLow51Bits v.l4 += v.l3 >> 51 v.l3 = v.l3 & maskLow51Bits // no additional carry v.l4 = v.l4 & maskLow51Bits return v } // Add sets v = a + b, and returns v. func (v *Element) Add(a, b *Element) *Element { v.l0 = a.l0 + b.l0 v.l1 = a.l1 + b.l1 v.l2 = a.l2 + b.l2 v.l3 = a.l3 + b.l3 v.l4 = a.l4 + b.l4 // Using the generic implementation here is actually faster than the // assembly. Probably because the body of this function is so simple that // the compiler can figure out better optimizations by inlining the carry // propagation. return v.carryPropagateGeneric() } // Subtract sets v = a - b, and returns v. func (v *Element) Subtract(a, b *Element) *Element { // We first add 2 * p, to guarantee the subtraction won't underflow, and // then subtract b (which can be up to 2^255 + 2^13 * 19). v.l0 = (a.l0 + 0xFFFFFFFFFFFDA) - b.l0 v.l1 = (a.l1 + 0xFFFFFFFFFFFFE) - b.l1 v.l2 = (a.l2 + 0xFFFFFFFFFFFFE) - b.l2 v.l3 = (a.l3 + 0xFFFFFFFFFFFFE) - b.l3 v.l4 = (a.l4 + 0xFFFFFFFFFFFFE) - b.l4 return v.carryPropagate() } // Negate sets v = -a, and returns v. func (v *Element) Negate(a *Element) *Element { return v.Subtract(feZero, a) } // Invert sets v = 1/z mod p, and returns v. // // If z == 0, Invert returns v = 0. func (v *Element) Invert(z *Element) *Element { // Inversion is implemented as exponentiation with exponent p − 2. It uses the // same sequence of 255 squarings and 11 multiplications as [Curve25519]. var z2, z9, z11, z2_5_0, z2_10_0, z2_20_0, z2_50_0, z2_100_0, t Element z2.Square(z) // 2 t.Square(&z2) // 4 t.Square(&t) // 8 z9.Multiply(&t, z) // 9 z11.Multiply(&z9, &z2) // 11 t.Square(&z11) // 22 z2_5_0.Multiply(&t, &z9) // 31 = 2^5 - 2^0 t.Square(&z2_5_0) // 2^6 - 2^1 for i := 0; i < 4; i++ { t.Square(&t) // 2^10 - 2^5 } z2_10_0.Multiply(&t, &z2_5_0) // 2^10 - 2^0 t.Square(&z2_10_0) // 2^11 - 2^1 for i := 0; i < 9; i++ { t.Square(&t) // 2^20 - 2^10 } z2_20_0.Multiply(&t, &z2_10_0) // 2^20 - 2^0 t.Square(&z2_20_0) // 2^21 - 2^1 for i := 0; i < 19; i++ { t.Square(&t) // 2^40 - 2^20 } t.Multiply(&t, &z2_20_0) // 2^40 - 2^0 t.Square(&t) // 2^41 - 2^1 for i := 0; i < 9; i++ { t.Square(&t) // 2^50 - 2^10 } z2_50_0.Multiply(&t, &z2_10_0) // 2^50 - 2^0 t.Square(&z2_50_0) // 2^51 - 2^1 for i := 0; i < 49; i++ { t.Square(&t) // 2^100 - 2^50 } z2_100_0.Multiply(&t, &z2_50_0) // 2^100 - 2^0 t.Square(&z2_100_0) // 2^101 - 2^1 for i := 0; i < 99; i++ { t.Square(&t) // 2^200 - 2^100 } t.Multiply(&t, &z2_100_0) // 2^200 - 2^0 t.Square(&t) // 2^201 - 2^1 for i := 0; i < 49; i++ { t.Square(&t) // 2^250 - 2^50 } t.Multiply(&t, &z2_50_0) // 2^250 - 2^0 t.Square(&t) // 2^251 - 2^1 t.Square(&t) // 2^252 - 2^2 t.Square(&t) // 2^253 - 2^3 t.Square(&t) // 2^254 - 2^4 t.Square(&t) // 2^255 - 2^5 return v.Multiply(&t, &z11) // 2^255 - 21 } // Set sets v = a, and returns v. func (v *Element) Set(a *Element) *Element { *v = *a return v } // SetBytes sets v to x, where x is a 32-byte little-endian encoding. If x is // not of the right length, SetBytes returns nil and an error, and the // receiver is unchanged. // // Consistent with RFC 7748, the most significant bit (the high bit of the // last byte) is ignored, and non-canonical values (2^255-19 through 2^255-1) // are accepted. Note that this is laxer than specified by RFC 8032, but // consistent with most Ed25519 implementations. func (v *Element) SetBytes(x []byte) (*Element, error) { if len(x) != 32 { return nil, errors.New("edwards25519: invalid field element input size") } // Bits 0:51 (bytes 0:8, bits 0:64, shift 0, mask 51). v.l0 = binary.LittleEndian.Uint64(x[0:8]) v.l0 &= maskLow51Bits // Bits 51:102 (bytes 6:14, bits 48:112, shift 3, mask 51). v.l1 = binary.LittleEndian.Uint64(x[6:14]) >> 3 v.l1 &= maskLow51Bits // Bits 102:153 (bytes 12:20, bits 96:160, shift 6, mask 51). v.l2 = binary.LittleEndian.Uint64(x[12:20]) >> 6 v.l2 &= maskLow51Bits // Bits 153:204 (bytes 19:27, bits 152:216, shift 1, mask 51). v.l3 = binary.LittleEndian.Uint64(x[19:27]) >> 1 v.l3 &= maskLow51Bits // Bits 204:255 (bytes 24:32, bits 192:256, shift 12, mask 51). // Note: not bytes 25:33, shift 4, to avoid overread. v.l4 = binary.LittleEndian.Uint64(x[24:32]) >> 12 v.l4 &= maskLow51Bits return v, nil } // Bytes returns the canonical 32-byte little-endian encoding of v. func (v *Element) Bytes() []byte { // This function is outlined to make the allocations inline in the caller // rather than happen on the heap. var out [32]byte return v.bytes(&out) } func (v *Element) bytes(out *[32]byte) []byte { t := *v t.reduce() var buf [8]byte for i, l := range [5]uint64{t.l0, t.l1, t.l2, t.l3, t.l4} { bitsOffset := i * 51 binary.LittleEndian.PutUint64(buf[:], l<= len(out) { break } out[off] |= bb } } return out[:] } // Equal returns 1 if v and u are equal, and 0 otherwise. func (v *Element) Equal(u *Element) int { sa, sv := u.Bytes(), v.Bytes() return subtle.ConstantTimeCompare(sa, sv) } // mask64Bits returns 0xffffffff if cond is 1, and 0 otherwise. func mask64Bits(cond int) uint64 { return ^(uint64(cond) - 1) } // Select sets v to a if cond == 1, and to b if cond == 0. func (v *Element) Select(a, b *Element, cond int) *Element { m := mask64Bits(cond) v.l0 = (m & a.l0) | (^m & b.l0) v.l1 = (m & a.l1) | (^m & b.l1) v.l2 = (m & a.l2) | (^m & b.l2) v.l3 = (m & a.l3) | (^m & b.l3) v.l4 = (m & a.l4) | (^m & b.l4) return v } // Swap swaps v and u if cond == 1 or leaves them unchanged if cond == 0, and returns v. func (v *Element) Swap(u *Element, cond int) { m := mask64Bits(cond) t := m & (v.l0 ^ u.l0) v.l0 ^= t u.l0 ^= t t = m & (v.l1 ^ u.l1) v.l1 ^= t u.l1 ^= t t = m & (v.l2 ^ u.l2) v.l2 ^= t u.l2 ^= t t = m & (v.l3 ^ u.l3) v.l3 ^= t u.l3 ^= t t = m & (v.l4 ^ u.l4) v.l4 ^= t u.l4 ^= t } // IsNegative returns 1 if v is negative, and 0 otherwise. func (v *Element) IsNegative() int { return int(v.Bytes()[0] & 1) } // Absolute sets v to |u|, and returns v. func (v *Element) Absolute(u *Element) *Element { return v.Select(new(Element).Negate(u), u, u.IsNegative()) } // Multiply sets v = x * y, and returns v. func (v *Element) Multiply(x, y *Element) *Element { feMul(v, x, y) return v } // Square sets v = x * x, and returns v. func (v *Element) Square(x *Element) *Element { feSquare(v, x) return v } // Mult32 sets v = x * y, and returns v. func (v *Element) Mult32(x *Element, y uint32) *Element { x0lo, x0hi := mul51(x.l0, y) x1lo, x1hi := mul51(x.l1, y) x2lo, x2hi := mul51(x.l2, y) x3lo, x3hi := mul51(x.l3, y) x4lo, x4hi := mul51(x.l4, y) v.l0 = x0lo + 19*x4hi // carried over per the reduction identity v.l1 = x1lo + x0hi v.l2 = x2lo + x1hi v.l3 = x3lo + x2hi v.l4 = x4lo + x3hi // The hi portions are going to be only 32 bits, plus any previous excess, // so we can skip the carry propagation. return v } // mul51 returns lo + hi * 2⁵¹ = a * b. func mul51(a uint64, b uint32) (lo uint64, hi uint64) { mh, ml := bits.Mul64(a, uint64(b)) lo = ml & maskLow51Bits hi = (mh << 13) | (ml >> 51) return } // Pow22523 set v = x^((p-5)/8), and returns v. (p-5)/8 is 2^252-3. func (v *Element) Pow22523(x *Element) *Element { var t0, t1, t2 Element t0.Square(x) // x^2 t1.Square(&t0) // x^4 t1.Square(&t1) // x^8 t1.Multiply(x, &t1) // x^9 t0.Multiply(&t0, &t1) // x^11 t0.Square(&t0) // x^22 t0.Multiply(&t1, &t0) // x^31 t1.Square(&t0) // x^62 for i := 1; i < 5; i++ { // x^992 t1.Square(&t1) } t0.Multiply(&t1, &t0) // x^1023 -> 1023 = 2^10 - 1 t1.Square(&t0) // 2^11 - 2 for i := 1; i < 10; i++ { // 2^20 - 2^10 t1.Square(&t1) } t1.Multiply(&t1, &t0) // 2^20 - 1 t2.Square(&t1) // 2^21 - 2 for i := 1; i < 20; i++ { // 2^40 - 2^20 t2.Square(&t2) } t1.Multiply(&t2, &t1) // 2^40 - 1 t1.Square(&t1) // 2^41 - 2 for i := 1; i < 10; i++ { // 2^50 - 2^10 t1.Square(&t1) } t0.Multiply(&t1, &t0) // 2^50 - 1 t1.Square(&t0) // 2^51 - 2 for i := 1; i < 50; i++ { // 2^100 - 2^50 t1.Square(&t1) } t1.Multiply(&t1, &t0) // 2^100 - 1 t2.Square(&t1) // 2^101 - 2 for i := 1; i < 100; i++ { // 2^200 - 2^100 t2.Square(&t2) } t1.Multiply(&t2, &t1) // 2^200 - 1 t1.Square(&t1) // 2^201 - 2 for i := 1; i < 50; i++ { // 2^250 - 2^50 t1.Square(&t1) } t0.Multiply(&t1, &t0) // 2^250 - 1 t0.Square(&t0) // 2^251 - 2 t0.Square(&t0) // 2^252 - 4 return v.Multiply(&t0, x) // 2^252 - 3 -> x^(2^252-3) } // sqrtM1 is 2^((p-1)/4), which squared is equal to -1 by Euler's Criterion. var sqrtM1 = &Element{1718705420411056, 234908883556509, 2233514472574048, 2117202627021982, 765476049583133} // SqrtRatio sets r to the non-negative square root of the ratio of u and v. // // If u/v is square, SqrtRatio returns r and 1. If u/v is not square, SqrtRatio // sets r according to Section 4.3 of draft-irtf-cfrg-ristretto255-decaf448-00, // and returns r and 0. func (r *Element) SqrtRatio(u, v *Element) (R *Element, wasSquare int) { t0 := new(Element) // r = (u * v3) * (u * v7)^((p-5)/8) v2 := new(Element).Square(v) uv3 := new(Element).Multiply(u, t0.Multiply(v2, v)) uv7 := new(Element).Multiply(uv3, t0.Square(v2)) rr := new(Element).Multiply(uv3, t0.Pow22523(uv7)) check := new(Element).Multiply(v, t0.Square(rr)) // check = v * r^2 uNeg := new(Element).Negate(u) correctSignSqrt := check.Equal(u) flippedSignSqrt := check.Equal(uNeg) flippedSignSqrtI := check.Equal(t0.Multiply(uNeg, sqrtM1)) rPrime := new(Element).Multiply(rr, sqrtM1) // r_prime = SQRT_M1 * r // r = CT_SELECT(r_prime IF flipped_sign_sqrt | flipped_sign_sqrt_i ELSE r) rr.Select(rPrime, rr, flippedSignSqrt|flippedSignSqrtI) r.Absolute(rr) // Choose the nonnegative square root. return r, correctSignSqrt | flippedSignSqrt } ================================================ FILE: vendor/filippo.io/edwards25519/field/fe_amd64.go ================================================ // Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT. //go:build amd64 && gc && !purego // +build amd64,gc,!purego package field // feMul sets out = a * b. It works like feMulGeneric. // //go:noescape func feMul(out *Element, a *Element, b *Element) // feSquare sets out = a * a. It works like feSquareGeneric. // //go:noescape func feSquare(out *Element, a *Element) ================================================ FILE: vendor/filippo.io/edwards25519/field/fe_amd64.s ================================================ // Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT. //go:build amd64 && gc && !purego // +build amd64,gc,!purego #include "textflag.h" // func feMul(out *Element, a *Element, b *Element) TEXT ·feMul(SB), NOSPLIT, $0-24 MOVQ a+8(FP), CX MOVQ b+16(FP), BX // r0 = a0×b0 MOVQ (CX), AX MULQ (BX) MOVQ AX, DI MOVQ DX, SI // r0 += 19×a1×b4 MOVQ 8(CX), AX IMUL3Q $0x13, AX, AX MULQ 32(BX) ADDQ AX, DI ADCQ DX, SI // r0 += 19×a2×b3 MOVQ 16(CX), AX IMUL3Q $0x13, AX, AX MULQ 24(BX) ADDQ AX, DI ADCQ DX, SI // r0 += 19×a3×b2 MOVQ 24(CX), AX IMUL3Q $0x13, AX, AX MULQ 16(BX) ADDQ AX, DI ADCQ DX, SI // r0 += 19×a4×b1 MOVQ 32(CX), AX IMUL3Q $0x13, AX, AX MULQ 8(BX) ADDQ AX, DI ADCQ DX, SI // r1 = a0×b1 MOVQ (CX), AX MULQ 8(BX) MOVQ AX, R9 MOVQ DX, R8 // r1 += a1×b0 MOVQ 8(CX), AX MULQ (BX) ADDQ AX, R9 ADCQ DX, R8 // r1 += 19×a2×b4 MOVQ 16(CX), AX IMUL3Q $0x13, AX, AX MULQ 32(BX) ADDQ AX, R9 ADCQ DX, R8 // r1 += 19×a3×b3 MOVQ 24(CX), AX IMUL3Q $0x13, AX, AX MULQ 24(BX) ADDQ AX, R9 ADCQ DX, R8 // r1 += 19×a4×b2 MOVQ 32(CX), AX IMUL3Q $0x13, AX, AX MULQ 16(BX) ADDQ AX, R9 ADCQ DX, R8 // r2 = a0×b2 MOVQ (CX), AX MULQ 16(BX) MOVQ AX, R11 MOVQ DX, R10 // r2 += a1×b1 MOVQ 8(CX), AX MULQ 8(BX) ADDQ AX, R11 ADCQ DX, R10 // r2 += a2×b0 MOVQ 16(CX), AX MULQ (BX) ADDQ AX, R11 ADCQ DX, R10 // r2 += 19×a3×b4 MOVQ 24(CX), AX IMUL3Q $0x13, AX, AX MULQ 32(BX) ADDQ AX, R11 ADCQ DX, R10 // r2 += 19×a4×b3 MOVQ 32(CX), AX IMUL3Q $0x13, AX, AX MULQ 24(BX) ADDQ AX, R11 ADCQ DX, R10 // r3 = a0×b3 MOVQ (CX), AX MULQ 24(BX) MOVQ AX, R13 MOVQ DX, R12 // r3 += a1×b2 MOVQ 8(CX), AX MULQ 16(BX) ADDQ AX, R13 ADCQ DX, R12 // r3 += a2×b1 MOVQ 16(CX), AX MULQ 8(BX) ADDQ AX, R13 ADCQ DX, R12 // r3 += a3×b0 MOVQ 24(CX), AX MULQ (BX) ADDQ AX, R13 ADCQ DX, R12 // r3 += 19×a4×b4 MOVQ 32(CX), AX IMUL3Q $0x13, AX, AX MULQ 32(BX) ADDQ AX, R13 ADCQ DX, R12 // r4 = a0×b4 MOVQ (CX), AX MULQ 32(BX) MOVQ AX, R15 MOVQ DX, R14 // r4 += a1×b3 MOVQ 8(CX), AX MULQ 24(BX) ADDQ AX, R15 ADCQ DX, R14 // r4 += a2×b2 MOVQ 16(CX), AX MULQ 16(BX) ADDQ AX, R15 ADCQ DX, R14 // r4 += a3×b1 MOVQ 24(CX), AX MULQ 8(BX) ADDQ AX, R15 ADCQ DX, R14 // r4 += a4×b0 MOVQ 32(CX), AX MULQ (BX) ADDQ AX, R15 ADCQ DX, R14 // First reduction chain MOVQ $0x0007ffffffffffff, AX SHLQ $0x0d, DI, SI SHLQ $0x0d, R9, R8 SHLQ $0x0d, R11, R10 SHLQ $0x0d, R13, R12 SHLQ $0x0d, R15, R14 ANDQ AX, DI IMUL3Q $0x13, R14, R14 ADDQ R14, DI ANDQ AX, R9 ADDQ SI, R9 ANDQ AX, R11 ADDQ R8, R11 ANDQ AX, R13 ADDQ R10, R13 ANDQ AX, R15 ADDQ R12, R15 // Second reduction chain (carryPropagate) MOVQ DI, SI SHRQ $0x33, SI MOVQ R9, R8 SHRQ $0x33, R8 MOVQ R11, R10 SHRQ $0x33, R10 MOVQ R13, R12 SHRQ $0x33, R12 MOVQ R15, R14 SHRQ $0x33, R14 ANDQ AX, DI IMUL3Q $0x13, R14, R14 ADDQ R14, DI ANDQ AX, R9 ADDQ SI, R9 ANDQ AX, R11 ADDQ R8, R11 ANDQ AX, R13 ADDQ R10, R13 ANDQ AX, R15 ADDQ R12, R15 // Store output MOVQ out+0(FP), AX MOVQ DI, (AX) MOVQ R9, 8(AX) MOVQ R11, 16(AX) MOVQ R13, 24(AX) MOVQ R15, 32(AX) RET // func feSquare(out *Element, a *Element) TEXT ·feSquare(SB), NOSPLIT, $0-16 MOVQ a+8(FP), CX // r0 = l0×l0 MOVQ (CX), AX MULQ (CX) MOVQ AX, SI MOVQ DX, BX // r0 += 38×l1×l4 MOVQ 8(CX), AX IMUL3Q $0x26, AX, AX MULQ 32(CX) ADDQ AX, SI ADCQ DX, BX // r0 += 38×l2×l3 MOVQ 16(CX), AX IMUL3Q $0x26, AX, AX MULQ 24(CX) ADDQ AX, SI ADCQ DX, BX // r1 = 2×l0×l1 MOVQ (CX), AX SHLQ $0x01, AX MULQ 8(CX) MOVQ AX, R8 MOVQ DX, DI // r1 += 38×l2×l4 MOVQ 16(CX), AX IMUL3Q $0x26, AX, AX MULQ 32(CX) ADDQ AX, R8 ADCQ DX, DI // r1 += 19×l3×l3 MOVQ 24(CX), AX IMUL3Q $0x13, AX, AX MULQ 24(CX) ADDQ AX, R8 ADCQ DX, DI // r2 = 2×l0×l2 MOVQ (CX), AX SHLQ $0x01, AX MULQ 16(CX) MOVQ AX, R10 MOVQ DX, R9 // r2 += l1×l1 MOVQ 8(CX), AX MULQ 8(CX) ADDQ AX, R10 ADCQ DX, R9 // r2 += 38×l3×l4 MOVQ 24(CX), AX IMUL3Q $0x26, AX, AX MULQ 32(CX) ADDQ AX, R10 ADCQ DX, R9 // r3 = 2×l0×l3 MOVQ (CX), AX SHLQ $0x01, AX MULQ 24(CX) MOVQ AX, R12 MOVQ DX, R11 // r3 += 2×l1×l2 MOVQ 8(CX), AX IMUL3Q $0x02, AX, AX MULQ 16(CX) ADDQ AX, R12 ADCQ DX, R11 // r3 += 19×l4×l4 MOVQ 32(CX), AX IMUL3Q $0x13, AX, AX MULQ 32(CX) ADDQ AX, R12 ADCQ DX, R11 // r4 = 2×l0×l4 MOVQ (CX), AX SHLQ $0x01, AX MULQ 32(CX) MOVQ AX, R14 MOVQ DX, R13 // r4 += 2×l1×l3 MOVQ 8(CX), AX IMUL3Q $0x02, AX, AX MULQ 24(CX) ADDQ AX, R14 ADCQ DX, R13 // r4 += l2×l2 MOVQ 16(CX), AX MULQ 16(CX) ADDQ AX, R14 ADCQ DX, R13 // First reduction chain MOVQ $0x0007ffffffffffff, AX SHLQ $0x0d, SI, BX SHLQ $0x0d, R8, DI SHLQ $0x0d, R10, R9 SHLQ $0x0d, R12, R11 SHLQ $0x0d, R14, R13 ANDQ AX, SI IMUL3Q $0x13, R13, R13 ADDQ R13, SI ANDQ AX, R8 ADDQ BX, R8 ANDQ AX, R10 ADDQ DI, R10 ANDQ AX, R12 ADDQ R9, R12 ANDQ AX, R14 ADDQ R11, R14 // Second reduction chain (carryPropagate) MOVQ SI, BX SHRQ $0x33, BX MOVQ R8, DI SHRQ $0x33, DI MOVQ R10, R9 SHRQ $0x33, R9 MOVQ R12, R11 SHRQ $0x33, R11 MOVQ R14, R13 SHRQ $0x33, R13 ANDQ AX, SI IMUL3Q $0x13, R13, R13 ADDQ R13, SI ANDQ AX, R8 ADDQ BX, R8 ANDQ AX, R10 ADDQ DI, R10 ANDQ AX, R12 ADDQ R9, R12 ANDQ AX, R14 ADDQ R11, R14 // Store output MOVQ out+0(FP), AX MOVQ SI, (AX) MOVQ R8, 8(AX) MOVQ R10, 16(AX) MOVQ R12, 24(AX) MOVQ R14, 32(AX) RET ================================================ FILE: vendor/filippo.io/edwards25519/field/fe_amd64_noasm.go ================================================ // Copyright (c) 2019 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build !amd64 || !gc || purego // +build !amd64 !gc purego package field func feMul(v, x, y *Element) { feMulGeneric(v, x, y) } func feSquare(v, x *Element) { feSquareGeneric(v, x) } ================================================ FILE: vendor/filippo.io/edwards25519/field/fe_arm64.go ================================================ // Copyright (c) 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build arm64 && gc && !purego // +build arm64,gc,!purego package field //go:noescape func carryPropagate(v *Element) func (v *Element) carryPropagate() *Element { carryPropagate(v) return v } ================================================ FILE: vendor/filippo.io/edwards25519/field/fe_arm64.s ================================================ // Copyright (c) 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build arm64 && gc && !purego #include "textflag.h" // carryPropagate works exactly like carryPropagateGeneric and uses the // same AND, ADD, and LSR+MADD instructions emitted by the compiler, but // avoids loading R0-R4 twice and uses LDP and STP. // // See https://golang.org/issues/43145 for the main compiler issue. // // func carryPropagate(v *Element) TEXT ·carryPropagate(SB),NOFRAME|NOSPLIT,$0-8 MOVD v+0(FP), R20 LDP 0(R20), (R0, R1) LDP 16(R20), (R2, R3) MOVD 32(R20), R4 AND $0x7ffffffffffff, R0, R10 AND $0x7ffffffffffff, R1, R11 AND $0x7ffffffffffff, R2, R12 AND $0x7ffffffffffff, R3, R13 AND $0x7ffffffffffff, R4, R14 ADD R0>>51, R11, R11 ADD R1>>51, R12, R12 ADD R2>>51, R13, R13 ADD R3>>51, R14, R14 // R4>>51 * 19 + R10 -> R10 LSR $51, R4, R21 MOVD $19, R22 MADD R22, R10, R21, R10 STP (R10, R11), 0(R20) STP (R12, R13), 16(R20) MOVD R14, 32(R20) RET ================================================ FILE: vendor/filippo.io/edwards25519/field/fe_arm64_noasm.go ================================================ // Copyright (c) 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build !arm64 || !gc || purego // +build !arm64 !gc purego package field func (v *Element) carryPropagate() *Element { return v.carryPropagateGeneric() } ================================================ FILE: vendor/filippo.io/edwards25519/field/fe_extra.go ================================================ // Copyright (c) 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package field import "errors" // This file contains additional functionality that is not included in the // upstream crypto/ed25519/edwards25519/field package. // SetWideBytes sets v to x, where x is a 64-byte little-endian encoding, which // is reduced modulo the field order. If x is not of the right length, // SetWideBytes returns nil and an error, and the receiver is unchanged. // // SetWideBytes is not necessary to select a uniformly distributed value, and is // only provided for compatibility: SetBytes can be used instead as the chance // of bias is less than 2⁻²⁵⁰. func (v *Element) SetWideBytes(x []byte) (*Element, error) { if len(x) != 64 { return nil, errors.New("edwards25519: invalid SetWideBytes input size") } // Split the 64 bytes into two elements, and extract the most significant // bit of each, which is ignored by SetBytes. lo, _ := new(Element).SetBytes(x[:32]) loMSB := uint64(x[31] >> 7) hi, _ := new(Element).SetBytes(x[32:]) hiMSB := uint64(x[63] >> 7) // The output we want is // // v = lo + loMSB * 2²⁵⁵ + hi * 2²⁵⁶ + hiMSB * 2⁵¹¹ // // which applying the reduction identity comes out to // // v = lo + loMSB * 19 + hi * 2 * 19 + hiMSB * 2 * 19² // // l0 will be the sum of a 52 bits value (lo.l0), plus a 5 bits value // (loMSB * 19), a 6 bits value (hi.l0 * 2 * 19), and a 10 bits value // (hiMSB * 2 * 19²), so it fits in a uint64. v.l0 = lo.l0 + loMSB*19 + hi.l0*2*19 + hiMSB*2*19*19 v.l1 = lo.l1 + hi.l1*2*19 v.l2 = lo.l2 + hi.l2*2*19 v.l3 = lo.l3 + hi.l3*2*19 v.l4 = lo.l4 + hi.l4*2*19 return v.carryPropagate(), nil } ================================================ FILE: vendor/filippo.io/edwards25519/field/fe_generic.go ================================================ // Copyright (c) 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package field import "math/bits" // uint128 holds a 128-bit number as two 64-bit limbs, for use with the // bits.Mul64 and bits.Add64 intrinsics. type uint128 struct { lo, hi uint64 } // mul64 returns a * b. func mul64(a, b uint64) uint128 { hi, lo := bits.Mul64(a, b) return uint128{lo, hi} } // addMul64 returns v + a * b. func addMul64(v uint128, a, b uint64) uint128 { hi, lo := bits.Mul64(a, b) lo, c := bits.Add64(lo, v.lo, 0) hi, _ = bits.Add64(hi, v.hi, c) return uint128{lo, hi} } // shiftRightBy51 returns a >> 51. a is assumed to be at most 115 bits. func shiftRightBy51(a uint128) uint64 { return (a.hi << (64 - 51)) | (a.lo >> 51) } func feMulGeneric(v, a, b *Element) { a0 := a.l0 a1 := a.l1 a2 := a.l2 a3 := a.l3 a4 := a.l4 b0 := b.l0 b1 := b.l1 b2 := b.l2 b3 := b.l3 b4 := b.l4 // Limb multiplication works like pen-and-paper columnar multiplication, but // with 51-bit limbs instead of digits. // // a4 a3 a2 a1 a0 x // b4 b3 b2 b1 b0 = // ------------------------ // a4b0 a3b0 a2b0 a1b0 a0b0 + // a4b1 a3b1 a2b1 a1b1 a0b1 + // a4b2 a3b2 a2b2 a1b2 a0b2 + // a4b3 a3b3 a2b3 a1b3 a0b3 + // a4b4 a3b4 a2b4 a1b4 a0b4 = // ---------------------------------------------- // r8 r7 r6 r5 r4 r3 r2 r1 r0 // // We can then use the reduction identity (a * 2²⁵⁵ + b = a * 19 + b) to // reduce the limbs that would overflow 255 bits. r5 * 2²⁵⁵ becomes 19 * r5, // r6 * 2³⁰⁶ becomes 19 * r6 * 2⁵¹, etc. // // Reduction can be carried out simultaneously to multiplication. For // example, we do not compute r5: whenever the result of a multiplication // belongs to r5, like a1b4, we multiply it by 19 and add the result to r0. // // a4b0 a3b0 a2b0 a1b0 a0b0 + // a3b1 a2b1 a1b1 a0b1 19×a4b1 + // a2b2 a1b2 a0b2 19×a4b2 19×a3b2 + // a1b3 a0b3 19×a4b3 19×a3b3 19×a2b3 + // a0b4 19×a4b4 19×a3b4 19×a2b4 19×a1b4 = // -------------------------------------- // r4 r3 r2 r1 r0 // // Finally we add up the columns into wide, overlapping limbs. a1_19 := a1 * 19 a2_19 := a2 * 19 a3_19 := a3 * 19 a4_19 := a4 * 19 // r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1) r0 := mul64(a0, b0) r0 = addMul64(r0, a1_19, b4) r0 = addMul64(r0, a2_19, b3) r0 = addMul64(r0, a3_19, b2) r0 = addMul64(r0, a4_19, b1) // r1 = a0×b1 + a1×b0 + 19×(a2×b4 + a3×b3 + a4×b2) r1 := mul64(a0, b1) r1 = addMul64(r1, a1, b0) r1 = addMul64(r1, a2_19, b4) r1 = addMul64(r1, a3_19, b3) r1 = addMul64(r1, a4_19, b2) // r2 = a0×b2 + a1×b1 + a2×b0 + 19×(a3×b4 + a4×b3) r2 := mul64(a0, b2) r2 = addMul64(r2, a1, b1) r2 = addMul64(r2, a2, b0) r2 = addMul64(r2, a3_19, b4) r2 = addMul64(r2, a4_19, b3) // r3 = a0×b3 + a1×b2 + a2×b1 + a3×b0 + 19×a4×b4 r3 := mul64(a0, b3) r3 = addMul64(r3, a1, b2) r3 = addMul64(r3, a2, b1) r3 = addMul64(r3, a3, b0) r3 = addMul64(r3, a4_19, b4) // r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0 r4 := mul64(a0, b4) r4 = addMul64(r4, a1, b3) r4 = addMul64(r4, a2, b2) r4 = addMul64(r4, a3, b1) r4 = addMul64(r4, a4, b0) // After the multiplication, we need to reduce (carry) the five coefficients // to obtain a result with limbs that are at most slightly larger than 2⁵¹, // to respect the Element invariant. // // Overall, the reduction works the same as carryPropagate, except with // wider inputs: we take the carry for each coefficient by shifting it right // by 51, and add it to the limb above it. The top carry is multiplied by 19 // according to the reduction identity and added to the lowest limb. // // The largest coefficient (r0) will be at most 111 bits, which guarantees // that all carries are at most 111 - 51 = 60 bits, which fits in a uint64. // // r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1) // r0 < 2⁵²×2⁵² + 19×(2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵²) // r0 < (1 + 19 × 4) × 2⁵² × 2⁵² // r0 < 2⁷ × 2⁵² × 2⁵² // r0 < 2¹¹¹ // // Moreover, the top coefficient (r4) is at most 107 bits, so c4 is at most // 56 bits, and c4 * 19 is at most 61 bits, which again fits in a uint64 and // allows us to easily apply the reduction identity. // // r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0 // r4 < 5 × 2⁵² × 2⁵² // r4 < 2¹⁰⁷ // c0 := shiftRightBy51(r0) c1 := shiftRightBy51(r1) c2 := shiftRightBy51(r2) c3 := shiftRightBy51(r3) c4 := shiftRightBy51(r4) rr0 := r0.lo&maskLow51Bits + c4*19 rr1 := r1.lo&maskLow51Bits + c0 rr2 := r2.lo&maskLow51Bits + c1 rr3 := r3.lo&maskLow51Bits + c2 rr4 := r4.lo&maskLow51Bits + c3 // Now all coefficients fit into 64-bit registers but are still too large to // be passed around as an Element. We therefore do one last carry chain, // where the carries will be small enough to fit in the wiggle room above 2⁵¹. *v = Element{rr0, rr1, rr2, rr3, rr4} v.carryPropagate() } func feSquareGeneric(v, a *Element) { l0 := a.l0 l1 := a.l1 l2 := a.l2 l3 := a.l3 l4 := a.l4 // Squaring works precisely like multiplication above, but thanks to its // symmetry we get to group a few terms together. // // l4 l3 l2 l1 l0 x // l4 l3 l2 l1 l0 = // ------------------------ // l4l0 l3l0 l2l0 l1l0 l0l0 + // l4l1 l3l1 l2l1 l1l1 l0l1 + // l4l2 l3l2 l2l2 l1l2 l0l2 + // l4l3 l3l3 l2l3 l1l3 l0l3 + // l4l4 l3l4 l2l4 l1l4 l0l4 = // ---------------------------------------------- // r8 r7 r6 r5 r4 r3 r2 r1 r0 // // l4l0 l3l0 l2l0 l1l0 l0l0 + // l3l1 l2l1 l1l1 l0l1 19×l4l1 + // l2l2 l1l2 l0l2 19×l4l2 19×l3l2 + // l1l3 l0l3 19×l4l3 19×l3l3 19×l2l3 + // l0l4 19×l4l4 19×l3l4 19×l2l4 19×l1l4 = // -------------------------------------- // r4 r3 r2 r1 r0 // // With precomputed 2×, 19×, and 2×19× terms, we can compute each limb with // only three Mul64 and four Add64, instead of five and eight. l0_2 := l0 * 2 l1_2 := l1 * 2 l1_38 := l1 * 38 l2_38 := l2 * 38 l3_38 := l3 * 38 l3_19 := l3 * 19 l4_19 := l4 * 19 // r0 = l0×l0 + 19×(l1×l4 + l2×l3 + l3×l2 + l4×l1) = l0×l0 + 19×2×(l1×l4 + l2×l3) r0 := mul64(l0, l0) r0 = addMul64(r0, l1_38, l4) r0 = addMul64(r0, l2_38, l3) // r1 = l0×l1 + l1×l0 + 19×(l2×l4 + l3×l3 + l4×l2) = 2×l0×l1 + 19×2×l2×l4 + 19×l3×l3 r1 := mul64(l0_2, l1) r1 = addMul64(r1, l2_38, l4) r1 = addMul64(r1, l3_19, l3) // r2 = l0×l2 + l1×l1 + l2×l0 + 19×(l3×l4 + l4×l3) = 2×l0×l2 + l1×l1 + 19×2×l3×l4 r2 := mul64(l0_2, l2) r2 = addMul64(r2, l1, l1) r2 = addMul64(r2, l3_38, l4) // r3 = l0×l3 + l1×l2 + l2×l1 + l3×l0 + 19×l4×l4 = 2×l0×l3 + 2×l1×l2 + 19×l4×l4 r3 := mul64(l0_2, l3) r3 = addMul64(r3, l1_2, l2) r3 = addMul64(r3, l4_19, l4) // r4 = l0×l4 + l1×l3 + l2×l2 + l3×l1 + l4×l0 = 2×l0×l4 + 2×l1×l3 + l2×l2 r4 := mul64(l0_2, l4) r4 = addMul64(r4, l1_2, l3) r4 = addMul64(r4, l2, l2) c0 := shiftRightBy51(r0) c1 := shiftRightBy51(r1) c2 := shiftRightBy51(r2) c3 := shiftRightBy51(r3) c4 := shiftRightBy51(r4) rr0 := r0.lo&maskLow51Bits + c4*19 rr1 := r1.lo&maskLow51Bits + c0 rr2 := r2.lo&maskLow51Bits + c1 rr3 := r3.lo&maskLow51Bits + c2 rr4 := r4.lo&maskLow51Bits + c3 *v = Element{rr0, rr1, rr2, rr3, rr4} v.carryPropagate() } // carryPropagateGeneric brings the limbs below 52 bits by applying the reduction // identity (a * 2²⁵⁵ + b = a * 19 + b) to the l4 carry. func (v *Element) carryPropagateGeneric() *Element { c0 := v.l0 >> 51 c1 := v.l1 >> 51 c2 := v.l2 >> 51 c3 := v.l3 >> 51 c4 := v.l4 >> 51 // c4 is at most 64 - 51 = 13 bits, so c4*19 is at most 18 bits, and // the final l0 will be at most 52 bits. Similarly for the rest. v.l0 = v.l0&maskLow51Bits + c4*19 v.l1 = v.l1&maskLow51Bits + c0 v.l2 = v.l2&maskLow51Bits + c1 v.l3 = v.l3&maskLow51Bits + c2 v.l4 = v.l4&maskLow51Bits + c3 return v } ================================================ FILE: vendor/filippo.io/edwards25519/scalar.go ================================================ // Copyright (c) 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package edwards25519 import ( "encoding/binary" "errors" ) // A Scalar is an integer modulo // // l = 2^252 + 27742317777372353535851937790883648493 // // which is the prime order of the edwards25519 group. // // This type works similarly to math/big.Int, and all arguments and // receivers are allowed to alias. // // The zero value is a valid zero element. type Scalar struct { // s is the scalar in the Montgomery domain, in the format of the // fiat-crypto implementation. s fiatScalarMontgomeryDomainFieldElement } // The field implementation in scalar_fiat.go is generated by the fiat-crypto // project (https://github.com/mit-plv/fiat-crypto) at version v0.0.9 (23d2dbc) // from a formally verified model. // // fiat-crypto code comes under the following license. // // Copyright (c) 2015-2020 The fiat-crypto Authors. 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. // // THIS SOFTWARE IS PROVIDED BY the fiat-crypto authors "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 Berkeley Software Design, // Inc. 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. // // NewScalar returns a new zero Scalar. func NewScalar() *Scalar { return &Scalar{} } // MultiplyAdd sets s = x * y + z mod l, and returns s. It is equivalent to // using Multiply and then Add. func (s *Scalar) MultiplyAdd(x, y, z *Scalar) *Scalar { // Make a copy of z in case it aliases s. zCopy := new(Scalar).Set(z) return s.Multiply(x, y).Add(s, zCopy) } // Add sets s = x + y mod l, and returns s. func (s *Scalar) Add(x, y *Scalar) *Scalar { // s = 1 * x + y mod l fiatScalarAdd(&s.s, &x.s, &y.s) return s } // Subtract sets s = x - y mod l, and returns s. func (s *Scalar) Subtract(x, y *Scalar) *Scalar { // s = -1 * y + x mod l fiatScalarSub(&s.s, &x.s, &y.s) return s } // Negate sets s = -x mod l, and returns s. func (s *Scalar) Negate(x *Scalar) *Scalar { // s = -1 * x + 0 mod l fiatScalarOpp(&s.s, &x.s) return s } // Multiply sets s = x * y mod l, and returns s. func (s *Scalar) Multiply(x, y *Scalar) *Scalar { // s = x * y + 0 mod l fiatScalarMul(&s.s, &x.s, &y.s) return s } // Set sets s = x, and returns s. func (s *Scalar) Set(x *Scalar) *Scalar { *s = *x return s } // SetUniformBytes sets s = x mod l, where x is a 64-byte little-endian integer. // If x is not of the right length, SetUniformBytes returns nil and an error, // and the receiver is unchanged. // // SetUniformBytes can be used to set s to a uniformly distributed value given // 64 uniformly distributed random bytes. func (s *Scalar) SetUniformBytes(x []byte) (*Scalar, error) { if len(x) != 64 { return nil, errors.New("edwards25519: invalid SetUniformBytes input length") } // We have a value x of 512 bits, but our fiatScalarFromBytes function // expects an input lower than l, which is a little over 252 bits. // // Instead of writing a reduction function that operates on wider inputs, we // can interpret x as the sum of three shorter values a, b, and c. // // x = a + b * 2^168 + c * 2^336 mod l // // We then precompute 2^168 and 2^336 modulo l, and perform the reduction // with two multiplications and two additions. s.setShortBytes(x[:21]) t := new(Scalar).setShortBytes(x[21:42]) s.Add(s, t.Multiply(t, scalarTwo168)) t.setShortBytes(x[42:]) s.Add(s, t.Multiply(t, scalarTwo336)) return s, nil } // scalarTwo168 and scalarTwo336 are 2^168 and 2^336 modulo l, encoded as a // fiatScalarMontgomeryDomainFieldElement, which is a little-endian 4-limb value // in the 2^256 Montgomery domain. var scalarTwo168 = &Scalar{s: [4]uint64{0x5b8ab432eac74798, 0x38afddd6de59d5d7, 0xa2c131b399411b7c, 0x6329a7ed9ce5a30}} var scalarTwo336 = &Scalar{s: [4]uint64{0xbd3d108e2b35ecc5, 0x5c3a3718bdf9c90b, 0x63aa97a331b4f2ee, 0x3d217f5be65cb5c}} // setShortBytes sets s = x mod l, where x is a little-endian integer shorter // than 32 bytes. func (s *Scalar) setShortBytes(x []byte) *Scalar { if len(x) >= 32 { panic("edwards25519: internal error: setShortBytes called with a long string") } var buf [32]byte copy(buf[:], x) fiatScalarFromBytes((*[4]uint64)(&s.s), &buf) fiatScalarToMontgomery(&s.s, (*fiatScalarNonMontgomeryDomainFieldElement)(&s.s)) return s } // SetCanonicalBytes sets s = x, where x is a 32-byte little-endian encoding of // s, and returns s. If x is not a canonical encoding of s, SetCanonicalBytes // returns nil and an error, and the receiver is unchanged. func (s *Scalar) SetCanonicalBytes(x []byte) (*Scalar, error) { if len(x) != 32 { return nil, errors.New("invalid scalar length") } if !isReduced(x) { return nil, errors.New("invalid scalar encoding") } fiatScalarFromBytes((*[4]uint64)(&s.s), (*[32]byte)(x)) fiatScalarToMontgomery(&s.s, (*fiatScalarNonMontgomeryDomainFieldElement)(&s.s)) return s, nil } // scalarMinusOneBytes is l - 1 in little endian. var scalarMinusOneBytes = [32]byte{236, 211, 245, 92, 26, 99, 18, 88, 214, 156, 247, 162, 222, 249, 222, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16} // isReduced returns whether the given scalar in 32-byte little endian encoded // form is reduced modulo l. func isReduced(s []byte) bool { if len(s) != 32 { return false } for i := len(s) - 1; i >= 0; i-- { switch { case s[i] > scalarMinusOneBytes[i]: return false case s[i] < scalarMinusOneBytes[i]: return true } } return true } // SetBytesWithClamping applies the buffer pruning described in RFC 8032, // Section 5.1.5 (also known as clamping) and sets s to the result. The input // must be 32 bytes, and it is not modified. If x is not of the right length, // SetBytesWithClamping returns nil and an error, and the receiver is unchanged. // // Note that since Scalar values are always reduced modulo the prime order of // the curve, the resulting value will not preserve any of the cofactor-clearing // properties that clamping is meant to provide. It will however work as // expected as long as it is applied to points on the prime order subgroup, like // in Ed25519. In fact, it is lost to history why RFC 8032 adopted the // irrelevant RFC 7748 clamping, but it is now required for compatibility. func (s *Scalar) SetBytesWithClamping(x []byte) (*Scalar, error) { // The description above omits the purpose of the high bits of the clamping // for brevity, but those are also lost to reductions, and are also // irrelevant to edwards25519 as they protect against a specific // implementation bug that was once observed in a generic Montgomery ladder. if len(x) != 32 { return nil, errors.New("edwards25519: invalid SetBytesWithClamping input length") } // We need to use the wide reduction from SetUniformBytes, since clamping // sets the 2^254 bit, making the value higher than the order. var wideBytes [64]byte copy(wideBytes[:], x[:]) wideBytes[0] &= 248 wideBytes[31] &= 63 wideBytes[31] |= 64 return s.SetUniformBytes(wideBytes[:]) } // Bytes returns the canonical 32-byte little-endian encoding of s. func (s *Scalar) Bytes() []byte { // This function is outlined to make the allocations inline in the caller // rather than happen on the heap. var encoded [32]byte return s.bytes(&encoded) } func (s *Scalar) bytes(out *[32]byte) []byte { var ss fiatScalarNonMontgomeryDomainFieldElement fiatScalarFromMontgomery(&ss, &s.s) fiatScalarToBytes(out, (*[4]uint64)(&ss)) return out[:] } // Equal returns 1 if s and t are equal, and 0 otherwise. func (s *Scalar) Equal(t *Scalar) int { var diff fiatScalarMontgomeryDomainFieldElement fiatScalarSub(&diff, &s.s, &t.s) var nonzero uint64 fiatScalarNonzero(&nonzero, (*[4]uint64)(&diff)) nonzero |= nonzero >> 32 nonzero |= nonzero >> 16 nonzero |= nonzero >> 8 nonzero |= nonzero >> 4 nonzero |= nonzero >> 2 nonzero |= nonzero >> 1 return int(^nonzero) & 1 } // nonAdjacentForm computes a width-w non-adjacent form for this scalar. // // w must be between 2 and 8, or nonAdjacentForm will panic. func (s *Scalar) nonAdjacentForm(w uint) [256]int8 { // This implementation is adapted from the one // in curve25519-dalek and is documented there: // https://github.com/dalek-cryptography/curve25519-dalek/blob/f630041af28e9a405255f98a8a93adca18e4315b/src/scalar.rs#L800-L871 b := s.Bytes() if b[31] > 127 { panic("scalar has high bit set illegally") } if w < 2 { panic("w must be at least 2 by the definition of NAF") } else if w > 8 { panic("NAF digits must fit in int8") } var naf [256]int8 var digits [5]uint64 for i := 0; i < 4; i++ { digits[i] = binary.LittleEndian.Uint64(b[i*8:]) } width := uint64(1 << w) windowMask := uint64(width - 1) pos := uint(0) carry := uint64(0) for pos < 256 { indexU64 := pos / 64 indexBit := pos % 64 var bitBuf uint64 if indexBit < 64-w { // This window's bits are contained in a single u64 bitBuf = digits[indexU64] >> indexBit } else { // Combine the current 64 bits with bits from the next 64 bitBuf = (digits[indexU64] >> indexBit) | (digits[1+indexU64] << (64 - indexBit)) } // Add carry into the current window window := carry + (bitBuf & windowMask) if window&1 == 0 { // If the window value is even, preserve the carry and continue. // Why is the carry preserved? // If carry == 0 and window & 1 == 0, // then the next carry should be 0 // If carry == 1 and window & 1 == 0, // then bit_buf & 1 == 1 so the next carry should be 1 pos += 1 continue } if window < width/2 { carry = 0 naf[pos] = int8(window) } else { carry = 1 naf[pos] = int8(window) - int8(width) } pos += w } return naf } func (s *Scalar) signedRadix16() [64]int8 { b := s.Bytes() if b[31] > 127 { panic("scalar has high bit set illegally") } var digits [64]int8 // Compute unsigned radix-16 digits: for i := 0; i < 32; i++ { digits[2*i] = int8(b[i] & 15) digits[2*i+1] = int8((b[i] >> 4) & 15) } // Recenter coefficients: for i := 0; i < 63; i++ { carry := (digits[i] + 8) >> 4 digits[i] -= carry << 4 digits[i+1] += carry } return digits } ================================================ FILE: vendor/filippo.io/edwards25519/scalar_fiat.go ================================================ // Code generated by Fiat Cryptography. DO NOT EDIT. // // Autogenerated: word_by_word_montgomery --lang Go --cmovznz-by-mul --relax-primitive-carry-to-bitwidth 32,64 --public-function-case camelCase --public-type-case camelCase --private-function-case camelCase --private-type-case camelCase --doc-text-before-function-name '' --doc-newline-before-package-declaration --doc-prepend-header 'Code generated by Fiat Cryptography. DO NOT EDIT.' --package-name edwards25519 Scalar 64 '2^252 + 27742317777372353535851937790883648493' mul add sub opp nonzero from_montgomery to_montgomery to_bytes from_bytes // // curve description: Scalar // // machine_wordsize = 64 (from "64") // // requested operations: mul, add, sub, opp, nonzero, from_montgomery, to_montgomery, to_bytes, from_bytes // // m = 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed (from "2^252 + 27742317777372353535851937790883648493") // // // // NOTE: In addition to the bounds specified above each function, all // // functions synthesized for this Montgomery arithmetic require the // // input to be strictly less than the prime modulus (m), and also // // require the input to be in the unique saturated representation. // // All functions also ensure that these two properties are true of // // return values. // // // // Computed values: // // eval z = z[0] + (z[1] << 64) + (z[2] << 128) + (z[3] << 192) // // bytes_eval z = z[0] + (z[1] << 8) + (z[2] << 16) + (z[3] << 24) + (z[4] << 32) + (z[5] << 40) + (z[6] << 48) + (z[7] << 56) + (z[8] << 64) + (z[9] << 72) + (z[10] << 80) + (z[11] << 88) + (z[12] << 96) + (z[13] << 104) + (z[14] << 112) + (z[15] << 120) + (z[16] << 128) + (z[17] << 136) + (z[18] << 144) + (z[19] << 152) + (z[20] << 160) + (z[21] << 168) + (z[22] << 176) + (z[23] << 184) + (z[24] << 192) + (z[25] << 200) + (z[26] << 208) + (z[27] << 216) + (z[28] << 224) + (z[29] << 232) + (z[30] << 240) + (z[31] << 248) // // twos_complement_eval z = let x1 := z[0] + (z[1] << 64) + (z[2] << 128) + (z[3] << 192) in // // if x1 & (2^256-1) < 2^255 then x1 & (2^256-1) else (x1 & (2^256-1)) - 2^256 package edwards25519 import "math/bits" type fiatScalarUint1 uint64 // We use uint64 instead of a more narrow type for performance reasons; see https://github.com/mit-plv/fiat-crypto/pull/1006#issuecomment-892625927 type fiatScalarInt1 int64 // We use uint64 instead of a more narrow type for performance reasons; see https://github.com/mit-plv/fiat-crypto/pull/1006#issuecomment-892625927 // The type fiatScalarMontgomeryDomainFieldElement is a field element in the Montgomery domain. // // Bounds: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] type fiatScalarMontgomeryDomainFieldElement [4]uint64 // The type fiatScalarNonMontgomeryDomainFieldElement is a field element NOT in the Montgomery domain. // // Bounds: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] type fiatScalarNonMontgomeryDomainFieldElement [4]uint64 // fiatScalarCmovznzU64 is a single-word conditional move. // // Postconditions: // // out1 = (if arg1 = 0 then arg2 else arg3) // // Input Bounds: // // arg1: [0x0 ~> 0x1] // arg2: [0x0 ~> 0xffffffffffffffff] // arg3: [0x0 ~> 0xffffffffffffffff] // // Output Bounds: // // out1: [0x0 ~> 0xffffffffffffffff] func fiatScalarCmovznzU64(out1 *uint64, arg1 fiatScalarUint1, arg2 uint64, arg3 uint64) { x1 := (uint64(arg1) * 0xffffffffffffffff) x2 := ((x1 & arg3) | ((^x1) & arg2)) *out1 = x2 } // fiatScalarMul multiplies two field elements in the Montgomery domain. // // Preconditions: // // 0 ≤ eval arg1 < m // 0 ≤ eval arg2 < m // // Postconditions: // // eval (from_montgomery out1) mod m = (eval (from_montgomery arg1) * eval (from_montgomery arg2)) mod m // 0 ≤ eval out1 < m func fiatScalarMul(out1 *fiatScalarMontgomeryDomainFieldElement, arg1 *fiatScalarMontgomeryDomainFieldElement, arg2 *fiatScalarMontgomeryDomainFieldElement) { x1 := arg1[1] x2 := arg1[2] x3 := arg1[3] x4 := arg1[0] var x5 uint64 var x6 uint64 x6, x5 = bits.Mul64(x4, arg2[3]) var x7 uint64 var x8 uint64 x8, x7 = bits.Mul64(x4, arg2[2]) var x9 uint64 var x10 uint64 x10, x9 = bits.Mul64(x4, arg2[1]) var x11 uint64 var x12 uint64 x12, x11 = bits.Mul64(x4, arg2[0]) var x13 uint64 var x14 uint64 x13, x14 = bits.Add64(x12, x9, uint64(0x0)) var x15 uint64 var x16 uint64 x15, x16 = bits.Add64(x10, x7, uint64(fiatScalarUint1(x14))) var x17 uint64 var x18 uint64 x17, x18 = bits.Add64(x8, x5, uint64(fiatScalarUint1(x16))) x19 := (uint64(fiatScalarUint1(x18)) + x6) var x20 uint64 _, x20 = bits.Mul64(x11, 0xd2b51da312547e1b) var x22 uint64 var x23 uint64 x23, x22 = bits.Mul64(x20, 0x1000000000000000) var x24 uint64 var x25 uint64 x25, x24 = bits.Mul64(x20, 0x14def9dea2f79cd6) var x26 uint64 var x27 uint64 x27, x26 = bits.Mul64(x20, 0x5812631a5cf5d3ed) var x28 uint64 var x29 uint64 x28, x29 = bits.Add64(x27, x24, uint64(0x0)) x30 := (uint64(fiatScalarUint1(x29)) + x25) var x32 uint64 _, x32 = bits.Add64(x11, x26, uint64(0x0)) var x33 uint64 var x34 uint64 x33, x34 = bits.Add64(x13, x28, uint64(fiatScalarUint1(x32))) var x35 uint64 var x36 uint64 x35, x36 = bits.Add64(x15, x30, uint64(fiatScalarUint1(x34))) var x37 uint64 var x38 uint64 x37, x38 = bits.Add64(x17, x22, uint64(fiatScalarUint1(x36))) var x39 uint64 var x40 uint64 x39, x40 = bits.Add64(x19, x23, uint64(fiatScalarUint1(x38))) var x41 uint64 var x42 uint64 x42, x41 = bits.Mul64(x1, arg2[3]) var x43 uint64 var x44 uint64 x44, x43 = bits.Mul64(x1, arg2[2]) var x45 uint64 var x46 uint64 x46, x45 = bits.Mul64(x1, arg2[1]) var x47 uint64 var x48 uint64 x48, x47 = bits.Mul64(x1, arg2[0]) var x49 uint64 var x50 uint64 x49, x50 = bits.Add64(x48, x45, uint64(0x0)) var x51 uint64 var x52 uint64 x51, x52 = bits.Add64(x46, x43, uint64(fiatScalarUint1(x50))) var x53 uint64 var x54 uint64 x53, x54 = bits.Add64(x44, x41, uint64(fiatScalarUint1(x52))) x55 := (uint64(fiatScalarUint1(x54)) + x42) var x56 uint64 var x57 uint64 x56, x57 = bits.Add64(x33, x47, uint64(0x0)) var x58 uint64 var x59 uint64 x58, x59 = bits.Add64(x35, x49, uint64(fiatScalarUint1(x57))) var x60 uint64 var x61 uint64 x60, x61 = bits.Add64(x37, x51, uint64(fiatScalarUint1(x59))) var x62 uint64 var x63 uint64 x62, x63 = bits.Add64(x39, x53, uint64(fiatScalarUint1(x61))) var x64 uint64 var x65 uint64 x64, x65 = bits.Add64(uint64(fiatScalarUint1(x40)), x55, uint64(fiatScalarUint1(x63))) var x66 uint64 _, x66 = bits.Mul64(x56, 0xd2b51da312547e1b) var x68 uint64 var x69 uint64 x69, x68 = bits.Mul64(x66, 0x1000000000000000) var x70 uint64 var x71 uint64 x71, x70 = bits.Mul64(x66, 0x14def9dea2f79cd6) var x72 uint64 var x73 uint64 x73, x72 = bits.Mul64(x66, 0x5812631a5cf5d3ed) var x74 uint64 var x75 uint64 x74, x75 = bits.Add64(x73, x70, uint64(0x0)) x76 := (uint64(fiatScalarUint1(x75)) + x71) var x78 uint64 _, x78 = bits.Add64(x56, x72, uint64(0x0)) var x79 uint64 var x80 uint64 x79, x80 = bits.Add64(x58, x74, uint64(fiatScalarUint1(x78))) var x81 uint64 var x82 uint64 x81, x82 = bits.Add64(x60, x76, uint64(fiatScalarUint1(x80))) var x83 uint64 var x84 uint64 x83, x84 = bits.Add64(x62, x68, uint64(fiatScalarUint1(x82))) var x85 uint64 var x86 uint64 x85, x86 = bits.Add64(x64, x69, uint64(fiatScalarUint1(x84))) x87 := (uint64(fiatScalarUint1(x86)) + uint64(fiatScalarUint1(x65))) var x88 uint64 var x89 uint64 x89, x88 = bits.Mul64(x2, arg2[3]) var x90 uint64 var x91 uint64 x91, x90 = bits.Mul64(x2, arg2[2]) var x92 uint64 var x93 uint64 x93, x92 = bits.Mul64(x2, arg2[1]) var x94 uint64 var x95 uint64 x95, x94 = bits.Mul64(x2, arg2[0]) var x96 uint64 var x97 uint64 x96, x97 = bits.Add64(x95, x92, uint64(0x0)) var x98 uint64 var x99 uint64 x98, x99 = bits.Add64(x93, x90, uint64(fiatScalarUint1(x97))) var x100 uint64 var x101 uint64 x100, x101 = bits.Add64(x91, x88, uint64(fiatScalarUint1(x99))) x102 := (uint64(fiatScalarUint1(x101)) + x89) var x103 uint64 var x104 uint64 x103, x104 = bits.Add64(x79, x94, uint64(0x0)) var x105 uint64 var x106 uint64 x105, x106 = bits.Add64(x81, x96, uint64(fiatScalarUint1(x104))) var x107 uint64 var x108 uint64 x107, x108 = bits.Add64(x83, x98, uint64(fiatScalarUint1(x106))) var x109 uint64 var x110 uint64 x109, x110 = bits.Add64(x85, x100, uint64(fiatScalarUint1(x108))) var x111 uint64 var x112 uint64 x111, x112 = bits.Add64(x87, x102, uint64(fiatScalarUint1(x110))) var x113 uint64 _, x113 = bits.Mul64(x103, 0xd2b51da312547e1b) var x115 uint64 var x116 uint64 x116, x115 = bits.Mul64(x113, 0x1000000000000000) var x117 uint64 var x118 uint64 x118, x117 = bits.Mul64(x113, 0x14def9dea2f79cd6) var x119 uint64 var x120 uint64 x120, x119 = bits.Mul64(x113, 0x5812631a5cf5d3ed) var x121 uint64 var x122 uint64 x121, x122 = bits.Add64(x120, x117, uint64(0x0)) x123 := (uint64(fiatScalarUint1(x122)) + x118) var x125 uint64 _, x125 = bits.Add64(x103, x119, uint64(0x0)) var x126 uint64 var x127 uint64 x126, x127 = bits.Add64(x105, x121, uint64(fiatScalarUint1(x125))) var x128 uint64 var x129 uint64 x128, x129 = bits.Add64(x107, x123, uint64(fiatScalarUint1(x127))) var x130 uint64 var x131 uint64 x130, x131 = bits.Add64(x109, x115, uint64(fiatScalarUint1(x129))) var x132 uint64 var x133 uint64 x132, x133 = bits.Add64(x111, x116, uint64(fiatScalarUint1(x131))) x134 := (uint64(fiatScalarUint1(x133)) + uint64(fiatScalarUint1(x112))) var x135 uint64 var x136 uint64 x136, x135 = bits.Mul64(x3, arg2[3]) var x137 uint64 var x138 uint64 x138, x137 = bits.Mul64(x3, arg2[2]) var x139 uint64 var x140 uint64 x140, x139 = bits.Mul64(x3, arg2[1]) var x141 uint64 var x142 uint64 x142, x141 = bits.Mul64(x3, arg2[0]) var x143 uint64 var x144 uint64 x143, x144 = bits.Add64(x142, x139, uint64(0x0)) var x145 uint64 var x146 uint64 x145, x146 = bits.Add64(x140, x137, uint64(fiatScalarUint1(x144))) var x147 uint64 var x148 uint64 x147, x148 = bits.Add64(x138, x135, uint64(fiatScalarUint1(x146))) x149 := (uint64(fiatScalarUint1(x148)) + x136) var x150 uint64 var x151 uint64 x150, x151 = bits.Add64(x126, x141, uint64(0x0)) var x152 uint64 var x153 uint64 x152, x153 = bits.Add64(x128, x143, uint64(fiatScalarUint1(x151))) var x154 uint64 var x155 uint64 x154, x155 = bits.Add64(x130, x145, uint64(fiatScalarUint1(x153))) var x156 uint64 var x157 uint64 x156, x157 = bits.Add64(x132, x147, uint64(fiatScalarUint1(x155))) var x158 uint64 var x159 uint64 x158, x159 = bits.Add64(x134, x149, uint64(fiatScalarUint1(x157))) var x160 uint64 _, x160 = bits.Mul64(x150, 0xd2b51da312547e1b) var x162 uint64 var x163 uint64 x163, x162 = bits.Mul64(x160, 0x1000000000000000) var x164 uint64 var x165 uint64 x165, x164 = bits.Mul64(x160, 0x14def9dea2f79cd6) var x166 uint64 var x167 uint64 x167, x166 = bits.Mul64(x160, 0x5812631a5cf5d3ed) var x168 uint64 var x169 uint64 x168, x169 = bits.Add64(x167, x164, uint64(0x0)) x170 := (uint64(fiatScalarUint1(x169)) + x165) var x172 uint64 _, x172 = bits.Add64(x150, x166, uint64(0x0)) var x173 uint64 var x174 uint64 x173, x174 = bits.Add64(x152, x168, uint64(fiatScalarUint1(x172))) var x175 uint64 var x176 uint64 x175, x176 = bits.Add64(x154, x170, uint64(fiatScalarUint1(x174))) var x177 uint64 var x178 uint64 x177, x178 = bits.Add64(x156, x162, uint64(fiatScalarUint1(x176))) var x179 uint64 var x180 uint64 x179, x180 = bits.Add64(x158, x163, uint64(fiatScalarUint1(x178))) x181 := (uint64(fiatScalarUint1(x180)) + uint64(fiatScalarUint1(x159))) var x182 uint64 var x183 uint64 x182, x183 = bits.Sub64(x173, 0x5812631a5cf5d3ed, uint64(0x0)) var x184 uint64 var x185 uint64 x184, x185 = bits.Sub64(x175, 0x14def9dea2f79cd6, uint64(fiatScalarUint1(x183))) var x186 uint64 var x187 uint64 x186, x187 = bits.Sub64(x177, uint64(0x0), uint64(fiatScalarUint1(x185))) var x188 uint64 var x189 uint64 x188, x189 = bits.Sub64(x179, 0x1000000000000000, uint64(fiatScalarUint1(x187))) var x191 uint64 _, x191 = bits.Sub64(x181, uint64(0x0), uint64(fiatScalarUint1(x189))) var x192 uint64 fiatScalarCmovznzU64(&x192, fiatScalarUint1(x191), x182, x173) var x193 uint64 fiatScalarCmovznzU64(&x193, fiatScalarUint1(x191), x184, x175) var x194 uint64 fiatScalarCmovznzU64(&x194, fiatScalarUint1(x191), x186, x177) var x195 uint64 fiatScalarCmovznzU64(&x195, fiatScalarUint1(x191), x188, x179) out1[0] = x192 out1[1] = x193 out1[2] = x194 out1[3] = x195 } // fiatScalarAdd adds two field elements in the Montgomery domain. // // Preconditions: // // 0 ≤ eval arg1 < m // 0 ≤ eval arg2 < m // // Postconditions: // // eval (from_montgomery out1) mod m = (eval (from_montgomery arg1) + eval (from_montgomery arg2)) mod m // 0 ≤ eval out1 < m func fiatScalarAdd(out1 *fiatScalarMontgomeryDomainFieldElement, arg1 *fiatScalarMontgomeryDomainFieldElement, arg2 *fiatScalarMontgomeryDomainFieldElement) { var x1 uint64 var x2 uint64 x1, x2 = bits.Add64(arg1[0], arg2[0], uint64(0x0)) var x3 uint64 var x4 uint64 x3, x4 = bits.Add64(arg1[1], arg2[1], uint64(fiatScalarUint1(x2))) var x5 uint64 var x6 uint64 x5, x6 = bits.Add64(arg1[2], arg2[2], uint64(fiatScalarUint1(x4))) var x7 uint64 var x8 uint64 x7, x8 = bits.Add64(arg1[3], arg2[3], uint64(fiatScalarUint1(x6))) var x9 uint64 var x10 uint64 x9, x10 = bits.Sub64(x1, 0x5812631a5cf5d3ed, uint64(0x0)) var x11 uint64 var x12 uint64 x11, x12 = bits.Sub64(x3, 0x14def9dea2f79cd6, uint64(fiatScalarUint1(x10))) var x13 uint64 var x14 uint64 x13, x14 = bits.Sub64(x5, uint64(0x0), uint64(fiatScalarUint1(x12))) var x15 uint64 var x16 uint64 x15, x16 = bits.Sub64(x7, 0x1000000000000000, uint64(fiatScalarUint1(x14))) var x18 uint64 _, x18 = bits.Sub64(uint64(fiatScalarUint1(x8)), uint64(0x0), uint64(fiatScalarUint1(x16))) var x19 uint64 fiatScalarCmovznzU64(&x19, fiatScalarUint1(x18), x9, x1) var x20 uint64 fiatScalarCmovznzU64(&x20, fiatScalarUint1(x18), x11, x3) var x21 uint64 fiatScalarCmovznzU64(&x21, fiatScalarUint1(x18), x13, x5) var x22 uint64 fiatScalarCmovznzU64(&x22, fiatScalarUint1(x18), x15, x7) out1[0] = x19 out1[1] = x20 out1[2] = x21 out1[3] = x22 } // fiatScalarSub subtracts two field elements in the Montgomery domain. // // Preconditions: // // 0 ≤ eval arg1 < m // 0 ≤ eval arg2 < m // // Postconditions: // // eval (from_montgomery out1) mod m = (eval (from_montgomery arg1) - eval (from_montgomery arg2)) mod m // 0 ≤ eval out1 < m func fiatScalarSub(out1 *fiatScalarMontgomeryDomainFieldElement, arg1 *fiatScalarMontgomeryDomainFieldElement, arg2 *fiatScalarMontgomeryDomainFieldElement) { var x1 uint64 var x2 uint64 x1, x2 = bits.Sub64(arg1[0], arg2[0], uint64(0x0)) var x3 uint64 var x4 uint64 x3, x4 = bits.Sub64(arg1[1], arg2[1], uint64(fiatScalarUint1(x2))) var x5 uint64 var x6 uint64 x5, x6 = bits.Sub64(arg1[2], arg2[2], uint64(fiatScalarUint1(x4))) var x7 uint64 var x8 uint64 x7, x8 = bits.Sub64(arg1[3], arg2[3], uint64(fiatScalarUint1(x6))) var x9 uint64 fiatScalarCmovznzU64(&x9, fiatScalarUint1(x8), uint64(0x0), 0xffffffffffffffff) var x10 uint64 var x11 uint64 x10, x11 = bits.Add64(x1, (x9 & 0x5812631a5cf5d3ed), uint64(0x0)) var x12 uint64 var x13 uint64 x12, x13 = bits.Add64(x3, (x9 & 0x14def9dea2f79cd6), uint64(fiatScalarUint1(x11))) var x14 uint64 var x15 uint64 x14, x15 = bits.Add64(x5, uint64(0x0), uint64(fiatScalarUint1(x13))) var x16 uint64 x16, _ = bits.Add64(x7, (x9 & 0x1000000000000000), uint64(fiatScalarUint1(x15))) out1[0] = x10 out1[1] = x12 out1[2] = x14 out1[3] = x16 } // fiatScalarOpp negates a field element in the Montgomery domain. // // Preconditions: // // 0 ≤ eval arg1 < m // // Postconditions: // // eval (from_montgomery out1) mod m = -eval (from_montgomery arg1) mod m // 0 ≤ eval out1 < m func fiatScalarOpp(out1 *fiatScalarMontgomeryDomainFieldElement, arg1 *fiatScalarMontgomeryDomainFieldElement) { var x1 uint64 var x2 uint64 x1, x2 = bits.Sub64(uint64(0x0), arg1[0], uint64(0x0)) var x3 uint64 var x4 uint64 x3, x4 = bits.Sub64(uint64(0x0), arg1[1], uint64(fiatScalarUint1(x2))) var x5 uint64 var x6 uint64 x5, x6 = bits.Sub64(uint64(0x0), arg1[2], uint64(fiatScalarUint1(x4))) var x7 uint64 var x8 uint64 x7, x8 = bits.Sub64(uint64(0x0), arg1[3], uint64(fiatScalarUint1(x6))) var x9 uint64 fiatScalarCmovznzU64(&x9, fiatScalarUint1(x8), uint64(0x0), 0xffffffffffffffff) var x10 uint64 var x11 uint64 x10, x11 = bits.Add64(x1, (x9 & 0x5812631a5cf5d3ed), uint64(0x0)) var x12 uint64 var x13 uint64 x12, x13 = bits.Add64(x3, (x9 & 0x14def9dea2f79cd6), uint64(fiatScalarUint1(x11))) var x14 uint64 var x15 uint64 x14, x15 = bits.Add64(x5, uint64(0x0), uint64(fiatScalarUint1(x13))) var x16 uint64 x16, _ = bits.Add64(x7, (x9 & 0x1000000000000000), uint64(fiatScalarUint1(x15))) out1[0] = x10 out1[1] = x12 out1[2] = x14 out1[3] = x16 } // fiatScalarNonzero outputs a single non-zero word if the input is non-zero and zero otherwise. // // Preconditions: // // 0 ≤ eval arg1 < m // // Postconditions: // // out1 = 0 ↔ eval (from_montgomery arg1) mod m = 0 // // Input Bounds: // // arg1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] // // Output Bounds: // // out1: [0x0 ~> 0xffffffffffffffff] func fiatScalarNonzero(out1 *uint64, arg1 *[4]uint64) { x1 := (arg1[0] | (arg1[1] | (arg1[2] | arg1[3]))) *out1 = x1 } // fiatScalarFromMontgomery translates a field element out of the Montgomery domain. // // Preconditions: // // 0 ≤ eval arg1 < m // // Postconditions: // // eval out1 mod m = (eval arg1 * ((2^64)⁻¹ mod m)^4) mod m // 0 ≤ eval out1 < m func fiatScalarFromMontgomery(out1 *fiatScalarNonMontgomeryDomainFieldElement, arg1 *fiatScalarMontgomeryDomainFieldElement) { x1 := arg1[0] var x2 uint64 _, x2 = bits.Mul64(x1, 0xd2b51da312547e1b) var x4 uint64 var x5 uint64 x5, x4 = bits.Mul64(x2, 0x1000000000000000) var x6 uint64 var x7 uint64 x7, x6 = bits.Mul64(x2, 0x14def9dea2f79cd6) var x8 uint64 var x9 uint64 x9, x8 = bits.Mul64(x2, 0x5812631a5cf5d3ed) var x10 uint64 var x11 uint64 x10, x11 = bits.Add64(x9, x6, uint64(0x0)) var x13 uint64 _, x13 = bits.Add64(x1, x8, uint64(0x0)) var x14 uint64 var x15 uint64 x14, x15 = bits.Add64(uint64(0x0), x10, uint64(fiatScalarUint1(x13))) var x16 uint64 var x17 uint64 x16, x17 = bits.Add64(x14, arg1[1], uint64(0x0)) var x18 uint64 _, x18 = bits.Mul64(x16, 0xd2b51da312547e1b) var x20 uint64 var x21 uint64 x21, x20 = bits.Mul64(x18, 0x1000000000000000) var x22 uint64 var x23 uint64 x23, x22 = bits.Mul64(x18, 0x14def9dea2f79cd6) var x24 uint64 var x25 uint64 x25, x24 = bits.Mul64(x18, 0x5812631a5cf5d3ed) var x26 uint64 var x27 uint64 x26, x27 = bits.Add64(x25, x22, uint64(0x0)) var x29 uint64 _, x29 = bits.Add64(x16, x24, uint64(0x0)) var x30 uint64 var x31 uint64 x30, x31 = bits.Add64((uint64(fiatScalarUint1(x17)) + (uint64(fiatScalarUint1(x15)) + (uint64(fiatScalarUint1(x11)) + x7))), x26, uint64(fiatScalarUint1(x29))) var x32 uint64 var x33 uint64 x32, x33 = bits.Add64(x4, (uint64(fiatScalarUint1(x27)) + x23), uint64(fiatScalarUint1(x31))) var x34 uint64 var x35 uint64 x34, x35 = bits.Add64(x5, x20, uint64(fiatScalarUint1(x33))) var x36 uint64 var x37 uint64 x36, x37 = bits.Add64(x30, arg1[2], uint64(0x0)) var x38 uint64 var x39 uint64 x38, x39 = bits.Add64(x32, uint64(0x0), uint64(fiatScalarUint1(x37))) var x40 uint64 var x41 uint64 x40, x41 = bits.Add64(x34, uint64(0x0), uint64(fiatScalarUint1(x39))) var x42 uint64 _, x42 = bits.Mul64(x36, 0xd2b51da312547e1b) var x44 uint64 var x45 uint64 x45, x44 = bits.Mul64(x42, 0x1000000000000000) var x46 uint64 var x47 uint64 x47, x46 = bits.Mul64(x42, 0x14def9dea2f79cd6) var x48 uint64 var x49 uint64 x49, x48 = bits.Mul64(x42, 0x5812631a5cf5d3ed) var x50 uint64 var x51 uint64 x50, x51 = bits.Add64(x49, x46, uint64(0x0)) var x53 uint64 _, x53 = bits.Add64(x36, x48, uint64(0x0)) var x54 uint64 var x55 uint64 x54, x55 = bits.Add64(x38, x50, uint64(fiatScalarUint1(x53))) var x56 uint64 var x57 uint64 x56, x57 = bits.Add64(x40, (uint64(fiatScalarUint1(x51)) + x47), uint64(fiatScalarUint1(x55))) var x58 uint64 var x59 uint64 x58, x59 = bits.Add64((uint64(fiatScalarUint1(x41)) + (uint64(fiatScalarUint1(x35)) + x21)), x44, uint64(fiatScalarUint1(x57))) var x60 uint64 var x61 uint64 x60, x61 = bits.Add64(x54, arg1[3], uint64(0x0)) var x62 uint64 var x63 uint64 x62, x63 = bits.Add64(x56, uint64(0x0), uint64(fiatScalarUint1(x61))) var x64 uint64 var x65 uint64 x64, x65 = bits.Add64(x58, uint64(0x0), uint64(fiatScalarUint1(x63))) var x66 uint64 _, x66 = bits.Mul64(x60, 0xd2b51da312547e1b) var x68 uint64 var x69 uint64 x69, x68 = bits.Mul64(x66, 0x1000000000000000) var x70 uint64 var x71 uint64 x71, x70 = bits.Mul64(x66, 0x14def9dea2f79cd6) var x72 uint64 var x73 uint64 x73, x72 = bits.Mul64(x66, 0x5812631a5cf5d3ed) var x74 uint64 var x75 uint64 x74, x75 = bits.Add64(x73, x70, uint64(0x0)) var x77 uint64 _, x77 = bits.Add64(x60, x72, uint64(0x0)) var x78 uint64 var x79 uint64 x78, x79 = bits.Add64(x62, x74, uint64(fiatScalarUint1(x77))) var x80 uint64 var x81 uint64 x80, x81 = bits.Add64(x64, (uint64(fiatScalarUint1(x75)) + x71), uint64(fiatScalarUint1(x79))) var x82 uint64 var x83 uint64 x82, x83 = bits.Add64((uint64(fiatScalarUint1(x65)) + (uint64(fiatScalarUint1(x59)) + x45)), x68, uint64(fiatScalarUint1(x81))) x84 := (uint64(fiatScalarUint1(x83)) + x69) var x85 uint64 var x86 uint64 x85, x86 = bits.Sub64(x78, 0x5812631a5cf5d3ed, uint64(0x0)) var x87 uint64 var x88 uint64 x87, x88 = bits.Sub64(x80, 0x14def9dea2f79cd6, uint64(fiatScalarUint1(x86))) var x89 uint64 var x90 uint64 x89, x90 = bits.Sub64(x82, uint64(0x0), uint64(fiatScalarUint1(x88))) var x91 uint64 var x92 uint64 x91, x92 = bits.Sub64(x84, 0x1000000000000000, uint64(fiatScalarUint1(x90))) var x94 uint64 _, x94 = bits.Sub64(uint64(0x0), uint64(0x0), uint64(fiatScalarUint1(x92))) var x95 uint64 fiatScalarCmovznzU64(&x95, fiatScalarUint1(x94), x85, x78) var x96 uint64 fiatScalarCmovznzU64(&x96, fiatScalarUint1(x94), x87, x80) var x97 uint64 fiatScalarCmovznzU64(&x97, fiatScalarUint1(x94), x89, x82) var x98 uint64 fiatScalarCmovznzU64(&x98, fiatScalarUint1(x94), x91, x84) out1[0] = x95 out1[1] = x96 out1[2] = x97 out1[3] = x98 } // fiatScalarToMontgomery translates a field element into the Montgomery domain. // // Preconditions: // // 0 ≤ eval arg1 < m // // Postconditions: // // eval (from_montgomery out1) mod m = eval arg1 mod m // 0 ≤ eval out1 < m func fiatScalarToMontgomery(out1 *fiatScalarMontgomeryDomainFieldElement, arg1 *fiatScalarNonMontgomeryDomainFieldElement) { x1 := arg1[1] x2 := arg1[2] x3 := arg1[3] x4 := arg1[0] var x5 uint64 var x6 uint64 x6, x5 = bits.Mul64(x4, 0x399411b7c309a3d) var x7 uint64 var x8 uint64 x8, x7 = bits.Mul64(x4, 0xceec73d217f5be65) var x9 uint64 var x10 uint64 x10, x9 = bits.Mul64(x4, 0xd00e1ba768859347) var x11 uint64 var x12 uint64 x12, x11 = bits.Mul64(x4, 0xa40611e3449c0f01) var x13 uint64 var x14 uint64 x13, x14 = bits.Add64(x12, x9, uint64(0x0)) var x15 uint64 var x16 uint64 x15, x16 = bits.Add64(x10, x7, uint64(fiatScalarUint1(x14))) var x17 uint64 var x18 uint64 x17, x18 = bits.Add64(x8, x5, uint64(fiatScalarUint1(x16))) var x19 uint64 _, x19 = bits.Mul64(x11, 0xd2b51da312547e1b) var x21 uint64 var x22 uint64 x22, x21 = bits.Mul64(x19, 0x1000000000000000) var x23 uint64 var x24 uint64 x24, x23 = bits.Mul64(x19, 0x14def9dea2f79cd6) var x25 uint64 var x26 uint64 x26, x25 = bits.Mul64(x19, 0x5812631a5cf5d3ed) var x27 uint64 var x28 uint64 x27, x28 = bits.Add64(x26, x23, uint64(0x0)) var x30 uint64 _, x30 = bits.Add64(x11, x25, uint64(0x0)) var x31 uint64 var x32 uint64 x31, x32 = bits.Add64(x13, x27, uint64(fiatScalarUint1(x30))) var x33 uint64 var x34 uint64 x33, x34 = bits.Add64(x15, (uint64(fiatScalarUint1(x28)) + x24), uint64(fiatScalarUint1(x32))) var x35 uint64 var x36 uint64 x35, x36 = bits.Add64(x17, x21, uint64(fiatScalarUint1(x34))) var x37 uint64 var x38 uint64 x38, x37 = bits.Mul64(x1, 0x399411b7c309a3d) var x39 uint64 var x40 uint64 x40, x39 = bits.Mul64(x1, 0xceec73d217f5be65) var x41 uint64 var x42 uint64 x42, x41 = bits.Mul64(x1, 0xd00e1ba768859347) var x43 uint64 var x44 uint64 x44, x43 = bits.Mul64(x1, 0xa40611e3449c0f01) var x45 uint64 var x46 uint64 x45, x46 = bits.Add64(x44, x41, uint64(0x0)) var x47 uint64 var x48 uint64 x47, x48 = bits.Add64(x42, x39, uint64(fiatScalarUint1(x46))) var x49 uint64 var x50 uint64 x49, x50 = bits.Add64(x40, x37, uint64(fiatScalarUint1(x48))) var x51 uint64 var x52 uint64 x51, x52 = bits.Add64(x31, x43, uint64(0x0)) var x53 uint64 var x54 uint64 x53, x54 = bits.Add64(x33, x45, uint64(fiatScalarUint1(x52))) var x55 uint64 var x56 uint64 x55, x56 = bits.Add64(x35, x47, uint64(fiatScalarUint1(x54))) var x57 uint64 var x58 uint64 x57, x58 = bits.Add64(((uint64(fiatScalarUint1(x36)) + (uint64(fiatScalarUint1(x18)) + x6)) + x22), x49, uint64(fiatScalarUint1(x56))) var x59 uint64 _, x59 = bits.Mul64(x51, 0xd2b51da312547e1b) var x61 uint64 var x62 uint64 x62, x61 = bits.Mul64(x59, 0x1000000000000000) var x63 uint64 var x64 uint64 x64, x63 = bits.Mul64(x59, 0x14def9dea2f79cd6) var x65 uint64 var x66 uint64 x66, x65 = bits.Mul64(x59, 0x5812631a5cf5d3ed) var x67 uint64 var x68 uint64 x67, x68 = bits.Add64(x66, x63, uint64(0x0)) var x70 uint64 _, x70 = bits.Add64(x51, x65, uint64(0x0)) var x71 uint64 var x72 uint64 x71, x72 = bits.Add64(x53, x67, uint64(fiatScalarUint1(x70))) var x73 uint64 var x74 uint64 x73, x74 = bits.Add64(x55, (uint64(fiatScalarUint1(x68)) + x64), uint64(fiatScalarUint1(x72))) var x75 uint64 var x76 uint64 x75, x76 = bits.Add64(x57, x61, uint64(fiatScalarUint1(x74))) var x77 uint64 var x78 uint64 x78, x77 = bits.Mul64(x2, 0x399411b7c309a3d) var x79 uint64 var x80 uint64 x80, x79 = bits.Mul64(x2, 0xceec73d217f5be65) var x81 uint64 var x82 uint64 x82, x81 = bits.Mul64(x2, 0xd00e1ba768859347) var x83 uint64 var x84 uint64 x84, x83 = bits.Mul64(x2, 0xa40611e3449c0f01) var x85 uint64 var x86 uint64 x85, x86 = bits.Add64(x84, x81, uint64(0x0)) var x87 uint64 var x88 uint64 x87, x88 = bits.Add64(x82, x79, uint64(fiatScalarUint1(x86))) var x89 uint64 var x90 uint64 x89, x90 = bits.Add64(x80, x77, uint64(fiatScalarUint1(x88))) var x91 uint64 var x92 uint64 x91, x92 = bits.Add64(x71, x83, uint64(0x0)) var x93 uint64 var x94 uint64 x93, x94 = bits.Add64(x73, x85, uint64(fiatScalarUint1(x92))) var x95 uint64 var x96 uint64 x95, x96 = bits.Add64(x75, x87, uint64(fiatScalarUint1(x94))) var x97 uint64 var x98 uint64 x97, x98 = bits.Add64(((uint64(fiatScalarUint1(x76)) + (uint64(fiatScalarUint1(x58)) + (uint64(fiatScalarUint1(x50)) + x38))) + x62), x89, uint64(fiatScalarUint1(x96))) var x99 uint64 _, x99 = bits.Mul64(x91, 0xd2b51da312547e1b) var x101 uint64 var x102 uint64 x102, x101 = bits.Mul64(x99, 0x1000000000000000) var x103 uint64 var x104 uint64 x104, x103 = bits.Mul64(x99, 0x14def9dea2f79cd6) var x105 uint64 var x106 uint64 x106, x105 = bits.Mul64(x99, 0x5812631a5cf5d3ed) var x107 uint64 var x108 uint64 x107, x108 = bits.Add64(x106, x103, uint64(0x0)) var x110 uint64 _, x110 = bits.Add64(x91, x105, uint64(0x0)) var x111 uint64 var x112 uint64 x111, x112 = bits.Add64(x93, x107, uint64(fiatScalarUint1(x110))) var x113 uint64 var x114 uint64 x113, x114 = bits.Add64(x95, (uint64(fiatScalarUint1(x108)) + x104), uint64(fiatScalarUint1(x112))) var x115 uint64 var x116 uint64 x115, x116 = bits.Add64(x97, x101, uint64(fiatScalarUint1(x114))) var x117 uint64 var x118 uint64 x118, x117 = bits.Mul64(x3, 0x399411b7c309a3d) var x119 uint64 var x120 uint64 x120, x119 = bits.Mul64(x3, 0xceec73d217f5be65) var x121 uint64 var x122 uint64 x122, x121 = bits.Mul64(x3, 0xd00e1ba768859347) var x123 uint64 var x124 uint64 x124, x123 = bits.Mul64(x3, 0xa40611e3449c0f01) var x125 uint64 var x126 uint64 x125, x126 = bits.Add64(x124, x121, uint64(0x0)) var x127 uint64 var x128 uint64 x127, x128 = bits.Add64(x122, x119, uint64(fiatScalarUint1(x126))) var x129 uint64 var x130 uint64 x129, x130 = bits.Add64(x120, x117, uint64(fiatScalarUint1(x128))) var x131 uint64 var x132 uint64 x131, x132 = bits.Add64(x111, x123, uint64(0x0)) var x133 uint64 var x134 uint64 x133, x134 = bits.Add64(x113, x125, uint64(fiatScalarUint1(x132))) var x135 uint64 var x136 uint64 x135, x136 = bits.Add64(x115, x127, uint64(fiatScalarUint1(x134))) var x137 uint64 var x138 uint64 x137, x138 = bits.Add64(((uint64(fiatScalarUint1(x116)) + (uint64(fiatScalarUint1(x98)) + (uint64(fiatScalarUint1(x90)) + x78))) + x102), x129, uint64(fiatScalarUint1(x136))) var x139 uint64 _, x139 = bits.Mul64(x131, 0xd2b51da312547e1b) var x141 uint64 var x142 uint64 x142, x141 = bits.Mul64(x139, 0x1000000000000000) var x143 uint64 var x144 uint64 x144, x143 = bits.Mul64(x139, 0x14def9dea2f79cd6) var x145 uint64 var x146 uint64 x146, x145 = bits.Mul64(x139, 0x5812631a5cf5d3ed) var x147 uint64 var x148 uint64 x147, x148 = bits.Add64(x146, x143, uint64(0x0)) var x150 uint64 _, x150 = bits.Add64(x131, x145, uint64(0x0)) var x151 uint64 var x152 uint64 x151, x152 = bits.Add64(x133, x147, uint64(fiatScalarUint1(x150))) var x153 uint64 var x154 uint64 x153, x154 = bits.Add64(x135, (uint64(fiatScalarUint1(x148)) + x144), uint64(fiatScalarUint1(x152))) var x155 uint64 var x156 uint64 x155, x156 = bits.Add64(x137, x141, uint64(fiatScalarUint1(x154))) x157 := ((uint64(fiatScalarUint1(x156)) + (uint64(fiatScalarUint1(x138)) + (uint64(fiatScalarUint1(x130)) + x118))) + x142) var x158 uint64 var x159 uint64 x158, x159 = bits.Sub64(x151, 0x5812631a5cf5d3ed, uint64(0x0)) var x160 uint64 var x161 uint64 x160, x161 = bits.Sub64(x153, 0x14def9dea2f79cd6, uint64(fiatScalarUint1(x159))) var x162 uint64 var x163 uint64 x162, x163 = bits.Sub64(x155, uint64(0x0), uint64(fiatScalarUint1(x161))) var x164 uint64 var x165 uint64 x164, x165 = bits.Sub64(x157, 0x1000000000000000, uint64(fiatScalarUint1(x163))) var x167 uint64 _, x167 = bits.Sub64(uint64(0x0), uint64(0x0), uint64(fiatScalarUint1(x165))) var x168 uint64 fiatScalarCmovznzU64(&x168, fiatScalarUint1(x167), x158, x151) var x169 uint64 fiatScalarCmovznzU64(&x169, fiatScalarUint1(x167), x160, x153) var x170 uint64 fiatScalarCmovznzU64(&x170, fiatScalarUint1(x167), x162, x155) var x171 uint64 fiatScalarCmovznzU64(&x171, fiatScalarUint1(x167), x164, x157) out1[0] = x168 out1[1] = x169 out1[2] = x170 out1[3] = x171 } // fiatScalarToBytes serializes a field element NOT in the Montgomery domain to bytes in little-endian order. // // Preconditions: // // 0 ≤ eval arg1 < m // // Postconditions: // // out1 = map (λ x, ⌊((eval arg1 mod m) mod 2^(8 * (x + 1))) / 2^(8 * x)⌋) [0..31] // // Input Bounds: // // arg1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0x1fffffffffffffff]] // // Output Bounds: // // out1: [[0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0x1f]] func fiatScalarToBytes(out1 *[32]uint8, arg1 *[4]uint64) { x1 := arg1[3] x2 := arg1[2] x3 := arg1[1] x4 := arg1[0] x5 := (uint8(x4) & 0xff) x6 := (x4 >> 8) x7 := (uint8(x6) & 0xff) x8 := (x6 >> 8) x9 := (uint8(x8) & 0xff) x10 := (x8 >> 8) x11 := (uint8(x10) & 0xff) x12 := (x10 >> 8) x13 := (uint8(x12) & 0xff) x14 := (x12 >> 8) x15 := (uint8(x14) & 0xff) x16 := (x14 >> 8) x17 := (uint8(x16) & 0xff) x18 := uint8((x16 >> 8)) x19 := (uint8(x3) & 0xff) x20 := (x3 >> 8) x21 := (uint8(x20) & 0xff) x22 := (x20 >> 8) x23 := (uint8(x22) & 0xff) x24 := (x22 >> 8) x25 := (uint8(x24) & 0xff) x26 := (x24 >> 8) x27 := (uint8(x26) & 0xff) x28 := (x26 >> 8) x29 := (uint8(x28) & 0xff) x30 := (x28 >> 8) x31 := (uint8(x30) & 0xff) x32 := uint8((x30 >> 8)) x33 := (uint8(x2) & 0xff) x34 := (x2 >> 8) x35 := (uint8(x34) & 0xff) x36 := (x34 >> 8) x37 := (uint8(x36) & 0xff) x38 := (x36 >> 8) x39 := (uint8(x38) & 0xff) x40 := (x38 >> 8) x41 := (uint8(x40) & 0xff) x42 := (x40 >> 8) x43 := (uint8(x42) & 0xff) x44 := (x42 >> 8) x45 := (uint8(x44) & 0xff) x46 := uint8((x44 >> 8)) x47 := (uint8(x1) & 0xff) x48 := (x1 >> 8) x49 := (uint8(x48) & 0xff) x50 := (x48 >> 8) x51 := (uint8(x50) & 0xff) x52 := (x50 >> 8) x53 := (uint8(x52) & 0xff) x54 := (x52 >> 8) x55 := (uint8(x54) & 0xff) x56 := (x54 >> 8) x57 := (uint8(x56) & 0xff) x58 := (x56 >> 8) x59 := (uint8(x58) & 0xff) x60 := uint8((x58 >> 8)) out1[0] = x5 out1[1] = x7 out1[2] = x9 out1[3] = x11 out1[4] = x13 out1[5] = x15 out1[6] = x17 out1[7] = x18 out1[8] = x19 out1[9] = x21 out1[10] = x23 out1[11] = x25 out1[12] = x27 out1[13] = x29 out1[14] = x31 out1[15] = x32 out1[16] = x33 out1[17] = x35 out1[18] = x37 out1[19] = x39 out1[20] = x41 out1[21] = x43 out1[22] = x45 out1[23] = x46 out1[24] = x47 out1[25] = x49 out1[26] = x51 out1[27] = x53 out1[28] = x55 out1[29] = x57 out1[30] = x59 out1[31] = x60 } // fiatScalarFromBytes deserializes a field element NOT in the Montgomery domain from bytes in little-endian order. // // Preconditions: // // 0 ≤ bytes_eval arg1 < m // // Postconditions: // // eval out1 mod m = bytes_eval arg1 mod m // 0 ≤ eval out1 < m // // Input Bounds: // // arg1: [[0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0x1f]] // // Output Bounds: // // out1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0x1fffffffffffffff]] func fiatScalarFromBytes(out1 *[4]uint64, arg1 *[32]uint8) { x1 := (uint64(arg1[31]) << 56) x2 := (uint64(arg1[30]) << 48) x3 := (uint64(arg1[29]) << 40) x4 := (uint64(arg1[28]) << 32) x5 := (uint64(arg1[27]) << 24) x6 := (uint64(arg1[26]) << 16) x7 := (uint64(arg1[25]) << 8) x8 := arg1[24] x9 := (uint64(arg1[23]) << 56) x10 := (uint64(arg1[22]) << 48) x11 := (uint64(arg1[21]) << 40) x12 := (uint64(arg1[20]) << 32) x13 := (uint64(arg1[19]) << 24) x14 := (uint64(arg1[18]) << 16) x15 := (uint64(arg1[17]) << 8) x16 := arg1[16] x17 := (uint64(arg1[15]) << 56) x18 := (uint64(arg1[14]) << 48) x19 := (uint64(arg1[13]) << 40) x20 := (uint64(arg1[12]) << 32) x21 := (uint64(arg1[11]) << 24) x22 := (uint64(arg1[10]) << 16) x23 := (uint64(arg1[9]) << 8) x24 := arg1[8] x25 := (uint64(arg1[7]) << 56) x26 := (uint64(arg1[6]) << 48) x27 := (uint64(arg1[5]) << 40) x28 := (uint64(arg1[4]) << 32) x29 := (uint64(arg1[3]) << 24) x30 := (uint64(arg1[2]) << 16) x31 := (uint64(arg1[1]) << 8) x32 := arg1[0] x33 := (x31 + uint64(x32)) x34 := (x30 + x33) x35 := (x29 + x34) x36 := (x28 + x35) x37 := (x27 + x36) x38 := (x26 + x37) x39 := (x25 + x38) x40 := (x23 + uint64(x24)) x41 := (x22 + x40) x42 := (x21 + x41) x43 := (x20 + x42) x44 := (x19 + x43) x45 := (x18 + x44) x46 := (x17 + x45) x47 := (x15 + uint64(x16)) x48 := (x14 + x47) x49 := (x13 + x48) x50 := (x12 + x49) x51 := (x11 + x50) x52 := (x10 + x51) x53 := (x9 + x52) x54 := (x7 + uint64(x8)) x55 := (x6 + x54) x56 := (x5 + x55) x57 := (x4 + x56) x58 := (x3 + x57) x59 := (x2 + x58) x60 := (x1 + x59) out1[0] = x39 out1[1] = x46 out1[2] = x53 out1[3] = x60 } ================================================ FILE: vendor/filippo.io/edwards25519/scalarmult.go ================================================ // Copyright (c) 2019 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package edwards25519 import "sync" // basepointTable is a set of 32 affineLookupTables, where table i is generated // from 256i * basepoint. It is precomputed the first time it's used. func basepointTable() *[32]affineLookupTable { basepointTablePrecomp.initOnce.Do(func() { p := NewGeneratorPoint() for i := 0; i < 32; i++ { basepointTablePrecomp.table[i].FromP3(p) for j := 0; j < 8; j++ { p.Add(p, p) } } }) return &basepointTablePrecomp.table } var basepointTablePrecomp struct { table [32]affineLookupTable initOnce sync.Once } // ScalarBaseMult sets v = x * B, where B is the canonical generator, and // returns v. // // The scalar multiplication is done in constant time. func (v *Point) ScalarBaseMult(x *Scalar) *Point { basepointTable := basepointTable() // Write x = sum(x_i * 16^i) so x*B = sum( B*x_i*16^i ) // as described in the Ed25519 paper // // Group even and odd coefficients // x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B // + x_1*16^1*B + x_3*16^3*B + ... + x_63*16^63*B // x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B // + 16*( x_1*16^0*B + x_3*16^2*B + ... + x_63*16^62*B) // // We use a lookup table for each i to get x_i*16^(2*i)*B // and do four doublings to multiply by 16. digits := x.signedRadix16() multiple := &affineCached{} tmp1 := &projP1xP1{} tmp2 := &projP2{} // Accumulate the odd components first v.Set(NewIdentityPoint()) for i := 1; i < 64; i += 2 { basepointTable[i/2].SelectInto(multiple, digits[i]) tmp1.AddAffine(v, multiple) v.fromP1xP1(tmp1) } // Multiply by 16 tmp2.FromP3(v) // tmp2 = v in P2 coords tmp1.Double(tmp2) // tmp1 = 2*v in P1xP1 coords tmp2.FromP1xP1(tmp1) // tmp2 = 2*v in P2 coords tmp1.Double(tmp2) // tmp1 = 4*v in P1xP1 coords tmp2.FromP1xP1(tmp1) // tmp2 = 4*v in P2 coords tmp1.Double(tmp2) // tmp1 = 8*v in P1xP1 coords tmp2.FromP1xP1(tmp1) // tmp2 = 8*v in P2 coords tmp1.Double(tmp2) // tmp1 = 16*v in P1xP1 coords v.fromP1xP1(tmp1) // now v = 16*(odd components) // Accumulate the even components for i := 0; i < 64; i += 2 { basepointTable[i/2].SelectInto(multiple, digits[i]) tmp1.AddAffine(v, multiple) v.fromP1xP1(tmp1) } return v } // ScalarMult sets v = x * q, and returns v. // // The scalar multiplication is done in constant time. func (v *Point) ScalarMult(x *Scalar, q *Point) *Point { checkInitialized(q) var table projLookupTable table.FromP3(q) // Write x = sum(x_i * 16^i) // so x*Q = sum( Q*x_i*16^i ) // = Q*x_0 + 16*(Q*x_1 + 16*( ... + Q*x_63) ... ) // <------compute inside out--------- // // We use the lookup table to get the x_i*Q values // and do four doublings to compute 16*Q digits := x.signedRadix16() // Unwrap first loop iteration to save computing 16*identity multiple := &projCached{} tmp1 := &projP1xP1{} tmp2 := &projP2{} table.SelectInto(multiple, digits[63]) v.Set(NewIdentityPoint()) tmp1.Add(v, multiple) // tmp1 = x_63*Q in P1xP1 coords for i := 62; i >= 0; i-- { tmp2.FromP1xP1(tmp1) // tmp2 = (prev) in P2 coords tmp1.Double(tmp2) // tmp1 = 2*(prev) in P1xP1 coords tmp2.FromP1xP1(tmp1) // tmp2 = 2*(prev) in P2 coords tmp1.Double(tmp2) // tmp1 = 4*(prev) in P1xP1 coords tmp2.FromP1xP1(tmp1) // tmp2 = 4*(prev) in P2 coords tmp1.Double(tmp2) // tmp1 = 8*(prev) in P1xP1 coords tmp2.FromP1xP1(tmp1) // tmp2 = 8*(prev) in P2 coords tmp1.Double(tmp2) // tmp1 = 16*(prev) in P1xP1 coords v.fromP1xP1(tmp1) // v = 16*(prev) in P3 coords table.SelectInto(multiple, digits[i]) tmp1.Add(v, multiple) // tmp1 = x_i*Q + 16*(prev) in P1xP1 coords } v.fromP1xP1(tmp1) return v } // basepointNafTable is the nafLookupTable8 for the basepoint. // It is precomputed the first time it's used. func basepointNafTable() *nafLookupTable8 { basepointNafTablePrecomp.initOnce.Do(func() { basepointNafTablePrecomp.table.FromP3(NewGeneratorPoint()) }) return &basepointNafTablePrecomp.table } var basepointNafTablePrecomp struct { table nafLookupTable8 initOnce sync.Once } // VarTimeDoubleScalarBaseMult sets v = a * A + b * B, where B is the canonical // generator, and returns v. // // Execution time depends on the inputs. func (v *Point) VarTimeDoubleScalarBaseMult(a *Scalar, A *Point, b *Scalar) *Point { checkInitialized(A) // Similarly to the single variable-base approach, we compute // digits and use them with a lookup table. However, because // we are allowed to do variable-time operations, we don't // need constant-time lookups or constant-time digit // computations. // // So we use a non-adjacent form of some width w instead of // radix 16. This is like a binary representation (one digit // for each binary place) but we allow the digits to grow in // magnitude up to 2^{w-1} so that the nonzero digits are as // sparse as possible. Intuitively, this "condenses" the // "mass" of the scalar onto sparse coefficients (meaning // fewer additions). basepointNafTable := basepointNafTable() var aTable nafLookupTable5 aTable.FromP3(A) // Because the basepoint is fixed, we can use a wider NAF // corresponding to a bigger table. aNaf := a.nonAdjacentForm(5) bNaf := b.nonAdjacentForm(8) // Find the first nonzero coefficient. i := 255 for j := i; j >= 0; j-- { if aNaf[j] != 0 || bNaf[j] != 0 { break } } multA := &projCached{} multB := &affineCached{} tmp1 := &projP1xP1{} tmp2 := &projP2{} tmp2.Zero() // Move from high to low bits, doubling the accumulator // at each iteration and checking whether there is a nonzero // coefficient to look up a multiple of. for ; i >= 0; i-- { tmp1.Double(tmp2) // Only update v if we have a nonzero coeff to add in. if aNaf[i] > 0 { v.fromP1xP1(tmp1) aTable.SelectInto(multA, aNaf[i]) tmp1.Add(v, multA) } else if aNaf[i] < 0 { v.fromP1xP1(tmp1) aTable.SelectInto(multA, -aNaf[i]) tmp1.Sub(v, multA) } if bNaf[i] > 0 { v.fromP1xP1(tmp1) basepointNafTable.SelectInto(multB, bNaf[i]) tmp1.AddAffine(v, multB) } else if bNaf[i] < 0 { v.fromP1xP1(tmp1) basepointNafTable.SelectInto(multB, -bNaf[i]) tmp1.SubAffine(v, multB) } tmp2.FromP1xP1(tmp1) } v.fromP2(tmp2) return v } ================================================ FILE: vendor/filippo.io/edwards25519/tables.go ================================================ // Copyright (c) 2019 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package edwards25519 import ( "crypto/subtle" ) // A dynamic lookup table for variable-base, constant-time scalar muls. type projLookupTable struct { points [8]projCached } // A precomputed lookup table for fixed-base, constant-time scalar muls. type affineLookupTable struct { points [8]affineCached } // A dynamic lookup table for variable-base, variable-time scalar muls. type nafLookupTable5 struct { points [8]projCached } // A precomputed lookup table for fixed-base, variable-time scalar muls. type nafLookupTable8 struct { points [64]affineCached } // Constructors. // Builds a lookup table at runtime. Fast. func (v *projLookupTable) FromP3(q *Point) { // Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q // This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q v.points[0].FromP3(q) tmpP3 := Point{} tmpP1xP1 := projP1xP1{} for i := 0; i < 7; i++ { // Compute (i+1)*Q as Q + i*Q and convert to a projCached // This is needlessly complicated because the API has explicit // receivers instead of creating stack objects and relying on RVO v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.Add(q, &v.points[i]))) } } // This is not optimised for speed; fixed-base tables should be precomputed. func (v *affineLookupTable) FromP3(q *Point) { // Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q // This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q v.points[0].FromP3(q) tmpP3 := Point{} tmpP1xP1 := projP1xP1{} for i := 0; i < 7; i++ { // Compute (i+1)*Q as Q + i*Q and convert to affineCached v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.AddAffine(q, &v.points[i]))) } } // Builds a lookup table at runtime. Fast. func (v *nafLookupTable5) FromP3(q *Point) { // Goal: v.points[i] = (2*i+1)*Q, i.e., Q, 3Q, 5Q, ..., 15Q // This allows lookup of -15Q, ..., -3Q, -Q, 0, Q, 3Q, ..., 15Q v.points[0].FromP3(q) q2 := Point{} q2.Add(q, q) tmpP3 := Point{} tmpP1xP1 := projP1xP1{} for i := 0; i < 7; i++ { v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.Add(&q2, &v.points[i]))) } } // This is not optimised for speed; fixed-base tables should be precomputed. func (v *nafLookupTable8) FromP3(q *Point) { v.points[0].FromP3(q) q2 := Point{} q2.Add(q, q) tmpP3 := Point{} tmpP1xP1 := projP1xP1{} for i := 0; i < 63; i++ { v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.AddAffine(&q2, &v.points[i]))) } } // Selectors. // Set dest to x*Q, where -8 <= x <= 8, in constant time. func (v *projLookupTable) SelectInto(dest *projCached, x int8) { // Compute xabs = |x| xmask := x >> 7 xabs := uint8((x + xmask) ^ xmask) dest.Zero() for j := 1; j <= 8; j++ { // Set dest = j*Q if |x| = j cond := subtle.ConstantTimeByteEq(xabs, uint8(j)) dest.Select(&v.points[j-1], dest, cond) } // Now dest = |x|*Q, conditionally negate to get x*Q dest.CondNeg(int(xmask & 1)) } // Set dest to x*Q, where -8 <= x <= 8, in constant time. func (v *affineLookupTable) SelectInto(dest *affineCached, x int8) { // Compute xabs = |x| xmask := x >> 7 xabs := uint8((x + xmask) ^ xmask) dest.Zero() for j := 1; j <= 8; j++ { // Set dest = j*Q if |x| = j cond := subtle.ConstantTimeByteEq(xabs, uint8(j)) dest.Select(&v.points[j-1], dest, cond) } // Now dest = |x|*Q, conditionally negate to get x*Q dest.CondNeg(int(xmask & 1)) } // Given odd x with 0 < x < 2^4, return x*Q (in variable time). func (v *nafLookupTable5) SelectInto(dest *projCached, x int8) { *dest = v.points[x/2] } // Given odd x with 0 < x < 2^7, return x*Q (in variable time). func (v *nafLookupTable8) SelectInto(dest *affineCached, x int8) { *dest = v.points[x/2] } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 Microsoft Corporation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: vendor/github.com/Azure/go-ansiterm/README.md ================================================ # go-ansiterm This is a cross platform Ansi Terminal Emulation library. It reads a stream of Ansi characters and produces the appropriate function calls. The results of the function calls are platform dependent. For example the parser might receive "ESC, [, A" as a stream of three characters. This is the code for Cursor Up (http://www.vt100.net/docs/vt510-rm/CUU). The parser then calls the cursor up function (CUU()) on an event handler. The event handler determines what platform specific work must be done to cause the cursor to move up one position. The parser (parser.go) is a partial implementation of this state machine (http://vt100.net/emu/vt500_parser.png). There are also two event handler implementations, one for tests (test_event_handler.go) to validate that the expected events are being produced and called, the other is a Windows implementation (winterm/win_event_handler.go). See parser_test.go for examples exercising the state machine and generating appropriate function calls. ----- This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. ================================================ FILE: vendor/github.com/Azure/go-ansiterm/constants.go ================================================ package ansiterm const LogEnv = "DEBUG_TERMINAL" // ANSI constants // References: // -- http://www.ecma-international.org/publications/standards/Ecma-048.htm // -- http://man7.org/linux/man-pages/man4/console_codes.4.html // -- http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html // -- http://en.wikipedia.org/wiki/ANSI_escape_code // -- http://vt100.net/emu/dec_ansi_parser // -- http://vt100.net/emu/vt500_parser.svg // -- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html // -- http://www.inwap.com/pdp10/ansicode.txt const ( // ECMA-48 Set Graphics Rendition // Note: // -- Constants leading with an underscore (e.g., _ANSI_xxx) are unsupported or reserved // -- Fonts could possibly be supported via SetCurrentConsoleFontEx // -- Windows does not expose the per-window cursor (i.e., caret) blink times ANSI_SGR_RESET = 0 ANSI_SGR_BOLD = 1 ANSI_SGR_DIM = 2 _ANSI_SGR_ITALIC = 3 ANSI_SGR_UNDERLINE = 4 _ANSI_SGR_BLINKSLOW = 5 _ANSI_SGR_BLINKFAST = 6 ANSI_SGR_REVERSE = 7 _ANSI_SGR_INVISIBLE = 8 _ANSI_SGR_LINETHROUGH = 9 _ANSI_SGR_FONT_00 = 10 _ANSI_SGR_FONT_01 = 11 _ANSI_SGR_FONT_02 = 12 _ANSI_SGR_FONT_03 = 13 _ANSI_SGR_FONT_04 = 14 _ANSI_SGR_FONT_05 = 15 _ANSI_SGR_FONT_06 = 16 _ANSI_SGR_FONT_07 = 17 _ANSI_SGR_FONT_08 = 18 _ANSI_SGR_FONT_09 = 19 _ANSI_SGR_FONT_10 = 20 _ANSI_SGR_DOUBLEUNDERLINE = 21 ANSI_SGR_BOLD_DIM_OFF = 22 _ANSI_SGR_ITALIC_OFF = 23 ANSI_SGR_UNDERLINE_OFF = 24 _ANSI_SGR_BLINK_OFF = 25 _ANSI_SGR_RESERVED_00 = 26 ANSI_SGR_REVERSE_OFF = 27 _ANSI_SGR_INVISIBLE_OFF = 28 _ANSI_SGR_LINETHROUGH_OFF = 29 ANSI_SGR_FOREGROUND_BLACK = 30 ANSI_SGR_FOREGROUND_RED = 31 ANSI_SGR_FOREGROUND_GREEN = 32 ANSI_SGR_FOREGROUND_YELLOW = 33 ANSI_SGR_FOREGROUND_BLUE = 34 ANSI_SGR_FOREGROUND_MAGENTA = 35 ANSI_SGR_FOREGROUND_CYAN = 36 ANSI_SGR_FOREGROUND_WHITE = 37 _ANSI_SGR_RESERVED_01 = 38 ANSI_SGR_FOREGROUND_DEFAULT = 39 ANSI_SGR_BACKGROUND_BLACK = 40 ANSI_SGR_BACKGROUND_RED = 41 ANSI_SGR_BACKGROUND_GREEN = 42 ANSI_SGR_BACKGROUND_YELLOW = 43 ANSI_SGR_BACKGROUND_BLUE = 44 ANSI_SGR_BACKGROUND_MAGENTA = 45 ANSI_SGR_BACKGROUND_CYAN = 46 ANSI_SGR_BACKGROUND_WHITE = 47 _ANSI_SGR_RESERVED_02 = 48 ANSI_SGR_BACKGROUND_DEFAULT = 49 // 50 - 65: Unsupported ANSI_MAX_CMD_LENGTH = 4096 MAX_INPUT_EVENTS = 128 DEFAULT_WIDTH = 80 DEFAULT_HEIGHT = 24 ANSI_BEL = 0x07 ANSI_BACKSPACE = 0x08 ANSI_TAB = 0x09 ANSI_LINE_FEED = 0x0A ANSI_VERTICAL_TAB = 0x0B ANSI_FORM_FEED = 0x0C ANSI_CARRIAGE_RETURN = 0x0D ANSI_ESCAPE_PRIMARY = 0x1B ANSI_ESCAPE_SECONDARY = 0x5B ANSI_OSC_STRING_ENTRY = 0x5D ANSI_COMMAND_FIRST = 0x40 ANSI_COMMAND_LAST = 0x7E DCS_ENTRY = 0x90 CSI_ENTRY = 0x9B OSC_STRING = 0x9D ANSI_PARAMETER_SEP = ";" ANSI_CMD_G0 = '(' ANSI_CMD_G1 = ')' ANSI_CMD_G2 = '*' ANSI_CMD_G3 = '+' ANSI_CMD_DECPNM = '>' ANSI_CMD_DECPAM = '=' ANSI_CMD_OSC = ']' ANSI_CMD_STR_TERM = '\\' KEY_CONTROL_PARAM_2 = ";2" KEY_CONTROL_PARAM_3 = ";3" KEY_CONTROL_PARAM_4 = ";4" KEY_CONTROL_PARAM_5 = ";5" KEY_CONTROL_PARAM_6 = ";6" KEY_CONTROL_PARAM_7 = ";7" KEY_CONTROL_PARAM_8 = ";8" KEY_ESC_CSI = "\x1B[" KEY_ESC_N = "\x1BN" KEY_ESC_O = "\x1BO" FILL_CHARACTER = ' ' ) func getByteRange(start byte, end byte) []byte { bytes := make([]byte, 0, 32) for i := start; i <= end; i++ { bytes = append(bytes, byte(i)) } return bytes } var toGroundBytes = getToGroundBytes() var executors = getExecuteBytes() // SPACE 20+A0 hex Always and everywhere a blank space // Intermediate 20-2F hex !"#$%&'()*+,-./ var intermeds = getByteRange(0x20, 0x2F) // Parameters 30-3F hex 0123456789:;<=>? // CSI Parameters 30-39, 3B hex 0123456789; var csiParams = getByteRange(0x30, 0x3F) var csiCollectables = append(getByteRange(0x30, 0x39), getByteRange(0x3B, 0x3F)...) // Uppercase 40-5F hex @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ var upperCase = getByteRange(0x40, 0x5F) // Lowercase 60-7E hex `abcdefghijlkmnopqrstuvwxyz{|}~ var lowerCase = getByteRange(0x60, 0x7E) // Alphabetics 40-7E hex (all of upper and lower case) var alphabetics = append(upperCase, lowerCase...) var printables = getByteRange(0x20, 0x7F) var escapeIntermediateToGroundBytes = getByteRange(0x30, 0x7E) var escapeToGroundBytes = getEscapeToGroundBytes() // See http://www.vt100.net/emu/vt500_parser.png for description of the complex // byte ranges below func getEscapeToGroundBytes() []byte { escapeToGroundBytes := getByteRange(0x30, 0x4F) escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x51, 0x57)...) escapeToGroundBytes = append(escapeToGroundBytes, 0x59) escapeToGroundBytes = append(escapeToGroundBytes, 0x5A) escapeToGroundBytes = append(escapeToGroundBytes, 0x5C) escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x60, 0x7E)...) return escapeToGroundBytes } func getExecuteBytes() []byte { executeBytes := getByteRange(0x00, 0x17) executeBytes = append(executeBytes, 0x19) executeBytes = append(executeBytes, getByteRange(0x1C, 0x1F)...) return executeBytes } func getToGroundBytes() []byte { groundBytes := []byte{0x18} groundBytes = append(groundBytes, 0x1A) groundBytes = append(groundBytes, getByteRange(0x80, 0x8F)...) groundBytes = append(groundBytes, getByteRange(0x91, 0x97)...) groundBytes = append(groundBytes, 0x99) groundBytes = append(groundBytes, 0x9A) groundBytes = append(groundBytes, 0x9C) return groundBytes } // Delete 7F hex Always and everywhere ignored // C1 Control 80-9F hex 32 additional control characters // G1 Displayable A1-FE hex 94 additional displayable characters // Special A0+FF hex Same as SPACE and DELETE ================================================ FILE: vendor/github.com/Azure/go-ansiterm/context.go ================================================ package ansiterm type ansiContext struct { currentChar byte paramBuffer []byte interBuffer []byte } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/csi_entry_state.go ================================================ package ansiterm type csiEntryState struct { baseState } func (csiState csiEntryState) Handle(b byte) (s state, e error) { csiState.parser.logf("CsiEntry::Handle %#x", b) nextState, err := csiState.baseState.Handle(b) if nextState != nil || err != nil { return nextState, err } switch { case sliceContains(alphabetics, b): return csiState.parser.ground, nil case sliceContains(csiCollectables, b): return csiState.parser.csiParam, nil case sliceContains(executors, b): return csiState, csiState.parser.execute() } return csiState, nil } func (csiState csiEntryState) Transition(s state) error { csiState.parser.logf("CsiEntry::Transition %s --> %s", csiState.Name(), s.Name()) csiState.baseState.Transition(s) switch s { case csiState.parser.ground: return csiState.parser.csiDispatch() case csiState.parser.csiParam: switch { case sliceContains(csiParams, csiState.parser.context.currentChar): csiState.parser.collectParam() case sliceContains(intermeds, csiState.parser.context.currentChar): csiState.parser.collectInter() } } return nil } func (csiState csiEntryState) Enter() error { csiState.parser.clear() return nil } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/csi_param_state.go ================================================ package ansiterm type csiParamState struct { baseState } func (csiState csiParamState) Handle(b byte) (s state, e error) { csiState.parser.logf("CsiParam::Handle %#x", b) nextState, err := csiState.baseState.Handle(b) if nextState != nil || err != nil { return nextState, err } switch { case sliceContains(alphabetics, b): return csiState.parser.ground, nil case sliceContains(csiCollectables, b): csiState.parser.collectParam() return csiState, nil case sliceContains(executors, b): return csiState, csiState.parser.execute() } return csiState, nil } func (csiState csiParamState) Transition(s state) error { csiState.parser.logf("CsiParam::Transition %s --> %s", csiState.Name(), s.Name()) csiState.baseState.Transition(s) switch s { case csiState.parser.ground: return csiState.parser.csiDispatch() } return nil } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/escape_intermediate_state.go ================================================ package ansiterm type escapeIntermediateState struct { baseState } func (escState escapeIntermediateState) Handle(b byte) (s state, e error) { escState.parser.logf("escapeIntermediateState::Handle %#x", b) nextState, err := escState.baseState.Handle(b) if nextState != nil || err != nil { return nextState, err } switch { case sliceContains(intermeds, b): return escState, escState.parser.collectInter() case sliceContains(executors, b): return escState, escState.parser.execute() case sliceContains(escapeIntermediateToGroundBytes, b): return escState.parser.ground, nil } return escState, nil } func (escState escapeIntermediateState) Transition(s state) error { escState.parser.logf("escapeIntermediateState::Transition %s --> %s", escState.Name(), s.Name()) escState.baseState.Transition(s) switch s { case escState.parser.ground: return escState.parser.escDispatch() } return nil } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/escape_state.go ================================================ package ansiterm type escapeState struct { baseState } func (escState escapeState) Handle(b byte) (s state, e error) { escState.parser.logf("escapeState::Handle %#x", b) nextState, err := escState.baseState.Handle(b) if nextState != nil || err != nil { return nextState, err } switch { case b == ANSI_ESCAPE_SECONDARY: return escState.parser.csiEntry, nil case b == ANSI_OSC_STRING_ENTRY: return escState.parser.oscString, nil case sliceContains(executors, b): return escState, escState.parser.execute() case sliceContains(escapeToGroundBytes, b): return escState.parser.ground, nil case sliceContains(intermeds, b): return escState.parser.escapeIntermediate, nil } return escState, nil } func (escState escapeState) Transition(s state) error { escState.parser.logf("Escape::Transition %s --> %s", escState.Name(), s.Name()) escState.baseState.Transition(s) switch s { case escState.parser.ground: return escState.parser.escDispatch() case escState.parser.escapeIntermediate: return escState.parser.collectInter() } return nil } func (escState escapeState) Enter() error { escState.parser.clear() return nil } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/event_handler.go ================================================ package ansiterm type AnsiEventHandler interface { // Print Print(b byte) error // Execute C0 commands Execute(b byte) error // CUrsor Up CUU(int) error // CUrsor Down CUD(int) error // CUrsor Forward CUF(int) error // CUrsor Backward CUB(int) error // Cursor to Next Line CNL(int) error // Cursor to Previous Line CPL(int) error // Cursor Horizontal position Absolute CHA(int) error // Vertical line Position Absolute VPA(int) error // CUrsor Position CUP(int, int) error // Horizontal and Vertical Position (depends on PUM) HVP(int, int) error // Text Cursor Enable Mode DECTCEM(bool) error // Origin Mode DECOM(bool) error // 132 Column Mode DECCOLM(bool) error // Erase in Display ED(int) error // Erase in Line EL(int) error // Insert Line IL(int) error // Delete Line DL(int) error // Insert Character ICH(int) error // Delete Character DCH(int) error // Set Graphics Rendition SGR([]int) error // Pan Down SU(int) error // Pan Up SD(int) error // Device Attributes DA([]string) error // Set Top and Bottom Margins DECSTBM(int, int) error // Index IND() error // Reverse Index RI() error // Flush updates from previous commands Flush() error } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/ground_state.go ================================================ package ansiterm type groundState struct { baseState } func (gs groundState) Handle(b byte) (s state, e error) { gs.parser.context.currentChar = b nextState, err := gs.baseState.Handle(b) if nextState != nil || err != nil { return nextState, err } switch { case sliceContains(printables, b): return gs, gs.parser.print() case sliceContains(executors, b): return gs, gs.parser.execute() } return gs, nil } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/osc_string_state.go ================================================ package ansiterm type oscStringState struct { baseState } func (oscState oscStringState) Handle(b byte) (s state, e error) { oscState.parser.logf("OscString::Handle %#x", b) nextState, err := oscState.baseState.Handle(b) if nextState != nil || err != nil { return nextState, err } switch { case isOscStringTerminator(b): return oscState.parser.ground, nil } return oscState, nil } // See below for OSC string terminators for linux // http://man7.org/linux/man-pages/man4/console_codes.4.html func isOscStringTerminator(b byte) bool { if b == ANSI_BEL || b == 0x5C { return true } return false } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/parser.go ================================================ package ansiterm import ( "errors" "log" "os" ) type AnsiParser struct { currState state eventHandler AnsiEventHandler context *ansiContext csiEntry state csiParam state dcsEntry state escape state escapeIntermediate state error state ground state oscString state stateMap []state logf func(string, ...interface{}) } type Option func(*AnsiParser) func WithLogf(f func(string, ...interface{})) Option { return func(ap *AnsiParser) { ap.logf = f } } func CreateParser(initialState string, evtHandler AnsiEventHandler, opts ...Option) *AnsiParser { ap := &AnsiParser{ eventHandler: evtHandler, context: &ansiContext{}, } for _, o := range opts { o(ap) } if isDebugEnv := os.Getenv(LogEnv); isDebugEnv == "1" { logFile, _ := os.Create("ansiParser.log") logger := log.New(logFile, "", log.LstdFlags) if ap.logf != nil { l := ap.logf ap.logf = func(s string, v ...interface{}) { l(s, v...) logger.Printf(s, v...) } } else { ap.logf = logger.Printf } } if ap.logf == nil { ap.logf = func(string, ...interface{}) {} } ap.csiEntry = csiEntryState{baseState{name: "CsiEntry", parser: ap}} ap.csiParam = csiParamState{baseState{name: "CsiParam", parser: ap}} ap.dcsEntry = dcsEntryState{baseState{name: "DcsEntry", parser: ap}} ap.escape = escapeState{baseState{name: "Escape", parser: ap}} ap.escapeIntermediate = escapeIntermediateState{baseState{name: "EscapeIntermediate", parser: ap}} ap.error = errorState{baseState{name: "Error", parser: ap}} ap.ground = groundState{baseState{name: "Ground", parser: ap}} ap.oscString = oscStringState{baseState{name: "OscString", parser: ap}} ap.stateMap = []state{ ap.csiEntry, ap.csiParam, ap.dcsEntry, ap.escape, ap.escapeIntermediate, ap.error, ap.ground, ap.oscString, } ap.currState = getState(initialState, ap.stateMap) ap.logf("CreateParser: parser %p", ap) return ap } func getState(name string, states []state) state { for _, el := range states { if el.Name() == name { return el } } return nil } func (ap *AnsiParser) Parse(bytes []byte) (int, error) { for i, b := range bytes { if err := ap.handle(b); err != nil { return i, err } } return len(bytes), ap.eventHandler.Flush() } func (ap *AnsiParser) handle(b byte) error { ap.context.currentChar = b newState, err := ap.currState.Handle(b) if err != nil { return err } if newState == nil { ap.logf("WARNING: newState is nil") return errors.New("New state of 'nil' is invalid.") } if newState != ap.currState { if err := ap.changeState(newState); err != nil { return err } } return nil } func (ap *AnsiParser) changeState(newState state) error { ap.logf("ChangeState %s --> %s", ap.currState.Name(), newState.Name()) // Exit old state if err := ap.currState.Exit(); err != nil { ap.logf("Exit state '%s' failed with : '%v'", ap.currState.Name(), err) return err } // Perform transition action if err := ap.currState.Transition(newState); err != nil { ap.logf("Transition from '%s' to '%s' failed with: '%v'", ap.currState.Name(), newState.Name, err) return err } // Enter new state if err := newState.Enter(); err != nil { ap.logf("Enter state '%s' failed with: '%v'", newState.Name(), err) return err } ap.currState = newState return nil } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/parser_action_helpers.go ================================================ package ansiterm import ( "strconv" ) func parseParams(bytes []byte) ([]string, error) { paramBuff := make([]byte, 0, 0) params := []string{} for _, v := range bytes { if v == ';' { if len(paramBuff) > 0 { // Completed parameter, append it to the list s := string(paramBuff) params = append(params, s) paramBuff = make([]byte, 0, 0) } } else { paramBuff = append(paramBuff, v) } } // Last parameter may not be terminated with ';' if len(paramBuff) > 0 { s := string(paramBuff) params = append(params, s) } return params, nil } func parseCmd(context ansiContext) (string, error) { return string(context.currentChar), nil } func getInt(params []string, dflt int) int { i := getInts(params, 1, dflt)[0] return i } func getInts(params []string, minCount int, dflt int) []int { ints := []int{} for _, v := range params { i, _ := strconv.Atoi(v) // Zero is mapped to the default value in VT100. if i == 0 { i = dflt } ints = append(ints, i) } if len(ints) < minCount { remaining := minCount - len(ints) for i := 0; i < remaining; i++ { ints = append(ints, dflt) } } return ints } func (ap *AnsiParser) modeDispatch(param string, set bool) error { switch param { case "?3": return ap.eventHandler.DECCOLM(set) case "?6": return ap.eventHandler.DECOM(set) case "?25": return ap.eventHandler.DECTCEM(set) } return nil } func (ap *AnsiParser) hDispatch(params []string) error { if len(params) == 1 { return ap.modeDispatch(params[0], true) } return nil } func (ap *AnsiParser) lDispatch(params []string) error { if len(params) == 1 { return ap.modeDispatch(params[0], false) } return nil } func getEraseParam(params []string) int { param := getInt(params, 0) if param < 0 || 3 < param { param = 0 } return param } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/parser_actions.go ================================================ package ansiterm func (ap *AnsiParser) collectParam() error { currChar := ap.context.currentChar ap.logf("collectParam %#x", currChar) ap.context.paramBuffer = append(ap.context.paramBuffer, currChar) return nil } func (ap *AnsiParser) collectInter() error { currChar := ap.context.currentChar ap.logf("collectInter %#x", currChar) ap.context.paramBuffer = append(ap.context.interBuffer, currChar) return nil } func (ap *AnsiParser) escDispatch() error { cmd, _ := parseCmd(*ap.context) intermeds := ap.context.interBuffer ap.logf("escDispatch currentChar: %#x", ap.context.currentChar) ap.logf("escDispatch: %v(%v)", cmd, intermeds) switch cmd { case "D": // IND return ap.eventHandler.IND() case "E": // NEL, equivalent to CRLF err := ap.eventHandler.Execute(ANSI_CARRIAGE_RETURN) if err == nil { err = ap.eventHandler.Execute(ANSI_LINE_FEED) } return err case "M": // RI return ap.eventHandler.RI() } return nil } func (ap *AnsiParser) csiDispatch() error { cmd, _ := parseCmd(*ap.context) params, _ := parseParams(ap.context.paramBuffer) ap.logf("Parsed params: %v with length: %d", params, len(params)) ap.logf("csiDispatch: %v(%v)", cmd, params) switch cmd { case "@": return ap.eventHandler.ICH(getInt(params, 1)) case "A": return ap.eventHandler.CUU(getInt(params, 1)) case "B": return ap.eventHandler.CUD(getInt(params, 1)) case "C": return ap.eventHandler.CUF(getInt(params, 1)) case "D": return ap.eventHandler.CUB(getInt(params, 1)) case "E": return ap.eventHandler.CNL(getInt(params, 1)) case "F": return ap.eventHandler.CPL(getInt(params, 1)) case "G": return ap.eventHandler.CHA(getInt(params, 1)) case "H": ints := getInts(params, 2, 1) x, y := ints[0], ints[1] return ap.eventHandler.CUP(x, y) case "J": param := getEraseParam(params) return ap.eventHandler.ED(param) case "K": param := getEraseParam(params) return ap.eventHandler.EL(param) case "L": return ap.eventHandler.IL(getInt(params, 1)) case "M": return ap.eventHandler.DL(getInt(params, 1)) case "P": return ap.eventHandler.DCH(getInt(params, 1)) case "S": return ap.eventHandler.SU(getInt(params, 1)) case "T": return ap.eventHandler.SD(getInt(params, 1)) case "c": return ap.eventHandler.DA(params) case "d": return ap.eventHandler.VPA(getInt(params, 1)) case "f": ints := getInts(params, 2, 1) x, y := ints[0], ints[1] return ap.eventHandler.HVP(x, y) case "h": return ap.hDispatch(params) case "l": return ap.lDispatch(params) case "m": return ap.eventHandler.SGR(getInts(params, 1, 0)) case "r": ints := getInts(params, 2, 1) top, bottom := ints[0], ints[1] return ap.eventHandler.DECSTBM(top, bottom) default: ap.logf("ERROR: Unsupported CSI command: '%s', with full context: %v", cmd, ap.context) return nil } } func (ap *AnsiParser) print() error { return ap.eventHandler.Print(ap.context.currentChar) } func (ap *AnsiParser) clear() error { ap.context = &ansiContext{} return nil } func (ap *AnsiParser) execute() error { return ap.eventHandler.Execute(ap.context.currentChar) } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/states.go ================================================ package ansiterm type stateID int type state interface { Enter() error Exit() error Handle(byte) (state, error) Name() string Transition(state) error } type baseState struct { name string parser *AnsiParser } func (base baseState) Enter() error { return nil } func (base baseState) Exit() error { return nil } func (base baseState) Handle(b byte) (s state, e error) { switch { case b == CSI_ENTRY: return base.parser.csiEntry, nil case b == DCS_ENTRY: return base.parser.dcsEntry, nil case b == ANSI_ESCAPE_PRIMARY: return base.parser.escape, nil case b == OSC_STRING: return base.parser.oscString, nil case sliceContains(toGroundBytes, b): return base.parser.ground, nil } return nil, nil } func (base baseState) Name() string { return base.name } func (base baseState) Transition(s state) error { if s == base.parser.ground { execBytes := []byte{0x18} execBytes = append(execBytes, 0x1A) execBytes = append(execBytes, getByteRange(0x80, 0x8F)...) execBytes = append(execBytes, getByteRange(0x91, 0x97)...) execBytes = append(execBytes, 0x99) execBytes = append(execBytes, 0x9A) if sliceContains(execBytes, base.parser.context.currentChar) { return base.parser.execute() } } return nil } type dcsEntryState struct { baseState } type errorState struct { baseState } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/utilities.go ================================================ package ansiterm import ( "strconv" ) func sliceContains(bytes []byte, b byte) bool { for _, v := range bytes { if v == b { return true } } return false } func convertBytesToInteger(bytes []byte) int { s := string(bytes) i, _ := strconv.Atoi(s) return i } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/winterm/ansi.go ================================================ // +build windows package winterm import ( "fmt" "os" "strconv" "strings" "syscall" "github.com/Azure/go-ansiterm" windows "golang.org/x/sys/windows" ) // Windows keyboard constants // See https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx. const ( VK_PRIOR = 0x21 // PAGE UP key VK_NEXT = 0x22 // PAGE DOWN key VK_END = 0x23 // END key VK_HOME = 0x24 // HOME key VK_LEFT = 0x25 // LEFT ARROW key VK_UP = 0x26 // UP ARROW key VK_RIGHT = 0x27 // RIGHT ARROW key VK_DOWN = 0x28 // DOWN ARROW key VK_SELECT = 0x29 // SELECT key VK_PRINT = 0x2A // PRINT key VK_EXECUTE = 0x2B // EXECUTE key VK_SNAPSHOT = 0x2C // PRINT SCREEN key VK_INSERT = 0x2D // INS key VK_DELETE = 0x2E // DEL key VK_HELP = 0x2F // HELP key VK_F1 = 0x70 // F1 key VK_F2 = 0x71 // F2 key VK_F3 = 0x72 // F3 key VK_F4 = 0x73 // F4 key VK_F5 = 0x74 // F5 key VK_F6 = 0x75 // F6 key VK_F7 = 0x76 // F7 key VK_F8 = 0x77 // F8 key VK_F9 = 0x78 // F9 key VK_F10 = 0x79 // F10 key VK_F11 = 0x7A // F11 key VK_F12 = 0x7B // F12 key RIGHT_ALT_PRESSED = 0x0001 LEFT_ALT_PRESSED = 0x0002 RIGHT_CTRL_PRESSED = 0x0004 LEFT_CTRL_PRESSED = 0x0008 SHIFT_PRESSED = 0x0010 NUMLOCK_ON = 0x0020 SCROLLLOCK_ON = 0x0040 CAPSLOCK_ON = 0x0080 ENHANCED_KEY = 0x0100 ) type ansiCommand struct { CommandBytes []byte Command string Parameters []string IsSpecial bool } func newAnsiCommand(command []byte) *ansiCommand { if isCharacterSelectionCmdChar(command[1]) { // Is Character Set Selection commands return &ansiCommand{ CommandBytes: command, Command: string(command), IsSpecial: true, } } // last char is command character lastCharIndex := len(command) - 1 ac := &ansiCommand{ CommandBytes: command, Command: string(command[lastCharIndex]), IsSpecial: false, } // more than a single escape if lastCharIndex != 0 { start := 1 // skip if double char escape sequence if command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_ESCAPE_SECONDARY { start++ } // convert this to GetNextParam method ac.Parameters = strings.Split(string(command[start:lastCharIndex]), ansiterm.ANSI_PARAMETER_SEP) } return ac } func (ac *ansiCommand) paramAsSHORT(index int, defaultValue int16) int16 { if index < 0 || index >= len(ac.Parameters) { return defaultValue } param, err := strconv.ParseInt(ac.Parameters[index], 10, 16) if err != nil { return defaultValue } return int16(param) } func (ac *ansiCommand) String() string { return fmt.Sprintf("0x%v \"%v\" (\"%v\")", bytesToHex(ac.CommandBytes), ac.Command, strings.Join(ac.Parameters, "\",\"")) } // isAnsiCommandChar returns true if the passed byte falls within the range of ANSI commands. // See http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html. func isAnsiCommandChar(b byte) bool { switch { case ansiterm.ANSI_COMMAND_FIRST <= b && b <= ansiterm.ANSI_COMMAND_LAST && b != ansiterm.ANSI_ESCAPE_SECONDARY: return true case b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_OSC || b == ansiterm.ANSI_CMD_DECPAM || b == ansiterm.ANSI_CMD_DECPNM: // non-CSI escape sequence terminator return true case b == ansiterm.ANSI_CMD_STR_TERM || b == ansiterm.ANSI_BEL: // String escape sequence terminator return true } return false } func isXtermOscSequence(command []byte, current byte) bool { return (len(command) >= 2 && command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_CMD_OSC && current != ansiterm.ANSI_BEL) } func isCharacterSelectionCmdChar(b byte) bool { return (b == ansiterm.ANSI_CMD_G0 || b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_G2 || b == ansiterm.ANSI_CMD_G3) } // bytesToHex converts a slice of bytes to a human-readable string. func bytesToHex(b []byte) string { hex := make([]string, len(b)) for i, ch := range b { hex[i] = fmt.Sprintf("%X", ch) } return strings.Join(hex, "") } // ensureInRange adjusts the passed value, if necessary, to ensure it is within // the passed min / max range. func ensureInRange(n int16, min int16, max int16) int16 { if n < min { return min } else if n > max { return max } else { return n } } func GetStdFile(nFile int) (*os.File, uintptr) { var file *os.File // syscall uses negative numbers // windows package uses very big uint32 // Keep these switches split so we don't have to convert ints too much. switch uint32(nFile) { case windows.STD_INPUT_HANDLE: file = os.Stdin case windows.STD_OUTPUT_HANDLE: file = os.Stdout case windows.STD_ERROR_HANDLE: file = os.Stderr default: switch nFile { case syscall.STD_INPUT_HANDLE: file = os.Stdin case syscall.STD_OUTPUT_HANDLE: file = os.Stdout case syscall.STD_ERROR_HANDLE: file = os.Stderr default: panic(fmt.Errorf("Invalid standard handle identifier: %v", nFile)) } } fd, err := syscall.GetStdHandle(nFile) if err != nil { panic(fmt.Errorf("Invalid standard handle identifier: %v -- %v", nFile, err)) } return file, uintptr(fd) } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/winterm/api.go ================================================ // +build windows package winterm import ( "fmt" "syscall" "unsafe" ) //=========================================================================================================== // IMPORTANT NOTE: // // The methods below make extensive use of the "unsafe" package to obtain the required pointers. // Beginning in Go 1.3, the garbage collector may release local variables (e.g., incoming arguments, stack // variables) the pointers reference *before* the API completes. // // As a result, in those cases, the code must hint that the variables remain in active by invoking the // dummy method "use" (see below). Newer versions of Go are planned to change the mechanism to no longer // require unsafe pointers. // // If you add or modify methods, ENSURE protection of local variables through the "use" builtin to inform // the garbage collector the variables remain in use if: // // -- The value is not a pointer (e.g., int32, struct) // -- The value is not referenced by the method after passing the pointer to Windows // // See http://golang.org/doc/go1.3. //=========================================================================================================== var ( kernel32DLL = syscall.NewLazyDLL("kernel32.dll") getConsoleCursorInfoProc = kernel32DLL.NewProc("GetConsoleCursorInfo") setConsoleCursorInfoProc = kernel32DLL.NewProc("SetConsoleCursorInfo") setConsoleCursorPositionProc = kernel32DLL.NewProc("SetConsoleCursorPosition") setConsoleModeProc = kernel32DLL.NewProc("SetConsoleMode") getConsoleScreenBufferInfoProc = kernel32DLL.NewProc("GetConsoleScreenBufferInfo") setConsoleScreenBufferSizeProc = kernel32DLL.NewProc("SetConsoleScreenBufferSize") scrollConsoleScreenBufferProc = kernel32DLL.NewProc("ScrollConsoleScreenBufferA") setConsoleTextAttributeProc = kernel32DLL.NewProc("SetConsoleTextAttribute") setConsoleWindowInfoProc = kernel32DLL.NewProc("SetConsoleWindowInfo") writeConsoleOutputProc = kernel32DLL.NewProc("WriteConsoleOutputW") readConsoleInputProc = kernel32DLL.NewProc("ReadConsoleInputW") waitForSingleObjectProc = kernel32DLL.NewProc("WaitForSingleObject") ) // Windows Console constants const ( // Console modes // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx. ENABLE_PROCESSED_INPUT = 0x0001 ENABLE_LINE_INPUT = 0x0002 ENABLE_ECHO_INPUT = 0x0004 ENABLE_WINDOW_INPUT = 0x0008 ENABLE_MOUSE_INPUT = 0x0010 ENABLE_INSERT_MODE = 0x0020 ENABLE_QUICK_EDIT_MODE = 0x0040 ENABLE_EXTENDED_FLAGS = 0x0080 ENABLE_AUTO_POSITION = 0x0100 ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 ENABLE_PROCESSED_OUTPUT = 0x0001 ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 DISABLE_NEWLINE_AUTO_RETURN = 0x0008 ENABLE_LVB_GRID_WORLDWIDE = 0x0010 // Character attributes // Note: // -- The attributes are combined to produce various colors (e.g., Blue + Green will create Cyan). // Clearing all foreground or background colors results in black; setting all creates white. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes. FOREGROUND_BLUE uint16 = 0x0001 FOREGROUND_GREEN uint16 = 0x0002 FOREGROUND_RED uint16 = 0x0004 FOREGROUND_INTENSITY uint16 = 0x0008 FOREGROUND_MASK uint16 = 0x000F BACKGROUND_BLUE uint16 = 0x0010 BACKGROUND_GREEN uint16 = 0x0020 BACKGROUND_RED uint16 = 0x0040 BACKGROUND_INTENSITY uint16 = 0x0080 BACKGROUND_MASK uint16 = 0x00F0 COMMON_LVB_MASK uint16 = 0xFF00 COMMON_LVB_REVERSE_VIDEO uint16 = 0x4000 COMMON_LVB_UNDERSCORE uint16 = 0x8000 // Input event types // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx. KEY_EVENT = 0x0001 MOUSE_EVENT = 0x0002 WINDOW_BUFFER_SIZE_EVENT = 0x0004 MENU_EVENT = 0x0008 FOCUS_EVENT = 0x0010 // WaitForSingleObject return codes WAIT_ABANDONED = 0x00000080 WAIT_FAILED = 0xFFFFFFFF WAIT_SIGNALED = 0x0000000 WAIT_TIMEOUT = 0x00000102 // WaitForSingleObject wait duration WAIT_INFINITE = 0xFFFFFFFF WAIT_ONE_SECOND = 1000 WAIT_HALF_SECOND = 500 WAIT_QUARTER_SECOND = 250 ) // Windows API Console types // -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682101(v=vs.85).aspx for Console specific types (e.g., COORD) // -- See https://msdn.microsoft.com/en-us/library/aa296569(v=vs.60).aspx for comments on alignment type ( CHAR_INFO struct { UnicodeChar uint16 Attributes uint16 } CONSOLE_CURSOR_INFO struct { Size uint32 Visible int32 } CONSOLE_SCREEN_BUFFER_INFO struct { Size COORD CursorPosition COORD Attributes uint16 Window SMALL_RECT MaximumWindowSize COORD } COORD struct { X int16 Y int16 } SMALL_RECT struct { Left int16 Top int16 Right int16 Bottom int16 } // INPUT_RECORD is a C/C++ union of which KEY_EVENT_RECORD is one case, it is also the largest // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx. INPUT_RECORD struct { EventType uint16 KeyEvent KEY_EVENT_RECORD } KEY_EVENT_RECORD struct { KeyDown int32 RepeatCount uint16 VirtualKeyCode uint16 VirtualScanCode uint16 UnicodeChar uint16 ControlKeyState uint32 } WINDOW_BUFFER_SIZE struct { Size COORD } ) // boolToBOOL converts a Go bool into a Windows int32. func boolToBOOL(f bool) int32 { if f { return int32(1) } else { return int32(0) } } // GetConsoleCursorInfo retrieves information about the size and visiblity of the console cursor. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683163(v=vs.85).aspx. func GetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error { r1, r2, err := getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0) return checkError(r1, r2, err) } // SetConsoleCursorInfo sets the size and visiblity of the console cursor. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686019(v=vs.85).aspx. func SetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error { r1, r2, err := setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0) return checkError(r1, r2, err) } // SetConsoleCursorPosition location of the console cursor. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx. func SetConsoleCursorPosition(handle uintptr, coord COORD) error { r1, r2, err := setConsoleCursorPositionProc.Call(handle, coordToPointer(coord)) use(coord) return checkError(r1, r2, err) } // GetConsoleMode gets the console mode for given file descriptor // See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx. func GetConsoleMode(handle uintptr) (mode uint32, err error) { err = syscall.GetConsoleMode(syscall.Handle(handle), &mode) return mode, err } // SetConsoleMode sets the console mode for given file descriptor // See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx. func SetConsoleMode(handle uintptr, mode uint32) error { r1, r2, err := setConsoleModeProc.Call(handle, uintptr(mode), 0) use(mode) return checkError(r1, r2, err) } // GetConsoleScreenBufferInfo retrieves information about the specified console screen buffer. // See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx. func GetConsoleScreenBufferInfo(handle uintptr) (*CONSOLE_SCREEN_BUFFER_INFO, error) { info := CONSOLE_SCREEN_BUFFER_INFO{} err := checkError(getConsoleScreenBufferInfoProc.Call(handle, uintptr(unsafe.Pointer(&info)), 0)) if err != nil { return nil, err } return &info, nil } func ScrollConsoleScreenBuffer(handle uintptr, scrollRect SMALL_RECT, clipRect SMALL_RECT, destOrigin COORD, char CHAR_INFO) error { r1, r2, err := scrollConsoleScreenBufferProc.Call(handle, uintptr(unsafe.Pointer(&scrollRect)), uintptr(unsafe.Pointer(&clipRect)), coordToPointer(destOrigin), uintptr(unsafe.Pointer(&char))) use(scrollRect) use(clipRect) use(destOrigin) use(char) return checkError(r1, r2, err) } // SetConsoleScreenBufferSize sets the size of the console screen buffer. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686044(v=vs.85).aspx. func SetConsoleScreenBufferSize(handle uintptr, coord COORD) error { r1, r2, err := setConsoleScreenBufferSizeProc.Call(handle, coordToPointer(coord)) use(coord) return checkError(r1, r2, err) } // SetConsoleTextAttribute sets the attributes of characters written to the // console screen buffer by the WriteFile or WriteConsole function. // See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx. func SetConsoleTextAttribute(handle uintptr, attribute uint16) error { r1, r2, err := setConsoleTextAttributeProc.Call(handle, uintptr(attribute), 0) use(attribute) return checkError(r1, r2, err) } // SetConsoleWindowInfo sets the size and position of the console screen buffer's window. // Note that the size and location must be within and no larger than the backing console screen buffer. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686125(v=vs.85).aspx. func SetConsoleWindowInfo(handle uintptr, isAbsolute bool, rect SMALL_RECT) error { r1, r2, err := setConsoleWindowInfoProc.Call(handle, uintptr(boolToBOOL(isAbsolute)), uintptr(unsafe.Pointer(&rect))) use(isAbsolute) use(rect) return checkError(r1, r2, err) } // WriteConsoleOutput writes the CHAR_INFOs from the provided buffer to the active console buffer. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687404(v=vs.85).aspx. func WriteConsoleOutput(handle uintptr, buffer []CHAR_INFO, bufferSize COORD, bufferCoord COORD, writeRegion *SMALL_RECT) error { r1, r2, err := writeConsoleOutputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), coordToPointer(bufferSize), coordToPointer(bufferCoord), uintptr(unsafe.Pointer(writeRegion))) use(buffer) use(bufferSize) use(bufferCoord) return checkError(r1, r2, err) } // ReadConsoleInput reads (and removes) data from the console input buffer. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx. func ReadConsoleInput(handle uintptr, buffer []INPUT_RECORD, count *uint32) error { r1, r2, err := readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), uintptr(len(buffer)), uintptr(unsafe.Pointer(count))) use(buffer) return checkError(r1, r2, err) } // WaitForSingleObject waits for the passed handle to be signaled. // It returns true if the handle was signaled; false otherwise. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx. func WaitForSingleObject(handle uintptr, msWait uint32) (bool, error) { r1, _, err := waitForSingleObjectProc.Call(handle, uintptr(uint32(msWait))) switch r1 { case WAIT_ABANDONED, WAIT_TIMEOUT: return false, nil case WAIT_SIGNALED: return true, nil } use(msWait) return false, err } // String helpers func (info CONSOLE_SCREEN_BUFFER_INFO) String() string { return fmt.Sprintf("Size(%v) Cursor(%v) Window(%v) Max(%v)", info.Size, info.CursorPosition, info.Window, info.MaximumWindowSize) } func (coord COORD) String() string { return fmt.Sprintf("%v,%v", coord.X, coord.Y) } func (rect SMALL_RECT) String() string { return fmt.Sprintf("(%v,%v),(%v,%v)", rect.Left, rect.Top, rect.Right, rect.Bottom) } // checkError evaluates the results of a Windows API call and returns the error if it failed. func checkError(r1, r2 uintptr, err error) error { // Windows APIs return non-zero to indicate success if r1 != 0 { return nil } // Return the error if provided, otherwise default to EINVAL if err != nil { return err } return syscall.EINVAL } // coordToPointer converts a COORD into a uintptr (by fooling the type system). func coordToPointer(c COORD) uintptr { // Note: This code assumes the two SHORTs are correctly laid out; the "cast" to uint32 is just to get a pointer to pass. return uintptr(*((*uint32)(unsafe.Pointer(&c)))) } // use is a no-op, but the compiler cannot see that it is. // Calling use(p) ensures that p is kept live until that point. func use(p interface{}) {} ================================================ FILE: vendor/github.com/Azure/go-ansiterm/winterm/attr_translation.go ================================================ // +build windows package winterm import "github.com/Azure/go-ansiterm" const ( FOREGROUND_COLOR_MASK = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE BACKGROUND_COLOR_MASK = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE ) // collectAnsiIntoWindowsAttributes modifies the passed Windows text mode flags to reflect the // request represented by the passed ANSI mode. func collectAnsiIntoWindowsAttributes(windowsMode uint16, inverted bool, baseMode uint16, ansiMode int16) (uint16, bool) { switch ansiMode { // Mode styles case ansiterm.ANSI_SGR_BOLD: windowsMode = windowsMode | FOREGROUND_INTENSITY case ansiterm.ANSI_SGR_DIM, ansiterm.ANSI_SGR_BOLD_DIM_OFF: windowsMode &^= FOREGROUND_INTENSITY case ansiterm.ANSI_SGR_UNDERLINE: windowsMode = windowsMode | COMMON_LVB_UNDERSCORE case ansiterm.ANSI_SGR_REVERSE: inverted = true case ansiterm.ANSI_SGR_REVERSE_OFF: inverted = false case ansiterm.ANSI_SGR_UNDERLINE_OFF: windowsMode &^= COMMON_LVB_UNDERSCORE // Foreground colors case ansiterm.ANSI_SGR_FOREGROUND_DEFAULT: windowsMode = (windowsMode &^ FOREGROUND_MASK) | (baseMode & FOREGROUND_MASK) case ansiterm.ANSI_SGR_FOREGROUND_BLACK: windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) case ansiterm.ANSI_SGR_FOREGROUND_RED: windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED case ansiterm.ANSI_SGR_FOREGROUND_GREEN: windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN case ansiterm.ANSI_SGR_FOREGROUND_YELLOW: windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN case ansiterm.ANSI_SGR_FOREGROUND_BLUE: windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_BLUE case ansiterm.ANSI_SGR_FOREGROUND_MAGENTA: windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_BLUE case ansiterm.ANSI_SGR_FOREGROUND_CYAN: windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN | FOREGROUND_BLUE case ansiterm.ANSI_SGR_FOREGROUND_WHITE: windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE // Background colors case ansiterm.ANSI_SGR_BACKGROUND_DEFAULT: // Black with no intensity windowsMode = (windowsMode &^ BACKGROUND_MASK) | (baseMode & BACKGROUND_MASK) case ansiterm.ANSI_SGR_BACKGROUND_BLACK: windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) case ansiterm.ANSI_SGR_BACKGROUND_RED: windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED case ansiterm.ANSI_SGR_BACKGROUND_GREEN: windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN case ansiterm.ANSI_SGR_BACKGROUND_YELLOW: windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN case ansiterm.ANSI_SGR_BACKGROUND_BLUE: windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_BLUE case ansiterm.ANSI_SGR_BACKGROUND_MAGENTA: windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_BLUE case ansiterm.ANSI_SGR_BACKGROUND_CYAN: windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN | BACKGROUND_BLUE case ansiterm.ANSI_SGR_BACKGROUND_WHITE: windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE } return windowsMode, inverted } // invertAttributes inverts the foreground and background colors of a Windows attributes value func invertAttributes(windowsMode uint16) uint16 { return (COMMON_LVB_MASK & windowsMode) | ((FOREGROUND_MASK & windowsMode) << 4) | ((BACKGROUND_MASK & windowsMode) >> 4) } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go ================================================ // +build windows package winterm const ( horizontal = iota vertical ) func (h *windowsAnsiEventHandler) getCursorWindow(info *CONSOLE_SCREEN_BUFFER_INFO) SMALL_RECT { if h.originMode { sr := h.effectiveSr(info.Window) return SMALL_RECT{ Top: sr.top, Bottom: sr.bottom, Left: 0, Right: info.Size.X - 1, } } else { return SMALL_RECT{ Top: info.Window.Top, Bottom: info.Window.Bottom, Left: 0, Right: info.Size.X - 1, } } } // setCursorPosition sets the cursor to the specified position, bounded to the screen size func (h *windowsAnsiEventHandler) setCursorPosition(position COORD, window SMALL_RECT) error { position.X = ensureInRange(position.X, window.Left, window.Right) position.Y = ensureInRange(position.Y, window.Top, window.Bottom) err := SetConsoleCursorPosition(h.fd, position) if err != nil { return err } h.logf("Cursor position set: (%d, %d)", position.X, position.Y) return err } func (h *windowsAnsiEventHandler) moveCursorVertical(param int) error { return h.moveCursor(vertical, param) } func (h *windowsAnsiEventHandler) moveCursorHorizontal(param int) error { return h.moveCursor(horizontal, param) } func (h *windowsAnsiEventHandler) moveCursor(moveMode int, param int) error { info, err := GetConsoleScreenBufferInfo(h.fd) if err != nil { return err } position := info.CursorPosition switch moveMode { case horizontal: position.X += int16(param) case vertical: position.Y += int16(param) } if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil { return err } return nil } func (h *windowsAnsiEventHandler) moveCursorLine(param int) error { info, err := GetConsoleScreenBufferInfo(h.fd) if err != nil { return err } position := info.CursorPosition position.X = 0 position.Y += int16(param) if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil { return err } return nil } func (h *windowsAnsiEventHandler) moveCursorColumn(param int) error { info, err := GetConsoleScreenBufferInfo(h.fd) if err != nil { return err } position := info.CursorPosition position.X = int16(param) - 1 if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil { return err } return nil } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/winterm/erase_helpers.go ================================================ // +build windows package winterm import "github.com/Azure/go-ansiterm" func (h *windowsAnsiEventHandler) clearRange(attributes uint16, fromCoord COORD, toCoord COORD) error { // Ignore an invalid (negative area) request if toCoord.Y < fromCoord.Y { return nil } var err error var coordStart = COORD{} var coordEnd = COORD{} xCurrent, yCurrent := fromCoord.X, fromCoord.Y xEnd, yEnd := toCoord.X, toCoord.Y // Clear any partial initial line if xCurrent > 0 { coordStart.X, coordStart.Y = xCurrent, yCurrent coordEnd.X, coordEnd.Y = xEnd, yCurrent err = h.clearRect(attributes, coordStart, coordEnd) if err != nil { return err } xCurrent = 0 yCurrent += 1 } // Clear intervening rectangular section if yCurrent < yEnd { coordStart.X, coordStart.Y = xCurrent, yCurrent coordEnd.X, coordEnd.Y = xEnd, yEnd-1 err = h.clearRect(attributes, coordStart, coordEnd) if err != nil { return err } xCurrent = 0 yCurrent = yEnd } // Clear remaining partial ending line coordStart.X, coordStart.Y = xCurrent, yCurrent coordEnd.X, coordEnd.Y = xEnd, yEnd err = h.clearRect(attributes, coordStart, coordEnd) if err != nil { return err } return nil } func (h *windowsAnsiEventHandler) clearRect(attributes uint16, fromCoord COORD, toCoord COORD) error { region := SMALL_RECT{Top: fromCoord.Y, Left: fromCoord.X, Bottom: toCoord.Y, Right: toCoord.X} width := toCoord.X - fromCoord.X + 1 height := toCoord.Y - fromCoord.Y + 1 size := uint32(width) * uint32(height) if size <= 0 { return nil } buffer := make([]CHAR_INFO, size) char := CHAR_INFO{ansiterm.FILL_CHARACTER, attributes} for i := 0; i < int(size); i++ { buffer[i] = char } err := WriteConsoleOutput(h.fd, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, ®ion) if err != nil { return err } return nil } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/winterm/scroll_helper.go ================================================ // +build windows package winterm // effectiveSr gets the current effective scroll region in buffer coordinates func (h *windowsAnsiEventHandler) effectiveSr(window SMALL_RECT) scrollRegion { top := addInRange(window.Top, h.sr.top, window.Top, window.Bottom) bottom := addInRange(window.Top, h.sr.bottom, window.Top, window.Bottom) if top >= bottom { top = window.Top bottom = window.Bottom } return scrollRegion{top: top, bottom: bottom} } func (h *windowsAnsiEventHandler) scrollUp(param int) error { info, err := GetConsoleScreenBufferInfo(h.fd) if err != nil { return err } sr := h.effectiveSr(info.Window) return h.scroll(param, sr, info) } func (h *windowsAnsiEventHandler) scrollDown(param int) error { return h.scrollUp(-param) } func (h *windowsAnsiEventHandler) deleteLines(param int) error { info, err := GetConsoleScreenBufferInfo(h.fd) if err != nil { return err } start := info.CursorPosition.Y sr := h.effectiveSr(info.Window) // Lines cannot be inserted or deleted outside the scrolling region. if start >= sr.top && start <= sr.bottom { sr.top = start return h.scroll(param, sr, info) } else { return nil } } func (h *windowsAnsiEventHandler) insertLines(param int) error { return h.deleteLines(-param) } // scroll scrolls the provided scroll region by param lines. The scroll region is in buffer coordinates. func (h *windowsAnsiEventHandler) scroll(param int, sr scrollRegion, info *CONSOLE_SCREEN_BUFFER_INFO) error { h.logf("scroll: scrollTop: %d, scrollBottom: %d", sr.top, sr.bottom) h.logf("scroll: windowTop: %d, windowBottom: %d", info.Window.Top, info.Window.Bottom) // Copy from and clip to the scroll region (full buffer width) scrollRect := SMALL_RECT{ Top: sr.top, Bottom: sr.bottom, Left: 0, Right: info.Size.X - 1, } // Origin to which area should be copied destOrigin := COORD{ X: 0, Y: sr.top - int16(param), } char := CHAR_INFO{ UnicodeChar: ' ', Attributes: h.attributes, } if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil { return err } return nil } func (h *windowsAnsiEventHandler) deleteCharacters(param int) error { info, err := GetConsoleScreenBufferInfo(h.fd) if err != nil { return err } return h.scrollLine(param, info.CursorPosition, info) } func (h *windowsAnsiEventHandler) insertCharacters(param int) error { return h.deleteCharacters(-param) } // scrollLine scrolls a line horizontally starting at the provided position by a number of columns. func (h *windowsAnsiEventHandler) scrollLine(columns int, position COORD, info *CONSOLE_SCREEN_BUFFER_INFO) error { // Copy from and clip to the scroll region (full buffer width) scrollRect := SMALL_RECT{ Top: position.Y, Bottom: position.Y, Left: position.X, Right: info.Size.X - 1, } // Origin to which area should be copied destOrigin := COORD{ X: position.X - int16(columns), Y: position.Y, } char := CHAR_INFO{ UnicodeChar: ' ', Attributes: h.attributes, } if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil { return err } return nil } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/winterm/utilities.go ================================================ // +build windows package winterm // AddInRange increments a value by the passed quantity while ensuring the values // always remain within the supplied min / max range. func addInRange(n int16, increment int16, min int16, max int16) int16 { return ensureInRange(n+increment, min, max) } ================================================ FILE: vendor/github.com/Azure/go-ansiterm/winterm/win_event_handler.go ================================================ // +build windows package winterm import ( "bytes" "log" "os" "strconv" "github.com/Azure/go-ansiterm" ) type windowsAnsiEventHandler struct { fd uintptr file *os.File infoReset *CONSOLE_SCREEN_BUFFER_INFO sr scrollRegion buffer bytes.Buffer attributes uint16 inverted bool wrapNext bool drewMarginByte bool originMode bool marginByte byte curInfo *CONSOLE_SCREEN_BUFFER_INFO curPos COORD logf func(string, ...interface{}) } type Option func(*windowsAnsiEventHandler) func WithLogf(f func(string, ...interface{})) Option { return func(w *windowsAnsiEventHandler) { w.logf = f } } func CreateWinEventHandler(fd uintptr, file *os.File, opts ...Option) ansiterm.AnsiEventHandler { infoReset, err := GetConsoleScreenBufferInfo(fd) if err != nil { return nil } h := &windowsAnsiEventHandler{ fd: fd, file: file, infoReset: infoReset, attributes: infoReset.Attributes, } for _, o := range opts { o(h) } if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" { logFile, _ := os.Create("winEventHandler.log") logger := log.New(logFile, "", log.LstdFlags) if h.logf != nil { l := h.logf h.logf = func(s string, v ...interface{}) { l(s, v...) logger.Printf(s, v...) } } else { h.logf = logger.Printf } } if h.logf == nil { h.logf = func(string, ...interface{}) {} } return h } type scrollRegion struct { top int16 bottom int16 } // simulateLF simulates a LF or CR+LF by scrolling if necessary to handle the // current cursor position and scroll region settings, in which case it returns // true. If no special handling is necessary, then it does nothing and returns // false. // // In the false case, the caller should ensure that a carriage return // and line feed are inserted or that the text is otherwise wrapped. func (h *windowsAnsiEventHandler) simulateLF(includeCR bool) (bool, error) { if h.wrapNext { if err := h.Flush(); err != nil { return false, err } h.clearWrap() } pos, info, err := h.getCurrentInfo() if err != nil { return false, err } sr := h.effectiveSr(info.Window) if pos.Y == sr.bottom { // Scrolling is necessary. Let Windows automatically scroll if the scrolling region // is the full window. if sr.top == info.Window.Top && sr.bottom == info.Window.Bottom { if includeCR { pos.X = 0 h.updatePos(pos) } return false, nil } // A custom scroll region is active. Scroll the window manually to simulate // the LF. if err := h.Flush(); err != nil { return false, err } h.logf("Simulating LF inside scroll region") if err := h.scrollUp(1); err != nil { return false, err } if includeCR { pos.X = 0 if err := SetConsoleCursorPosition(h.fd, pos); err != nil { return false, err } } return true, nil } else if pos.Y < info.Window.Bottom { // Let Windows handle the LF. pos.Y++ if includeCR { pos.X = 0 } h.updatePos(pos) return false, nil } else { // The cursor is at the bottom of the screen but outside the scroll // region. Skip the LF. h.logf("Simulating LF outside scroll region") if includeCR { if err := h.Flush(); err != nil { return false, err } pos.X = 0 if err := SetConsoleCursorPosition(h.fd, pos); err != nil { return false, err } } return true, nil } } // executeLF executes a LF without a CR. func (h *windowsAnsiEventHandler) executeLF() error { handled, err := h.simulateLF(false) if err != nil { return err } if !handled { // Windows LF will reset the cursor column position. Write the LF // and restore the cursor position. pos, _, err := h.getCurrentInfo() if err != nil { return err } h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED) if pos.X != 0 { if err := h.Flush(); err != nil { return err } h.logf("Resetting cursor position for LF without CR") if err := SetConsoleCursorPosition(h.fd, pos); err != nil { return err } } } return nil } func (h *windowsAnsiEventHandler) Print(b byte) error { if h.wrapNext { h.buffer.WriteByte(h.marginByte) h.clearWrap() if _, err := h.simulateLF(true); err != nil { return err } } pos, info, err := h.getCurrentInfo() if err != nil { return err } if pos.X == info.Size.X-1 { h.wrapNext = true h.marginByte = b } else { pos.X++ h.updatePos(pos) h.buffer.WriteByte(b) } return nil } func (h *windowsAnsiEventHandler) Execute(b byte) error { switch b { case ansiterm.ANSI_TAB: h.logf("Execute(TAB)") // Move to the next tab stop, but preserve auto-wrap if already set. if !h.wrapNext { pos, info, err := h.getCurrentInfo() if err != nil { return err } pos.X = (pos.X + 8) - pos.X%8 if pos.X >= info.Size.X { pos.X = info.Size.X - 1 } if err := h.Flush(); err != nil { return err } if err := SetConsoleCursorPosition(h.fd, pos); err != nil { return err } } return nil case ansiterm.ANSI_BEL: h.buffer.WriteByte(ansiterm.ANSI_BEL) return nil case ansiterm.ANSI_BACKSPACE: if h.wrapNext { if err := h.Flush(); err != nil { return err } h.clearWrap() } pos, _, err := h.getCurrentInfo() if err != nil { return err } if pos.X > 0 { pos.X-- h.updatePos(pos) h.buffer.WriteByte(ansiterm.ANSI_BACKSPACE) } return nil case ansiterm.ANSI_VERTICAL_TAB, ansiterm.ANSI_FORM_FEED: // Treat as true LF. return h.executeLF() case ansiterm.ANSI_LINE_FEED: // Simulate a CR and LF for now since there is no way in go-ansiterm // to tell if the LF should include CR (and more things break when it's // missing than when it's incorrectly added). handled, err := h.simulateLF(true) if handled || err != nil { return err } return h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED) case ansiterm.ANSI_CARRIAGE_RETURN: if h.wrapNext { if err := h.Flush(); err != nil { return err } h.clearWrap() } pos, _, err := h.getCurrentInfo() if err != nil { return err } if pos.X != 0 { pos.X = 0 h.updatePos(pos) h.buffer.WriteByte(ansiterm.ANSI_CARRIAGE_RETURN) } return nil default: return nil } } func (h *windowsAnsiEventHandler) CUU(param int) error { if err := h.Flush(); err != nil { return err } h.logf("CUU: [%v]", []string{strconv.Itoa(param)}) h.clearWrap() return h.moveCursorVertical(-param) } func (h *windowsAnsiEventHandler) CUD(param int) error { if err := h.Flush(); err != nil { return err } h.logf("CUD: [%v]", []string{strconv.Itoa(param)}) h.clearWrap() return h.moveCursorVertical(param) } func (h *windowsAnsiEventHandler) CUF(param int) error { if err := h.Flush(); err != nil { return err } h.logf("CUF: [%v]", []string{strconv.Itoa(param)}) h.clearWrap() return h.moveCursorHorizontal(param) } func (h *windowsAnsiEventHandler) CUB(param int) error { if err := h.Flush(); err != nil { return err } h.logf("CUB: [%v]", []string{strconv.Itoa(param)}) h.clearWrap() return h.moveCursorHorizontal(-param) } func (h *windowsAnsiEventHandler) CNL(param int) error { if err := h.Flush(); err != nil { return err } h.logf("CNL: [%v]", []string{strconv.Itoa(param)}) h.clearWrap() return h.moveCursorLine(param) } func (h *windowsAnsiEventHandler) CPL(param int) error { if err := h.Flush(); err != nil { return err } h.logf("CPL: [%v]", []string{strconv.Itoa(param)}) h.clearWrap() return h.moveCursorLine(-param) } func (h *windowsAnsiEventHandler) CHA(param int) error { if err := h.Flush(); err != nil { return err } h.logf("CHA: [%v]", []string{strconv.Itoa(param)}) h.clearWrap() return h.moveCursorColumn(param) } func (h *windowsAnsiEventHandler) VPA(param int) error { if err := h.Flush(); err != nil { return err } h.logf("VPA: [[%d]]", param) h.clearWrap() info, err := GetConsoleScreenBufferInfo(h.fd) if err != nil { return err } window := h.getCursorWindow(info) position := info.CursorPosition position.Y = window.Top + int16(param) - 1 return h.setCursorPosition(position, window) } func (h *windowsAnsiEventHandler) CUP(row int, col int) error { if err := h.Flush(); err != nil { return err } h.logf("CUP: [[%d %d]]", row, col) h.clearWrap() info, err := GetConsoleScreenBufferInfo(h.fd) if err != nil { return err } window := h.getCursorWindow(info) position := COORD{window.Left + int16(col) - 1, window.Top + int16(row) - 1} return h.setCursorPosition(position, window) } func (h *windowsAnsiEventHandler) HVP(row int, col int) error { if err := h.Flush(); err != nil { return err } h.logf("HVP: [[%d %d]]", row, col) h.clearWrap() return h.CUP(row, col) } func (h *windowsAnsiEventHandler) DECTCEM(visible bool) error { if err := h.Flush(); err != nil { return err } h.logf("DECTCEM: [%v]", []string{strconv.FormatBool(visible)}) h.clearWrap() return nil } func (h *windowsAnsiEventHandler) DECOM(enable bool) error { if err := h.Flush(); err != nil { return err } h.logf("DECOM: [%v]", []string{strconv.FormatBool(enable)}) h.clearWrap() h.originMode = enable return h.CUP(1, 1) } func (h *windowsAnsiEventHandler) DECCOLM(use132 bool) error { if err := h.Flush(); err != nil { return err } h.logf("DECCOLM: [%v]", []string{strconv.FormatBool(use132)}) h.clearWrap() if err := h.ED(2); err != nil { return err } info, err := GetConsoleScreenBufferInfo(h.fd) if err != nil { return err } targetWidth := int16(80) if use132 { targetWidth = 132 } if info.Size.X < targetWidth { if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil { h.logf("set buffer failed: %v", err) return err } } window := info.Window window.Left = 0 window.Right = targetWidth - 1 if err := SetConsoleWindowInfo(h.fd, true, window); err != nil { h.logf("set window failed: %v", err) return err } if info.Size.X > targetWidth { if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil { h.logf("set buffer failed: %v", err) return err } } return SetConsoleCursorPosition(h.fd, COORD{0, 0}) } func (h *windowsAnsiEventHandler) ED(param int) error { if err := h.Flush(); err != nil { return err } h.logf("ED: [%v]", []string{strconv.Itoa(param)}) h.clearWrap() // [J -- Erases from the cursor to the end of the screen, including the cursor position. // [1J -- Erases from the beginning of the screen to the cursor, including the cursor position. // [2J -- Erases the complete display. The cursor does not move. // Notes: // -- Clearing the entire buffer, versus just the Window, works best for Windows Consoles info, err := GetConsoleScreenBufferInfo(h.fd) if err != nil { return err } var start COORD var end COORD switch param { case 0: start = info.CursorPosition end = COORD{info.Size.X - 1, info.Size.Y - 1} case 1: start = COORD{0, 0} end = info.CursorPosition case 2: start = COORD{0, 0} end = COORD{info.Size.X - 1, info.Size.Y - 1} } err = h.clearRange(h.attributes, start, end) if err != nil { return err } // If the whole buffer was cleared, move the window to the top while preserving // the window-relative cursor position. if param == 2 { pos := info.CursorPosition window := info.Window pos.Y -= window.Top window.Bottom -= window.Top window.Top = 0 if err := SetConsoleCursorPosition(h.fd, pos); err != nil { return err } if err := SetConsoleWindowInfo(h.fd, true, window); err != nil { return err } } return nil } func (h *windowsAnsiEventHandler) EL(param int) error { if err := h.Flush(); err != nil { return err } h.logf("EL: [%v]", strconv.Itoa(param)) h.clearWrap() // [K -- Erases from the cursor to the end of the line, including the cursor position. // [1K -- Erases from the beginning of the line to the cursor, including the cursor position. // [2K -- Erases the complete line. info, err := GetConsoleScreenBufferInfo(h.fd) if err != nil { return err } var start COORD var end COORD switch param { case 0: start = info.CursorPosition end = COORD{info.Size.X, info.CursorPosition.Y} case 1: start = COORD{0, info.CursorPosition.Y} end = info.CursorPosition case 2: start = COORD{0, info.CursorPosition.Y} end = COORD{info.Size.X, info.CursorPosition.Y} } err = h.clearRange(h.attributes, start, end) if err != nil { return err } return nil } func (h *windowsAnsiEventHandler) IL(param int) error { if err := h.Flush(); err != nil { return err } h.logf("IL: [%v]", strconv.Itoa(param)) h.clearWrap() return h.insertLines(param) } func (h *windowsAnsiEventHandler) DL(param int) error { if err := h.Flush(); err != nil { return err } h.logf("DL: [%v]", strconv.Itoa(param)) h.clearWrap() return h.deleteLines(param) } func (h *windowsAnsiEventHandler) ICH(param int) error { if err := h.Flush(); err != nil { return err } h.logf("ICH: [%v]", strconv.Itoa(param)) h.clearWrap() return h.insertCharacters(param) } func (h *windowsAnsiEventHandler) DCH(param int) error { if err := h.Flush(); err != nil { return err } h.logf("DCH: [%v]", strconv.Itoa(param)) h.clearWrap() return h.deleteCharacters(param) } func (h *windowsAnsiEventHandler) SGR(params []int) error { if err := h.Flush(); err != nil { return err } strings := []string{} for _, v := range params { strings = append(strings, strconv.Itoa(v)) } h.logf("SGR: [%v]", strings) if len(params) <= 0 { h.attributes = h.infoReset.Attributes h.inverted = false } else { for _, attr := range params { if attr == ansiterm.ANSI_SGR_RESET { h.attributes = h.infoReset.Attributes h.inverted = false continue } h.attributes, h.inverted = collectAnsiIntoWindowsAttributes(h.attributes, h.inverted, h.infoReset.Attributes, int16(attr)) } } attributes := h.attributes if h.inverted { attributes = invertAttributes(attributes) } err := SetConsoleTextAttribute(h.fd, attributes) if err != nil { return err } return nil } func (h *windowsAnsiEventHandler) SU(param int) error { if err := h.Flush(); err != nil { return err } h.logf("SU: [%v]", []string{strconv.Itoa(param)}) h.clearWrap() return h.scrollUp(param) } func (h *windowsAnsiEventHandler) SD(param int) error { if err := h.Flush(); err != nil { return err } h.logf("SD: [%v]", []string{strconv.Itoa(param)}) h.clearWrap() return h.scrollDown(param) } func (h *windowsAnsiEventHandler) DA(params []string) error { h.logf("DA: [%v]", params) // DA cannot be implemented because it must send data on the VT100 input stream, // which is not available to go-ansiterm. return nil } func (h *windowsAnsiEventHandler) DECSTBM(top int, bottom int) error { if err := h.Flush(); err != nil { return err } h.logf("DECSTBM: [%d, %d]", top, bottom) // Windows is 0 indexed, Linux is 1 indexed h.sr.top = int16(top - 1) h.sr.bottom = int16(bottom - 1) // This command also moves the cursor to the origin. h.clearWrap() return h.CUP(1, 1) } func (h *windowsAnsiEventHandler) RI() error { if err := h.Flush(); err != nil { return err } h.logf("RI: []") h.clearWrap() info, err := GetConsoleScreenBufferInfo(h.fd) if err != nil { return err } sr := h.effectiveSr(info.Window) if info.CursorPosition.Y == sr.top { return h.scrollDown(1) } return h.moveCursorVertical(-1) } func (h *windowsAnsiEventHandler) IND() error { h.logf("IND: []") return h.executeLF() } func (h *windowsAnsiEventHandler) Flush() error { h.curInfo = nil if h.buffer.Len() > 0 { h.logf("Flush: [%s]", h.buffer.Bytes()) if _, err := h.buffer.WriteTo(h.file); err != nil { return err } } if h.wrapNext && !h.drewMarginByte { h.logf("Flush: drawing margin byte '%c'", h.marginByte) info, err := GetConsoleScreenBufferInfo(h.fd) if err != nil { return err } charInfo := []CHAR_INFO{{UnicodeChar: uint16(h.marginByte), Attributes: info.Attributes}} size := COORD{1, 1} position := COORD{0, 0} region := SMALL_RECT{Left: info.CursorPosition.X, Top: info.CursorPosition.Y, Right: info.CursorPosition.X, Bottom: info.CursorPosition.Y} if err := WriteConsoleOutput(h.fd, charInfo, size, position, ®ion); err != nil { return err } h.drewMarginByte = true } return nil } // cacheConsoleInfo ensures that the current console screen information has been queried // since the last call to Flush(). It must be called before accessing h.curInfo or h.curPos. func (h *windowsAnsiEventHandler) getCurrentInfo() (COORD, *CONSOLE_SCREEN_BUFFER_INFO, error) { if h.curInfo == nil { info, err := GetConsoleScreenBufferInfo(h.fd) if err != nil { return COORD{}, nil, err } h.curInfo = info h.curPos = info.CursorPosition } return h.curPos, h.curInfo, nil } func (h *windowsAnsiEventHandler) updatePos(pos COORD) { if h.curInfo == nil { panic("failed to call getCurrentInfo before calling updatePos") } h.curPos = pos } // clearWrap clears the state where the cursor is in the margin // waiting for the next character before wrapping the line. This must // be done before most operations that act on the cursor. func (h *windowsAnsiEventHandler) clearWrap() { h.wrapNext = false h.drewMarginByte = false } ================================================ FILE: vendor/github.com/Masterminds/semver/.travis.yml ================================================ language: go go: - 1.6.x - 1.7.x - 1.8.x - 1.9.x - 1.10.x - 1.11.x - 1.12.x - tip # Setting sudo access to false will let Travis CI use containers rather than # VMs to run the tests. For more details see: # - http://docs.travis-ci.com/user/workers/container-based-infrastructure/ # - http://docs.travis-ci.com/user/workers/standard-infrastructure/ sudo: false script: - make setup - make test notifications: webhooks: urls: - https://webhooks.gitter.im/e/06e3328629952dabe3e0 on_success: change # options: [always|never|change] default: always on_failure: always # options: [always|never|change] default: always on_start: never # options: [always|never|change] default: always ================================================ FILE: vendor/github.com/Masterminds/semver/CHANGELOG.md ================================================ # 1.5.0 (2019-09-11) ## Added - #103: Add basic fuzzing for `NewVersion()` (thanks @jesse-c) ## Changed - #82: Clarify wildcard meaning in range constraints and update tests for it (thanks @greysteil) - #83: Clarify caret operator range for pre-1.0.0 dependencies (thanks @greysteil) - #72: Adding docs comment pointing to vert for a cli - #71: Update the docs on pre-release comparator handling - #89: Test with new go versions (thanks @thedevsaddam) - #87: Added $ to ValidPrerelease for better validation (thanks @jeremycarroll) ## Fixed - #78: Fix unchecked error in example code (thanks @ravron) - #70: Fix the handling of pre-releases and the 0.0.0 release edge case - #97: Fixed copyright file for proper display on GitHub - #107: Fix handling prerelease when sorting alphanum and num - #109: Fixed where Validate sometimes returns wrong message on error # 1.4.2 (2018-04-10) ## Changed - #72: Updated the docs to point to vert for a console appliaction - #71: Update the docs on pre-release comparator handling ## Fixed - #70: Fix the handling of pre-releases and the 0.0.0 release edge case # 1.4.1 (2018-04-02) ## Fixed - Fixed #64: Fix pre-release precedence issue (thanks @uudashr) # 1.4.0 (2017-10-04) ## Changed - #61: Update NewVersion to parse ints with a 64bit int size (thanks @zknill) # 1.3.1 (2017-07-10) ## Fixed - Fixed #57: number comparisons in prerelease sometimes inaccurate # 1.3.0 (2017-05-02) ## Added - #45: Added json (un)marshaling support (thanks @mh-cbon) - Stability marker. See https://masterminds.github.io/stability/ ## Fixed - #51: Fix handling of single digit tilde constraint (thanks @dgodd) ## Changed - #55: The godoc icon moved from png to svg # 1.2.3 (2017-04-03) ## Fixed - #46: Fixed 0.x.x and 0.0.x in constraints being treated as * # Release 1.2.2 (2016-12-13) ## Fixed - #34: Fixed issue where hyphen range was not working with pre-release parsing. # Release 1.2.1 (2016-11-28) ## Fixed - #24: Fixed edge case issue where constraint "> 0" does not handle "0.0.1-alpha" properly. # Release 1.2.0 (2016-11-04) ## Added - #20: Added MustParse function for versions (thanks @adamreese) - #15: Added increment methods on versions (thanks @mh-cbon) ## Fixed - Issue #21: Per the SemVer spec (section 9) a pre-release is unstable and might not satisfy the intended compatibility. The change here ignores pre-releases on constraint checks (e.g., ~ or ^) when a pre-release is not part of the constraint. For example, `^1.2.3` will ignore pre-releases while `^1.2.3-alpha` will include them. # Release 1.1.1 (2016-06-30) ## Changed - Issue #9: Speed up version comparison performance (thanks @sdboyer) - Issue #8: Added benchmarks (thanks @sdboyer) - Updated Go Report Card URL to new location - Updated Readme to add code snippet formatting (thanks @mh-cbon) - Updating tagging to v[SemVer] structure for compatibility with other tools. # Release 1.1.0 (2016-03-11) - Issue #2: Implemented validation to provide reasons a versions failed a constraint. # Release 1.0.1 (2015-12-31) - Fixed #1: * constraint failing on valid versions. # Release 1.0.0 (2015-10-20) - Initial release ================================================ FILE: vendor/github.com/Masterminds/semver/LICENSE.txt ================================================ Copyright (C) 2014-2019, Matt Butcher and Matt Farina Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: vendor/github.com/Masterminds/semver/Makefile ================================================ .PHONY: setup setup: go get -u gopkg.in/alecthomas/gometalinter.v1 gometalinter.v1 --install .PHONY: test test: validate lint @echo "==> Running tests" go test -v .PHONY: validate validate: @echo "==> Running static validations" @gometalinter.v1 \ --disable-all \ --enable deadcode \ --severity deadcode:error \ --enable gofmt \ --enable gosimple \ --enable ineffassign \ --enable misspell \ --enable vet \ --tests \ --vendor \ --deadline 60s \ ./... || exit_code=1 .PHONY: lint lint: @echo "==> Running linters" @gometalinter.v1 \ --disable-all \ --enable golint \ --vendor \ --deadline 60s \ ./... || : ================================================ FILE: vendor/github.com/Masterminds/semver/README.md ================================================ # SemVer The `semver` package provides the ability to work with [Semantic Versions](http://semver.org) in Go. Specifically it provides the ability to: * Parse semantic versions * Sort semantic versions * Check if a semantic version fits within a set of constraints * Optionally work with a `v` prefix [![Stability: Active](https://masterminds.github.io/stability/active.svg)](https://masterminds.github.io/stability/active.html) [![Build Status](https://travis-ci.org/Masterminds/semver.svg)](https://travis-ci.org/Masterminds/semver) [![Build status](https://ci.appveyor.com/api/projects/status/jfk66lib7hb985k8/branch/master?svg=true&passingText=windows%20build%20passing&failingText=windows%20build%20failing)](https://ci.appveyor.com/project/mattfarina/semver/branch/master) [![GoDoc](https://godoc.org/github.com/Masterminds/semver?status.svg)](https://godoc.org/github.com/Masterminds/semver) [![Go Report Card](https://goreportcard.com/badge/github.com/Masterminds/semver)](https://goreportcard.com/report/github.com/Masterminds/semver) If you are looking for a command line tool for version comparisons please see [vert](https://github.com/Masterminds/vert) which uses this library. ## Parsing Semantic Versions To parse a semantic version use the `NewVersion` function. For example, ```go v, err := semver.NewVersion("1.2.3-beta.1+build345") ``` If there is an error the version wasn't parseable. The version object has methods to get the parts of the version, compare it to other versions, convert the version back into a string, and get the original string. For more details please see the [documentation](https://godoc.org/github.com/Masterminds/semver). ## Sorting Semantic Versions A set of versions can be sorted using the [`sort`](https://golang.org/pkg/sort/) package from the standard library. For example, ```go raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",} vs := make([]*semver.Version, len(raw)) for i, r := range raw { v, err := semver.NewVersion(r) if err != nil { t.Errorf("Error parsing version: %s", err) } vs[i] = v } sort.Sort(semver.Collection(vs)) ``` ## Checking Version Constraints Checking a version against version constraints is one of the most featureful parts of the package. ```go c, err := semver.NewConstraint(">= 1.2.3") if err != nil { // Handle constraint not being parseable. } v, _ := semver.NewVersion("1.3") if err != nil { // Handle version not being parseable. } // Check if the version meets the constraints. The a variable will be true. a := c.Check(v) ``` ## Basic Comparisons There are two elements to the comparisons. First, a comparison string is a list of comma separated and comparisons. These are then separated by || separated or comparisons. For example, `">= 1.2, < 3.0.0 || >= 4.2.3"` is looking for a comparison that's greater than or equal to 1.2 and less than 3.0.0 or is greater than or equal to 4.2.3. The basic comparisons are: * `=`: equal (aliased to no operator) * `!=`: not equal * `>`: greater than * `<`: less than * `>=`: greater than or equal to * `<=`: less than or equal to ## Working With Pre-release Versions Pre-releases, for those not familiar with them, are used for software releases prior to stable or generally available releases. Examples of pre-releases include development, alpha, beta, and release candidate releases. A pre-release may be a version such as `1.2.3-beta.1` while the stable release would be `1.2.3`. In the order of precidence, pre-releases come before their associated releases. In this example `1.2.3-beta.1 < 1.2.3`. According to the Semantic Version specification pre-releases may not be API compliant with their release counterpart. It says, > A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version. SemVer comparisons without a pre-release comparator will skip pre-release versions. For example, `>=1.2.3` will skip pre-releases when looking at a list of releases while `>=1.2.3-0` will evaluate and find pre-releases. The reason for the `0` as a pre-release version in the example comparison is because pre-releases can only contain ASCII alphanumerics and hyphens (along with `.` separators), per the spec. Sorting happens in ASCII sort order, again per the spec. The lowest character is a `0` in ASCII sort order (see an [ASCII Table](http://www.asciitable.com/)) Understanding ASCII sort ordering is important because A-Z comes before a-z. That means `>=1.2.3-BETA` will return `1.2.3-alpha`. What you might expect from case sensitivity doesn't apply here. This is due to ASCII sort ordering which is what the spec specifies. ## Hyphen Range Comparisons There are multiple methods to handle ranges and the first is hyphens ranges. These look like: * `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5` * `2.3.4 - 4.5` which is equivalent to `>= 2.3.4, <= 4.5` ## Wildcards In Comparisons The `x`, `X`, and `*` characters can be used as a wildcard character. This works for all comparison operators. When used on the `=` operator it falls back to the pack level comparison (see tilde below). For example, * `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` * `>= 1.2.x` is equivalent to `>= 1.2.0` * `<= 2.x` is equivalent to `< 3` * `*` is equivalent to `>= 0.0.0` ## Tilde Range Comparisons (Patch) The tilde (`~`) comparison operator is for patch level ranges when a minor version is specified and major level changes when the minor number is missing. For example, * `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0` * `~1` is equivalent to `>= 1, < 2` * `~2.3` is equivalent to `>= 2.3, < 2.4` * `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` * `~1.x` is equivalent to `>= 1, < 2` ## Caret Range Comparisons (Major) The caret (`^`) comparison operator is for major level changes. This is useful when comparisons of API versions as a major change is API breaking. For example, * `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0` * `^0.0.1` is equivalent to `>= 0.0.1, < 1.0.0` * `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0` * `^2.3` is equivalent to `>= 2.3, < 3` * `^2.x` is equivalent to `>= 2.0.0, < 3` # Validation In addition to testing a version against a constraint, a version can be validated against a constraint. When validation fails a slice of errors containing why a version didn't meet the constraint is returned. For example, ```go c, err := semver.NewConstraint("<= 1.2.3, >= 1.4") if err != nil { // Handle constraint not being parseable. } v, _ := semver.NewVersion("1.3") if err != nil { // Handle version not being parseable. } // Validate a version against a constraint. a, msgs := c.Validate(v) // a is false for _, m := range msgs { fmt.Println(m) // Loops over the errors which would read // "1.3 is greater than 1.2.3" // "1.3 is less than 1.4" } ``` # Fuzzing [dvyukov/go-fuzz](https://github.com/dvyukov/go-fuzz) is used for fuzzing. 1. `go-fuzz-build` 2. `go-fuzz -workdir=fuzz` # Contribute If you find an issue or want to contribute please file an [issue](https://github.com/Masterminds/semver/issues) or [create a pull request](https://github.com/Masterminds/semver/pulls). ================================================ FILE: vendor/github.com/Masterminds/semver/appveyor.yml ================================================ version: build-{build}.{branch} clone_folder: C:\gopath\src\github.com\Masterminds\semver shallow_clone: true environment: GOPATH: C:\gopath platform: - x64 install: - go version - go env - go get -u gopkg.in/alecthomas/gometalinter.v1 - set PATH=%PATH%;%GOPATH%\bin - gometalinter.v1.exe --install build_script: - go install -v ./... test_script: - "gometalinter.v1 \ --disable-all \ --enable deadcode \ --severity deadcode:error \ --enable gofmt \ --enable gosimple \ --enable ineffassign \ --enable misspell \ --enable vet \ --tests \ --vendor \ --deadline 60s \ ./... || exit_code=1" - "gometalinter.v1 \ --disable-all \ --enable golint \ --vendor \ --deadline 60s \ ./... || :" - go test -v deploy: off ================================================ FILE: vendor/github.com/Masterminds/semver/collection.go ================================================ package semver // Collection is a collection of Version instances and implements the sort // interface. See the sort package for more details. // https://golang.org/pkg/sort/ type Collection []*Version // Len returns the length of a collection. The number of Version instances // on the slice. func (c Collection) Len() int { return len(c) } // Less is needed for the sort interface to compare two Version objects on the // slice. If checks if one is less than the other. func (c Collection) Less(i, j int) bool { return c[i].LessThan(c[j]) } // Swap is needed for the sort interface to replace the Version objects // at two different positions in the slice. func (c Collection) Swap(i, j int) { c[i], c[j] = c[j], c[i] } ================================================ FILE: vendor/github.com/Masterminds/semver/constraints.go ================================================ package semver import ( "errors" "fmt" "regexp" "strings" ) // Constraints is one or more constraint that a semantic version can be // checked against. type Constraints struct { constraints [][]*constraint } // NewConstraint returns a Constraints instance that a Version instance can // be checked against. If there is a parse error it will be returned. func NewConstraint(c string) (*Constraints, error) { // Rewrite - ranges into a comparison operation. c = rewriteRange(c) ors := strings.Split(c, "||") or := make([][]*constraint, len(ors)) for k, v := range ors { cs := strings.Split(v, ",") result := make([]*constraint, len(cs)) for i, s := range cs { pc, err := parseConstraint(s) if err != nil { return nil, err } result[i] = pc } or[k] = result } o := &Constraints{constraints: or} return o, nil } // Check tests if a version satisfies the constraints. func (cs Constraints) Check(v *Version) bool { // loop over the ORs and check the inner ANDs for _, o := range cs.constraints { joy := true for _, c := range o { if !c.check(v) { joy = false break } } if joy { return true } } return false } // Validate checks if a version satisfies a constraint. If not a slice of // reasons for the failure are returned in addition to a bool. func (cs Constraints) Validate(v *Version) (bool, []error) { // loop over the ORs and check the inner ANDs var e []error // Capture the prerelease message only once. When it happens the first time // this var is marked var prerelesase bool for _, o := range cs.constraints { joy := true for _, c := range o { // Before running the check handle the case there the version is // a prerelease and the check is not searching for prereleases. if c.con.pre == "" && v.pre != "" { if !prerelesase { em := fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) e = append(e, em) prerelesase = true } joy = false } else { if !c.check(v) { em := fmt.Errorf(c.msg, v, c.orig) e = append(e, em) joy = false } } } if joy { return true, []error{} } } return false, e } var constraintOps map[string]cfunc var constraintMsg map[string]string var constraintRegex *regexp.Regexp func init() { constraintOps = map[string]cfunc{ "": constraintTildeOrEqual, "=": constraintTildeOrEqual, "!=": constraintNotEqual, ">": constraintGreaterThan, "<": constraintLessThan, ">=": constraintGreaterThanEqual, "=>": constraintGreaterThanEqual, "<=": constraintLessThanEqual, "=<": constraintLessThanEqual, "~": constraintTilde, "~>": constraintTilde, "^": constraintCaret, } constraintMsg = map[string]string{ "": "%s is not equal to %s", "=": "%s is not equal to %s", "!=": "%s is equal to %s", ">": "%s is less than or equal to %s", "<": "%s is greater than or equal to %s", ">=": "%s is less than %s", "=>": "%s is less than %s", "<=": "%s is greater than %s", "=<": "%s is greater than %s", "~": "%s does not have same major and minor version as %s", "~>": "%s does not have same major and minor version as %s", "^": "%s does not have same major version as %s", } ops := make([]string, 0, len(constraintOps)) for k := range constraintOps { ops = append(ops, regexp.QuoteMeta(k)) } constraintRegex = regexp.MustCompile(fmt.Sprintf( `^\s*(%s)\s*(%s)\s*$`, strings.Join(ops, "|"), cvRegex)) constraintRangeRegex = regexp.MustCompile(fmt.Sprintf( `\s*(%s)\s+-\s+(%s)\s*`, cvRegex, cvRegex)) } // An individual constraint type constraint struct { // The callback function for the restraint. It performs the logic for // the constraint. function cfunc msg string // The version used in the constraint check. For example, if a constraint // is '<= 2.0.0' the con a version instance representing 2.0.0. con *Version // The original parsed version (e.g., 4.x from != 4.x) orig string // When an x is used as part of the version (e.g., 1.x) minorDirty bool dirty bool patchDirty bool } // Check if a version meets the constraint func (c *constraint) check(v *Version) bool { return c.function(v, c) } type cfunc func(v *Version, c *constraint) bool func parseConstraint(c string) (*constraint, error) { m := constraintRegex.FindStringSubmatch(c) if m == nil { return nil, fmt.Errorf("improper constraint: %s", c) } ver := m[2] orig := ver minorDirty := false patchDirty := false dirty := false if isX(m[3]) { ver = "0.0.0" dirty = true } else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" { minorDirty = true dirty = true ver = fmt.Sprintf("%s.0.0%s", m[3], m[6]) } else if isX(strings.TrimPrefix(m[5], ".")) { dirty = true patchDirty = true ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6]) } con, err := NewVersion(ver) if err != nil { // The constraintRegex should catch any regex parsing errors. So, // we should never get here. return nil, errors.New("constraint Parser Error") } cs := &constraint{ function: constraintOps[m[1]], msg: constraintMsg[m[1]], con: con, orig: orig, minorDirty: minorDirty, patchDirty: patchDirty, dirty: dirty, } return cs, nil } // Constraint functions func constraintNotEqual(v *Version, c *constraint) bool { if c.dirty { // If there is a pre-release on the version but the constraint isn't looking // for them assume that pre-releases are not compatible. See issue 21 for // more details. if v.Prerelease() != "" && c.con.Prerelease() == "" { return false } if c.con.Major() != v.Major() { return true } if c.con.Minor() != v.Minor() && !c.minorDirty { return true } else if c.minorDirty { return false } return false } return !v.Equal(c.con) } func constraintGreaterThan(v *Version, c *constraint) bool { // If there is a pre-release on the version but the constraint isn't looking // for them assume that pre-releases are not compatible. See issue 21 for // more details. if v.Prerelease() != "" && c.con.Prerelease() == "" { return false } return v.Compare(c.con) == 1 } func constraintLessThan(v *Version, c *constraint) bool { // If there is a pre-release on the version but the constraint isn't looking // for them assume that pre-releases are not compatible. See issue 21 for // more details. if v.Prerelease() != "" && c.con.Prerelease() == "" { return false } if !c.dirty { return v.Compare(c.con) < 0 } if v.Major() > c.con.Major() { return false } else if v.Minor() > c.con.Minor() && !c.minorDirty { return false } return true } func constraintGreaterThanEqual(v *Version, c *constraint) bool { // If there is a pre-release on the version but the constraint isn't looking // for them assume that pre-releases are not compatible. See issue 21 for // more details. if v.Prerelease() != "" && c.con.Prerelease() == "" { return false } return v.Compare(c.con) >= 0 } func constraintLessThanEqual(v *Version, c *constraint) bool { // If there is a pre-release on the version but the constraint isn't looking // for them assume that pre-releases are not compatible. See issue 21 for // more details. if v.Prerelease() != "" && c.con.Prerelease() == "" { return false } if !c.dirty { return v.Compare(c.con) <= 0 } if v.Major() > c.con.Major() { return false } else if v.Minor() > c.con.Minor() && !c.minorDirty { return false } return true } // ~*, ~>* --> >= 0.0.0 (any) // ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0, <3.0.0 // ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0, <2.1.0 // ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0 // ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0 // ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0 func constraintTilde(v *Version, c *constraint) bool { // If there is a pre-release on the version but the constraint isn't looking // for them assume that pre-releases are not compatible. See issue 21 for // more details. if v.Prerelease() != "" && c.con.Prerelease() == "" { return false } if v.LessThan(c.con) { return false } // ~0.0.0 is a special case where all constraints are accepted. It's // equivalent to >= 0.0.0. if c.con.Major() == 0 && c.con.Minor() == 0 && c.con.Patch() == 0 && !c.minorDirty && !c.patchDirty { return true } if v.Major() != c.con.Major() { return false } if v.Minor() != c.con.Minor() && !c.minorDirty { return false } return true } // When there is a .x (dirty) status it automatically opts in to ~. Otherwise // it's a straight = func constraintTildeOrEqual(v *Version, c *constraint) bool { // If there is a pre-release on the version but the constraint isn't looking // for them assume that pre-releases are not compatible. See issue 21 for // more details. if v.Prerelease() != "" && c.con.Prerelease() == "" { return false } if c.dirty { c.msg = constraintMsg["~"] return constraintTilde(v, c) } return v.Equal(c.con) } // ^* --> (any) // ^2, ^2.x, ^2.x.x --> >=2.0.0, <3.0.0 // ^2.0, ^2.0.x --> >=2.0.0, <3.0.0 // ^1.2, ^1.2.x --> >=1.2.0, <2.0.0 // ^1.2.3 --> >=1.2.3, <2.0.0 // ^1.2.0 --> >=1.2.0, <2.0.0 func constraintCaret(v *Version, c *constraint) bool { // If there is a pre-release on the version but the constraint isn't looking // for them assume that pre-releases are not compatible. See issue 21 for // more details. if v.Prerelease() != "" && c.con.Prerelease() == "" { return false } if v.LessThan(c.con) { return false } if v.Major() != c.con.Major() { return false } return true } var constraintRangeRegex *regexp.Regexp const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` + `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` func isX(x string) bool { switch x { case "x", "*", "X": return true default: return false } } func rewriteRange(i string) string { m := constraintRangeRegex.FindAllStringSubmatch(i, -1) if m == nil { return i } o := i for _, v := range m { t := fmt.Sprintf(">= %s, <= %s", v[1], v[11]) o = strings.Replace(o, v[0], t, 1) } return o } ================================================ FILE: vendor/github.com/Masterminds/semver/doc.go ================================================ /* Package semver provides the ability to work with Semantic Versions (http://semver.org) in Go. Specifically it provides the ability to: * Parse semantic versions * Sort semantic versions * Check if a semantic version fits within a set of constraints * Optionally work with a `v` prefix Parsing Semantic Versions To parse a semantic version use the `NewVersion` function. For example, v, err := semver.NewVersion("1.2.3-beta.1+build345") If there is an error the version wasn't parseable. The version object has methods to get the parts of the version, compare it to other versions, convert the version back into a string, and get the original string. For more details please see the documentation at https://godoc.org/github.com/Masterminds/semver. Sorting Semantic Versions A set of versions can be sorted using the `sort` package from the standard library. For example, raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",} vs := make([]*semver.Version, len(raw)) for i, r := range raw { v, err := semver.NewVersion(r) if err != nil { t.Errorf("Error parsing version: %s", err) } vs[i] = v } sort.Sort(semver.Collection(vs)) Checking Version Constraints Checking a version against version constraints is one of the most featureful parts of the package. c, err := semver.NewConstraint(">= 1.2.3") if err != nil { // Handle constraint not being parseable. } v, err := semver.NewVersion("1.3") if err != nil { // Handle version not being parseable. } // Check if the version meets the constraints. The a variable will be true. a := c.Check(v) Basic Comparisons There are two elements to the comparisons. First, a comparison string is a list of comma separated and comparisons. These are then separated by || separated or comparisons. For example, `">= 1.2, < 3.0.0 || >= 4.2.3"` is looking for a comparison that's greater than or equal to 1.2 and less than 3.0.0 or is greater than or equal to 4.2.3. The basic comparisons are: * `=`: equal (aliased to no operator) * `!=`: not equal * `>`: greater than * `<`: less than * `>=`: greater than or equal to * `<=`: less than or equal to Hyphen Range Comparisons There are multiple methods to handle ranges and the first is hyphens ranges. These look like: * `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5` * `2.3.4 - 4.5` which is equivalent to `>= 2.3.4, <= 4.5` Wildcards In Comparisons The `x`, `X`, and `*` characters can be used as a wildcard character. This works for all comparison operators. When used on the `=` operator it falls back to the pack level comparison (see tilde below). For example, * `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` * `>= 1.2.x` is equivalent to `>= 1.2.0` * `<= 2.x` is equivalent to `<= 3` * `*` is equivalent to `>= 0.0.0` Tilde Range Comparisons (Patch) The tilde (`~`) comparison operator is for patch level ranges when a minor version is specified and major level changes when the minor number is missing. For example, * `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0` * `~1` is equivalent to `>= 1, < 2` * `~2.3` is equivalent to `>= 2.3, < 2.4` * `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` * `~1.x` is equivalent to `>= 1, < 2` Caret Range Comparisons (Major) The caret (`^`) comparison operator is for major level changes. This is useful when comparisons of API versions as a major change is API breaking. For example, * `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0` * `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0` * `^2.3` is equivalent to `>= 2.3, < 3` * `^2.x` is equivalent to `>= 2.0.0, < 3` */ package semver ================================================ FILE: vendor/github.com/Masterminds/semver/version.go ================================================ package semver import ( "bytes" "encoding/json" "errors" "fmt" "regexp" "strconv" "strings" ) // The compiled version of the regex created at init() is cached here so it // only needs to be created once. var versionRegex *regexp.Regexp var validPrereleaseRegex *regexp.Regexp var ( // ErrInvalidSemVer is returned a version is found to be invalid when // being parsed. ErrInvalidSemVer = errors.New("Invalid Semantic Version") // ErrInvalidMetadata is returned when the metadata is an invalid format ErrInvalidMetadata = errors.New("Invalid Metadata string") // ErrInvalidPrerelease is returned when the pre-release is an invalid format ErrInvalidPrerelease = errors.New("Invalid Prerelease string") ) // SemVerRegex is the regular expression used to parse a semantic version. const SemVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` + `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` // ValidPrerelease is the regular expression which validates // both prerelease and metadata values. const ValidPrerelease string = `^([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*)$` // Version represents a single semantic version. type Version struct { major, minor, patch int64 pre string metadata string original string } func init() { versionRegex = regexp.MustCompile("^" + SemVerRegex + "$") validPrereleaseRegex = regexp.MustCompile(ValidPrerelease) } // NewVersion parses a given version and returns an instance of Version or // an error if unable to parse the version. func NewVersion(v string) (*Version, error) { m := versionRegex.FindStringSubmatch(v) if m == nil { return nil, ErrInvalidSemVer } sv := &Version{ metadata: m[8], pre: m[5], original: v, } var temp int64 temp, err := strconv.ParseInt(m[1], 10, 64) if err != nil { return nil, fmt.Errorf("Error parsing version segment: %s", err) } sv.major = temp if m[2] != "" { temp, err = strconv.ParseInt(strings.TrimPrefix(m[2], "."), 10, 64) if err != nil { return nil, fmt.Errorf("Error parsing version segment: %s", err) } sv.minor = temp } else { sv.minor = 0 } if m[3] != "" { temp, err = strconv.ParseInt(strings.TrimPrefix(m[3], "."), 10, 64) if err != nil { return nil, fmt.Errorf("Error parsing version segment: %s", err) } sv.patch = temp } else { sv.patch = 0 } return sv, nil } // MustParse parses a given version and panics on error. func MustParse(v string) *Version { sv, err := NewVersion(v) if err != nil { panic(err) } return sv } // String converts a Version object to a string. // Note, if the original version contained a leading v this version will not. // See the Original() method to retrieve the original value. Semantic Versions // don't contain a leading v per the spec. Instead it's optional on // implementation. func (v *Version) String() string { var buf bytes.Buffer fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch) if v.pre != "" { fmt.Fprintf(&buf, "-%s", v.pre) } if v.metadata != "" { fmt.Fprintf(&buf, "+%s", v.metadata) } return buf.String() } // Original returns the original value passed in to be parsed. func (v *Version) Original() string { return v.original } // Major returns the major version. func (v *Version) Major() int64 { return v.major } // Minor returns the minor version. func (v *Version) Minor() int64 { return v.minor } // Patch returns the patch version. func (v *Version) Patch() int64 { return v.patch } // Prerelease returns the pre-release version. func (v *Version) Prerelease() string { return v.pre } // Metadata returns the metadata on the version. func (v *Version) Metadata() string { return v.metadata } // originalVPrefix returns the original 'v' prefix if any. func (v *Version) originalVPrefix() string { // Note, only lowercase v is supported as a prefix by the parser. if v.original != "" && v.original[:1] == "v" { return v.original[:1] } return "" } // IncPatch produces the next patch version. // If the current version does not have prerelease/metadata information, // it unsets metadata and prerelease values, increments patch number. // If the current version has any of prerelease or metadata information, // it unsets both values and keeps curent patch value func (v Version) IncPatch() Version { vNext := v // according to http://semver.org/#spec-item-9 // Pre-release versions have a lower precedence than the associated normal version. // according to http://semver.org/#spec-item-10 // Build metadata SHOULD be ignored when determining version precedence. if v.pre != "" { vNext.metadata = "" vNext.pre = "" } else { vNext.metadata = "" vNext.pre = "" vNext.patch = v.patch + 1 } vNext.original = v.originalVPrefix() + "" + vNext.String() return vNext } // IncMinor produces the next minor version. // Sets patch to 0. // Increments minor number. // Unsets metadata. // Unsets prerelease status. func (v Version) IncMinor() Version { vNext := v vNext.metadata = "" vNext.pre = "" vNext.patch = 0 vNext.minor = v.minor + 1 vNext.original = v.originalVPrefix() + "" + vNext.String() return vNext } // IncMajor produces the next major version. // Sets patch to 0. // Sets minor to 0. // Increments major number. // Unsets metadata. // Unsets prerelease status. func (v Version) IncMajor() Version { vNext := v vNext.metadata = "" vNext.pre = "" vNext.patch = 0 vNext.minor = 0 vNext.major = v.major + 1 vNext.original = v.originalVPrefix() + "" + vNext.String() return vNext } // SetPrerelease defines the prerelease value. // Value must not include the required 'hypen' prefix. func (v Version) SetPrerelease(prerelease string) (Version, error) { vNext := v if len(prerelease) > 0 && !validPrereleaseRegex.MatchString(prerelease) { return vNext, ErrInvalidPrerelease } vNext.pre = prerelease vNext.original = v.originalVPrefix() + "" + vNext.String() return vNext, nil } // SetMetadata defines metadata value. // Value must not include the required 'plus' prefix. func (v Version) SetMetadata(metadata string) (Version, error) { vNext := v if len(metadata) > 0 && !validPrereleaseRegex.MatchString(metadata) { return vNext, ErrInvalidMetadata } vNext.metadata = metadata vNext.original = v.originalVPrefix() + "" + vNext.String() return vNext, nil } // LessThan tests if one version is less than another one. func (v *Version) LessThan(o *Version) bool { return v.Compare(o) < 0 } // GreaterThan tests if one version is greater than another one. func (v *Version) GreaterThan(o *Version) bool { return v.Compare(o) > 0 } // Equal tests if two versions are equal to each other. // Note, versions can be equal with different metadata since metadata // is not considered part of the comparable version. func (v *Version) Equal(o *Version) bool { return v.Compare(o) == 0 } // Compare compares this version to another one. It returns -1, 0, or 1 if // the version smaller, equal, or larger than the other version. // // Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is // lower than the version without a prerelease. func (v *Version) Compare(o *Version) int { // Compare the major, minor, and patch version for differences. If a // difference is found return the comparison. if d := compareSegment(v.Major(), o.Major()); d != 0 { return d } if d := compareSegment(v.Minor(), o.Minor()); d != 0 { return d } if d := compareSegment(v.Patch(), o.Patch()); d != 0 { return d } // At this point the major, minor, and patch versions are the same. ps := v.pre po := o.Prerelease() if ps == "" && po == "" { return 0 } if ps == "" { return 1 } if po == "" { return -1 } return comparePrerelease(ps, po) } // UnmarshalJSON implements JSON.Unmarshaler interface. func (v *Version) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err != nil { return err } temp, err := NewVersion(s) if err != nil { return err } v.major = temp.major v.minor = temp.minor v.patch = temp.patch v.pre = temp.pre v.metadata = temp.metadata v.original = temp.original temp = nil return nil } // MarshalJSON implements JSON.Marshaler interface. func (v *Version) MarshalJSON() ([]byte, error) { return json.Marshal(v.String()) } func compareSegment(v, o int64) int { if v < o { return -1 } if v > o { return 1 } return 0 } func comparePrerelease(v, o string) int { // split the prelease versions by their part. The separator, per the spec, // is a . sparts := strings.Split(v, ".") oparts := strings.Split(o, ".") // Find the longer length of the parts to know how many loop iterations to // go through. slen := len(sparts) olen := len(oparts) l := slen if olen > slen { l = olen } // Iterate over each part of the prereleases to compare the differences. for i := 0; i < l; i++ { // Since the lentgh of the parts can be different we need to create // a placeholder. This is to avoid out of bounds issues. stemp := "" if i < slen { stemp = sparts[i] } otemp := "" if i < olen { otemp = oparts[i] } d := comparePrePart(stemp, otemp) if d != 0 { return d } } // Reaching here means two versions are of equal value but have different // metadata (the part following a +). They are not identical in string form // but the version comparison finds them to be equal. return 0 } func comparePrePart(s, o string) int { // Fastpath if they are equal if s == o { return 0 } // When s or o are empty we can use the other in an attempt to determine // the response. if s == "" { if o != "" { return -1 } return 1 } if o == "" { if s != "" { return 1 } return -1 } // When comparing strings "99" is greater than "103". To handle // cases like this we need to detect numbers and compare them. According // to the semver spec, numbers are always positive. If there is a - at the // start like -99 this is to be evaluated as an alphanum. numbers always // have precedence over alphanum. Parsing as Uints because negative numbers // are ignored. oi, n1 := strconv.ParseUint(o, 10, 64) si, n2 := strconv.ParseUint(s, 10, 64) // The case where both are strings compare the strings if n1 != nil && n2 != nil { if s > o { return 1 } return -1 } else if n1 != nil { // o is a string and s is a number return -1 } else if n2 != nil { // s is a string and o is a number return 1 } // Both are numbers if si > oi { return 1 } return -1 } ================================================ FILE: vendor/github.com/Masterminds/semver/version_fuzz.go ================================================ // +build gofuzz package semver func Fuzz(data []byte) int { if _, err := NewVersion(string(data)); err != nil { return 0 } return 1 } ================================================ FILE: vendor/github.com/Microsoft/go-winio/.gitattributes ================================================ * text=auto eol=lf ================================================ FILE: vendor/github.com/Microsoft/go-winio/.gitignore ================================================ .vscode/ *.exe # testing testdata # go workspaces go.work go.work.sum ================================================ FILE: vendor/github.com/Microsoft/go-winio/.golangci.yml ================================================ linters: enable: # style - containedctx # struct contains a context - dupl # duplicate code - errname # erorrs are named correctly - nolintlint # "//nolint" directives are properly explained - revive # golint replacement - unconvert # unnecessary conversions - wastedassign # bugs, performance, unused, etc ... - contextcheck # function uses a non-inherited context - errorlint # errors not wrapped for 1.13 - exhaustive # check exhaustiveness of enum switch statements - gofmt # files are gofmt'ed - gosec # security - nilerr # returns nil even with non-nil error - thelper # test helpers without t.Helper() - unparam # unused function params issues: exclude-dirs: - pkg/etw/sample exclude-rules: # err is very often shadowed in nested scopes - linters: - govet text: '^shadow: declaration of "err" shadows declaration' # ignore long lines for skip autogen directives - linters: - revive text: "^line-length-limit: " source: "^//(go:generate|sys) " #TODO: remove after upgrading to go1.18 # ignore comment spacing for nolint and sys directives - linters: - revive text: "^comment-spacings: no space between comment delimiter and comment text" source: "//(cspell:|nolint:|sys |todo)" # not on go 1.18 yet, so no any - linters: - revive text: "^use-any: since GO 1.18 'interface{}' can be replaced by 'any'" # allow unjustified ignores of error checks in defer statements - linters: - nolintlint text: "^directive `//nolint:errcheck` should provide explanation" source: '^\s*defer ' # allow unjustified ignores of error lints for io.EOF - linters: - nolintlint text: "^directive `//nolint:errorlint` should provide explanation" source: '[=|!]= io.EOF' linters-settings: exhaustive: default-signifies-exhaustive: true govet: enable-all: true disable: # struct order is often for Win32 compat # also, ignore pointer bytes/GC issues for now until performance becomes an issue - fieldalignment nolintlint: require-explanation: true require-specific: true revive: # revive is more configurable than static check, so likely the preferred alternative to static-check # (once the perf issue is solved: https://github.com/golangci/golangci-lint/issues/2997) enable-all-rules: true # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md rules: # rules with required arguments - name: argument-limit disabled: true - name: banned-characters disabled: true - name: cognitive-complexity disabled: true - name: cyclomatic disabled: true - name: file-header disabled: true - name: function-length disabled: true - name: function-result-limit disabled: true - name: max-public-structs disabled: true # geneally annoying rules - name: add-constant # complains about any and all strings and integers disabled: true - name: confusing-naming # we frequently use "Foo()" and "foo()" together disabled: true - name: flag-parameter # excessive, and a common idiom we use disabled: true - name: unhandled-error # warns over common fmt.Print* and io.Close; rely on errcheck instead disabled: true # general config - name: line-length-limit arguments: - 140 - name: var-naming arguments: - [] - - CID - CRI - CTRD - DACL - DLL - DOS - ETW - FSCTL - GCS - GMSA - HCS - HV - IO - LCOW - LDAP - LPAC - LTSC - MMIO - NT - OCI - PMEM - PWSH - RX - SACl - SID - SMB - TX - VHD - VHDX - VMID - VPCI - WCOW - WIM ================================================ FILE: vendor/github.com/Microsoft/go-winio/CODEOWNERS ================================================ * @microsoft/containerplat ================================================ FILE: vendor/github.com/Microsoft/go-winio/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 Microsoft Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: vendor/github.com/Microsoft/go-winio/README.md ================================================ # go-winio [![Build Status](https://github.com/microsoft/go-winio/actions/workflows/ci.yml/badge.svg)](https://github.com/microsoft/go-winio/actions/workflows/ci.yml) This repository contains utilities for efficiently performing Win32 IO operations in Go. Currently, this is focused on accessing named pipes and other file handles, and for using named pipes as a net transport. This code relies on IO completion ports to avoid blocking IO on system threads, allowing Go to reuse the thread to schedule another goroutine. This limits support to Windows Vista and newer operating systems. This is similar to the implementation of network sockets in Go's net package. Please see the LICENSE file for licensing information. ## Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit [Microsoft CLA](https://cla.microsoft.com). When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. Additionally, the pull request pipeline requires the following steps to be performed before mergining. ### Code Sign-Off We require that contributors sign their commits using [`git commit --signoff`][git-commit-s] to certify they either authored the work themselves or otherwise have permission to use it in this project. A range of commits can be signed off using [`git rebase --signoff`][git-rebase-s]. Please see [the developer certificate](https://developercertificate.org) for more info, as well as to make sure that you can attest to the rules listed. Our CI uses the DCO Github app to ensure that all commits in a given PR are signed-off. ### Linting Code must pass a linting stage, which uses [`golangci-lint`][lint]. The linting settings are stored in [`.golangci.yaml`](./.golangci.yaml), and can be run automatically with VSCode by adding the following to your workspace or folder settings: ```json "go.lintTool": "golangci-lint", "go.lintOnSave": "package", ``` Additional editor [integrations options are also available][lint-ide]. Alternatively, `golangci-lint` can be [installed locally][lint-install] and run from the repo root: ```shell # use . or specify a path to only lint a package # to show all lint errors, use flags "--max-issues-per-linter=0 --max-same-issues=0" > golangci-lint run ./... ``` ### Go Generate The pipeline checks that auto-generated code, via `go generate`, are up to date. This can be done for the entire repo: ```shell > go generate ./... ``` ## Code of Conduct This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. ## Special Thanks Thanks to [natefinch][natefinch] for the inspiration for this library. See [npipe](https://github.com/natefinch/npipe) for another named pipe implementation. [lint]: https://golangci-lint.run/ [lint-ide]: https://golangci-lint.run/usage/integrations/#editor-integration [lint-install]: https://golangci-lint.run/usage/install/#local-installation [git-commit-s]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s [git-rebase-s]: https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---signoff [natefinch]: https://github.com/natefinch ================================================ FILE: vendor/github.com/Microsoft/go-winio/SECURITY.md ================================================ ## Security Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. ## Reporting Security Issues **Please do not report security vulnerabilities through public GitHub issues.** Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) * Full paths of source file(s) related to the manifestation of the issue * The location of the affected source code (tag/branch/commit or direct URL) * Any special configuration required to reproduce the issue * Step-by-step instructions to reproduce the issue * Proof-of-concept or exploit code (if possible) * Impact of the issue, including how an attacker might exploit the issue This information will help us triage your report more quickly. If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. ## Preferred Languages We prefer all communications to be in English. ## Policy Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). ================================================ FILE: vendor/github.com/Microsoft/go-winio/backup.go ================================================ //go:build windows // +build windows package winio import ( "encoding/binary" "errors" "fmt" "io" "os" "runtime" "unicode/utf16" "github.com/Microsoft/go-winio/internal/fs" "golang.org/x/sys/windows" ) //sys backupRead(h windows.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead //sys backupWrite(h windows.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupWrite const ( BackupData = uint32(iota + 1) BackupEaData BackupSecurity BackupAlternateData BackupLink BackupPropertyData BackupObjectId //revive:disable-line:var-naming ID, not Id BackupReparseData BackupSparseBlock BackupTxfsData ) const ( StreamSparseAttributes = uint32(8) ) //nolint:revive // var-naming: ALL_CAPS const ( WRITE_DAC = windows.WRITE_DAC WRITE_OWNER = windows.WRITE_OWNER ACCESS_SYSTEM_SECURITY = windows.ACCESS_SYSTEM_SECURITY ) // BackupHeader represents a backup stream of a file. type BackupHeader struct { //revive:disable-next-line:var-naming ID, not Id Id uint32 // The backup stream ID Attributes uint32 // Stream attributes Size int64 // The size of the stream in bytes Name string // The name of the stream (for BackupAlternateData only). Offset int64 // The offset of the stream in the file (for BackupSparseBlock only). } type win32StreamID struct { StreamID uint32 Attributes uint32 Size uint64 NameSize uint32 } // BackupStreamReader reads from a stream produced by the BackupRead Win32 API and produces a series // of BackupHeader values. type BackupStreamReader struct { r io.Reader bytesLeft int64 } // NewBackupStreamReader produces a BackupStreamReader from any io.Reader. func NewBackupStreamReader(r io.Reader) *BackupStreamReader { return &BackupStreamReader{r, 0} } // Next returns the next backup stream and prepares for calls to Read(). It skips the remainder of the current stream if // it was not completely read. func (r *BackupStreamReader) Next() (*BackupHeader, error) { if r.bytesLeft > 0 { //nolint:nestif // todo: flatten this if s, ok := r.r.(io.Seeker); ok { // Make sure Seek on io.SeekCurrent sometimes succeeds // before trying the actual seek. if _, err := s.Seek(0, io.SeekCurrent); err == nil { if _, err = s.Seek(r.bytesLeft, io.SeekCurrent); err != nil { return nil, err } r.bytesLeft = 0 } } if _, err := io.Copy(io.Discard, r); err != nil { return nil, err } } var wsi win32StreamID if err := binary.Read(r.r, binary.LittleEndian, &wsi); err != nil { return nil, err } hdr := &BackupHeader{ Id: wsi.StreamID, Attributes: wsi.Attributes, Size: int64(wsi.Size), } if wsi.NameSize != 0 { name := make([]uint16, int(wsi.NameSize/2)) if err := binary.Read(r.r, binary.LittleEndian, name); err != nil { return nil, err } hdr.Name = windows.UTF16ToString(name) } if wsi.StreamID == BackupSparseBlock { if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil { return nil, err } hdr.Size -= 8 } r.bytesLeft = hdr.Size return hdr, nil } // Read reads from the current backup stream. func (r *BackupStreamReader) Read(b []byte) (int, error) { if r.bytesLeft == 0 { return 0, io.EOF } if int64(len(b)) > r.bytesLeft { b = b[:r.bytesLeft] } n, err := r.r.Read(b) r.bytesLeft -= int64(n) if err == io.EOF { err = io.ErrUnexpectedEOF } else if r.bytesLeft == 0 && err == nil { err = io.EOF } return n, err } // BackupStreamWriter writes a stream compatible with the BackupWrite Win32 API. type BackupStreamWriter struct { w io.Writer bytesLeft int64 } // NewBackupStreamWriter produces a BackupStreamWriter on top of an io.Writer. func NewBackupStreamWriter(w io.Writer) *BackupStreamWriter { return &BackupStreamWriter{w, 0} } // WriteHeader writes the next backup stream header and prepares for calls to Write(). func (w *BackupStreamWriter) WriteHeader(hdr *BackupHeader) error { if w.bytesLeft != 0 { return fmt.Errorf("missing %d bytes", w.bytesLeft) } name := utf16.Encode([]rune(hdr.Name)) wsi := win32StreamID{ StreamID: hdr.Id, Attributes: hdr.Attributes, Size: uint64(hdr.Size), NameSize: uint32(len(name) * 2), } if hdr.Id == BackupSparseBlock { // Include space for the int64 block offset wsi.Size += 8 } if err := binary.Write(w.w, binary.LittleEndian, &wsi); err != nil { return err } if len(name) != 0 { if err := binary.Write(w.w, binary.LittleEndian, name); err != nil { return err } } if hdr.Id == BackupSparseBlock { if err := binary.Write(w.w, binary.LittleEndian, hdr.Offset); err != nil { return err } } w.bytesLeft = hdr.Size return nil } // Write writes to the current backup stream. func (w *BackupStreamWriter) Write(b []byte) (int, error) { if w.bytesLeft < int64(len(b)) { return 0, fmt.Errorf("too many bytes by %d", int64(len(b))-w.bytesLeft) } n, err := w.w.Write(b) w.bytesLeft -= int64(n) return n, err } // BackupFileReader provides an io.ReadCloser interface on top of the BackupRead Win32 API. type BackupFileReader struct { f *os.File includeSecurity bool ctx uintptr } // NewBackupFileReader returns a new BackupFileReader from a file handle. If includeSecurity is true, // Read will attempt to read the security descriptor of the file. func NewBackupFileReader(f *os.File, includeSecurity bool) *BackupFileReader { r := &BackupFileReader{f, includeSecurity, 0} return r } // Read reads a backup stream from the file by calling the Win32 API BackupRead(). func (r *BackupFileReader) Read(b []byte) (int, error) { var bytesRead uint32 err := backupRead(windows.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx) if err != nil { return 0, &os.PathError{Op: "BackupRead", Path: r.f.Name(), Err: err} } runtime.KeepAlive(r.f) if bytesRead == 0 { return 0, io.EOF } return int(bytesRead), nil } // Close frees Win32 resources associated with the BackupFileReader. It does not close // the underlying file. func (r *BackupFileReader) Close() error { if r.ctx != 0 { _ = backupRead(windows.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx) runtime.KeepAlive(r.f) r.ctx = 0 } return nil } // BackupFileWriter provides an io.WriteCloser interface on top of the BackupWrite Win32 API. type BackupFileWriter struct { f *os.File includeSecurity bool ctx uintptr } // NewBackupFileWriter returns a new BackupFileWriter from a file handle. If includeSecurity is true, // Write() will attempt to restore the security descriptor from the stream. func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter { w := &BackupFileWriter{f, includeSecurity, 0} return w } // Write restores a portion of the file using the provided backup stream. func (w *BackupFileWriter) Write(b []byte) (int, error) { var bytesWritten uint32 err := backupWrite(windows.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx) if err != nil { return 0, &os.PathError{Op: "BackupWrite", Path: w.f.Name(), Err: err} } runtime.KeepAlive(w.f) if int(bytesWritten) != len(b) { return int(bytesWritten), errors.New("not all bytes could be written") } return len(b), nil } // Close frees Win32 resources associated with the BackupFileWriter. It does not // close the underlying file. func (w *BackupFileWriter) Close() error { if w.ctx != 0 { _ = backupWrite(windows.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx) runtime.KeepAlive(w.f) w.ctx = 0 } return nil } // OpenForBackup opens a file or directory, potentially skipping access checks if the backup // or restore privileges have been acquired. // // If the file opened was a directory, it cannot be used with Readdir(). func OpenForBackup(path string, access uint32, share uint32, createmode uint32) (*os.File, error) { h, err := fs.CreateFile(path, fs.AccessMask(access), fs.FileShareMode(share), nil, fs.FileCreationDisposition(createmode), fs.FILE_FLAG_BACKUP_SEMANTICS|fs.FILE_FLAG_OPEN_REPARSE_POINT, 0, ) if err != nil { err = &os.PathError{Op: "open", Path: path, Err: err} return nil, err } return os.NewFile(uintptr(h), path), nil } ================================================ FILE: vendor/github.com/Microsoft/go-winio/doc.go ================================================ // This package provides utilities for efficiently performing Win32 IO operations in Go. // Currently, this package is provides support for genreal IO and management of // - named pipes // - files // - [Hyper-V sockets] // // This code is similar to Go's [net] package, and uses IO completion ports to avoid // blocking IO on system threads, allowing Go to reuse the thread to schedule other goroutines. // // This limits support to Windows Vista and newer operating systems. // // Additionally, this package provides support for: // - creating and managing GUIDs // - writing to [ETW] // - opening and manageing VHDs // - parsing [Windows Image files] // - auto-generating Win32 API code // // [Hyper-V sockets]: https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service // [ETW]: https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/event-tracing-for-windows--etw- // [Windows Image files]: https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/work-with-windows-images package winio ================================================ FILE: vendor/github.com/Microsoft/go-winio/ea.go ================================================ package winio import ( "bytes" "encoding/binary" "errors" ) type fileFullEaInformation struct { NextEntryOffset uint32 Flags uint8 NameLength uint8 ValueLength uint16 } var ( fileFullEaInformationSize = binary.Size(&fileFullEaInformation{}) errInvalidEaBuffer = errors.New("invalid extended attribute buffer") errEaNameTooLarge = errors.New("extended attribute name too large") errEaValueTooLarge = errors.New("extended attribute value too large") ) // ExtendedAttribute represents a single Windows EA. type ExtendedAttribute struct { Name string Value []byte Flags uint8 } func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) { var info fileFullEaInformation err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info) if err != nil { err = errInvalidEaBuffer return ea, nb, err } nameOffset := fileFullEaInformationSize nameLen := int(info.NameLength) valueOffset := nameOffset + int(info.NameLength) + 1 valueLen := int(info.ValueLength) nextOffset := int(info.NextEntryOffset) if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) { err = errInvalidEaBuffer return ea, nb, err } ea.Name = string(b[nameOffset : nameOffset+nameLen]) ea.Value = b[valueOffset : valueOffset+valueLen] ea.Flags = info.Flags if info.NextEntryOffset != 0 { nb = b[info.NextEntryOffset:] } return ea, nb, err } // DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION // buffer retrieved from BackupRead, ZwQueryEaFile, etc. func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) { for len(b) != 0 { ea, nb, err := parseEa(b) if err != nil { return nil, err } eas = append(eas, ea) b = nb } return eas, err } func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error { if int(uint8(len(ea.Name))) != len(ea.Name) { return errEaNameTooLarge } if int(uint16(len(ea.Value))) != len(ea.Value) { return errEaValueTooLarge } entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value)) withPadding := (entrySize + 3) &^ 3 nextOffset := uint32(0) if !last { nextOffset = withPadding } info := fileFullEaInformation{ NextEntryOffset: nextOffset, Flags: ea.Flags, NameLength: uint8(len(ea.Name)), ValueLength: uint16(len(ea.Value)), } err := binary.Write(buf, binary.LittleEndian, &info) if err != nil { return err } _, err = buf.Write([]byte(ea.Name)) if err != nil { return err } err = buf.WriteByte(0) if err != nil { return err } _, err = buf.Write(ea.Value) if err != nil { return err } _, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize]) if err != nil { return err } return nil } // EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION // buffer for use with BackupWrite, ZwSetEaFile, etc. func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) { var buf bytes.Buffer for i := range eas { last := false if i == len(eas)-1 { last = true } err := writeEa(&buf, &eas[i], last) if err != nil { return nil, err } } return buf.Bytes(), nil } ================================================ FILE: vendor/github.com/Microsoft/go-winio/file.go ================================================ //go:build windows // +build windows package winio import ( "errors" "io" "runtime" "sync" "sync/atomic" "syscall" "time" "golang.org/x/sys/windows" ) //sys cancelIoEx(file windows.Handle, o *windows.Overlapped) (err error) = CancelIoEx //sys createIoCompletionPort(file windows.Handle, port windows.Handle, key uintptr, threadCount uint32) (newport windows.Handle, err error) = CreateIoCompletionPort //sys getQueuedCompletionStatus(port windows.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus //sys setFileCompletionNotificationModes(h windows.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes //sys wsaGetOverlappedResult(h windows.Handle, o *windows.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult var ( ErrFileClosed = errors.New("file has already been closed") ErrTimeout = &timeoutError{} ) type timeoutError struct{} func (*timeoutError) Error() string { return "i/o timeout" } func (*timeoutError) Timeout() bool { return true } func (*timeoutError) Temporary() bool { return true } type timeoutChan chan struct{} var ioInitOnce sync.Once var ioCompletionPort windows.Handle // ioResult contains the result of an asynchronous IO operation. type ioResult struct { bytes uint32 err error } // ioOperation represents an outstanding asynchronous Win32 IO. type ioOperation struct { o windows.Overlapped ch chan ioResult } func initIO() { h, err := createIoCompletionPort(windows.InvalidHandle, 0, 0, 0xffffffff) if err != nil { panic(err) } ioCompletionPort = h go ioCompletionProcessor(h) } // win32File implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall. // It takes ownership of this handle and will close it if it is garbage collected. type win32File struct { handle windows.Handle wg sync.WaitGroup wgLock sync.RWMutex closing atomic.Bool socket bool readDeadline deadlineHandler writeDeadline deadlineHandler } type deadlineHandler struct { setLock sync.Mutex channel timeoutChan channelLock sync.RWMutex timer *time.Timer timedout atomic.Bool } // makeWin32File makes a new win32File from an existing file handle. func makeWin32File(h windows.Handle) (*win32File, error) { f := &win32File{handle: h} ioInitOnce.Do(initIO) _, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff) if err != nil { return nil, err } err = setFileCompletionNotificationModes(h, windows.FILE_SKIP_COMPLETION_PORT_ON_SUCCESS|windows.FILE_SKIP_SET_EVENT_ON_HANDLE) if err != nil { return nil, err } f.readDeadline.channel = make(timeoutChan) f.writeDeadline.channel = make(timeoutChan) return f, nil } // Deprecated: use NewOpenFile instead. func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) { return NewOpenFile(windows.Handle(h)) } func NewOpenFile(h windows.Handle) (io.ReadWriteCloser, error) { // If we return the result of makeWin32File directly, it can result in an // interface-wrapped nil, rather than a nil interface value. f, err := makeWin32File(h) if err != nil { return nil, err } return f, nil } // closeHandle closes the resources associated with a Win32 handle. func (f *win32File) closeHandle() { f.wgLock.Lock() // Atomically set that we are closing, releasing the resources only once. if !f.closing.Swap(true) { f.wgLock.Unlock() // cancel all IO and wait for it to complete _ = cancelIoEx(f.handle, nil) f.wg.Wait() // at this point, no new IO can start windows.Close(f.handle) f.handle = 0 } else { f.wgLock.Unlock() } } // Close closes a win32File. func (f *win32File) Close() error { f.closeHandle() return nil } // IsClosed checks if the file has been closed. func (f *win32File) IsClosed() bool { return f.closing.Load() } // prepareIO prepares for a new IO operation. // The caller must call f.wg.Done() when the IO is finished, prior to Close() returning. func (f *win32File) prepareIO() (*ioOperation, error) { f.wgLock.RLock() if f.closing.Load() { f.wgLock.RUnlock() return nil, ErrFileClosed } f.wg.Add(1) f.wgLock.RUnlock() c := &ioOperation{} c.ch = make(chan ioResult) return c, nil } // ioCompletionProcessor processes completed async IOs forever. func ioCompletionProcessor(h windows.Handle) { for { var bytes uint32 var key uintptr var op *ioOperation err := getQueuedCompletionStatus(h, &bytes, &key, &op, windows.INFINITE) if op == nil { panic(err) } op.ch <- ioResult{bytes, err} } } // todo: helsaawy - create an asyncIO version that takes a context // asyncIO processes the return value from ReadFile or WriteFile, blocking until // the operation has actually completed. func (f *win32File) asyncIO(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) { if err != windows.ERROR_IO_PENDING { //nolint:errorlint // err is Errno return int(bytes), err } if f.closing.Load() { _ = cancelIoEx(f.handle, &c.o) } var timeout timeoutChan if d != nil { d.channelLock.Lock() timeout = d.channel d.channelLock.Unlock() } var r ioResult select { case r = <-c.ch: err = r.err if err == windows.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno if f.closing.Load() { err = ErrFileClosed } } else if err != nil && f.socket { // err is from Win32. Query the overlapped structure to get the winsock error. var bytes, flags uint32 err = wsaGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags) } case <-timeout: _ = cancelIoEx(f.handle, &c.o) r = <-c.ch err = r.err if err == windows.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno err = ErrTimeout } } // runtime.KeepAlive is needed, as c is passed via native // code to ioCompletionProcessor, c must remain alive // until the channel read is complete. // todo: (de)allocate *ioOperation via win32 heap functions, instead of needing to KeepAlive? runtime.KeepAlive(c) return int(r.bytes), err } // Read reads from a file handle. func (f *win32File) Read(b []byte) (int, error) { c, err := f.prepareIO() if err != nil { return 0, err } defer f.wg.Done() if f.readDeadline.timedout.Load() { return 0, ErrTimeout } var bytes uint32 err = windows.ReadFile(f.handle, b, &bytes, &c.o) n, err := f.asyncIO(c, &f.readDeadline, bytes, err) runtime.KeepAlive(b) // Handle EOF conditions. if err == nil && n == 0 && len(b) != 0 { return 0, io.EOF } else if err == windows.ERROR_BROKEN_PIPE { //nolint:errorlint // err is Errno return 0, io.EOF } return n, err } // Write writes to a file handle. func (f *win32File) Write(b []byte) (int, error) { c, err := f.prepareIO() if err != nil { return 0, err } defer f.wg.Done() if f.writeDeadline.timedout.Load() { return 0, ErrTimeout } var bytes uint32 err = windows.WriteFile(f.handle, b, &bytes, &c.o) n, err := f.asyncIO(c, &f.writeDeadline, bytes, err) runtime.KeepAlive(b) return n, err } func (f *win32File) SetReadDeadline(deadline time.Time) error { return f.readDeadline.set(deadline) } func (f *win32File) SetWriteDeadline(deadline time.Time) error { return f.writeDeadline.set(deadline) } func (f *win32File) Flush() error { return windows.FlushFileBuffers(f.handle) } func (f *win32File) Fd() uintptr { return uintptr(f.handle) } func (d *deadlineHandler) set(deadline time.Time) error { d.setLock.Lock() defer d.setLock.Unlock() if d.timer != nil { if !d.timer.Stop() { <-d.channel } d.timer = nil } d.timedout.Store(false) select { case <-d.channel: d.channelLock.Lock() d.channel = make(chan struct{}) d.channelLock.Unlock() default: } if deadline.IsZero() { return nil } timeoutIO := func() { d.timedout.Store(true) close(d.channel) } now := time.Now() duration := deadline.Sub(now) if deadline.After(now) { // Deadline is in the future, set a timer to wait d.timer = time.AfterFunc(duration, timeoutIO) } else { // Deadline is in the past. Cancel all pending IO now. timeoutIO() } return nil } ================================================ FILE: vendor/github.com/Microsoft/go-winio/fileinfo.go ================================================ //go:build windows // +build windows package winio import ( "os" "runtime" "unsafe" "golang.org/x/sys/windows" ) // FileBasicInfo contains file access time and file attributes information. type FileBasicInfo struct { CreationTime, LastAccessTime, LastWriteTime, ChangeTime windows.Filetime FileAttributes uint32 _ uint32 // padding } // alignedFileBasicInfo is a FileBasicInfo, but aligned to uint64 by containing // uint64 rather than windows.Filetime. Filetime contains two uint32s. uint64 // alignment is necessary to pass this as FILE_BASIC_INFO. type alignedFileBasicInfo struct { CreationTime, LastAccessTime, LastWriteTime, ChangeTime uint64 FileAttributes uint32 _ uint32 // padding } // GetFileBasicInfo retrieves times and attributes for a file. func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) { bi := &alignedFileBasicInfo{} if err := windows.GetFileInformationByHandleEx( windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi)), ); err != nil { return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} } runtime.KeepAlive(f) // Reinterpret the alignedFileBasicInfo as a FileBasicInfo so it matches the // public API of this module. The data may be unnecessarily aligned. return (*FileBasicInfo)(unsafe.Pointer(bi)), nil } // SetFileBasicInfo sets times and attributes for a file. func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error { // Create an alignedFileBasicInfo based on a FileBasicInfo. The copy is // suitable to pass to GetFileInformationByHandleEx. biAligned := *(*alignedFileBasicInfo)(unsafe.Pointer(bi)) if err := windows.SetFileInformationByHandle( windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(&biAligned)), uint32(unsafe.Sizeof(biAligned)), ); err != nil { return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err} } runtime.KeepAlive(f) return nil } // FileStandardInfo contains extended information for the file. // FILE_STANDARD_INFO in WinBase.h // https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_standard_info type FileStandardInfo struct { AllocationSize, EndOfFile int64 NumberOfLinks uint32 DeletePending, Directory bool } // GetFileStandardInfo retrieves ended information for the file. func GetFileStandardInfo(f *os.File) (*FileStandardInfo, error) { si := &FileStandardInfo{} if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileStandardInfo, (*byte)(unsafe.Pointer(si)), uint32(unsafe.Sizeof(*si))); err != nil { return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} } runtime.KeepAlive(f) return si, nil } // FileIDInfo contains the volume serial number and file ID for a file. This pair should be // unique on a system. type FileIDInfo struct { VolumeSerialNumber uint64 FileID [16]byte } // GetFileID retrieves the unique (volume, file ID) pair for a file. func GetFileID(f *os.File) (*FileIDInfo, error) { fileID := &FileIDInfo{} if err := windows.GetFileInformationByHandleEx( windows.Handle(f.Fd()), windows.FileIdInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID)), ); err != nil { return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} } runtime.KeepAlive(f) return fileID, nil } ================================================ FILE: vendor/github.com/Microsoft/go-winio/hvsock.go ================================================ //go:build windows // +build windows package winio import ( "context" "errors" "fmt" "io" "net" "os" "time" "unsafe" "golang.org/x/sys/windows" "github.com/Microsoft/go-winio/internal/socket" "github.com/Microsoft/go-winio/pkg/guid" ) const afHVSock = 34 // AF_HYPERV // Well known Service and VM IDs // https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service#vmid-wildcards // HvsockGUIDWildcard is the wildcard VmId for accepting connections from all partitions. func HvsockGUIDWildcard() guid.GUID { // 00000000-0000-0000-0000-000000000000 return guid.GUID{} } // HvsockGUIDBroadcast is the wildcard VmId for broadcasting sends to all partitions. func HvsockGUIDBroadcast() guid.GUID { // ffffffff-ffff-ffff-ffff-ffffffffffff return guid.GUID{ Data1: 0xffffffff, Data2: 0xffff, Data3: 0xffff, Data4: [8]uint8{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, } } // HvsockGUIDLoopback is the Loopback VmId for accepting connections to the same partition as the connector. func HvsockGUIDLoopback() guid.GUID { // e0e16197-dd56-4a10-9195-5ee7a155a838 return guid.GUID{ Data1: 0xe0e16197, Data2: 0xdd56, Data3: 0x4a10, Data4: [8]uint8{0x91, 0x95, 0x5e, 0xe7, 0xa1, 0x55, 0xa8, 0x38}, } } // HvsockGUIDSiloHost is the address of a silo's host partition: // - The silo host of a hosted silo is the utility VM. // - The silo host of a silo on a physical host is the physical host. func HvsockGUIDSiloHost() guid.GUID { // 36bd0c5c-7276-4223-88ba-7d03b654c568 return guid.GUID{ Data1: 0x36bd0c5c, Data2: 0x7276, Data3: 0x4223, Data4: [8]byte{0x88, 0xba, 0x7d, 0x03, 0xb6, 0x54, 0xc5, 0x68}, } } // HvsockGUIDChildren is the wildcard VmId for accepting connections from the connector's child partitions. func HvsockGUIDChildren() guid.GUID { // 90db8b89-0d35-4f79-8ce9-49ea0ac8b7cd return guid.GUID{ Data1: 0x90db8b89, Data2: 0xd35, Data3: 0x4f79, Data4: [8]uint8{0x8c, 0xe9, 0x49, 0xea, 0xa, 0xc8, 0xb7, 0xcd}, } } // HvsockGUIDParent is the wildcard VmId for accepting connections from the connector's parent partition. // Listening on this VmId accepts connection from: // - Inside silos: silo host partition. // - Inside hosted silo: host of the VM. // - Inside VM: VM host. // - Physical host: Not supported. func HvsockGUIDParent() guid.GUID { // a42e7cda-d03f-480c-9cc2-a4de20abb878 return guid.GUID{ Data1: 0xa42e7cda, Data2: 0xd03f, Data3: 0x480c, Data4: [8]uint8{0x9c, 0xc2, 0xa4, 0xde, 0x20, 0xab, 0xb8, 0x78}, } } // hvsockVsockServiceTemplate is the Service GUID used for the VSOCK protocol. func hvsockVsockServiceTemplate() guid.GUID { // 00000000-facb-11e6-bd58-64006a7986d3 return guid.GUID{ Data2: 0xfacb, Data3: 0x11e6, Data4: [8]uint8{0xbd, 0x58, 0x64, 0x00, 0x6a, 0x79, 0x86, 0xd3}, } } // An HvsockAddr is an address for a AF_HYPERV socket. type HvsockAddr struct { VMID guid.GUID ServiceID guid.GUID } type rawHvsockAddr struct { Family uint16 _ uint16 VMID guid.GUID ServiceID guid.GUID } var _ socket.RawSockaddr = &rawHvsockAddr{} // Network returns the address's network name, "hvsock". func (*HvsockAddr) Network() string { return "hvsock" } func (addr *HvsockAddr) String() string { return fmt.Sprintf("%s:%s", &addr.VMID, &addr.ServiceID) } // VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port. func VsockServiceID(port uint32) guid.GUID { g := hvsockVsockServiceTemplate() // make a copy g.Data1 = port return g } func (addr *HvsockAddr) raw() rawHvsockAddr { return rawHvsockAddr{ Family: afHVSock, VMID: addr.VMID, ServiceID: addr.ServiceID, } } func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) { addr.VMID = raw.VMID addr.ServiceID = raw.ServiceID } // Sockaddr returns a pointer to and the size of this struct. // // Implements the [socket.RawSockaddr] interface, and allows use in // [socket.Bind] and [socket.ConnectEx]. func (r *rawHvsockAddr) Sockaddr() (unsafe.Pointer, int32, error) { return unsafe.Pointer(r), int32(unsafe.Sizeof(rawHvsockAddr{})), nil } // Sockaddr interface allows use with `sockets.Bind()` and `.ConnectEx()`. func (r *rawHvsockAddr) FromBytes(b []byte) error { n := int(unsafe.Sizeof(rawHvsockAddr{})) if len(b) < n { return fmt.Errorf("got %d, want %d: %w", len(b), n, socket.ErrBufferSize) } copy(unsafe.Slice((*byte)(unsafe.Pointer(r)), n), b[:n]) if r.Family != afHVSock { return fmt.Errorf("got %d, want %d: %w", r.Family, afHVSock, socket.ErrAddrFamily) } return nil } // HvsockListener is a socket listener for the AF_HYPERV address family. type HvsockListener struct { sock *win32File addr HvsockAddr } var _ net.Listener = &HvsockListener{} // HvsockConn is a connected socket of the AF_HYPERV address family. type HvsockConn struct { sock *win32File local, remote HvsockAddr } var _ net.Conn = &HvsockConn{} func newHVSocket() (*win32File, error) { fd, err := windows.Socket(afHVSock, windows.SOCK_STREAM, 1) if err != nil { return nil, os.NewSyscallError("socket", err) } f, err := makeWin32File(fd) if err != nil { windows.Close(fd) return nil, err } f.socket = true return f, nil } // ListenHvsock listens for connections on the specified hvsock address. func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) { l := &HvsockListener{addr: *addr} var sock *win32File sock, err = newHVSocket() if err != nil { return nil, l.opErr("listen", err) } defer func() { if err != nil { _ = sock.Close() } }() sa := addr.raw() err = socket.Bind(sock.handle, &sa) if err != nil { return nil, l.opErr("listen", os.NewSyscallError("socket", err)) } err = windows.Listen(sock.handle, 16) if err != nil { return nil, l.opErr("listen", os.NewSyscallError("listen", err)) } return &HvsockListener{sock: sock, addr: *addr}, nil } func (l *HvsockListener) opErr(op string, err error) error { return &net.OpError{Op: op, Net: "hvsock", Addr: &l.addr, Err: err} } // Addr returns the listener's network address. func (l *HvsockListener) Addr() net.Addr { return &l.addr } // Accept waits for the next connection and returns it. func (l *HvsockListener) Accept() (_ net.Conn, err error) { sock, err := newHVSocket() if err != nil { return nil, l.opErr("accept", err) } defer func() { if sock != nil { sock.Close() } }() c, err := l.sock.prepareIO() if err != nil { return nil, l.opErr("accept", err) } defer l.sock.wg.Done() // AcceptEx, per documentation, requires an extra 16 bytes per address. // // https://docs.microsoft.com/en-us/windows/win32/api/mswsock/nf-mswsock-acceptex const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{})) var addrbuf [addrlen * 2]byte var bytes uint32 err = windows.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0 /* rxdatalen */, addrlen, addrlen, &bytes, &c.o) if _, err = l.sock.asyncIO(c, nil, bytes, err); err != nil { return nil, l.opErr("accept", os.NewSyscallError("acceptex", err)) } conn := &HvsockConn{ sock: sock, } // The local address returned in the AcceptEx buffer is the same as the Listener socket's // address. However, the service GUID reported by GetSockName is different from the Listeners // socket, and is sometimes the same as the local address of the socket that dialed the // address, with the service GUID.Data1 incremented, but othertimes is different. // todo: does the local address matter? is the listener's address or the actual address appropriate? conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0]))) conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen]))) // initialize the accepted socket and update its properties with those of the listening socket if err = windows.Setsockopt(sock.handle, windows.SOL_SOCKET, windows.SO_UPDATE_ACCEPT_CONTEXT, (*byte)(unsafe.Pointer(&l.sock.handle)), int32(unsafe.Sizeof(l.sock.handle))); err != nil { return nil, conn.opErr("accept", os.NewSyscallError("setsockopt", err)) } sock = nil return conn, nil } // Close closes the listener, causing any pending Accept calls to fail. func (l *HvsockListener) Close() error { return l.sock.Close() } // HvsockDialer configures and dials a Hyper-V Socket (ie, [HvsockConn]). type HvsockDialer struct { // Deadline is the time the Dial operation must connect before erroring. Deadline time.Time // Retries is the number of additional connects to try if the connection times out, is refused, // or the host is unreachable Retries uint // RetryWait is the time to wait after a connection error to retry RetryWait time.Duration rt *time.Timer // redial wait timer } // Dial the Hyper-V socket at addr. // // See [HvsockDialer.Dial] for more information. func Dial(ctx context.Context, addr *HvsockAddr) (conn *HvsockConn, err error) { return (&HvsockDialer{}).Dial(ctx, addr) } // Dial attempts to connect to the Hyper-V socket at addr, and returns a connection if successful. // Will attempt (HvsockDialer).Retries if dialing fails, waiting (HvsockDialer).RetryWait between // retries. // // Dialing can be cancelled either by providing (HvsockDialer).Deadline, or cancelling ctx. func (d *HvsockDialer) Dial(ctx context.Context, addr *HvsockAddr) (conn *HvsockConn, err error) { op := "dial" // create the conn early to use opErr() conn = &HvsockConn{ remote: *addr, } if !d.Deadline.IsZero() { var cancel context.CancelFunc ctx, cancel = context.WithDeadline(ctx, d.Deadline) defer cancel() } // preemptive timeout/cancellation check if err = ctx.Err(); err != nil { return nil, conn.opErr(op, err) } sock, err := newHVSocket() if err != nil { return nil, conn.opErr(op, err) } defer func() { if sock != nil { sock.Close() } }() sa := addr.raw() err = socket.Bind(sock.handle, &sa) if err != nil { return nil, conn.opErr(op, os.NewSyscallError("bind", err)) } c, err := sock.prepareIO() if err != nil { return nil, conn.opErr(op, err) } defer sock.wg.Done() var bytes uint32 for i := uint(0); i <= d.Retries; i++ { err = socket.ConnectEx( sock.handle, &sa, nil, // sendBuf 0, // sendDataLen &bytes, (*windows.Overlapped)(unsafe.Pointer(&c.o))) _, err = sock.asyncIO(c, nil, bytes, err) if i < d.Retries && canRedial(err) { if err = d.redialWait(ctx); err == nil { continue } } break } if err != nil { return nil, conn.opErr(op, os.NewSyscallError("connectex", err)) } // update the connection properties, so shutdown can be used if err = windows.Setsockopt( sock.handle, windows.SOL_SOCKET, windows.SO_UPDATE_CONNECT_CONTEXT, nil, // optvalue 0, // optlen ); err != nil { return nil, conn.opErr(op, os.NewSyscallError("setsockopt", err)) } // get the local name var sal rawHvsockAddr err = socket.GetSockName(sock.handle, &sal) if err != nil { return nil, conn.opErr(op, os.NewSyscallError("getsockname", err)) } conn.local.fromRaw(&sal) // one last check for timeout, since asyncIO doesn't check the context if err = ctx.Err(); err != nil { return nil, conn.opErr(op, err) } conn.sock = sock sock = nil return conn, nil } // redialWait waits before attempting to redial, resetting the timer as appropriate. func (d *HvsockDialer) redialWait(ctx context.Context) (err error) { if d.RetryWait == 0 { return nil } if d.rt == nil { d.rt = time.NewTimer(d.RetryWait) } else { // should already be stopped and drained d.rt.Reset(d.RetryWait) } select { case <-ctx.Done(): case <-d.rt.C: return nil } // stop and drain the timer if !d.rt.Stop() { <-d.rt.C } return ctx.Err() } // assumes error is a plain, unwrapped windows.Errno provided by direct syscall. func canRedial(err error) bool { //nolint:errorlint // guaranteed to be an Errno switch err { case windows.WSAECONNREFUSED, windows.WSAENETUNREACH, windows.WSAETIMEDOUT, windows.ERROR_CONNECTION_REFUSED, windows.ERROR_CONNECTION_UNAVAIL: return true default: return false } } func (conn *HvsockConn) opErr(op string, err error) error { // translate from "file closed" to "socket closed" if errors.Is(err, ErrFileClosed) { err = socket.ErrSocketClosed } return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err} } func (conn *HvsockConn) Read(b []byte) (int, error) { c, err := conn.sock.prepareIO() if err != nil { return 0, conn.opErr("read", err) } defer conn.sock.wg.Done() buf := windows.WSABuf{Buf: &b[0], Len: uint32(len(b))} var flags, bytes uint32 err = windows.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil) n, err := conn.sock.asyncIO(c, &conn.sock.readDeadline, bytes, err) if err != nil { var eno windows.Errno if errors.As(err, &eno) { err = os.NewSyscallError("wsarecv", eno) } return 0, conn.opErr("read", err) } else if n == 0 { err = io.EOF } return n, err } func (conn *HvsockConn) Write(b []byte) (int, error) { t := 0 for len(b) != 0 { n, err := conn.write(b) if err != nil { return t + n, err } t += n b = b[n:] } return t, nil } func (conn *HvsockConn) write(b []byte) (int, error) { c, err := conn.sock.prepareIO() if err != nil { return 0, conn.opErr("write", err) } defer conn.sock.wg.Done() buf := windows.WSABuf{Buf: &b[0], Len: uint32(len(b))} var bytes uint32 err = windows.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil) n, err := conn.sock.asyncIO(c, &conn.sock.writeDeadline, bytes, err) if err != nil { var eno windows.Errno if errors.As(err, &eno) { err = os.NewSyscallError("wsasend", eno) } return 0, conn.opErr("write", err) } return n, err } // Close closes the socket connection, failing any pending read or write calls. func (conn *HvsockConn) Close() error { return conn.sock.Close() } func (conn *HvsockConn) IsClosed() bool { return conn.sock.IsClosed() } // shutdown disables sending or receiving on a socket. func (conn *HvsockConn) shutdown(how int) error { if conn.IsClosed() { return socket.ErrSocketClosed } err := windows.Shutdown(conn.sock.handle, how) if err != nil { // If the connection was closed, shutdowns fail with "not connected" if errors.Is(err, windows.WSAENOTCONN) || errors.Is(err, windows.WSAESHUTDOWN) { err = socket.ErrSocketClosed } return os.NewSyscallError("shutdown", err) } return nil } // CloseRead shuts down the read end of the socket, preventing future read operations. func (conn *HvsockConn) CloseRead() error { err := conn.shutdown(windows.SHUT_RD) if err != nil { return conn.opErr("closeread", err) } return nil } // CloseWrite shuts down the write end of the socket, preventing future write operations and // notifying the other endpoint that no more data will be written. func (conn *HvsockConn) CloseWrite() error { err := conn.shutdown(windows.SHUT_WR) if err != nil { return conn.opErr("closewrite", err) } return nil } // LocalAddr returns the local address of the connection. func (conn *HvsockConn) LocalAddr() net.Addr { return &conn.local } // RemoteAddr returns the remote address of the connection. func (conn *HvsockConn) RemoteAddr() net.Addr { return &conn.remote } // SetDeadline implements the net.Conn SetDeadline method. func (conn *HvsockConn) SetDeadline(t time.Time) error { // todo: implement `SetDeadline` for `win32File` if err := conn.SetReadDeadline(t); err != nil { return fmt.Errorf("set read deadline: %w", err) } if err := conn.SetWriteDeadline(t); err != nil { return fmt.Errorf("set write deadline: %w", err) } return nil } // SetReadDeadline implements the net.Conn SetReadDeadline method. func (conn *HvsockConn) SetReadDeadline(t time.Time) error { return conn.sock.SetReadDeadline(t) } // SetWriteDeadline implements the net.Conn SetWriteDeadline method. func (conn *HvsockConn) SetWriteDeadline(t time.Time) error { return conn.sock.SetWriteDeadline(t) } ================================================ FILE: vendor/github.com/Microsoft/go-winio/internal/fs/doc.go ================================================ // This package contains Win32 filesystem functionality. package fs ================================================ FILE: vendor/github.com/Microsoft/go-winio/internal/fs/fs.go ================================================ //go:build windows package fs import ( "golang.org/x/sys/windows" "github.com/Microsoft/go-winio/internal/stringbuffer" ) //go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go fs.go // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew //sys CreateFile(name string, access AccessMask, mode FileShareMode, sa *windows.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) [failretval==windows.InvalidHandle] = CreateFileW const NullHandle windows.Handle = 0 // AccessMask defines standard, specific, and generic rights. // // Used with CreateFile and NtCreateFile (and co.). // // Bitmask: // 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 // +---------------+---------------+-------------------------------+ // |G|G|G|G|Resvd|A| StandardRights| SpecificRights | // |R|W|E|A| |S| | | // +-+-------------+---------------+-------------------------------+ // // GR Generic Read // GW Generic Write // GE Generic Exectue // GA Generic All // Resvd Reserved // AS Access Security System // // https://learn.microsoft.com/en-us/windows/win32/secauthz/access-mask // // https://learn.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights // // https://learn.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants type AccessMask = windows.ACCESS_MASK //nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. const ( // Not actually any. // // For CreateFile: "query certain metadata such as file, directory, or device attributes without accessing that file or device" // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew#parameters FILE_ANY_ACCESS AccessMask = 0 GENERIC_READ AccessMask = 0x8000_0000 GENERIC_WRITE AccessMask = 0x4000_0000 GENERIC_EXECUTE AccessMask = 0x2000_0000 GENERIC_ALL AccessMask = 0x1000_0000 ACCESS_SYSTEM_SECURITY AccessMask = 0x0100_0000 // Specific Object Access // from ntioapi.h FILE_READ_DATA AccessMask = (0x0001) // file & pipe FILE_LIST_DIRECTORY AccessMask = (0x0001) // directory FILE_WRITE_DATA AccessMask = (0x0002) // file & pipe FILE_ADD_FILE AccessMask = (0x0002) // directory FILE_APPEND_DATA AccessMask = (0x0004) // file FILE_ADD_SUBDIRECTORY AccessMask = (0x0004) // directory FILE_CREATE_PIPE_INSTANCE AccessMask = (0x0004) // named pipe FILE_READ_EA AccessMask = (0x0008) // file & directory FILE_READ_PROPERTIES AccessMask = FILE_READ_EA FILE_WRITE_EA AccessMask = (0x0010) // file & directory FILE_WRITE_PROPERTIES AccessMask = FILE_WRITE_EA FILE_EXECUTE AccessMask = (0x0020) // file FILE_TRAVERSE AccessMask = (0x0020) // directory FILE_DELETE_CHILD AccessMask = (0x0040) // directory FILE_READ_ATTRIBUTES AccessMask = (0x0080) // all FILE_WRITE_ATTRIBUTES AccessMask = (0x0100) // all FILE_ALL_ACCESS AccessMask = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF) FILE_GENERIC_READ AccessMask = (STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE) FILE_GENERIC_WRITE AccessMask = (STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE) FILE_GENERIC_EXECUTE AccessMask = (STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE) SPECIFIC_RIGHTS_ALL AccessMask = 0x0000FFFF // Standard Access // from ntseapi.h DELETE AccessMask = 0x0001_0000 READ_CONTROL AccessMask = 0x0002_0000 WRITE_DAC AccessMask = 0x0004_0000 WRITE_OWNER AccessMask = 0x0008_0000 SYNCHRONIZE AccessMask = 0x0010_0000 STANDARD_RIGHTS_REQUIRED AccessMask = 0x000F_0000 STANDARD_RIGHTS_READ AccessMask = READ_CONTROL STANDARD_RIGHTS_WRITE AccessMask = READ_CONTROL STANDARD_RIGHTS_EXECUTE AccessMask = READ_CONTROL STANDARD_RIGHTS_ALL AccessMask = 0x001F_0000 ) type FileShareMode uint32 //nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. const ( FILE_SHARE_NONE FileShareMode = 0x00 FILE_SHARE_READ FileShareMode = 0x01 FILE_SHARE_WRITE FileShareMode = 0x02 FILE_SHARE_DELETE FileShareMode = 0x04 FILE_SHARE_VALID_FLAGS FileShareMode = 0x07 ) type FileCreationDisposition uint32 //nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. const ( // from winbase.h CREATE_NEW FileCreationDisposition = 0x01 CREATE_ALWAYS FileCreationDisposition = 0x02 OPEN_EXISTING FileCreationDisposition = 0x03 OPEN_ALWAYS FileCreationDisposition = 0x04 TRUNCATE_EXISTING FileCreationDisposition = 0x05 ) // Create disposition values for NtCreate* type NTFileCreationDisposition uint32 //nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. const ( // From ntioapi.h FILE_SUPERSEDE NTFileCreationDisposition = 0x00 FILE_OPEN NTFileCreationDisposition = 0x01 FILE_CREATE NTFileCreationDisposition = 0x02 FILE_OPEN_IF NTFileCreationDisposition = 0x03 FILE_OVERWRITE NTFileCreationDisposition = 0x04 FILE_OVERWRITE_IF NTFileCreationDisposition = 0x05 FILE_MAXIMUM_DISPOSITION NTFileCreationDisposition = 0x05 ) // CreateFile and co. take flags or attributes together as one parameter. // Define alias until we can use generics to allow both // // https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants type FileFlagOrAttribute uint32 //nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. const ( // from winnt.h FILE_FLAG_WRITE_THROUGH FileFlagOrAttribute = 0x8000_0000 FILE_FLAG_OVERLAPPED FileFlagOrAttribute = 0x4000_0000 FILE_FLAG_NO_BUFFERING FileFlagOrAttribute = 0x2000_0000 FILE_FLAG_RANDOM_ACCESS FileFlagOrAttribute = 0x1000_0000 FILE_FLAG_SEQUENTIAL_SCAN FileFlagOrAttribute = 0x0800_0000 FILE_FLAG_DELETE_ON_CLOSE FileFlagOrAttribute = 0x0400_0000 FILE_FLAG_BACKUP_SEMANTICS FileFlagOrAttribute = 0x0200_0000 FILE_FLAG_POSIX_SEMANTICS FileFlagOrAttribute = 0x0100_0000 FILE_FLAG_OPEN_REPARSE_POINT FileFlagOrAttribute = 0x0020_0000 FILE_FLAG_OPEN_NO_RECALL FileFlagOrAttribute = 0x0010_0000 FILE_FLAG_FIRST_PIPE_INSTANCE FileFlagOrAttribute = 0x0008_0000 ) // NtCreate* functions take a dedicated CreateOptions parameter. // // https://learn.microsoft.com/en-us/windows/win32/api/Winternl/nf-winternl-ntcreatefile // // https://learn.microsoft.com/en-us/windows/win32/devnotes/nt-create-named-pipe-file type NTCreateOptions uint32 //nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. const ( // From ntioapi.h FILE_DIRECTORY_FILE NTCreateOptions = 0x0000_0001 FILE_WRITE_THROUGH NTCreateOptions = 0x0000_0002 FILE_SEQUENTIAL_ONLY NTCreateOptions = 0x0000_0004 FILE_NO_INTERMEDIATE_BUFFERING NTCreateOptions = 0x0000_0008 FILE_SYNCHRONOUS_IO_ALERT NTCreateOptions = 0x0000_0010 FILE_SYNCHRONOUS_IO_NONALERT NTCreateOptions = 0x0000_0020 FILE_NON_DIRECTORY_FILE NTCreateOptions = 0x0000_0040 FILE_CREATE_TREE_CONNECTION NTCreateOptions = 0x0000_0080 FILE_COMPLETE_IF_OPLOCKED NTCreateOptions = 0x0000_0100 FILE_NO_EA_KNOWLEDGE NTCreateOptions = 0x0000_0200 FILE_DISABLE_TUNNELING NTCreateOptions = 0x0000_0400 FILE_RANDOM_ACCESS NTCreateOptions = 0x0000_0800 FILE_DELETE_ON_CLOSE NTCreateOptions = 0x0000_1000 FILE_OPEN_BY_FILE_ID NTCreateOptions = 0x0000_2000 FILE_OPEN_FOR_BACKUP_INTENT NTCreateOptions = 0x0000_4000 FILE_NO_COMPRESSION NTCreateOptions = 0x0000_8000 ) type FileSQSFlag = FileFlagOrAttribute //nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. const ( // from winbase.h SECURITY_ANONYMOUS FileSQSFlag = FileSQSFlag(SecurityAnonymous << 16) SECURITY_IDENTIFICATION FileSQSFlag = FileSQSFlag(SecurityIdentification << 16) SECURITY_IMPERSONATION FileSQSFlag = FileSQSFlag(SecurityImpersonation << 16) SECURITY_DELEGATION FileSQSFlag = FileSQSFlag(SecurityDelegation << 16) SECURITY_SQOS_PRESENT FileSQSFlag = 0x0010_0000 SECURITY_VALID_SQOS_FLAGS FileSQSFlag = 0x001F_0000 ) // GetFinalPathNameByHandle flags // // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew#parameters type GetFinalPathFlag uint32 //nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. const ( GetFinalPathDefaultFlag GetFinalPathFlag = 0x0 FILE_NAME_NORMALIZED GetFinalPathFlag = 0x0 FILE_NAME_OPENED GetFinalPathFlag = 0x8 VOLUME_NAME_DOS GetFinalPathFlag = 0x0 VOLUME_NAME_GUID GetFinalPathFlag = 0x1 VOLUME_NAME_NT GetFinalPathFlag = 0x2 VOLUME_NAME_NONE GetFinalPathFlag = 0x4 ) // getFinalPathNameByHandle facilitates calling the Windows API GetFinalPathNameByHandle // with the given handle and flags. It transparently takes care of creating a buffer of the // correct size for the call. // // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew func GetFinalPathNameByHandle(h windows.Handle, flags GetFinalPathFlag) (string, error) { b := stringbuffer.NewWString() //TODO: can loop infinitely if Win32 keeps returning the same (or a larger) n? for { n, err := windows.GetFinalPathNameByHandle(h, b.Pointer(), b.Cap(), uint32(flags)) if err != nil { return "", err } // If the buffer wasn't large enough, n will be the total size needed (including null terminator). // Resize and try again. if n > b.Cap() { b.ResizeTo(n) continue } // If the buffer is large enough, n will be the size not including the null terminator. // Convert to a Go string and return. return b.String(), nil } } ================================================ FILE: vendor/github.com/Microsoft/go-winio/internal/fs/security.go ================================================ package fs // https://learn.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level type SecurityImpersonationLevel int32 // C default enums underlying type is `int`, which is Go `int32` // Impersonation levels const ( SecurityAnonymous SecurityImpersonationLevel = 0 SecurityIdentification SecurityImpersonationLevel = 1 SecurityImpersonation SecurityImpersonationLevel = 2 SecurityDelegation SecurityImpersonationLevel = 3 ) ================================================ FILE: vendor/github.com/Microsoft/go-winio/internal/fs/zsyscall_windows.go ================================================ //go:build windows // Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT. package fs import ( "syscall" "unsafe" "golang.org/x/sys/windows" ) var _ unsafe.Pointer // Do the interface allocations only once for common // Errno values. const ( errnoERROR_IO_PENDING = 997 ) var ( errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) errERROR_EINVAL error = syscall.EINVAL ) // errnoErr returns common boxed Errno values, to prevent // allocations at runtime. func errnoErr(e syscall.Errno) error { switch e { case 0: return errERROR_EINVAL case errnoERROR_IO_PENDING: return errERROR_IO_PENDING } return e } var ( modkernel32 = windows.NewLazySystemDLL("kernel32.dll") procCreateFileW = modkernel32.NewProc("CreateFileW") ) func CreateFile(name string, access AccessMask, mode FileShareMode, sa *windows.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) { var _p0 *uint16 _p0, err = syscall.UTF16PtrFromString(name) if err != nil { return } return _CreateFile(_p0, access, mode, sa, createmode, attrs, templatefile) } func _CreateFile(name *uint16, access AccessMask, mode FileShareMode, sa *windows.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) { r0, _, e1 := syscall.SyscallN(procCreateFileW.Addr(), uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile)) handle = windows.Handle(r0) if handle == windows.InvalidHandle { err = errnoErr(e1) } return } ================================================ FILE: vendor/github.com/Microsoft/go-winio/internal/socket/rawaddr.go ================================================ package socket import ( "unsafe" ) // RawSockaddr allows structs to be used with [Bind] and [ConnectEx]. The // struct must meet the Win32 sockaddr requirements specified here: // https://docs.microsoft.com/en-us/windows/win32/winsock/sockaddr-2 // // Specifically, the struct size must be least larger than an int16 (unsigned short) // for the address family. type RawSockaddr interface { // Sockaddr returns a pointer to the RawSockaddr and its struct size, allowing // for the RawSockaddr's data to be overwritten by syscalls (if necessary). // // It is the callers responsibility to validate that the values are valid; invalid // pointers or size can cause a panic. Sockaddr() (unsafe.Pointer, int32, error) } ================================================ FILE: vendor/github.com/Microsoft/go-winio/internal/socket/socket.go ================================================ //go:build windows package socket import ( "errors" "fmt" "net" "sync" "syscall" "unsafe" "github.com/Microsoft/go-winio/pkg/guid" "golang.org/x/sys/windows" ) //go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go socket.go //sys getsockname(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) [failretval==socketError] = ws2_32.getsockname //sys getpeername(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) [failretval==socketError] = ws2_32.getpeername //sys bind(s windows.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind const socketError = uintptr(^uint32(0)) var ( // todo(helsaawy): create custom error types to store the desired vs actual size and addr family? ErrBufferSize = errors.New("buffer size") ErrAddrFamily = errors.New("address family") ErrInvalidPointer = errors.New("invalid pointer") ErrSocketClosed = fmt.Errorf("socket closed: %w", net.ErrClosed) ) // todo(helsaawy): replace these with generics, ie: GetSockName[S RawSockaddr](s windows.Handle) (S, error) // GetSockName writes the local address of socket s to the [RawSockaddr] rsa. // If rsa is not large enough, the [windows.WSAEFAULT] is returned. func GetSockName(s windows.Handle, rsa RawSockaddr) error { ptr, l, err := rsa.Sockaddr() if err != nil { return fmt.Errorf("could not retrieve socket pointer and size: %w", err) } // although getsockname returns WSAEFAULT if the buffer is too small, it does not set // &l to the correct size, so--apart from doubling the buffer repeatedly--there is no remedy return getsockname(s, ptr, &l) } // GetPeerName returns the remote address the socket is connected to. // // See [GetSockName] for more information. func GetPeerName(s windows.Handle, rsa RawSockaddr) error { ptr, l, err := rsa.Sockaddr() if err != nil { return fmt.Errorf("could not retrieve socket pointer and size: %w", err) } return getpeername(s, ptr, &l) } func Bind(s windows.Handle, rsa RawSockaddr) (err error) { ptr, l, err := rsa.Sockaddr() if err != nil { return fmt.Errorf("could not retrieve socket pointer and size: %w", err) } return bind(s, ptr, l) } // "golang.org/x/sys/windows".ConnectEx and .Bind only accept internal implementations of the // their sockaddr interface, so they cannot be used with HvsockAddr // Replicate functionality here from // https://cs.opensource.google/go/x/sys/+/master:windows/syscall_windows.go // The function pointers to `AcceptEx`, `ConnectEx` and `GetAcceptExSockaddrs` must be loaded at // runtime via a WSAIoctl call: // https://docs.microsoft.com/en-us/windows/win32/api/Mswsock/nc-mswsock-lpfn_connectex#remarks type runtimeFunc struct { id guid.GUID once sync.Once addr uintptr err error } func (f *runtimeFunc) Load() error { f.once.Do(func() { var s windows.Handle s, f.err = windows.Socket(windows.AF_INET, windows.SOCK_STREAM, windows.IPPROTO_TCP) if f.err != nil { return } defer windows.CloseHandle(s) //nolint:errcheck var n uint32 f.err = windows.WSAIoctl(s, windows.SIO_GET_EXTENSION_FUNCTION_POINTER, (*byte)(unsafe.Pointer(&f.id)), uint32(unsafe.Sizeof(f.id)), (*byte)(unsafe.Pointer(&f.addr)), uint32(unsafe.Sizeof(f.addr)), &n, nil, // overlapped 0, // completionRoutine ) }) return f.err } var ( // todo: add `AcceptEx` and `GetAcceptExSockaddrs` WSAID_CONNECTEX = guid.GUID{ //revive:disable-line:var-naming ALL_CAPS Data1: 0x25a207b9, Data2: 0xddf3, Data3: 0x4660, Data4: [8]byte{0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e}, } connectExFunc = runtimeFunc{id: WSAID_CONNECTEX} ) func ConnectEx( fd windows.Handle, rsa RawSockaddr, sendBuf *byte, sendDataLen uint32, bytesSent *uint32, overlapped *windows.Overlapped, ) error { if err := connectExFunc.Load(); err != nil { return fmt.Errorf("failed to load ConnectEx function pointer: %w", err) } ptr, n, err := rsa.Sockaddr() if err != nil { return err } return connectEx(fd, ptr, n, sendBuf, sendDataLen, bytesSent, overlapped) } // BOOL LpfnConnectex( // [in] SOCKET s, // [in] const sockaddr *name, // [in] int namelen, // [in, optional] PVOID lpSendBuffer, // [in] DWORD dwSendDataLength, // [out] LPDWORD lpdwBytesSent, // [in] LPOVERLAPPED lpOverlapped // ) func connectEx( s windows.Handle, name unsafe.Pointer, namelen int32, sendBuf *byte, sendDataLen uint32, bytesSent *uint32, overlapped *windows.Overlapped, ) (err error) { r1, _, e1 := syscall.SyscallN(connectExFunc.addr, uintptr(s), uintptr(name), uintptr(namelen), uintptr(unsafe.Pointer(sendBuf)), uintptr(sendDataLen), uintptr(unsafe.Pointer(bytesSent)), uintptr(unsafe.Pointer(overlapped)), ) if r1 == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return err } ================================================ FILE: vendor/github.com/Microsoft/go-winio/internal/socket/zsyscall_windows.go ================================================ //go:build windows // Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT. package socket import ( "syscall" "unsafe" "golang.org/x/sys/windows" ) var _ unsafe.Pointer // Do the interface allocations only once for common // Errno values. const ( errnoERROR_IO_PENDING = 997 ) var ( errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) errERROR_EINVAL error = syscall.EINVAL ) // errnoErr returns common boxed Errno values, to prevent // allocations at runtime. func errnoErr(e syscall.Errno) error { switch e { case 0: return errERROR_EINVAL case errnoERROR_IO_PENDING: return errERROR_IO_PENDING } return e } var ( modws2_32 = windows.NewLazySystemDLL("ws2_32.dll") procbind = modws2_32.NewProc("bind") procgetpeername = modws2_32.NewProc("getpeername") procgetsockname = modws2_32.NewProc("getsockname") ) func bind(s windows.Handle, name unsafe.Pointer, namelen int32) (err error) { r1, _, e1 := syscall.SyscallN(procbind.Addr(), uintptr(s), uintptr(name), uintptr(namelen)) if r1 == socketError { err = errnoErr(e1) } return } func getpeername(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) { r1, _, e1 := syscall.SyscallN(procgetpeername.Addr(), uintptr(s), uintptr(name), uintptr(unsafe.Pointer(namelen))) if r1 == socketError { err = errnoErr(e1) } return } func getsockname(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) { r1, _, e1 := syscall.SyscallN(procgetsockname.Addr(), uintptr(s), uintptr(name), uintptr(unsafe.Pointer(namelen))) if r1 == socketError { err = errnoErr(e1) } return } ================================================ FILE: vendor/github.com/Microsoft/go-winio/internal/stringbuffer/wstring.go ================================================ package stringbuffer import ( "sync" "unicode/utf16" ) // TODO: worth exporting and using in mkwinsyscall? // Uint16BufferSize is the buffer size in the pool, chosen somewhat arbitrarily to accommodate // large path strings: // MAX_PATH (260) + size of volume GUID prefix (49) + null terminator = 310. const MinWStringCap = 310 // use *[]uint16 since []uint16 creates an extra allocation where the slice header // is copied to heap and then referenced via pointer in the interface header that sync.Pool // stores. var pathPool = sync.Pool{ // if go1.18+ adds Pool[T], use that to store []uint16 directly New: func() interface{} { b := make([]uint16, MinWStringCap) return &b }, } func newBuffer() []uint16 { return *(pathPool.Get().(*[]uint16)) } // freeBuffer copies the slice header data, and puts a pointer to that in the pool. // This avoids taking a pointer to the slice header in WString, which can be set to nil. func freeBuffer(b []uint16) { pathPool.Put(&b) } // WString is a wide string buffer ([]uint16) meant for storing UTF-16 encoded strings // for interacting with Win32 APIs. // Sizes are specified as uint32 and not int. // // It is not thread safe. type WString struct { // type-def allows casting to []uint16 directly, use struct to prevent that and allow adding fields in the future. // raw buffer b []uint16 } // NewWString returns a [WString] allocated from a shared pool with an // initial capacity of at least [MinWStringCap]. // Since the buffer may have been previously used, its contents are not guaranteed to be empty. // // The buffer should be freed via [WString.Free] func NewWString() *WString { return &WString{ b: newBuffer(), } } func (b *WString) Free() { if b.empty() { return } freeBuffer(b.b) b.b = nil } // ResizeTo grows the buffer to at least c and returns the new capacity, freeing the // previous buffer back into pool. func (b *WString) ResizeTo(c uint32) uint32 { // already sufficient (or n is 0) if c <= b.Cap() { return b.Cap() } if c <= MinWStringCap { c = MinWStringCap } // allocate at-least double buffer size, as is done in [bytes.Buffer] and other places if c <= 2*b.Cap() { c = 2 * b.Cap() } b2 := make([]uint16, c) if !b.empty() { copy(b2, b.b) freeBuffer(b.b) } b.b = b2 return c } // Buffer returns the underlying []uint16 buffer. func (b *WString) Buffer() []uint16 { if b.empty() { return nil } return b.b } // Pointer returns a pointer to the first uint16 in the buffer. // If the [WString.Free] has already been called, the pointer will be nil. func (b *WString) Pointer() *uint16 { if b.empty() { return nil } return &b.b[0] } // String returns the returns the UTF-8 encoding of the UTF-16 string in the buffer. // // It assumes that the data is null-terminated. func (b *WString) String() string { // Using [windows.UTF16ToString] would require importing "golang.org/x/sys/windows" // and would make this code Windows-only, which makes no sense. // So copy UTF16ToString code into here. // If other windows-specific code is added, switch to [windows.UTF16ToString] s := b.b for i, v := range s { if v == 0 { s = s[:i] break } } return string(utf16.Decode(s)) } // Cap returns the underlying buffer capacity. func (b *WString) Cap() uint32 { if b.empty() { return 0 } return b.cap() } func (b *WString) cap() uint32 { return uint32(cap(b.b)) } func (b *WString) empty() bool { return b == nil || b.cap() == 0 } ================================================ FILE: vendor/github.com/Microsoft/go-winio/pipe.go ================================================ //go:build windows // +build windows package winio import ( "context" "errors" "fmt" "io" "net" "os" "runtime" "time" "unsafe" "golang.org/x/sys/windows" "github.com/Microsoft/go-winio/internal/fs" ) //sys connectNamedPipe(pipe windows.Handle, o *windows.Overlapped) (err error) = ConnectNamedPipe //sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *windows.SecurityAttributes) (handle windows.Handle, err error) [failretval==windows.InvalidHandle] = CreateNamedPipeW //sys disconnectNamedPipe(pipe windows.Handle) (err error) = DisconnectNamedPipe //sys getNamedPipeInfo(pipe windows.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo //sys getNamedPipeHandleState(pipe windows.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW //sys ntCreateNamedPipeFile(pipe *windows.Handle, access ntAccessMask, oa *objectAttributes, iosb *ioStatusBlock, share ntFileShareMode, disposition ntFileCreationDisposition, options ntFileOptions, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntStatus) = ntdll.NtCreateNamedPipeFile //sys rtlNtStatusToDosError(status ntStatus) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb //sys rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntStatus) = ntdll.RtlDosPathNameToNtPathName_U //sys rtlDefaultNpAcl(dacl *uintptr) (status ntStatus) = ntdll.RtlDefaultNpAcl type PipeConn interface { net.Conn Disconnect() error Flush() error } // type aliases for mkwinsyscall code type ( ntAccessMask = fs.AccessMask ntFileShareMode = fs.FileShareMode ntFileCreationDisposition = fs.NTFileCreationDisposition ntFileOptions = fs.NTCreateOptions ) type ioStatusBlock struct { Status, Information uintptr } // typedef struct _OBJECT_ATTRIBUTES { // ULONG Length; // HANDLE RootDirectory; // PUNICODE_STRING ObjectName; // ULONG Attributes; // PVOID SecurityDescriptor; // PVOID SecurityQualityOfService; // } OBJECT_ATTRIBUTES; // // https://learn.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-_object_attributes type objectAttributes struct { Length uintptr RootDirectory uintptr ObjectName *unicodeString Attributes uintptr SecurityDescriptor *securityDescriptor SecurityQoS uintptr } type unicodeString struct { Length uint16 MaximumLength uint16 Buffer uintptr } // typedef struct _SECURITY_DESCRIPTOR { // BYTE Revision; // BYTE Sbz1; // SECURITY_DESCRIPTOR_CONTROL Control; // PSID Owner; // PSID Group; // PACL Sacl; // PACL Dacl; // } SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR; // // https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-security_descriptor type securityDescriptor struct { Revision byte Sbz1 byte Control uint16 Owner uintptr Group uintptr Sacl uintptr //revive:disable-line:var-naming SACL, not Sacl Dacl uintptr //revive:disable-line:var-naming DACL, not Dacl } type ntStatus int32 func (status ntStatus) Err() error { if status >= 0 { return nil } return rtlNtStatusToDosError(status) } var ( // ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed. ErrPipeListenerClosed = net.ErrClosed errPipeWriteClosed = errors.New("pipe has been closed for write") ) type win32Pipe struct { *win32File path string } var _ PipeConn = (*win32Pipe)(nil) type win32MessageBytePipe struct { win32Pipe writeClosed bool readEOF bool } type pipeAddress string func (f *win32Pipe) LocalAddr() net.Addr { return pipeAddress(f.path) } func (f *win32Pipe) RemoteAddr() net.Addr { return pipeAddress(f.path) } func (f *win32Pipe) SetDeadline(t time.Time) error { if err := f.SetReadDeadline(t); err != nil { return err } return f.SetWriteDeadline(t) } func (f *win32Pipe) Disconnect() error { return disconnectNamedPipe(f.win32File.handle) } // CloseWrite closes the write side of a message pipe in byte mode. func (f *win32MessageBytePipe) CloseWrite() error { if f.writeClosed { return errPipeWriteClosed } err := f.win32File.Flush() if err != nil { return err } _, err = f.win32File.Write(nil) if err != nil { return err } f.writeClosed = true return nil } // Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since // they are used to implement CloseWrite(). func (f *win32MessageBytePipe) Write(b []byte) (int, error) { if f.writeClosed { return 0, errPipeWriteClosed } if len(b) == 0 { return 0, nil } return f.win32File.Write(b) } // Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message // mode pipe will return io.EOF, as will all subsequent reads. func (f *win32MessageBytePipe) Read(b []byte) (int, error) { if f.readEOF { return 0, io.EOF } n, err := f.win32File.Read(b) if err == io.EOF { //nolint:errorlint // If this was the result of a zero-byte read, then // it is possible that the read was due to a zero-size // message. Since we are simulating CloseWrite with a // zero-byte message, ensure that all future Read() calls // also return EOF. f.readEOF = true } else if err == windows.ERROR_MORE_DATA { //nolint:errorlint // err is Errno // ERROR_MORE_DATA indicates that the pipe's read mode is message mode // and the message still has more bytes. Treat this as a success, since // this package presents all named pipes as byte streams. err = nil } return n, err } func (pipeAddress) Network() string { return "pipe" } func (s pipeAddress) String() string { return string(s) } // tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout. func tryDialPipe(ctx context.Context, path *string, access fs.AccessMask, impLevel PipeImpLevel) (windows.Handle, error) { for { select { case <-ctx.Done(): return windows.Handle(0), ctx.Err() default: h, err := fs.CreateFile(*path, access, 0, // mode nil, // security attributes fs.OPEN_EXISTING, fs.FILE_FLAG_OVERLAPPED|fs.SECURITY_SQOS_PRESENT|fs.FileSQSFlag(impLevel), 0, // template file handle ) if err == nil { return h, nil } if err != windows.ERROR_PIPE_BUSY { //nolint:errorlint // err is Errno return h, &os.PathError{Err: err, Op: "open", Path: *path} } // Wait 10 msec and try again. This is a rather simplistic // view, as we always try each 10 milliseconds. time.Sleep(10 * time.Millisecond) } } } // DialPipe connects to a named pipe by path, timing out if the connection // takes longer than the specified duration. If timeout is nil, then we use // a default timeout of 2 seconds. (We do not use WaitNamedPipe.) func DialPipe(path string, timeout *time.Duration) (net.Conn, error) { var absTimeout time.Time if timeout != nil { absTimeout = time.Now().Add(*timeout) } else { absTimeout = time.Now().Add(2 * time.Second) } ctx, cancel := context.WithDeadline(context.Background(), absTimeout) defer cancel() conn, err := DialPipeContext(ctx, path) if errors.Is(err, context.DeadlineExceeded) { return nil, ErrTimeout } return conn, err } // DialPipeContext attempts to connect to a named pipe by `path` until `ctx` // cancellation or timeout. func DialPipeContext(ctx context.Context, path string) (net.Conn, error) { return DialPipeAccess(ctx, path, uint32(fs.GENERIC_READ|fs.GENERIC_WRITE)) } // PipeImpLevel is an enumeration of impersonation levels that may be set // when calling DialPipeAccessImpersonation. type PipeImpLevel uint32 const ( PipeImpLevelAnonymous = PipeImpLevel(fs.SECURITY_ANONYMOUS) PipeImpLevelIdentification = PipeImpLevel(fs.SECURITY_IDENTIFICATION) PipeImpLevelImpersonation = PipeImpLevel(fs.SECURITY_IMPERSONATION) PipeImpLevelDelegation = PipeImpLevel(fs.SECURITY_DELEGATION) ) // DialPipeAccess attempts to connect to a named pipe by `path` with `access` until `ctx` // cancellation or timeout. func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn, error) { return DialPipeAccessImpLevel(ctx, path, access, PipeImpLevelAnonymous) } // DialPipeAccessImpLevel attempts to connect to a named pipe by `path` with // `access` at `impLevel` until `ctx` cancellation or timeout. The other // DialPipe* implementations use PipeImpLevelAnonymous. func DialPipeAccessImpLevel(ctx context.Context, path string, access uint32, impLevel PipeImpLevel) (net.Conn, error) { var err error var h windows.Handle h, err = tryDialPipe(ctx, &path, fs.AccessMask(access), impLevel) if err != nil { return nil, err } var flags uint32 err = getNamedPipeInfo(h, &flags, nil, nil, nil) if err != nil { return nil, err } f, err := makeWin32File(h) if err != nil { windows.Close(h) return nil, err } // If the pipe is in message mode, return a message byte pipe, which // supports CloseWrite(). if flags&windows.PIPE_TYPE_MESSAGE != 0 { return &win32MessageBytePipe{ win32Pipe: win32Pipe{win32File: f, path: path}, }, nil } return &win32Pipe{win32File: f, path: path}, nil } type acceptResponse struct { f *win32File err error } type win32PipeListener struct { firstHandle windows.Handle path string config PipeConfig acceptCh chan (chan acceptResponse) closeCh chan int doneCh chan int } func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (windows.Handle, error) { path16, err := windows.UTF16FromString(path) if err != nil { return 0, &os.PathError{Op: "open", Path: path, Err: err} } var oa objectAttributes oa.Length = unsafe.Sizeof(oa) var ntPath unicodeString if err := rtlDosPathNameToNtPathName(&path16[0], &ntPath, 0, 0, ).Err(); err != nil { return 0, &os.PathError{Op: "open", Path: path, Err: err} } defer windows.LocalFree(windows.Handle(ntPath.Buffer)) //nolint:errcheck oa.ObjectName = &ntPath oa.Attributes = windows.OBJ_CASE_INSENSITIVE // The security descriptor is only needed for the first pipe. if first { if sd != nil { //todo: does `sdb` need to be allocated on the heap, or can go allocate it? l := uint32(len(sd)) sdb, err := windows.LocalAlloc(0, l) if err != nil { return 0, fmt.Errorf("LocalAlloc for security descriptor with of length %d: %w", l, err) } defer windows.LocalFree(windows.Handle(sdb)) //nolint:errcheck copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd) oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb)) } else { // Construct the default named pipe security descriptor. var dacl uintptr if err := rtlDefaultNpAcl(&dacl).Err(); err != nil { return 0, fmt.Errorf("getting default named pipe ACL: %w", err) } defer windows.LocalFree(windows.Handle(dacl)) //nolint:errcheck sdb := &securityDescriptor{ Revision: 1, Control: windows.SE_DACL_PRESENT, Dacl: dacl, } oa.SecurityDescriptor = sdb } } typ := uint32(windows.FILE_PIPE_REJECT_REMOTE_CLIENTS) if c.MessageMode { typ |= windows.FILE_PIPE_MESSAGE_TYPE } disposition := fs.FILE_OPEN access := fs.GENERIC_READ | fs.GENERIC_WRITE | fs.SYNCHRONIZE if first { disposition = fs.FILE_CREATE // By not asking for read or write access, the named pipe file system // will put this pipe into an initially disconnected state, blocking // client connections until the next call with first == false. access = fs.SYNCHRONIZE } timeout := int64(-50 * 10000) // 50ms var ( h windows.Handle iosb ioStatusBlock ) err = ntCreateNamedPipeFile(&h, access, &oa, &iosb, fs.FILE_SHARE_READ|fs.FILE_SHARE_WRITE, disposition, 0, typ, 0, 0, 0xffffffff, uint32(c.InputBufferSize), uint32(c.OutputBufferSize), &timeout).Err() if err != nil { return 0, &os.PathError{Op: "open", Path: path, Err: err} } runtime.KeepAlive(ntPath) return h, nil } func (l *win32PipeListener) makeServerPipe() (*win32File, error) { h, err := makeServerPipeHandle(l.path, nil, &l.config, false) if err != nil { return nil, err } f, err := makeWin32File(h) if err != nil { windows.Close(h) return nil, err } return f, nil } func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) { p, err := l.makeServerPipe() if err != nil { return nil, err } // Wait for the client to connect. ch := make(chan error) go func(p *win32File) { ch <- connectPipe(p) }(p) select { case err = <-ch: if err != nil { p.Close() p = nil } case <-l.closeCh: // Abort the connect request by closing the handle. p.Close() p = nil err = <-ch if err == nil || err == ErrFileClosed { //nolint:errorlint // err is Errno err = ErrPipeListenerClosed } } return p, err } func (l *win32PipeListener) listenerRoutine() { closed := false for !closed { select { case <-l.closeCh: closed = true case responseCh := <-l.acceptCh: var ( p *win32File err error ) for { p, err = l.makeConnectedServerPipe() // If the connection was immediately closed by the client, try // again. if err != windows.ERROR_NO_DATA { //nolint:errorlint // err is Errno break } } responseCh <- acceptResponse{p, err} closed = err == ErrPipeListenerClosed //nolint:errorlint // err is Errno } } windows.Close(l.firstHandle) l.firstHandle = 0 // Notify Close() and Accept() callers that the handle has been closed. close(l.doneCh) } // PipeConfig contain configuration for the pipe listener. type PipeConfig struct { // SecurityDescriptor contains a Windows security descriptor in SDDL format. SecurityDescriptor string // MessageMode determines whether the pipe is in byte or message mode. In either // case the pipe is read in byte mode by default. The only practical difference in // this implementation is that CloseWrite() is only supported for message mode pipes; // CloseWrite() is implemented as a zero-byte write, but zero-byte writes are only // transferred to the reader (and returned as io.EOF in this implementation) // when the pipe is in message mode. MessageMode bool // InputBufferSize specifies the size of the input buffer, in bytes. InputBufferSize int32 // OutputBufferSize specifies the size of the output buffer, in bytes. OutputBufferSize int32 } // ListenPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe. // The pipe must not already exist. func ListenPipe(path string, c *PipeConfig) (net.Listener, error) { var ( sd []byte err error ) if c == nil { c = &PipeConfig{} } if c.SecurityDescriptor != "" { sd, err = SddlToSecurityDescriptor(c.SecurityDescriptor) if err != nil { return nil, err } } h, err := makeServerPipeHandle(path, sd, c, true) if err != nil { return nil, err } l := &win32PipeListener{ firstHandle: h, path: path, config: *c, acceptCh: make(chan (chan acceptResponse)), closeCh: make(chan int), doneCh: make(chan int), } go l.listenerRoutine() return l, nil } func connectPipe(p *win32File) error { c, err := p.prepareIO() if err != nil { return err } defer p.wg.Done() err = connectNamedPipe(p.handle, &c.o) _, err = p.asyncIO(c, nil, 0, err) if err != nil && err != windows.ERROR_PIPE_CONNECTED { //nolint:errorlint // err is Errno return err } return nil } func (l *win32PipeListener) Accept() (net.Conn, error) { ch := make(chan acceptResponse) select { case l.acceptCh <- ch: response := <-ch err := response.err if err != nil { return nil, err } if l.config.MessageMode { return &win32MessageBytePipe{ win32Pipe: win32Pipe{win32File: response.f, path: l.path}, }, nil } return &win32Pipe{win32File: response.f, path: l.path}, nil case <-l.doneCh: return nil, ErrPipeListenerClosed } } func (l *win32PipeListener) Close() error { select { case l.closeCh <- 1: <-l.doneCh case <-l.doneCh: } return nil } func (l *win32PipeListener) Addr() net.Addr { return pipeAddress(l.path) } ================================================ FILE: vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go ================================================ // Package guid provides a GUID type. The backing structure for a GUID is // identical to that used by the golang.org/x/sys/windows GUID type. // There are two main binary encodings used for a GUID, the big-endian encoding, // and the Windows (mixed-endian) encoding. See here for details: // https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding package guid import ( "crypto/rand" "crypto/sha1" //nolint:gosec // not used for secure application "encoding" "encoding/binary" "fmt" "strconv" ) //go:generate go run golang.org/x/tools/cmd/stringer -type=Variant -trimprefix=Variant -linecomment // Variant specifies which GUID variant (or "type") of the GUID. It determines // how the entirety of the rest of the GUID is interpreted. type Variant uint8 // The variants specified by RFC 4122 section 4.1.1. const ( // VariantUnknown specifies a GUID variant which does not conform to one of // the variant encodings specified in RFC 4122. VariantUnknown Variant = iota VariantNCS VariantRFC4122 // RFC 4122 VariantMicrosoft VariantFuture ) // Version specifies how the bits in the GUID were generated. For instance, a // version 4 GUID is randomly generated, and a version 5 is generated from the // hash of an input string. type Version uint8 func (v Version) String() string { return strconv.FormatUint(uint64(v), 10) } var _ = (encoding.TextMarshaler)(GUID{}) var _ = (encoding.TextUnmarshaler)(&GUID{}) // NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122. func NewV4() (GUID, error) { var b [16]byte if _, err := rand.Read(b[:]); err != nil { return GUID{}, err } g := FromArray(b) g.setVersion(4) // Version 4 means randomly generated. g.setVariant(VariantRFC4122) return g, nil } // NewV5 returns a new version 5 (generated from a string via SHA-1 hashing) // GUID, as defined by RFC 4122. The RFC is unclear on the encoding of the name, // and the sample code treats it as a series of bytes, so we do the same here. // // Some implementations, such as those found on Windows, treat the name as a // big-endian UTF16 stream of bytes. If that is desired, the string can be // encoded as such before being passed to this function. func NewV5(namespace GUID, name []byte) (GUID, error) { b := sha1.New() //nolint:gosec // not used for secure application namespaceBytes := namespace.ToArray() b.Write(namespaceBytes[:]) b.Write(name) a := [16]byte{} copy(a[:], b.Sum(nil)) g := FromArray(a) g.setVersion(5) // Version 5 means generated from a string. g.setVariant(VariantRFC4122) return g, nil } func fromArray(b [16]byte, order binary.ByteOrder) GUID { var g GUID g.Data1 = order.Uint32(b[0:4]) g.Data2 = order.Uint16(b[4:6]) g.Data3 = order.Uint16(b[6:8]) copy(g.Data4[:], b[8:16]) return g } func (g GUID) toArray(order binary.ByteOrder) [16]byte { b := [16]byte{} order.PutUint32(b[0:4], g.Data1) order.PutUint16(b[4:6], g.Data2) order.PutUint16(b[6:8], g.Data3) copy(b[8:16], g.Data4[:]) return b } // FromArray constructs a GUID from a big-endian encoding array of 16 bytes. func FromArray(b [16]byte) GUID { return fromArray(b, binary.BigEndian) } // ToArray returns an array of 16 bytes representing the GUID in big-endian // encoding. func (g GUID) ToArray() [16]byte { return g.toArray(binary.BigEndian) } // FromWindowsArray constructs a GUID from a Windows encoding array of bytes. func FromWindowsArray(b [16]byte) GUID { return fromArray(b, binary.LittleEndian) } // ToWindowsArray returns an array of 16 bytes representing the GUID in Windows // encoding. func (g GUID) ToWindowsArray() [16]byte { return g.toArray(binary.LittleEndian) } func (g GUID) String() string { return fmt.Sprintf( "%08x-%04x-%04x-%04x-%012x", g.Data1, g.Data2, g.Data3, g.Data4[:2], g.Data4[2:]) } // FromString parses a string containing a GUID and returns the GUID. The only // format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` // format. func FromString(s string) (GUID, error) { if len(s) != 36 { return GUID{}, fmt.Errorf("invalid GUID %q", s) } if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { return GUID{}, fmt.Errorf("invalid GUID %q", s) } var g GUID data1, err := strconv.ParseUint(s[0:8], 16, 32) if err != nil { return GUID{}, fmt.Errorf("invalid GUID %q", s) } g.Data1 = uint32(data1) data2, err := strconv.ParseUint(s[9:13], 16, 16) if err != nil { return GUID{}, fmt.Errorf("invalid GUID %q", s) } g.Data2 = uint16(data2) data3, err := strconv.ParseUint(s[14:18], 16, 16) if err != nil { return GUID{}, fmt.Errorf("invalid GUID %q", s) } g.Data3 = uint16(data3) for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} { v, err := strconv.ParseUint(s[x:x+2], 16, 8) if err != nil { return GUID{}, fmt.Errorf("invalid GUID %q", s) } g.Data4[i] = uint8(v) } return g, nil } func (g *GUID) setVariant(v Variant) { d := g.Data4[0] switch v { case VariantNCS: d = (d & 0x7f) case VariantRFC4122: d = (d & 0x3f) | 0x80 case VariantMicrosoft: d = (d & 0x1f) | 0xc0 case VariantFuture: d = (d & 0x0f) | 0xe0 case VariantUnknown: fallthrough default: panic(fmt.Sprintf("invalid variant: %d", v)) } g.Data4[0] = d } // Variant returns the GUID variant, as defined in RFC 4122. func (g GUID) Variant() Variant { b := g.Data4[0] if b&0x80 == 0 { return VariantNCS } else if b&0xc0 == 0x80 { return VariantRFC4122 } else if b&0xe0 == 0xc0 { return VariantMicrosoft } else if b&0xe0 == 0xe0 { return VariantFuture } return VariantUnknown } func (g *GUID) setVersion(v Version) { g.Data3 = (g.Data3 & 0x0fff) | (uint16(v) << 12) } // Version returns the GUID version, as defined in RFC 4122. func (g GUID) Version() Version { return Version((g.Data3 & 0xF000) >> 12) } // MarshalText returns the textual representation of the GUID. func (g GUID) MarshalText() ([]byte, error) { return []byte(g.String()), nil } // UnmarshalText takes the textual representation of a GUID, and unmarhals it // into this GUID. func (g *GUID) UnmarshalText(text []byte) error { g2, err := FromString(string(text)) if err != nil { return err } *g = g2 return nil } ================================================ FILE: vendor/github.com/Microsoft/go-winio/pkg/guid/guid_nonwindows.go ================================================ //go:build !windows // +build !windows package guid // GUID represents a GUID/UUID. It has the same structure as // golang.org/x/sys/windows.GUID so that it can be used with functions expecting // that type. It is defined as its own type as that is only available to builds // targeted at `windows`. The representation matches that used by native Windows // code. type GUID struct { Data1 uint32 Data2 uint16 Data3 uint16 Data4 [8]byte } ================================================ FILE: vendor/github.com/Microsoft/go-winio/pkg/guid/guid_windows.go ================================================ //go:build windows // +build windows package guid import "golang.org/x/sys/windows" // GUID represents a GUID/UUID. It has the same structure as // golang.org/x/sys/windows.GUID so that it can be used with functions expecting // that type. It is defined as its own type so that stringification and // marshaling can be supported. The representation matches that used by native // Windows code. type GUID windows.GUID ================================================ FILE: vendor/github.com/Microsoft/go-winio/pkg/guid/variant_string.go ================================================ // Code generated by "stringer -type=Variant -trimprefix=Variant -linecomment"; DO NOT EDIT. package guid import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[VariantUnknown-0] _ = x[VariantNCS-1] _ = x[VariantRFC4122-2] _ = x[VariantMicrosoft-3] _ = x[VariantFuture-4] } const _Variant_name = "UnknownNCSRFC 4122MicrosoftFuture" var _Variant_index = [...]uint8{0, 7, 10, 18, 27, 33} func (i Variant) String() string { if i >= Variant(len(_Variant_index)-1) { return "Variant(" + strconv.FormatInt(int64(i), 10) + ")" } return _Variant_name[_Variant_index[i]:_Variant_index[i+1]] } ================================================ FILE: vendor/github.com/Microsoft/go-winio/privilege.go ================================================ //go:build windows // +build windows package winio import ( "bytes" "encoding/binary" "fmt" "runtime" "sync" "unicode/utf16" "golang.org/x/sys/windows" ) //sys adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges //sys impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf //sys revertToSelf() (err error) = advapi32.RevertToSelf //sys openThreadToken(thread windows.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken //sys getCurrentThread() (h windows.Handle) = GetCurrentThread //sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW //sys lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW //sys lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW const ( //revive:disable-next-line:var-naming ALL_CAPS SE_PRIVILEGE_ENABLED = windows.SE_PRIVILEGE_ENABLED //revive:disable-next-line:var-naming ALL_CAPS ERROR_NOT_ALL_ASSIGNED windows.Errno = windows.ERROR_NOT_ALL_ASSIGNED SeBackupPrivilege = "SeBackupPrivilege" SeRestorePrivilege = "SeRestorePrivilege" SeSecurityPrivilege = "SeSecurityPrivilege" ) var ( privNames = make(map[string]uint64) privNameMutex sync.Mutex ) // PrivilegeError represents an error enabling privileges. type PrivilegeError struct { privileges []uint64 } func (e *PrivilegeError) Error() string { s := "Could not enable privilege " if len(e.privileges) > 1 { s = "Could not enable privileges " } for i, p := range e.privileges { if i != 0 { s += ", " } s += `"` s += getPrivilegeName(p) s += `"` } return s } // RunWithPrivilege enables a single privilege for a function call. func RunWithPrivilege(name string, fn func() error) error { return RunWithPrivileges([]string{name}, fn) } // RunWithPrivileges enables privileges for a function call. func RunWithPrivileges(names []string, fn func() error) error { privileges, err := mapPrivileges(names) if err != nil { return err } runtime.LockOSThread() defer runtime.UnlockOSThread() token, err := newThreadToken() if err != nil { return err } defer releaseThreadToken(token) err = adjustPrivileges(token, privileges, SE_PRIVILEGE_ENABLED) if err != nil { return err } return fn() } func mapPrivileges(names []string) ([]uint64, error) { privileges := make([]uint64, 0, len(names)) privNameMutex.Lock() defer privNameMutex.Unlock() for _, name := range names { p, ok := privNames[name] if !ok { err := lookupPrivilegeValue("", name, &p) if err != nil { return nil, err } privNames[name] = p } privileges = append(privileges, p) } return privileges, nil } // EnableProcessPrivileges enables privileges globally for the process. func EnableProcessPrivileges(names []string) error { return enableDisableProcessPrivilege(names, SE_PRIVILEGE_ENABLED) } // DisableProcessPrivileges disables privileges globally for the process. func DisableProcessPrivileges(names []string) error { return enableDisableProcessPrivilege(names, 0) } func enableDisableProcessPrivilege(names []string, action uint32) error { privileges, err := mapPrivileges(names) if err != nil { return err } p := windows.CurrentProcess() var token windows.Token err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token) if err != nil { return err } defer token.Close() return adjustPrivileges(token, privileges, action) } func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error { var b bytes.Buffer _ = binary.Write(&b, binary.LittleEndian, uint32(len(privileges))) for _, p := range privileges { _ = binary.Write(&b, binary.LittleEndian, p) _ = binary.Write(&b, binary.LittleEndian, action) } prevState := make([]byte, b.Len()) reqSize := uint32(0) success, err := adjustTokenPrivileges(token, false, &b.Bytes()[0], uint32(len(prevState)), &prevState[0], &reqSize) if !success { return err } if err == ERROR_NOT_ALL_ASSIGNED { //nolint:errorlint // err is Errno return &PrivilegeError{privileges} } return nil } func getPrivilegeName(luid uint64) string { var nameBuffer [256]uint16 bufSize := uint32(len(nameBuffer)) err := lookupPrivilegeName("", &luid, &nameBuffer[0], &bufSize) if err != nil { return fmt.Sprintf("", luid) } var displayNameBuffer [256]uint16 displayBufSize := uint32(len(displayNameBuffer)) var langID uint32 err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID) if err != nil { return fmt.Sprintf("", string(utf16.Decode(nameBuffer[:bufSize]))) } return string(utf16.Decode(displayNameBuffer[:displayBufSize])) } func newThreadToken() (windows.Token, error) { err := impersonateSelf(windows.SecurityImpersonation) if err != nil { return 0, err } var token windows.Token err = openThreadToken(getCurrentThread(), windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, false, &token) if err != nil { rerr := revertToSelf() if rerr != nil { panic(rerr) } return 0, err } return token, nil } func releaseThreadToken(h windows.Token) { err := revertToSelf() if err != nil { panic(err) } h.Close() } ================================================ FILE: vendor/github.com/Microsoft/go-winio/reparse.go ================================================ //go:build windows // +build windows package winio import ( "bytes" "encoding/binary" "fmt" "strings" "unicode/utf16" "unsafe" ) const ( reparseTagMountPoint = 0xA0000003 reparseTagSymlink = 0xA000000C ) type reparseDataBuffer struct { ReparseTag uint32 ReparseDataLength uint16 Reserved uint16 SubstituteNameOffset uint16 SubstituteNameLength uint16 PrintNameOffset uint16 PrintNameLength uint16 } // ReparsePoint describes a Win32 symlink or mount point. type ReparsePoint struct { Target string IsMountPoint bool } // UnsupportedReparsePointError is returned when trying to decode a non-symlink or // mount point reparse point. type UnsupportedReparsePointError struct { Tag uint32 } func (e *UnsupportedReparsePointError) Error() string { return fmt.Sprintf("unsupported reparse point %x", e.Tag) } // DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink // or a mount point. func DecodeReparsePoint(b []byte) (*ReparsePoint, error) { tag := binary.LittleEndian.Uint32(b[0:4]) return DecodeReparsePointData(tag, b[8:]) } func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) { isMountPoint := false switch tag { case reparseTagMountPoint: isMountPoint = true case reparseTagSymlink: default: return nil, &UnsupportedReparsePointError{tag} } nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6]) if !isMountPoint { nameOffset += 4 } nameLength := binary.LittleEndian.Uint16(b[6:8]) name := make([]uint16, nameLength/2) err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name) if err != nil { return nil, err } return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil } func isDriveLetter(c byte) bool { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') } // EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or // mount point. func EncodeReparsePoint(rp *ReparsePoint) []byte { // Generate an NT path and determine if this is a relative path. var ntTarget string relative := false if strings.HasPrefix(rp.Target, `\\?\`) { ntTarget = `\??\` + rp.Target[4:] } else if strings.HasPrefix(rp.Target, `\\`) { ntTarget = `\??\UNC\` + rp.Target[2:] } else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' { ntTarget = `\??\` + rp.Target } else { ntTarget = rp.Target relative = true } // The paths must be NUL-terminated even though they are counted strings. target16 := utf16.Encode([]rune(rp.Target + "\x00")) ntTarget16 := utf16.Encode([]rune(ntTarget + "\x00")) size := int(unsafe.Sizeof(reparseDataBuffer{})) - 8 size += len(ntTarget16)*2 + len(target16)*2 tag := uint32(reparseTagMountPoint) if !rp.IsMountPoint { tag = reparseTagSymlink size += 4 // Add room for symlink flags } data := reparseDataBuffer{ ReparseTag: tag, ReparseDataLength: uint16(size), SubstituteNameOffset: 0, SubstituteNameLength: uint16((len(ntTarget16) - 1) * 2), PrintNameOffset: uint16(len(ntTarget16) * 2), PrintNameLength: uint16((len(target16) - 1) * 2), } var b bytes.Buffer _ = binary.Write(&b, binary.LittleEndian, &data) if !rp.IsMountPoint { flags := uint32(0) if relative { flags |= 1 } _ = binary.Write(&b, binary.LittleEndian, flags) } _ = binary.Write(&b, binary.LittleEndian, ntTarget16) _ = binary.Write(&b, binary.LittleEndian, target16) return b.Bytes() } ================================================ FILE: vendor/github.com/Microsoft/go-winio/sd.go ================================================ //go:build windows // +build windows package winio import ( "errors" "fmt" "unsafe" "golang.org/x/sys/windows" ) //sys lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountNameW //sys lookupAccountSid(systemName *uint16, sid *byte, name *uint16, nameSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountSidW //sys convertSidToStringSid(sid *byte, str **uint16) (err error) = advapi32.ConvertSidToStringSidW //sys convertStringSidToSid(str *uint16, sid **byte) (err error) = advapi32.ConvertStringSidToSidW type AccountLookupError struct { Name string Err error } func (e *AccountLookupError) Error() string { if e.Name == "" { return "lookup account: empty account name specified" } var s string switch { case errors.Is(e.Err, windows.ERROR_INVALID_SID): s = "the security ID structure is invalid" case errors.Is(e.Err, windows.ERROR_NONE_MAPPED): s = "not found" default: s = e.Err.Error() } return "lookup account " + e.Name + ": " + s } func (e *AccountLookupError) Unwrap() error { return e.Err } type SddlConversionError struct { Sddl string Err error } func (e *SddlConversionError) Error() string { return "convert " + e.Sddl + ": " + e.Err.Error() } func (e *SddlConversionError) Unwrap() error { return e.Err } // LookupSidByName looks up the SID of an account by name // //revive:disable-next-line:var-naming SID, not Sid func LookupSidByName(name string) (sid string, err error) { if name == "" { return "", &AccountLookupError{name, windows.ERROR_NONE_MAPPED} } var sidSize, sidNameUse, refDomainSize uint32 err = lookupAccountName(nil, name, nil, &sidSize, nil, &refDomainSize, &sidNameUse) if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // err is Errno return "", &AccountLookupError{name, err} } sidBuffer := make([]byte, sidSize) refDomainBuffer := make([]uint16, refDomainSize) err = lookupAccountName(nil, name, &sidBuffer[0], &sidSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse) if err != nil { return "", &AccountLookupError{name, err} } var strBuffer *uint16 err = convertSidToStringSid(&sidBuffer[0], &strBuffer) if err != nil { return "", &AccountLookupError{name, err} } sid = windows.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(strBuffer))[:]) _, _ = windows.LocalFree(windows.Handle(unsafe.Pointer(strBuffer))) return sid, nil } // LookupNameBySid looks up the name of an account by SID // //revive:disable-next-line:var-naming SID, not Sid func LookupNameBySid(sid string) (name string, err error) { if sid == "" { return "", &AccountLookupError{sid, windows.ERROR_NONE_MAPPED} } sidBuffer, err := windows.UTF16PtrFromString(sid) if err != nil { return "", &AccountLookupError{sid, err} } var sidPtr *byte if err = convertStringSidToSid(sidBuffer, &sidPtr); err != nil { return "", &AccountLookupError{sid, err} } defer windows.LocalFree(windows.Handle(unsafe.Pointer(sidPtr))) //nolint:errcheck var nameSize, refDomainSize, sidNameUse uint32 err = lookupAccountSid(nil, sidPtr, nil, &nameSize, nil, &refDomainSize, &sidNameUse) if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // err is Errno return "", &AccountLookupError{sid, err} } nameBuffer := make([]uint16, nameSize) refDomainBuffer := make([]uint16, refDomainSize) err = lookupAccountSid(nil, sidPtr, &nameBuffer[0], &nameSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse) if err != nil { return "", &AccountLookupError{sid, err} } name = windows.UTF16ToString(nameBuffer) return name, nil } func SddlToSecurityDescriptor(sddl string) ([]byte, error) { sd, err := windows.SecurityDescriptorFromString(sddl) if err != nil { return nil, &SddlConversionError{Sddl: sddl, Err: err} } b := unsafe.Slice((*byte)(unsafe.Pointer(sd)), sd.Length()) return b, nil } func SecurityDescriptorToSddl(sd []byte) (string, error) { if l := int(unsafe.Sizeof(windows.SECURITY_DESCRIPTOR{})); len(sd) < l { return "", fmt.Errorf("SecurityDescriptor (%d) smaller than expected (%d): %w", len(sd), l, windows.ERROR_INCORRECT_SIZE) } s := (*windows.SECURITY_DESCRIPTOR)(unsafe.Pointer(&sd[0])) return s.String(), nil } ================================================ FILE: vendor/github.com/Microsoft/go-winio/syscall.go ================================================ //go:build windows package winio //go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go ./*.go ================================================ FILE: vendor/github.com/Microsoft/go-winio/zsyscall_windows.go ================================================ //go:build windows // Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT. package winio import ( "syscall" "unsafe" "golang.org/x/sys/windows" ) var _ unsafe.Pointer // Do the interface allocations only once for common // Errno values. const ( errnoERROR_IO_PENDING = 997 ) var ( errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) errERROR_EINVAL error = syscall.EINVAL ) // errnoErr returns common boxed Errno values, to prevent // allocations at runtime. func errnoErr(e syscall.Errno) error { switch e { case 0: return errERROR_EINVAL case errnoERROR_IO_PENDING: return errERROR_IO_PENDING } return e } var ( modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") modkernel32 = windows.NewLazySystemDLL("kernel32.dll") modntdll = windows.NewLazySystemDLL("ntdll.dll") modws2_32 = windows.NewLazySystemDLL("ws2_32.dll") procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges") procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW") procConvertStringSidToSidW = modadvapi32.NewProc("ConvertStringSidToSidW") procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf") procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW") procLookupAccountSidW = modadvapi32.NewProc("LookupAccountSidW") procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW") procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW") procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW") procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken") procRevertToSelf = modadvapi32.NewProc("RevertToSelf") procBackupRead = modkernel32.NewProc("BackupRead") procBackupWrite = modkernel32.NewProc("BackupWrite") procCancelIoEx = modkernel32.NewProc("CancelIoEx") procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe") procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort") procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW") procDisconnectNamedPipe = modkernel32.NewProc("DisconnectNamedPipe") procGetCurrentThread = modkernel32.NewProc("GetCurrentThread") procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW") procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo") procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus") procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes") procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile") procRtlDefaultNpAcl = modntdll.NewProc("RtlDefaultNpAcl") procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U") procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb") procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult") ) func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) { var _p0 uint32 if releaseAll { _p0 = 1 } r0, _, e1 := syscall.SyscallN(procAdjustTokenPrivileges.Addr(), uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize))) success = r0 != 0 if true { err = errnoErr(e1) } return } func convertSidToStringSid(sid *byte, str **uint16) (err error) { r1, _, e1 := syscall.SyscallN(procConvertSidToStringSidW.Addr(), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str))) if r1 == 0 { err = errnoErr(e1) } return } func convertStringSidToSid(str *uint16, sid **byte) (err error) { r1, _, e1 := syscall.SyscallN(procConvertStringSidToSidW.Addr(), uintptr(unsafe.Pointer(str)), uintptr(unsafe.Pointer(sid))) if r1 == 0 { err = errnoErr(e1) } return } func impersonateSelf(level uint32) (err error) { r1, _, e1 := syscall.SyscallN(procImpersonateSelf.Addr(), uintptr(level)) if r1 == 0 { err = errnoErr(e1) } return } func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) { var _p0 *uint16 _p0, err = syscall.UTF16PtrFromString(accountName) if err != nil { return } return _lookupAccountName(systemName, _p0, sid, sidSize, refDomain, refDomainSize, sidNameUse) } func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) { r1, _, e1 := syscall.SyscallN(procLookupAccountNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse))) if r1 == 0 { err = errnoErr(e1) } return } func lookupAccountSid(systemName *uint16, sid *byte, name *uint16, nameSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) { r1, _, e1 := syscall.SyscallN(procLookupAccountSidW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse))) if r1 == 0 { err = errnoErr(e1) } return } func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) { var _p0 *uint16 _p0, err = syscall.UTF16PtrFromString(systemName) if err != nil { return } return _lookupPrivilegeDisplayName(_p0, name, buffer, size, languageId) } func _lookupPrivilegeDisplayName(systemName *uint16, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) { r1, _, e1 := syscall.SyscallN(procLookupPrivilegeDisplayNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId))) if r1 == 0 { err = errnoErr(e1) } return } func lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) { var _p0 *uint16 _p0, err = syscall.UTF16PtrFromString(systemName) if err != nil { return } return _lookupPrivilegeName(_p0, luid, buffer, size) } func _lookupPrivilegeName(systemName *uint16, luid *uint64, buffer *uint16, size *uint32) (err error) { r1, _, e1 := syscall.SyscallN(procLookupPrivilegeNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size))) if r1 == 0 { err = errnoErr(e1) } return } func lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) { var _p0 *uint16 _p0, err = syscall.UTF16PtrFromString(systemName) if err != nil { return } var _p1 *uint16 _p1, err = syscall.UTF16PtrFromString(name) if err != nil { return } return _lookupPrivilegeValue(_p0, _p1, luid) } func _lookupPrivilegeValue(systemName *uint16, name *uint16, luid *uint64) (err error) { r1, _, e1 := syscall.SyscallN(procLookupPrivilegeValueW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid))) if r1 == 0 { err = errnoErr(e1) } return } func openThreadToken(thread windows.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) { var _p0 uint32 if openAsSelf { _p0 = 1 } r1, _, e1 := syscall.SyscallN(procOpenThreadToken.Addr(), uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token))) if r1 == 0 { err = errnoErr(e1) } return } func revertToSelf() (err error) { r1, _, e1 := syscall.SyscallN(procRevertToSelf.Addr()) if r1 == 0 { err = errnoErr(e1) } return } func backupRead(h windows.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) { var _p0 *byte if len(b) > 0 { _p0 = &b[0] } var _p1 uint32 if abort { _p1 = 1 } var _p2 uint32 if processSecurity { _p2 = 1 } r1, _, e1 := syscall.SyscallN(procBackupRead.Addr(), uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context))) if r1 == 0 { err = errnoErr(e1) } return } func backupWrite(h windows.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) { var _p0 *byte if len(b) > 0 { _p0 = &b[0] } var _p1 uint32 if abort { _p1 = 1 } var _p2 uint32 if processSecurity { _p2 = 1 } r1, _, e1 := syscall.SyscallN(procBackupWrite.Addr(), uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context))) if r1 == 0 { err = errnoErr(e1) } return } func cancelIoEx(file windows.Handle, o *windows.Overlapped) (err error) { r1, _, e1 := syscall.SyscallN(procCancelIoEx.Addr(), uintptr(file), uintptr(unsafe.Pointer(o))) if r1 == 0 { err = errnoErr(e1) } return } func connectNamedPipe(pipe windows.Handle, o *windows.Overlapped) (err error) { r1, _, e1 := syscall.SyscallN(procConnectNamedPipe.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(o))) if r1 == 0 { err = errnoErr(e1) } return } func createIoCompletionPort(file windows.Handle, port windows.Handle, key uintptr, threadCount uint32) (newport windows.Handle, err error) { r0, _, e1 := syscall.SyscallN(procCreateIoCompletionPort.Addr(), uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount)) newport = windows.Handle(r0) if newport == 0 { err = errnoErr(e1) } return } func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *windows.SecurityAttributes) (handle windows.Handle, err error) { var _p0 *uint16 _p0, err = syscall.UTF16PtrFromString(name) if err != nil { return } return _createNamedPipe(_p0, flags, pipeMode, maxInstances, outSize, inSize, defaultTimeout, sa) } func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *windows.SecurityAttributes) (handle windows.Handle, err error) { r0, _, e1 := syscall.SyscallN(procCreateNamedPipeW.Addr(), uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa))) handle = windows.Handle(r0) if handle == windows.InvalidHandle { err = errnoErr(e1) } return } func disconnectNamedPipe(pipe windows.Handle) (err error) { r1, _, e1 := syscall.SyscallN(procDisconnectNamedPipe.Addr(), uintptr(pipe)) if r1 == 0 { err = errnoErr(e1) } return } func getCurrentThread() (h windows.Handle) { r0, _, _ := syscall.SyscallN(procGetCurrentThread.Addr()) h = windows.Handle(r0) return } func getNamedPipeHandleState(pipe windows.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) { r1, _, e1 := syscall.SyscallN(procGetNamedPipeHandleStateW.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize)) if r1 == 0 { err = errnoErr(e1) } return } func getNamedPipeInfo(pipe windows.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) { r1, _, e1 := syscall.SyscallN(procGetNamedPipeInfo.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances))) if r1 == 0 { err = errnoErr(e1) } return } func getQueuedCompletionStatus(port windows.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) { r1, _, e1 := syscall.SyscallN(procGetQueuedCompletionStatus.Addr(), uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout)) if r1 == 0 { err = errnoErr(e1) } return } func setFileCompletionNotificationModes(h windows.Handle, flags uint8) (err error) { r1, _, e1 := syscall.SyscallN(procSetFileCompletionNotificationModes.Addr(), uintptr(h), uintptr(flags)) if r1 == 0 { err = errnoErr(e1) } return } func ntCreateNamedPipeFile(pipe *windows.Handle, access ntAccessMask, oa *objectAttributes, iosb *ioStatusBlock, share ntFileShareMode, disposition ntFileCreationDisposition, options ntFileOptions, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntStatus) { r0, _, _ := syscall.SyscallN(procNtCreateNamedPipeFile.Addr(), uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout))) status = ntStatus(r0) return } func rtlDefaultNpAcl(dacl *uintptr) (status ntStatus) { r0, _, _ := syscall.SyscallN(procRtlDefaultNpAcl.Addr(), uintptr(unsafe.Pointer(dacl))) status = ntStatus(r0) return } func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntStatus) { r0, _, _ := syscall.SyscallN(procRtlDosPathNameToNtPathName_U.Addr(), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved)) status = ntStatus(r0) return } func rtlNtStatusToDosError(status ntStatus) (winerr error) { r0, _, _ := syscall.SyscallN(procRtlNtStatusToDosErrorNoTeb.Addr(), uintptr(status)) if r0 != 0 { winerr = syscall.Errno(r0) } return } func wsaGetOverlappedResult(h windows.Handle, o *windows.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) { var _p0 uint32 if wait { _p0 = 1 } r1, _, e1 := syscall.SyscallN(procWSAGetOverlappedResult.Addr(), uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags))) if r1 == 0 { err = errnoErr(e1) } return } ================================================ FILE: vendor/github.com/cenkalti/backoff/v4/.gitignore ================================================ # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe # IDEs .idea/ ================================================ FILE: vendor/github.com/cenkalti/backoff/v4/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 Cenk Altı Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: vendor/github.com/cenkalti/backoff/v4/README.md ================================================ # Exponential Backoff [![GoDoc][godoc image]][godoc] [![Build Status][travis image]][travis] [![Coverage Status][coveralls image]][coveralls] This is a Go port of the exponential backoff algorithm from [Google's HTTP Client Library for Java][google-http-java-client]. [Exponential backoff][exponential backoff wiki] is an algorithm that uses feedback to multiplicatively decrease the rate of some process, in order to gradually find an acceptable rate. The retries exponentially increase and stop increasing when a certain threshold is met. ## Usage Import path is `github.com/cenkalti/backoff/v4`. Please note the version part at the end. Use https://pkg.go.dev/github.com/cenkalti/backoff/v4 to view the documentation. ## Contributing * I would like to keep this library as small as possible. * Please don't send a PR without opening an issue and discussing it first. * If proposed change is not a common use case, I will probably not accept it. [godoc]: https://pkg.go.dev/github.com/cenkalti/backoff/v4 [godoc image]: https://godoc.org/github.com/cenkalti/backoff?status.png [travis]: https://travis-ci.org/cenkalti/backoff [travis image]: https://travis-ci.org/cenkalti/backoff.png?branch=master [coveralls]: https://coveralls.io/github/cenkalti/backoff?branch=master [coveralls image]: https://coveralls.io/repos/github/cenkalti/backoff/badge.svg?branch=master [google-http-java-client]: https://github.com/google/google-http-java-client/blob/da1aa993e90285ec18579f1553339b00e19b3ab5/google-http-client/src/main/java/com/google/api/client/util/ExponentialBackOff.java [exponential backoff wiki]: http://en.wikipedia.org/wiki/Exponential_backoff [advanced example]: https://pkg.go.dev/github.com/cenkalti/backoff/v4?tab=doc#pkg-examples ================================================ FILE: vendor/github.com/cenkalti/backoff/v4/backoff.go ================================================ // Package backoff implements backoff algorithms for retrying operations. // // Use Retry function for retrying operations that may fail. // If Retry does not meet your needs, // copy/paste the function into your project and modify as you wish. // // There is also Ticker type similar to time.Ticker. // You can use it if you need to work with channels. // // See Examples section below for usage examples. package backoff import "time" // BackOff is a backoff policy for retrying an operation. type BackOff interface { // NextBackOff returns the duration to wait before retrying the operation, // or backoff. Stop to indicate that no more retries should be made. // // Example usage: // // duration := backoff.NextBackOff(); // if (duration == backoff.Stop) { // // Do not retry operation. // } else { // // Sleep for duration and retry operation. // } // NextBackOff() time.Duration // Reset to initial state. Reset() } // Stop indicates that no more retries should be made for use in NextBackOff(). const Stop time.Duration = -1 // ZeroBackOff is a fixed backoff policy whose backoff time is always zero, // meaning that the operation is retried immediately without waiting, indefinitely. type ZeroBackOff struct{} func (b *ZeroBackOff) Reset() {} func (b *ZeroBackOff) NextBackOff() time.Duration { return 0 } // StopBackOff is a fixed backoff policy that always returns backoff.Stop for // NextBackOff(), meaning that the operation should never be retried. type StopBackOff struct{} func (b *StopBackOff) Reset() {} func (b *StopBackOff) NextBackOff() time.Duration { return Stop } // ConstantBackOff is a backoff policy that always returns the same backoff delay. // This is in contrast to an exponential backoff policy, // which returns a delay that grows longer as you call NextBackOff() over and over again. type ConstantBackOff struct { Interval time.Duration } func (b *ConstantBackOff) Reset() {} func (b *ConstantBackOff) NextBackOff() time.Duration { return b.Interval } func NewConstantBackOff(d time.Duration) *ConstantBackOff { return &ConstantBackOff{Interval: d} } ================================================ FILE: vendor/github.com/cenkalti/backoff/v4/context.go ================================================ package backoff import ( "context" "time" ) // BackOffContext is a backoff policy that stops retrying after the context // is canceled. type BackOffContext interface { // nolint: golint BackOff Context() context.Context } type backOffContext struct { BackOff ctx context.Context } // WithContext returns a BackOffContext with context ctx // // ctx must not be nil func WithContext(b BackOff, ctx context.Context) BackOffContext { // nolint: golint if ctx == nil { panic("nil context") } if b, ok := b.(*backOffContext); ok { return &backOffContext{ BackOff: b.BackOff, ctx: ctx, } } return &backOffContext{ BackOff: b, ctx: ctx, } } func getContext(b BackOff) context.Context { if cb, ok := b.(BackOffContext); ok { return cb.Context() } if tb, ok := b.(*backOffTries); ok { return getContext(tb.delegate) } return context.Background() } func (b *backOffContext) Context() context.Context { return b.ctx } func (b *backOffContext) NextBackOff() time.Duration { select { case <-b.ctx.Done(): return Stop default: return b.BackOff.NextBackOff() } } ================================================ FILE: vendor/github.com/cenkalti/backoff/v4/exponential.go ================================================ package backoff import ( "math/rand" "time" ) /* ExponentialBackOff is a backoff implementation that increases the backoff period for each retry attempt using a randomization function that grows exponentially. NextBackOff() is calculated using the following formula: randomized interval = RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor]) In other words NextBackOff() will range between the randomization factor percentage below and above the retry interval. For example, given the following parameters: RetryInterval = 2 RandomizationFactor = 0.5 Multiplier = 2 the actual backoff period used in the next retry attempt will range between 1 and 3 seconds, multiplied by the exponential, that is, between 2 and 6 seconds. Note: MaxInterval caps the RetryInterval and not the randomized interval. If the time elapsed since an ExponentialBackOff instance is created goes past the MaxElapsedTime, then the method NextBackOff() starts returning backoff.Stop. The elapsed time can be reset by calling Reset(). Example: Given the following default arguments, for 10 tries the sequence will be, and assuming we go over the MaxElapsedTime on the 10th try: Request # RetryInterval (seconds) Randomized Interval (seconds) 1 0.5 [0.25, 0.75] 2 0.75 [0.375, 1.125] 3 1.125 [0.562, 1.687] 4 1.687 [0.8435, 2.53] 5 2.53 [1.265, 3.795] 6 3.795 [1.897, 5.692] 7 5.692 [2.846, 8.538] 8 8.538 [4.269, 12.807] 9 12.807 [6.403, 19.210] 10 19.210 backoff.Stop Note: Implementation is not thread-safe. */ type ExponentialBackOff struct { InitialInterval time.Duration RandomizationFactor float64 Multiplier float64 MaxInterval time.Duration // After MaxElapsedTime the ExponentialBackOff returns Stop. // It never stops if MaxElapsedTime == 0. MaxElapsedTime time.Duration Stop time.Duration Clock Clock currentInterval time.Duration startTime time.Time } // Clock is an interface that returns current time for BackOff. type Clock interface { Now() time.Time } // Default values for ExponentialBackOff. const ( DefaultInitialInterval = 500 * time.Millisecond DefaultRandomizationFactor = 0.5 DefaultMultiplier = 1.5 DefaultMaxInterval = 60 * time.Second DefaultMaxElapsedTime = 15 * time.Minute ) // NewExponentialBackOff creates an instance of ExponentialBackOff using default values. func NewExponentialBackOff() *ExponentialBackOff { b := &ExponentialBackOff{ InitialInterval: DefaultInitialInterval, RandomizationFactor: DefaultRandomizationFactor, Multiplier: DefaultMultiplier, MaxInterval: DefaultMaxInterval, MaxElapsedTime: DefaultMaxElapsedTime, Stop: Stop, Clock: SystemClock, } b.Reset() return b } type systemClock struct{} func (t systemClock) Now() time.Time { return time.Now() } // SystemClock implements Clock interface that uses time.Now(). var SystemClock = systemClock{} // Reset the interval back to the initial retry interval and restarts the timer. // Reset must be called before using b. func (b *ExponentialBackOff) Reset() { b.currentInterval = b.InitialInterval b.startTime = b.Clock.Now() } // NextBackOff calculates the next backoff interval using the formula: // Randomized interval = RetryInterval * (1 ± RandomizationFactor) func (b *ExponentialBackOff) NextBackOff() time.Duration { // Make sure we have not gone over the maximum elapsed time. elapsed := b.GetElapsedTime() next := getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval) b.incrementCurrentInterval() if b.MaxElapsedTime != 0 && elapsed+next > b.MaxElapsedTime { return b.Stop } return next } // GetElapsedTime returns the elapsed time since an ExponentialBackOff instance // is created and is reset when Reset() is called. // // The elapsed time is computed using time.Now().UnixNano(). It is // safe to call even while the backoff policy is used by a running // ticker. func (b *ExponentialBackOff) GetElapsedTime() time.Duration { return b.Clock.Now().Sub(b.startTime) } // Increments the current interval by multiplying it with the multiplier. func (b *ExponentialBackOff) incrementCurrentInterval() { // Check for overflow, if overflow is detected set the current interval to the max interval. if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier { b.currentInterval = b.MaxInterval } else { b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier) } } // Returns a random value from the following interval: // [currentInterval - randomizationFactor * currentInterval, currentInterval + randomizationFactor * currentInterval]. func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration { if randomizationFactor == 0 { return currentInterval // make sure no randomness is used when randomizationFactor is 0. } var delta = randomizationFactor * float64(currentInterval) var minInterval = float64(currentInterval) - delta var maxInterval = float64(currentInterval) + delta // Get a random value from the range [minInterval, maxInterval]. // The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then // we want a 33% chance for selecting either 1, 2 or 3. return time.Duration(minInterval + (random * (maxInterval - minInterval + 1))) } ================================================ FILE: vendor/github.com/cenkalti/backoff/v4/retry.go ================================================ package backoff import ( "errors" "time" ) // An OperationWithData is executing by RetryWithData() or RetryNotifyWithData(). // The operation will be retried using a backoff policy if it returns an error. type OperationWithData[T any] func() (T, error) // An Operation is executing by Retry() or RetryNotify(). // The operation will be retried using a backoff policy if it returns an error. type Operation func() error func (o Operation) withEmptyData() OperationWithData[struct{}] { return func() (struct{}, error) { return struct{}{}, o() } } // Notify is a notify-on-error function. It receives an operation error and // backoff delay if the operation failed (with an error). // // NOTE that if the backoff policy stated to stop retrying, // the notify function isn't called. type Notify func(error, time.Duration) // Retry the operation o until it does not return error or BackOff stops. // o is guaranteed to be run at least once. // // If o returns a *PermanentError, the operation is not retried, and the // wrapped error is returned. // // Retry sleeps the goroutine for the duration returned by BackOff after a // failed operation returns. func Retry(o Operation, b BackOff) error { return RetryNotify(o, b, nil) } // RetryWithData is like Retry but returns data in the response too. func RetryWithData[T any](o OperationWithData[T], b BackOff) (T, error) { return RetryNotifyWithData(o, b, nil) } // RetryNotify calls notify function with the error and wait duration // for each failed attempt before sleep. func RetryNotify(operation Operation, b BackOff, notify Notify) error { return RetryNotifyWithTimer(operation, b, notify, nil) } // RetryNotifyWithData is like RetryNotify but returns data in the response too. func RetryNotifyWithData[T any](operation OperationWithData[T], b BackOff, notify Notify) (T, error) { return doRetryNotify(operation, b, notify, nil) } // RetryNotifyWithTimer calls notify function with the error and wait duration using the given Timer // for each failed attempt before sleep. // A default timer that uses system timer is used when nil is passed. func RetryNotifyWithTimer(operation Operation, b BackOff, notify Notify, t Timer) error { _, err := doRetryNotify(operation.withEmptyData(), b, notify, t) return err } // RetryNotifyWithTimerAndData is like RetryNotifyWithTimer but returns data in the response too. func RetryNotifyWithTimerAndData[T any](operation OperationWithData[T], b BackOff, notify Notify, t Timer) (T, error) { return doRetryNotify(operation, b, notify, t) } func doRetryNotify[T any](operation OperationWithData[T], b BackOff, notify Notify, t Timer) (T, error) { var ( err error next time.Duration res T ) if t == nil { t = &defaultTimer{} } defer func() { t.Stop() }() ctx := getContext(b) b.Reset() for { res, err = operation() if err == nil { return res, nil } var permanent *PermanentError if errors.As(err, &permanent) { return res, permanent.Err } if next = b.NextBackOff(); next == Stop { if cerr := ctx.Err(); cerr != nil { return res, cerr } return res, err } if notify != nil { notify(err, next) } t.Start(next) select { case <-ctx.Done(): return res, ctx.Err() case <-t.C(): } } } // PermanentError signals that the operation should not be retried. type PermanentError struct { Err error } func (e *PermanentError) Error() string { return e.Err.Error() } func (e *PermanentError) Unwrap() error { return e.Err } func (e *PermanentError) Is(target error) bool { _, ok := target.(*PermanentError) return ok } // Permanent wraps the given err in a *PermanentError. func Permanent(err error) error { if err == nil { return nil } return &PermanentError{ Err: err, } } ================================================ FILE: vendor/github.com/cenkalti/backoff/v4/ticker.go ================================================ package backoff import ( "context" "sync" "time" ) // Ticker holds a channel that delivers `ticks' of a clock at times reported by a BackOff. // // Ticks will continue to arrive when the previous operation is still running, // so operations that take a while to fail could run in quick succession. type Ticker struct { C <-chan time.Time c chan time.Time b BackOff ctx context.Context timer Timer stop chan struct{} stopOnce sync.Once } // NewTicker returns a new Ticker containing a channel that will send // the time at times specified by the BackOff argument. Ticker is // guaranteed to tick at least once. The channel is closed when Stop // method is called or BackOff stops. It is not safe to manipulate the // provided backoff policy (notably calling NextBackOff or Reset) // while the ticker is running. func NewTicker(b BackOff) *Ticker { return NewTickerWithTimer(b, &defaultTimer{}) } // NewTickerWithTimer returns a new Ticker with a custom timer. // A default timer that uses system timer is used when nil is passed. func NewTickerWithTimer(b BackOff, timer Timer) *Ticker { if timer == nil { timer = &defaultTimer{} } c := make(chan time.Time) t := &Ticker{ C: c, c: c, b: b, ctx: getContext(b), timer: timer, stop: make(chan struct{}), } t.b.Reset() go t.run() return t } // Stop turns off a ticker. After Stop, no more ticks will be sent. func (t *Ticker) Stop() { t.stopOnce.Do(func() { close(t.stop) }) } func (t *Ticker) run() { c := t.c defer close(c) // Ticker is guaranteed to tick at least once. afterC := t.send(time.Now()) for { if afterC == nil { return } select { case tick := <-afterC: afterC = t.send(tick) case <-t.stop: t.c = nil // Prevent future ticks from being sent to the channel. return case <-t.ctx.Done(): return } } } func (t *Ticker) send(tick time.Time) <-chan time.Time { select { case t.c <- tick: case <-t.stop: return nil } next := t.b.NextBackOff() if next == Stop { t.Stop() return nil } t.timer.Start(next) return t.timer.C() } ================================================ FILE: vendor/github.com/cenkalti/backoff/v4/timer.go ================================================ package backoff import "time" type Timer interface { Start(duration time.Duration) Stop() C() <-chan time.Time } // defaultTimer implements Timer interface using time.Timer type defaultTimer struct { timer *time.Timer } // C returns the timers channel which receives the current time when the timer fires. func (t *defaultTimer) C() <-chan time.Time { return t.timer.C } // Start starts the timer to fire after the given duration func (t *defaultTimer) Start(duration time.Duration) { if t.timer == nil { t.timer = time.NewTimer(duration) } else { t.timer.Reset(duration) } } // Stop is called when the timer is not used anymore and resources may be freed. func (t *defaultTimer) Stop() { if t.timer != nil { t.timer.Stop() } } ================================================ FILE: vendor/github.com/cenkalti/backoff/v4/tries.go ================================================ package backoff import "time" /* WithMaxRetries creates a wrapper around another BackOff, which will return Stop if NextBackOff() has been called too many times since the last time Reset() was called Note: Implementation is not thread-safe. */ func WithMaxRetries(b BackOff, max uint64) BackOff { return &backOffTries{delegate: b, maxTries: max} } type backOffTries struct { delegate BackOff maxTries uint64 numTries uint64 } func (b *backOffTries) NextBackOff() time.Duration { if b.maxTries == 0 { return Stop } if b.maxTries > 0 { if b.maxTries <= b.numTries { return Stop } b.numTries++ } return b.delegate.NextBackOff() } func (b *backOffTries) Reset() { b.numTries = 0 b.delegate.Reset() } ================================================ FILE: vendor/github.com/containerd/log/.golangci.yml ================================================ linters: enable: - exportloopref # Checks for pointers to enclosing loop variables - gofmt - goimports - gosec - ineffassign - misspell - nolintlint - revive - staticcheck - tenv # Detects using os.Setenv instead of t.Setenv since Go 1.17 - unconvert - unused - vet - dupword # Checks for duplicate words in the source code disable: - errcheck run: timeout: 5m skip-dirs: - api - cluster - design - docs - docs/man - releases - reports - test # e2e scripts ================================================ FILE: vendor/github.com/containerd/log/LICENSE ================================================ Apache License Version 2.0, January 2004 https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Copyright The containerd Authors 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 https://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. ================================================ FILE: vendor/github.com/containerd/log/README.md ================================================ # log A Go package providing a common logging interface across containerd repositories and a way for clients to use and configure logging in containerd packages. This package is not intended to be used as a standalone logging package outside of the containerd ecosystem and is intended as an interface wrapper around a logging implementation. In the future this package may be replaced with a common go logging interface. ## Project details **log** is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE). As a containerd sub-project, you will find the: * [Project governance](https://github.com/containerd/project/blob/main/GOVERNANCE.md), * [Maintainers](https://github.com/containerd/project/blob/main/MAINTAINERS), * and [Contributing guidelines](https://github.com/containerd/project/blob/main/CONTRIBUTING.md) information in our [`containerd/project`](https://github.com/containerd/project) repository. ================================================ FILE: vendor/github.com/containerd/log/context.go ================================================ /* Copyright The containerd Authors. 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. */ // Package log provides types and functions related to logging, passing // loggers through a context, and attaching context to the logger. // // # Transitional types // // This package contains various types that are aliases for types in [logrus]. // These aliases are intended for transitioning away from hard-coding logrus // as logging implementation. Consumers of this package are encouraged to use // the type-aliases from this package instead of directly using their logrus // equivalent. // // The intent is to replace these aliases with locally defined types and // interfaces once all consumers are no longer directly importing logrus // types. // // IMPORTANT: due to the transitional purpose of this package, it is not // guaranteed for the full logrus API to be provided in the future. As // outlined, these aliases are provided as a step to transition away from // a specific implementation which, as a result, exposes the full logrus API. // While no decisions have been made on the ultimate design and interface // provided by this package, we do not expect carrying "less common" features. package log import ( "context" "fmt" "github.com/sirupsen/logrus" ) // G is a shorthand for [GetLogger]. // // We may want to define this locally to a package to get package tagged log // messages. var G = GetLogger // L is an alias for the standard logger. var L = &Entry{ Logger: logrus.StandardLogger(), // Default is three fields plus a little extra room. Data: make(Fields, 6), } type loggerKey struct{} // Fields type to pass to "WithFields". type Fields = map[string]any // Entry is a logging entry. It contains all the fields passed with // [Entry.WithFields]. It's finally logged when Trace, Debug, Info, Warn, // Error, Fatal or Panic is called on it. These objects can be reused and // passed around as much as you wish to avoid field duplication. // // Entry is a transitional type, and currently an alias for [logrus.Entry]. type Entry = logrus.Entry // RFC3339NanoFixed is [time.RFC3339Nano] with nanoseconds padded using // zeros to ensure the formatted time is always the same number of // characters. const RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00" // Level is a logging level. type Level = logrus.Level // Supported log levels. const ( // TraceLevel level. Designates finer-grained informational events // than [DebugLevel]. TraceLevel Level = logrus.TraceLevel // DebugLevel level. Usually only enabled when debugging. Very verbose // logging. DebugLevel Level = logrus.DebugLevel // InfoLevel level. General operational entries about what's going on // inside the application. InfoLevel Level = logrus.InfoLevel // WarnLevel level. Non-critical entries that deserve eyes. WarnLevel Level = logrus.WarnLevel // ErrorLevel level. Logs errors that should definitely be noted. // Commonly used for hooks to send errors to an error tracking service. ErrorLevel Level = logrus.ErrorLevel // FatalLevel level. Logs and then calls "logger.Exit(1)". It exits // even if the logging level is set to Panic. FatalLevel Level = logrus.FatalLevel // PanicLevel level. This is the highest level of severity. Logs and // then calls panic with the message passed to Debug, Info, ... PanicLevel Level = logrus.PanicLevel ) // SetLevel sets log level globally. It returns an error if the given // level is not supported. // // level can be one of: // // - "trace" ([TraceLevel]) // - "debug" ([DebugLevel]) // - "info" ([InfoLevel]) // - "warn" ([WarnLevel]) // - "error" ([ErrorLevel]) // - "fatal" ([FatalLevel]) // - "panic" ([PanicLevel]) func SetLevel(level string) error { lvl, err := logrus.ParseLevel(level) if err != nil { return err } L.Logger.SetLevel(lvl) return nil } // GetLevel returns the current log level. func GetLevel() Level { return L.Logger.GetLevel() } // OutputFormat specifies a log output format. type OutputFormat string // Supported log output formats. const ( // TextFormat represents the text logging format. TextFormat OutputFormat = "text" // JSONFormat represents the JSON logging format. JSONFormat OutputFormat = "json" ) // SetFormat sets the log output format ([TextFormat] or [JSONFormat]). func SetFormat(format OutputFormat) error { switch format { case TextFormat: L.Logger.SetFormatter(&logrus.TextFormatter{ TimestampFormat: RFC3339NanoFixed, FullTimestamp: true, }) return nil case JSONFormat: L.Logger.SetFormatter(&logrus.JSONFormatter{ TimestampFormat: RFC3339NanoFixed, }) return nil default: return fmt.Errorf("unknown log format: %s", format) } } // WithLogger returns a new context with the provided logger. Use in // combination with logger.WithField(s) for great effect. func WithLogger(ctx context.Context, logger *Entry) context.Context { return context.WithValue(ctx, loggerKey{}, logger.WithContext(ctx)) } // GetLogger retrieves the current logger from the context. If no logger is // available, the default logger is returned. func GetLogger(ctx context.Context) *Entry { if logger := ctx.Value(loggerKey{}); logger != nil { return logger.(*Entry) } return L.WithContext(ctx) } ================================================ FILE: vendor/github.com/containerd/platforms/.gitattributes ================================================ *.go text eol=lf ================================================ FILE: vendor/github.com/containerd/platforms/.golangci.yml ================================================ linters: enable: - exportloopref # Checks for pointers to enclosing loop variables - gofmt - goimports - gosec - ineffassign - misspell - nolintlint - revive - staticcheck - tenv # Detects using os.Setenv instead of t.Setenv since Go 1.17 - unconvert - unused - vet - dupword # Checks for duplicate words in the source code disable: - errcheck run: timeout: 5m skip-dirs: - api - cluster - design - docs - docs/man - releases - reports - test # e2e scripts ================================================ FILE: vendor/github.com/containerd/platforms/LICENSE ================================================ Apache License Version 2.0, January 2004 https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Copyright The containerd Authors 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 https://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. ================================================ FILE: vendor/github.com/containerd/platforms/README.md ================================================ # platforms A Go package for formatting, normalizing and matching container platforms. This package is based on the Open Containers Image Spec definition of a [platform](https://github.com/opencontainers/image-spec/blob/main/specs-go/v1/descriptor.go#L52). ## Platform Specifier While the OCI platform specifications provide a tool for components to specify structured information, user input typically doesn't need the full context and much can be inferred. To solve this problem, this package introduces "specifiers". A specifier has the format `||/[/]`. The user can provide either the operating system or the architecture or both. An example of a common specifier is `linux/amd64`. If the host has a default runtime that matches this, the user can simply provide the component that matters. For example, if an image provides `amd64` and `arm64` support, the operating system, `linux` can be inferred, so they only have to provide `arm64` or `amd64`. Similar behavior is implemented for operating systems, where the architecture may be known but a runtime may support images from different operating systems. ## Project details **platforms** is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE). As a containerd sub-project, you will find the: * [Project governance](https://github.com/containerd/project/blob/main/GOVERNANCE.md), * [Maintainers](https://github.com/containerd/project/blob/main/MAINTAINERS), * and [Contributing guidelines](https://github.com/containerd/project/blob/main/CONTRIBUTING.md) information in our [`containerd/project`](https://github.com/containerd/project) repository. ================================================ FILE: vendor/github.com/containerd/platforms/compare.go ================================================ /* Copyright The containerd Authors. 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. */ package platforms import ( "strconv" "strings" specs "github.com/opencontainers/image-spec/specs-go/v1" ) // MatchComparer is able to match and compare platforms to // filter and sort platforms. type MatchComparer interface { Matcher Less(specs.Platform, specs.Platform) bool } // platformVector returns an (ordered) vector of appropriate specs.Platform // objects to try matching for the given platform object (see platforms.Only). func platformVector(platform specs.Platform) []specs.Platform { vector := []specs.Platform{platform} switch platform.Architecture { case "amd64": if amd64Version, err := strconv.Atoi(strings.TrimPrefix(platform.Variant, "v")); err == nil && amd64Version > 1 { for amd64Version--; amd64Version >= 1; amd64Version-- { vector = append(vector, specs.Platform{ Architecture: platform.Architecture, OS: platform.OS, OSVersion: platform.OSVersion, OSFeatures: platform.OSFeatures, Variant: "v" + strconv.Itoa(amd64Version), }) } } vector = append(vector, specs.Platform{ Architecture: "386", OS: platform.OS, OSVersion: platform.OSVersion, OSFeatures: platform.OSFeatures, }) case "arm": if armVersion, err := strconv.Atoi(strings.TrimPrefix(platform.Variant, "v")); err == nil && armVersion > 5 { for armVersion--; armVersion >= 5; armVersion-- { vector = append(vector, specs.Platform{ Architecture: platform.Architecture, OS: platform.OS, OSVersion: platform.OSVersion, OSFeatures: platform.OSFeatures, Variant: "v" + strconv.Itoa(armVersion), }) } } case "arm64": variant := platform.Variant if variant == "" { variant = "v8" } vector = append(vector, platformVector(specs.Platform{ Architecture: "arm", OS: platform.OS, OSVersion: platform.OSVersion, OSFeatures: platform.OSFeatures, Variant: variant, })...) } return vector } // Only returns a match comparer for a single platform // using default resolution logic for the platform. // // For arm/v8, will also match arm/v7, arm/v6 and arm/v5 // For arm/v7, will also match arm/v6 and arm/v5 // For arm/v6, will also match arm/v5 // For amd64, will also match 386 func Only(platform specs.Platform) MatchComparer { return Ordered(platformVector(Normalize(platform))...) } // OnlyStrict returns a match comparer for a single platform. // // Unlike Only, OnlyStrict does not match sub platforms. // So, "arm/vN" will not match "arm/vM" where M < N, // and "amd64" will not also match "386". // // OnlyStrict matches non-canonical forms. // So, "arm64" matches "arm/64/v8". func OnlyStrict(platform specs.Platform) MatchComparer { return Ordered(Normalize(platform)) } // Ordered returns a platform MatchComparer which matches any of the platforms // but orders them in order they are provided. func Ordered(platforms ...specs.Platform) MatchComparer { matchers := make([]Matcher, len(platforms)) for i := range platforms { matchers[i] = NewMatcher(platforms[i]) } return orderedPlatformComparer{ matchers: matchers, } } // Any returns a platform MatchComparer which matches any of the platforms // with no preference for ordering. func Any(platforms ...specs.Platform) MatchComparer { matchers := make([]Matcher, len(platforms)) for i := range platforms { matchers[i] = NewMatcher(platforms[i]) } return anyPlatformComparer{ matchers: matchers, } } // All is a platform MatchComparer which matches all platforms // with preference for ordering. var All MatchComparer = allPlatformComparer{} type orderedPlatformComparer struct { matchers []Matcher } func (c orderedPlatformComparer) Match(platform specs.Platform) bool { for _, m := range c.matchers { if m.Match(platform) { return true } } return false } func (c orderedPlatformComparer) Less(p1 specs.Platform, p2 specs.Platform) bool { for _, m := range c.matchers { p1m := m.Match(p1) p2m := m.Match(p2) if p1m && !p2m { return true } if p1m || p2m { return false } } return false } type anyPlatformComparer struct { matchers []Matcher } func (c anyPlatformComparer) Match(platform specs.Platform) bool { for _, m := range c.matchers { if m.Match(platform) { return true } } return false } func (c anyPlatformComparer) Less(p1, p2 specs.Platform) bool { var p1m, p2m bool for _, m := range c.matchers { if !p1m && m.Match(p1) { p1m = true } if !p2m && m.Match(p2) { p2m = true } if p1m && p2m { return false } } // If one matches, and the other does, sort match first return p1m && !p2m } type allPlatformComparer struct{} func (allPlatformComparer) Match(specs.Platform) bool { return true } func (allPlatformComparer) Less(specs.Platform, specs.Platform) bool { return false } ================================================ FILE: vendor/github.com/containerd/platforms/cpuinfo.go ================================================ /* Copyright The containerd Authors. 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. */ package platforms import ( "runtime" "sync" "github.com/containerd/log" ) // Present the ARM instruction set architecture, eg: v7, v8 // Don't use this value directly; call cpuVariant() instead. var cpuVariantValue string var cpuVariantOnce sync.Once func cpuVariant() string { cpuVariantOnce.Do(func() { if isArmArch(runtime.GOARCH) { var err error cpuVariantValue, err = getCPUVariant() if err != nil { log.L.Errorf("Error getCPUVariant for OS %s: %v", runtime.GOOS, err) } } }) return cpuVariantValue } ================================================ FILE: vendor/github.com/containerd/platforms/cpuinfo_linux.go ================================================ /* Copyright The containerd Authors. 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. */ package platforms import ( "bufio" "bytes" "errors" "fmt" "os" "runtime" "strings" "golang.org/x/sys/unix" ) // getMachineArch retrieves the machine architecture through system call func getMachineArch() (string, error) { var uname unix.Utsname err := unix.Uname(&uname) if err != nil { return "", err } arch := string(uname.Machine[:bytes.IndexByte(uname.Machine[:], 0)]) return arch, nil } // For Linux, the kernel has already detected the ABI, ISA and Features. // So we don't need to access the ARM registers to detect platform information // by ourselves. We can just parse these information from /proc/cpuinfo func getCPUInfo(pattern string) (info string, err error) { cpuinfo, err := os.Open("/proc/cpuinfo") if err != nil { return "", err } defer cpuinfo.Close() // Start to Parse the Cpuinfo line by line. For SMP SoC, we parse // the first core is enough. scanner := bufio.NewScanner(cpuinfo) for scanner.Scan() { newline := scanner.Text() list := strings.Split(newline, ":") if len(list) > 1 && strings.EqualFold(strings.TrimSpace(list[0]), pattern) { return strings.TrimSpace(list[1]), nil } } // Check whether the scanner encountered errors err = scanner.Err() if err != nil { return "", err } return "", fmt.Errorf("getCPUInfo for pattern %s: %w", pattern, errNotFound) } // getCPUVariantFromArch get CPU variant from arch through a system call func getCPUVariantFromArch(arch string) (string, error) { var variant string arch = strings.ToLower(arch) if arch == "aarch64" { variant = "8" } else if arch[0:4] == "armv" && len(arch) >= 5 { // Valid arch format is in form of armvXx switch arch[3:5] { case "v8": variant = "8" case "v7": variant = "7" case "v6": variant = "6" case "v5": variant = "5" case "v4": variant = "4" case "v3": variant = "3" default: variant = "unknown" } } else { return "", fmt.Errorf("getCPUVariantFromArch invalid arch: %s, %w", arch, errInvalidArgument) } return variant, nil } // getCPUVariant returns cpu variant for ARM // We first try reading "Cpu architecture" field from /proc/cpuinfo // If we can't find it, then fall back using a system call // This is to cover running ARM in emulated environment on x86 host as this field in /proc/cpuinfo // was not present. func getCPUVariant() (string, error) { variant, err := getCPUInfo("Cpu architecture") if err != nil { if errors.Is(err, errNotFound) { // Let's try getting CPU variant from machine architecture arch, err := getMachineArch() if err != nil { return "", fmt.Errorf("failure getting machine architecture: %v", err) } variant, err = getCPUVariantFromArch(arch) if err != nil { return "", fmt.Errorf("failure getting CPU variant from machine architecture: %v", err) } } else { return "", fmt.Errorf("failure getting CPU variant: %v", err) } } // handle edge case for Raspberry Pi ARMv6 devices (which due to a kernel quirk, report "CPU architecture: 7") // https://www.raspberrypi.org/forums/viewtopic.php?t=12614 if runtime.GOARCH == "arm" && variant == "7" { model, err := getCPUInfo("model name") if err == nil && strings.HasPrefix(strings.ToLower(model), "armv6-compatible") { variant = "6" } } switch strings.ToLower(variant) { case "8", "aarch64": variant = "v8" case "7", "7m", "?(12)", "?(13)", "?(14)", "?(15)", "?(16)", "?(17)": variant = "v7" case "6", "6tej": variant = "v6" case "5", "5t", "5te", "5tej": variant = "v5" case "4", "4t": variant = "v4" case "3": variant = "v3" default: variant = "unknown" } return variant, nil } ================================================ FILE: vendor/github.com/containerd/platforms/cpuinfo_other.go ================================================ //go:build !linux /* Copyright The containerd Authors. 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. */ package platforms import ( "fmt" "runtime" ) func getCPUVariant() (string, error) { var variant string if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { // Windows/Darwin only supports v7 for ARM32 and v8 for ARM64 and so we can use // runtime.GOARCH to determine the variants switch runtime.GOARCH { case "arm64": variant = "v8" case "arm": variant = "v7" default: variant = "unknown" } } else if runtime.GOOS == "freebsd" { // FreeBSD supports ARMv6 and ARMv7 as well as ARMv4 and ARMv5 (though deprecated) // detecting those variants is currently unimplemented switch runtime.GOARCH { case "arm64": variant = "v8" default: variant = "unknown" } } else { return "", fmt.Errorf("getCPUVariant for OS %s: %v", runtime.GOOS, errNotImplemented) } return variant, nil } ================================================ FILE: vendor/github.com/containerd/platforms/database.go ================================================ /* Copyright The containerd Authors. 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. */ package platforms import ( "runtime" "strings" ) // These function are generated from https://golang.org/src/go/build/syslist.go. // // We use switch statements because they are slightly faster than map lookups // and use a little less memory. // isKnownOS returns true if we know about the operating system. // // The OS value should be normalized before calling this function. func isKnownOS(os string) bool { switch os { case "aix", "android", "darwin", "dragonfly", "freebsd", "hurd", "illumos", "ios", "js", "linux", "nacl", "netbsd", "openbsd", "plan9", "solaris", "windows", "zos": return true } return false } // isArmArch returns true if the architecture is ARM. // // The arch value should be normalized before being passed to this function. func isArmArch(arch string) bool { switch arch { case "arm", "arm64": return true } return false } // isKnownArch returns true if we know about the architecture. // // The arch value should be normalized before being passed to this function. func isKnownArch(arch string) bool { switch arch { case "386", "amd64", "amd64p32", "arm", "armbe", "arm64", "arm64be", "ppc64", "ppc64le", "loong64", "mips", "mipsle", "mips64", "mips64le", "mips64p32", "mips64p32le", "ppc", "riscv", "riscv64", "s390", "s390x", "sparc", "sparc64", "wasm": return true } return false } func normalizeOS(os string) string { if os == "" { return runtime.GOOS } os = strings.ToLower(os) switch os { case "macos": os = "darwin" } return os } // normalizeArch normalizes the architecture. func normalizeArch(arch, variant string) (string, string) { arch, variant = strings.ToLower(arch), strings.ToLower(variant) switch arch { case "i386": arch = "386" variant = "" case "x86_64", "x86-64", "amd64": arch = "amd64" if variant == "v1" { variant = "" } case "aarch64", "arm64": arch = "arm64" switch variant { case "8", "v8": variant = "" } case "armhf": arch = "arm" variant = "v7" case "armel": arch = "arm" variant = "v6" case "arm": switch variant { case "", "7": variant = "v7" case "5", "6", "8": variant = "v" + variant } } return arch, variant } ================================================ FILE: vendor/github.com/containerd/platforms/defaults.go ================================================ /* Copyright The containerd Authors. 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. */ package platforms // DefaultString returns the default string specifier for the platform, // with [PR#6](https://github.com/containerd/platforms/pull/6) the result // may now also include the OSVersion from the provided platform specification. func DefaultString() string { return FormatAll(DefaultSpec()) } // DefaultStrict returns strict form of Default. func DefaultStrict() MatchComparer { return OnlyStrict(DefaultSpec()) } ================================================ FILE: vendor/github.com/containerd/platforms/defaults_darwin.go ================================================ //go:build darwin /* Copyright The containerd Authors. 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. */ package platforms import ( "runtime" specs "github.com/opencontainers/image-spec/specs-go/v1" ) // DefaultSpec returns the current platform's default platform specification. func DefaultSpec() specs.Platform { return specs.Platform{ OS: runtime.GOOS, Architecture: runtime.GOARCH, // The Variant field will be empty if arch != ARM. Variant: cpuVariant(), } } // Default returns the default matcher for the platform. func Default() MatchComparer { return Ordered(DefaultSpec(), specs.Platform{ // darwin runtime also supports Linux binary via runu/LKL OS: "linux", Architecture: runtime.GOARCH, }) } ================================================ FILE: vendor/github.com/containerd/platforms/defaults_freebsd.go ================================================ /* Copyright The containerd Authors. 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. */ package platforms import ( "runtime" specs "github.com/opencontainers/image-spec/specs-go/v1" ) // DefaultSpec returns the current platform's default platform specification. func DefaultSpec() specs.Platform { return specs.Platform{ OS: runtime.GOOS, Architecture: runtime.GOARCH, // The Variant field will be empty if arch != ARM. Variant: cpuVariant(), } } // Default returns the default matcher for the platform. func Default() MatchComparer { return Ordered(DefaultSpec(), specs.Platform{ OS: "linux", Architecture: runtime.GOARCH, // The Variant field will be empty if arch != ARM. Variant: cpuVariant(), }) } ================================================ FILE: vendor/github.com/containerd/platforms/defaults_unix.go ================================================ //go:build !windows && !darwin && !freebsd /* Copyright The containerd Authors. 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. */ package platforms import ( "runtime" specs "github.com/opencontainers/image-spec/specs-go/v1" ) // DefaultSpec returns the current platform's default platform specification. func DefaultSpec() specs.Platform { return specs.Platform{ OS: runtime.GOOS, Architecture: runtime.GOARCH, // The Variant field will be empty if arch != ARM. Variant: cpuVariant(), } } // Default returns the default matcher for the platform. func Default() MatchComparer { return Only(DefaultSpec()) } ================================================ FILE: vendor/github.com/containerd/platforms/defaults_windows.go ================================================ /* Copyright The containerd Authors. 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. */ package platforms import ( "fmt" "runtime" "strconv" "strings" specs "github.com/opencontainers/image-spec/specs-go/v1" "golang.org/x/sys/windows" ) // DefaultSpec returns the current platform's default platform specification. func DefaultSpec() specs.Platform { major, minor, build := windows.RtlGetNtVersionNumbers() return specs.Platform{ OS: runtime.GOOS, Architecture: runtime.GOARCH, OSVersion: fmt.Sprintf("%d.%d.%d", major, minor, build), // The Variant field will be empty if arch != ARM. Variant: cpuVariant(), } } type windowsmatcher struct { specs.Platform osVersionPrefix string defaultMatcher Matcher } // Match matches platform with the same windows major, minor // and build version. func (m windowsmatcher) Match(p specs.Platform) bool { match := m.defaultMatcher.Match(p) if match && m.OS == "windows" { // HPC containers do not have OS version filled if m.OSVersion == "" || p.OSVersion == "" { return true } hostOsVersion := getOSVersion(m.osVersionPrefix) ctrOsVersion := getOSVersion(p.OSVersion) return checkHostAndContainerCompat(hostOsVersion, ctrOsVersion) } return match } func getOSVersion(osVersionPrefix string) osVersion { parts := strings.Split(osVersionPrefix, ".") if len(parts) < 3 { return osVersion{} } majorVersion, _ := strconv.Atoi(parts[0]) minorVersion, _ := strconv.Atoi(parts[1]) buildNumber, _ := strconv.Atoi(parts[2]) return osVersion{ MajorVersion: uint8(majorVersion), MinorVersion: uint8(minorVersion), Build: uint16(buildNumber), } } // Less sorts matched platforms in front of other platforms. // For matched platforms, it puts platforms with larger revision // number in front. func (m windowsmatcher) Less(p1, p2 specs.Platform) bool { m1, m2 := m.Match(p1), m.Match(p2) if m1 && m2 { r1, r2 := revision(p1.OSVersion), revision(p2.OSVersion) return r1 > r2 } return m1 && !m2 } func revision(v string) int { parts := strings.Split(v, ".") if len(parts) < 4 { return 0 } r, err := strconv.Atoi(parts[3]) if err != nil { return 0 } return r } func prefix(v string) string { parts := strings.Split(v, ".") if len(parts) < 4 { return v } return strings.Join(parts[0:3], ".") } // Default returns the current platform's default platform specification. func Default() MatchComparer { return Only(DefaultSpec()) } ================================================ FILE: vendor/github.com/containerd/platforms/errors.go ================================================ /* Copyright The containerd Authors. 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. */ package platforms import "errors" // These errors mirror the errors defined in [github.com/containerd/containerd/errdefs], // however, they are not exported as they are not expected to be used as sentinel // errors by consumers of this package. // //nolint:unused // not all errors are used on all platforms. var ( errNotFound = errors.New("not found") errInvalidArgument = errors.New("invalid argument") errNotImplemented = errors.New("not implemented") ) ================================================ FILE: vendor/github.com/containerd/platforms/platform_compat_windows.go ================================================ /* Copyright The containerd Authors. 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. */ package platforms // osVersion is a wrapper for Windows version information // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx type osVersion struct { Version uint32 MajorVersion uint8 MinorVersion uint8 Build uint16 } // Windows Client and Server build numbers. // // See: // https://learn.microsoft.com/en-us/windows/release-health/release-information // https://learn.microsoft.com/en-us/windows/release-health/windows-server-release-info // https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information const ( // rs5 (version 1809, codename "Redstone 5") corresponds to Windows Server // 2019 (ltsc2019), and Windows 10 (October 2018 Update). rs5 = 17763 // v21H2Server corresponds to Windows Server 2022 (ltsc2022). v21H2Server = 20348 // v22H2Win11 corresponds to Windows 11 (2022 Update). v22H2Win11 = 22621 ) // List of stable ABI compliant ltsc releases // Note: List must be sorted in ascending order var compatLTSCReleases = []uint16{ v21H2Server, } // CheckHostAndContainerCompat checks if given host and container // OS versions are compatible. // It includes support for stable ABI compliant versions as well. // Every release after WS 2022 will support the previous ltsc // container image. Stable ABI is in preview mode for windows 11 client. // Refer: https://learn.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/version-compatibility?tabs=windows-server-2022%2Cwindows-10#windows-server-host-os-compatibility func checkHostAndContainerCompat(host, ctr osVersion) bool { // check major minor versions of host and guest if host.MajorVersion != ctr.MajorVersion || host.MinorVersion != ctr.MinorVersion { return false } // If host is < WS 2022, exact version match is required if host.Build < v21H2Server { return host.Build == ctr.Build } var supportedLtscRelease uint16 for i := len(compatLTSCReleases) - 1; i >= 0; i-- { if host.Build >= compatLTSCReleases[i] { supportedLtscRelease = compatLTSCReleases[i] break } } return ctr.Build >= supportedLtscRelease && ctr.Build <= host.Build } ================================================ FILE: vendor/github.com/containerd/platforms/platforms.go ================================================ /* Copyright The containerd Authors. 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. */ // Package platforms provides a toolkit for normalizing, matching and // specifying container platforms. // // Centered around OCI platform specifications, we define a string-based // specifier syntax that can be used for user input. With a specifier, users // only need to specify the parts of the platform that are relevant to their // context, providing an operating system or architecture or both. // // How do I use this package? // // The vast majority of use cases should simply use the match function with // user input. The first step is to parse a specifier into a matcher: // // m, err := Parse("linux") // if err != nil { ... } // // Once you have a matcher, use it to match against the platform declared by a // component, typically from an image or runtime. Since extracting an images // platform is a little more involved, we'll use an example against the // platform default: // // if ok := m.Match(Default()); !ok { /* doesn't match */ } // // This can be composed in loops for resolving runtimes or used as a filter for // fetch and select images. // // More details of the specifier syntax and platform spec follow. // // # Declaring Platform Support // // Components that have strict platform requirements should use the OCI // platform specification to declare their support. Typically, this will be // images and runtimes that should make these declaring which platform they // support specifically. This looks roughly as follows: // // type Platform struct { // Architecture string // OS string // Variant string // } // // Most images and runtimes should at least set Architecture and OS, according // to their GOARCH and GOOS values, respectively (follow the OCI image // specification when in doubt). ARM should set variant under certain // discussions, which are outlined below. // // # Platform Specifiers // // While the OCI platform specifications provide a tool for components to // specify structured information, user input typically doesn't need the full // context and much can be inferred. To solve this problem, we introduced // "specifiers". A specifier has the format // `||/[/]`. The user can provide either the // operating system or the architecture or both. // // An example of a common specifier is `linux/amd64`. If the host has a default // of runtime that matches this, the user can simply provide the component that // matters. For example, if a image provides amd64 and arm64 support, the // operating system, `linux` can be inferred, so they only have to provide // `arm64` or `amd64`. Similar behavior is implemented for operating systems, // where the architecture may be known but a runtime may support images from // different operating systems. // // # Normalization // // Because not all users are familiar with the way the Go runtime represents // platforms, several normalizations have been provided to make this package // easier to user. // // The following are performed for architectures: // // Value Normalized // aarch64 arm64 // armhf arm // armel arm/v6 // i386 386 // x86_64 amd64 // x86-64 amd64 // // We also normalize the operating system `macos` to `darwin`. // // # ARM Support // // To qualify ARM architecture, the Variant field is used to qualify the arm // version. The most common arm version, v7, is represented without the variant // unless it is explicitly provided. This is treated as equivalent to armhf. A // previous architecture, armel, will be normalized to arm/v6. // // Similarly, the most common arm64 version v8, and most common amd64 version v1 // are represented without the variant. // // While these normalizations are provided, their support on arm platforms has // not yet been fully implemented and tested. package platforms import ( "fmt" "path" "regexp" "runtime" "strconv" "strings" specs "github.com/opencontainers/image-spec/specs-go/v1" ) var ( specifierRe = regexp.MustCompile(`^[A-Za-z0-9_-]+$`) osAndVersionRe = regexp.MustCompile(`^([A-Za-z0-9_-]+)(?:\(([A-Za-z0-9_.-]*)\))?$`) ) const osAndVersionFormat = "%s(%s)" // Platform is a type alias for convenience, so there is no need to import image-spec package everywhere. type Platform = specs.Platform // Matcher matches platforms specifications, provided by an image or runtime. type Matcher interface { Match(platform specs.Platform) bool } // NewMatcher returns a simple matcher based on the provided platform // specification. The returned matcher only looks for equality based on os, // architecture and variant. // // One may implement their own matcher if this doesn't provide the required // functionality. // // Applications should opt to use `Match` over directly parsing specifiers. func NewMatcher(platform specs.Platform) Matcher { return newDefaultMatcher(platform) } type matcher struct { specs.Platform } func (m *matcher) Match(platform specs.Platform) bool { normalized := Normalize(platform) return m.OS == normalized.OS && m.Architecture == normalized.Architecture && m.Variant == normalized.Variant } func (m *matcher) String() string { return FormatAll(m.Platform) } // ParseAll parses a list of platform specifiers into a list of platform. func ParseAll(specifiers []string) ([]specs.Platform, error) { platforms := make([]specs.Platform, len(specifiers)) for i, s := range specifiers { p, err := Parse(s) if err != nil { return nil, fmt.Errorf("invalid platform %s: %w", s, err) } platforms[i] = p } return platforms, nil } // Parse parses the platform specifier syntax into a platform declaration. // // Platform specifiers are in the format `[()]||[()]/[/]`. // The minimum required information for a platform specifier is the operating // system or architecture. The OSVersion can be part of the OS like `windows(10.0.17763)` // When an OSVersion is specified, then specs.Platform.OSVersion is populated with that value, // and an empty string otherwise. // If there is only a single string (no slashes), the // value will be matched against the known set of operating systems, then fall // back to the known set of architectures. The missing component will be // inferred based on the local environment. func Parse(specifier string) (specs.Platform, error) { if strings.Contains(specifier, "*") { // TODO(stevvooe): need to work out exact wildcard handling return specs.Platform{}, fmt.Errorf("%q: wildcards not yet supported: %w", specifier, errInvalidArgument) } // Limit to 4 elements to prevent unbounded split parts := strings.SplitN(specifier, "/", 4) var p specs.Platform for i, part := range parts { if i == 0 { // First element is [()] osVer := osAndVersionRe.FindStringSubmatch(part) if osVer == nil { return specs.Platform{}, fmt.Errorf("%q is an invalid OS component of %q: OSAndVersion specifier component must match %q: %w", part, specifier, osAndVersionRe.String(), errInvalidArgument) } p.OS = normalizeOS(osVer[1]) p.OSVersion = osVer[2] } else { if !specifierRe.MatchString(part) { return specs.Platform{}, fmt.Errorf("%q is an invalid component of %q: platform specifier component must match %q: %w", part, specifier, specifierRe.String(), errInvalidArgument) } } } switch len(parts) { case 1: // in this case, we will test that the value might be an OS (with or // without the optional OSVersion specified) and look it up. // If it is not known, we'll treat it as an architecture. Since // we have very little information about the platform here, we are // going to be a little more strict if we don't know about the argument // value. if isKnownOS(p.OS) { // picks a default architecture p.Architecture = runtime.GOARCH if p.Architecture == "arm" && cpuVariant() != "v7" { p.Variant = cpuVariant() } return p, nil } p.Architecture, p.Variant = normalizeArch(parts[0], "") if p.Architecture == "arm" && p.Variant == "v7" { p.Variant = "" } if isKnownArch(p.Architecture) { p.OS = runtime.GOOS return p, nil } return specs.Platform{}, fmt.Errorf("%q: unknown operating system or architecture: %w", specifier, errInvalidArgument) case 2: // In this case, we treat as a regular OS[(OSVersion)]/arch pair. We don't care // about whether or not we know of the platform. p.Architecture, p.Variant = normalizeArch(parts[1], "") if p.Architecture == "arm" && p.Variant == "v7" { p.Variant = "" } return p, nil case 3: // we have a fully specified variant, this is rare p.Architecture, p.Variant = normalizeArch(parts[1], parts[2]) if p.Architecture == "arm64" && p.Variant == "" { p.Variant = "v8" } return p, nil } return specs.Platform{}, fmt.Errorf("%q: cannot parse platform specifier: %w", specifier, errInvalidArgument) } // MustParse is like Parses but panics if the specifier cannot be parsed. // Simplifies initialization of global variables. func MustParse(specifier string) specs.Platform { p, err := Parse(specifier) if err != nil { panic("platform: Parse(" + strconv.Quote(specifier) + "): " + err.Error()) } return p } // Format returns a string specifier from the provided platform specification. func Format(platform specs.Platform) string { if platform.OS == "" { return "unknown" } return path.Join(platform.OS, platform.Architecture, platform.Variant) } // FormatAll returns a string specifier that also includes the OSVersion from the // provided platform specification. func FormatAll(platform specs.Platform) string { if platform.OS == "" { return "unknown" } if platform.OSVersion != "" { OSAndVersion := fmt.Sprintf(osAndVersionFormat, platform.OS, platform.OSVersion) return path.Join(OSAndVersion, platform.Architecture, platform.Variant) } return path.Join(platform.OS, platform.Architecture, platform.Variant) } // Normalize validates and translate the platform to the canonical value. // // For example, if "Aarch64" is encountered, we change it to "arm64" or if // "x86_64" is encountered, it becomes "amd64". func Normalize(platform specs.Platform) specs.Platform { platform.OS = normalizeOS(platform.OS) platform.Architecture, platform.Variant = normalizeArch(platform.Architecture, platform.Variant) return platform } ================================================ FILE: vendor/github.com/containerd/platforms/platforms_other.go ================================================ //go:build !windows /* Copyright The containerd Authors. 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. */ package platforms import ( specs "github.com/opencontainers/image-spec/specs-go/v1" ) // NewMatcher returns the default Matcher for containerd func newDefaultMatcher(platform specs.Platform) Matcher { return &matcher{ Platform: Normalize(platform), } } ================================================ FILE: vendor/github.com/containerd/platforms/platforms_windows.go ================================================ /* Copyright The containerd Authors. 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. */ package platforms import ( specs "github.com/opencontainers/image-spec/specs-go/v1" ) // NewMatcher returns a Windows matcher that will match on osVersionPrefix if // the platform is Windows otherwise use the default matcher func newDefaultMatcher(platform specs.Platform) Matcher { prefix := prefix(platform.OSVersion) return windowsmatcher{ Platform: platform, osVersionPrefix: prefix, defaultMatcher: &matcher{ Platform: Normalize(platform), }, } } ================================================ FILE: vendor/github.com/cpuguy83/dockercfg/LICENSE ================================================ MIT License Copyright (c) 2020 Brian Goff Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: vendor/github.com/cpuguy83/dockercfg/README.md ================================================ ### github.com/cpuguy83/dockercfg Go library to load docker CLI configs, auths, etc. with minimal deps. So far the only deps are on the stdlib. ### Usage See the [godoc](https://godoc.org/github.com/cpuguy83/dockercfg) for API details. I'm currently using this in [zapp](https://github.com/cpuguy83/zapp/blob/d25c43d4cd7ccf29fba184aafbc720a753e1a15d/main.go#L58-L83) to handle registry auth instead of always asking the user to enter it. ================================================ FILE: vendor/github.com/cpuguy83/dockercfg/auth.go ================================================ package dockercfg import ( "bytes" "encoding/base64" "encoding/json" "errors" "fmt" "io/fs" "os/exec" "runtime" "strings" ) // This is used by the docker CLI in cases where an oauth identity token is used. // In that case the username is stored literally as `` // When fetching the credentials we check for this value to determine if. const tokenUsername = "" // GetRegistryCredentials gets registry credentials for the passed in registry host. // // This will use [LoadDefaultConfig] to read registry auth details from the config. // If the config doesn't exist, it will attempt to load registry credentials using the default credential helper for the platform. func GetRegistryCredentials(hostname string) (string, string, error) { cfg, err := LoadDefaultConfig() if err != nil { if !errors.Is(err, fs.ErrNotExist) { return "", "", fmt.Errorf("load default config: %w", err) } return GetCredentialsFromHelper("", hostname) } return cfg.GetRegistryCredentials(hostname) } // ResolveRegistryHost can be used to transform a docker registry host name into what is used for the docker config/cred helpers // // This is useful for using with containerd authorizers. // Naturally this only transforms docker hub URLs. func ResolveRegistryHost(host string) string { switch host { case "index.docker.io", "docker.io", "https://index.docker.io/v1/", "registry-1.docker.io": return "https://index.docker.io/v1/" } return host } // GetRegistryCredentials gets credentials, if any, for the provided hostname. // // Hostnames should already be resolved using [ResolveRegistryHost]. // // If the returned username string is empty, the password is an identity token. func (c *Config) GetRegistryCredentials(hostname string) (string, string, error) { h, ok := c.CredentialHelpers[hostname] if ok { return GetCredentialsFromHelper(h, hostname) } if c.CredentialsStore != "" { username, password, err := GetCredentialsFromHelper(c.CredentialsStore, hostname) if err != nil { return "", "", fmt.Errorf("get credentials from store: %w", err) } if username != "" || password != "" { return username, password, nil } } auth, ok := c.AuthConfigs[hostname] if !ok { return GetCredentialsFromHelper("", hostname) } if auth.IdentityToken != "" { return "", auth.IdentityToken, nil } if auth.Username != "" && auth.Password != "" { return auth.Username, auth.Password, nil } return DecodeBase64Auth(auth) } // DecodeBase64Auth decodes the legacy file-based auth storage from the docker CLI. // It takes the "Auth" filed from AuthConfig and decodes that into a username and password. // // If "Auth" is empty, an empty user/pass will be returned, but not an error. func DecodeBase64Auth(auth AuthConfig) (string, string, error) { if auth.Auth == "" { return "", "", nil } decLen := base64.StdEncoding.DecodedLen(len(auth.Auth)) decoded := make([]byte, decLen) n, err := base64.StdEncoding.Decode(decoded, []byte(auth.Auth)) if err != nil { return "", "", fmt.Errorf("decode auth: %w", err) } decoded = decoded[:n] const sep = ":" user, pass, found := strings.Cut(string(decoded), sep) if !found { return "", "", fmt.Errorf("invalid auth: missing %q separator", sep) } return user, pass, nil } // Errors from credential helpers. var ( ErrCredentialsNotFound = errors.New("credentials not found in native keychain") ErrCredentialsMissingServerURL = errors.New("no credentials server URL") ) //nolint:gochecknoglobals // These are used to mock exec in tests. var ( // execLookPath is a variable that can be used to mock exec.LookPath in tests. execLookPath = exec.LookPath // execCommand is a variable that can be used to mock exec.Command in tests. execCommand = exec.Command ) // GetCredentialsFromHelper attempts to lookup credentials from the passed in docker credential helper. // // The credential helper should just be the suffix name (no "docker-credential-"). // If the passed in helper program is empty this will look up the default helper for the platform. // // If the credentials are not found, no error is returned, only empty credentials. // // Hostnames should already be resolved using [ResolveRegistryHost] // // If the username string is empty, the password string is an identity token. func GetCredentialsFromHelper(helper, hostname string) (string, string, error) { if helper == "" { helper, helperErr := getCredentialHelper() if helperErr != nil { return "", "", fmt.Errorf("get credential helper: %w", helperErr) } if helper == "" { return "", "", nil } } helper = "docker-credential-" + helper p, err := execLookPath(helper) if err != nil { if !errors.Is(err, exec.ErrNotFound) { return "", "", fmt.Errorf("look up %q: %w", helper, err) } return "", "", nil } var outBuf, errBuf bytes.Buffer cmd := execCommand(p, "get") cmd.Stdin = strings.NewReader(hostname) cmd.Stdout = &outBuf cmd.Stderr = &errBuf if err = cmd.Run(); err != nil { out := strings.TrimSpace(outBuf.String()) switch out { case ErrCredentialsNotFound.Error(): return "", "", nil case ErrCredentialsMissingServerURL.Error(): return "", "", ErrCredentialsMissingServerURL default: return "", "", fmt.Errorf("execute %q stdout: %q stderr: %q: %w", helper, out, strings.TrimSpace(errBuf.String()), err, ) } } var creds struct { Username string `json:"Username"` Secret string `json:"Secret"` } if err = json.Unmarshal(outBuf.Bytes(), &creds); err != nil { return "", "", fmt.Errorf("unmarshal credentials from: %q: %w", helper, err) } // When tokenUsername is used, the output is an identity token and the username is garbage. if creds.Username == tokenUsername { creds.Username = "" } return creds.Username, creds.Secret, nil } // getCredentialHelper gets the default credential helper name for the current platform. func getCredentialHelper() (string, error) { switch runtime.GOOS { case "linux": if _, err := exec.LookPath("pass"); err != nil { if errors.Is(err, exec.ErrNotFound) { return "secretservice", nil } return "", fmt.Errorf(`look up "pass": %w`, err) } return "pass", nil case "darwin": return "osxkeychain", nil case "windows": return "wincred", nil default: return "", nil } } ================================================ FILE: vendor/github.com/cpuguy83/dockercfg/config.go ================================================ package dockercfg // Config represents the on disk format of the docker CLI's config file. type Config struct { AuthConfigs map[string]AuthConfig `json:"auths"` HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"` PsFormat string `json:"psFormat,omitempty"` ImagesFormat string `json:"imagesFormat,omitempty"` NetworksFormat string `json:"networksFormat,omitempty"` PluginsFormat string `json:"pluginsFormat,omitempty"` VolumesFormat string `json:"volumesFormat,omitempty"` StatsFormat string `json:"statsFormat,omitempty"` DetachKeys string `json:"detachKeys,omitempty"` CredentialsStore string `json:"credsStore,omitempty"` CredentialHelpers map[string]string `json:"credHelpers,omitempty"` Filename string `json:"-"` // Note: for internal use only. ServiceInspectFormat string `json:"serviceInspectFormat,omitempty"` ServicesFormat string `json:"servicesFormat,omitempty"` TasksFormat string `json:"tasksFormat,omitempty"` SecretFormat string `json:"secretFormat,omitempty"` ConfigFormat string `json:"configFormat,omitempty"` NodesFormat string `json:"nodesFormat,omitempty"` PruneFilters []string `json:"pruneFilters,omitempty"` Proxies map[string]ProxyConfig `json:"proxies,omitempty"` Experimental string `json:"experimental,omitempty"` StackOrchestrator string `json:"stackOrchestrator,omitempty"` Kubernetes *KubernetesConfig `json:"kubernetes,omitempty"` CurrentContext string `json:"currentContext,omitempty"` CLIPluginsExtraDirs []string `json:"cliPluginsExtraDirs,omitempty"` Aliases map[string]string `json:"aliases,omitempty"` } // ProxyConfig contains proxy configuration settings. type ProxyConfig struct { HTTPProxy string `json:"httpProxy,omitempty"` HTTPSProxy string `json:"httpsProxy,omitempty"` NoProxy string `json:"noProxy,omitempty"` FTPProxy string `json:"ftpProxy,omitempty"` } // AuthConfig contains authorization information for connecting to a Registry. type AuthConfig struct { Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` Auth string `json:"auth,omitempty"` // Email is an optional value associated with the username. // This field is deprecated and will be removed in a later // version of docker. Email string `json:"email,omitempty"` ServerAddress string `json:"serveraddress,omitempty"` // IdentityToken is used to authenticate the user and get // an access token for the registry. IdentityToken string `json:"identitytoken,omitempty"` // RegistryToken is a bearer token to be sent to a registry. RegistryToken string `json:"registrytoken,omitempty"` } // KubernetesConfig contains Kubernetes orchestrator settings. type KubernetesConfig struct { AllNamespaces string `json:"allNamespaces,omitempty"` } ================================================ FILE: vendor/github.com/cpuguy83/dockercfg/load.go ================================================ package dockercfg import ( "encoding/json" "fmt" "os" "path/filepath" ) // UserHomeConfigPath returns the path to the docker config in the current user's home dir. func UserHomeConfigPath() (string, error) { home, err := os.UserHomeDir() if err != nil { return "", fmt.Errorf("user home dir: %w", err) } return filepath.Join(home, ".docker", "config.json"), nil } // ConfigPath returns the path to the docker cli config. // // It will either use the DOCKER_CONFIG env var if set, or the value from [UserHomeConfigPath] // DOCKER_CONFIG would be the dir path where `config.json` is stored, this returns the path to config.json. func ConfigPath() (string, error) { if p := os.Getenv("DOCKER_CONFIG"); p != "" { return filepath.Join(p, "config.json"), nil } return UserHomeConfigPath() } // LoadDefaultConfig loads the docker cli config from the path returned from [ConfigPath]. func LoadDefaultConfig() (Config, error) { var cfg Config p, err := ConfigPath() if err != nil { return cfg, fmt.Errorf("config path: %w", err) } return cfg, FromFile(p, &cfg) } // FromFile loads config from the specified path into cfg. func FromFile(configPath string, cfg *Config) error { f, err := os.Open(configPath) if err != nil { return fmt.Errorf("open config: %w", err) } defer f.Close() if err = json.NewDecoder(f).Decode(&cfg); err != nil { return fmt.Errorf("decode config: %w", err) } return nil } ================================================ FILE: vendor/github.com/davecgh/go-spew/LICENSE ================================================ ISC License Copyright (c) 2012-2016 Dave Collins Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ================================================ FILE: vendor/github.com/davecgh/go-spew/spew/bypass.go ================================================ // Copyright (c) 2015-2016 Dave Collins // // Permission to use, copy, modify, and distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // NOTE: Due to the following build constraints, this file will only be compiled // when the code is not running on Google App Engine, compiled by GopherJS, and // "-tags safe" is not added to the go build command line. The "disableunsafe" // tag is deprecated and thus should not be used. // Go versions prior to 1.4 are disabled because they use a different layout // for interfaces which make the implementation of unsafeReflectValue more complex. // +build !js,!appengine,!safe,!disableunsafe,go1.4 package spew import ( "reflect" "unsafe" ) const ( // UnsafeDisabled is a build-time constant which specifies whether or // not access to the unsafe package is available. UnsafeDisabled = false // ptrSize is the size of a pointer on the current arch. ptrSize = unsafe.Sizeof((*byte)(nil)) ) type flag uintptr var ( // flagRO indicates whether the value field of a reflect.Value // is read-only. flagRO flag // flagAddr indicates whether the address of the reflect.Value's // value may be taken. flagAddr flag ) // flagKindMask holds the bits that make up the kind // part of the flags field. In all the supported versions, // it is in the lower 5 bits. const flagKindMask = flag(0x1f) // Different versions of Go have used different // bit layouts for the flags type. This table // records the known combinations. var okFlags = []struct { ro, addr flag }{{ // From Go 1.4 to 1.5 ro: 1 << 5, addr: 1 << 7, }, { // Up to Go tip. ro: 1<<5 | 1<<6, addr: 1 << 8, }} var flagValOffset = func() uintptr { field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag") if !ok { panic("reflect.Value has no flag field") } return field.Offset }() // flagField returns a pointer to the flag field of a reflect.Value. func flagField(v *reflect.Value) *flag { return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset)) } // unsafeReflectValue converts the passed reflect.Value into a one that bypasses // the typical safety restrictions preventing access to unaddressable and // unexported data. It works by digging the raw pointer to the underlying // value out of the protected value and generating a new unprotected (unsafe) // reflect.Value to it. // // This allows us to check for implementations of the Stringer and error // interfaces to be used for pretty printing ordinarily unaddressable and // inaccessible values such as unexported struct fields. func unsafeReflectValue(v reflect.Value) reflect.Value { if !v.IsValid() || (v.CanInterface() && v.CanAddr()) { return v } flagFieldPtr := flagField(&v) *flagFieldPtr &^= flagRO *flagFieldPtr |= flagAddr return v } // Sanity checks against future reflect package changes // to the type or semantics of the Value.flag field. func init() { field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag") if !ok { panic("reflect.Value has no flag field") } if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() { panic("reflect.Value flag field has changed kind") } type t0 int var t struct { A t0 // t0 will have flagEmbedRO set. t0 // a will have flagStickyRO set a t0 } vA := reflect.ValueOf(t).FieldByName("A") va := reflect.ValueOf(t).FieldByName("a") vt0 := reflect.ValueOf(t).FieldByName("t0") // Infer flagRO from the difference between the flags // for the (otherwise identical) fields in t. flagPublic := *flagField(&vA) flagWithRO := *flagField(&va) | *flagField(&vt0) flagRO = flagPublic ^ flagWithRO // Infer flagAddr from the difference between a value // taken from a pointer and not. vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A") flagNoPtr := *flagField(&vA) flagPtr := *flagField(&vPtrA) flagAddr = flagNoPtr ^ flagPtr // Check that the inferred flags tally with one of the known versions. for _, f := range okFlags { if flagRO == f.ro && flagAddr == f.addr { return } } panic("reflect.Value read-only flag has changed semantics") } ================================================ FILE: vendor/github.com/davecgh/go-spew/spew/bypasssafe.go ================================================ // Copyright (c) 2015-2016 Dave Collins // // Permission to use, copy, modify, and distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // NOTE: Due to the following build constraints, this file will only be compiled // when the code is running on Google App Engine, compiled by GopherJS, or // "-tags safe" is added to the go build command line. The "disableunsafe" // tag is deprecated and thus should not be used. // +build js appengine safe disableunsafe !go1.4 package spew import "reflect" const ( // UnsafeDisabled is a build-time constant which specifies whether or // not access to the unsafe package is available. UnsafeDisabled = true ) // unsafeReflectValue typically converts the passed reflect.Value into a one // that bypasses the typical safety restrictions preventing access to // unaddressable and unexported data. However, doing this relies on access to // the unsafe package. This is a stub version which simply returns the passed // reflect.Value when the unsafe package is not available. func unsafeReflectValue(v reflect.Value) reflect.Value { return v } ================================================ FILE: vendor/github.com/davecgh/go-spew/spew/common.go ================================================ /* * Copyright (c) 2013-2016 Dave Collins * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ package spew import ( "bytes" "fmt" "io" "reflect" "sort" "strconv" ) // Some constants in the form of bytes to avoid string overhead. This mirrors // the technique used in the fmt package. var ( panicBytes = []byte("(PANIC=") plusBytes = []byte("+") iBytes = []byte("i") trueBytes = []byte("true") falseBytes = []byte("false") interfaceBytes = []byte("(interface {})") commaNewlineBytes = []byte(",\n") newlineBytes = []byte("\n") openBraceBytes = []byte("{") openBraceNewlineBytes = []byte("{\n") closeBraceBytes = []byte("}") asteriskBytes = []byte("*") colonBytes = []byte(":") colonSpaceBytes = []byte(": ") openParenBytes = []byte("(") closeParenBytes = []byte(")") spaceBytes = []byte(" ") pointerChainBytes = []byte("->") nilAngleBytes = []byte("") maxNewlineBytes = []byte("\n") maxShortBytes = []byte("") circularBytes = []byte("") circularShortBytes = []byte("") invalidAngleBytes = []byte("") openBracketBytes = []byte("[") closeBracketBytes = []byte("]") percentBytes = []byte("%") precisionBytes = []byte(".") openAngleBytes = []byte("<") closeAngleBytes = []byte(">") openMapBytes = []byte("map[") closeMapBytes = []byte("]") lenEqualsBytes = []byte("len=") capEqualsBytes = []byte("cap=") ) // hexDigits is used to map a decimal value to a hex digit. var hexDigits = "0123456789abcdef" // catchPanic handles any panics that might occur during the handleMethods // calls. func catchPanic(w io.Writer, v reflect.Value) { if err := recover(); err != nil { w.Write(panicBytes) fmt.Fprintf(w, "%v", err) w.Write(closeParenBytes) } } // handleMethods attempts to call the Error and String methods on the underlying // type the passed reflect.Value represents and outputes the result to Writer w. // // It handles panics in any called methods by catching and displaying the error // as the formatted value. func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) { // We need an interface to check if the type implements the error or // Stringer interface. However, the reflect package won't give us an // interface on certain things like unexported struct fields in order // to enforce visibility rules. We use unsafe, when it's available, // to bypass these restrictions since this package does not mutate the // values. if !v.CanInterface() { if UnsafeDisabled { return false } v = unsafeReflectValue(v) } // Choose whether or not to do error and Stringer interface lookups against // the base type or a pointer to the base type depending on settings. // Technically calling one of these methods with a pointer receiver can // mutate the value, however, types which choose to satisify an error or // Stringer interface with a pointer receiver should not be mutating their // state inside these interface methods. if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() { v = unsafeReflectValue(v) } if v.CanAddr() { v = v.Addr() } // Is it an error or Stringer? switch iface := v.Interface().(type) { case error: defer catchPanic(w, v) if cs.ContinueOnMethod { w.Write(openParenBytes) w.Write([]byte(iface.Error())) w.Write(closeParenBytes) w.Write(spaceBytes) return false } w.Write([]byte(iface.Error())) return true case fmt.Stringer: defer catchPanic(w, v) if cs.ContinueOnMethod { w.Write(openParenBytes) w.Write([]byte(iface.String())) w.Write(closeParenBytes) w.Write(spaceBytes) return false } w.Write([]byte(iface.String())) return true } return false } // printBool outputs a boolean value as true or false to Writer w. func printBool(w io.Writer, val bool) { if val { w.Write(trueBytes) } else { w.Write(falseBytes) } } // printInt outputs a signed integer value to Writer w. func printInt(w io.Writer, val int64, base int) { w.Write([]byte(strconv.FormatInt(val, base))) } // printUint outputs an unsigned integer value to Writer w. func printUint(w io.Writer, val uint64, base int) { w.Write([]byte(strconv.FormatUint(val, base))) } // printFloat outputs a floating point value using the specified precision, // which is expected to be 32 or 64bit, to Writer w. func printFloat(w io.Writer, val float64, precision int) { w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision))) } // printComplex outputs a complex value using the specified float precision // for the real and imaginary parts to Writer w. func printComplex(w io.Writer, c complex128, floatPrecision int) { r := real(c) w.Write(openParenBytes) w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision))) i := imag(c) if i >= 0 { w.Write(plusBytes) } w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision))) w.Write(iBytes) w.Write(closeParenBytes) } // printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x' // prefix to Writer w. func printHexPtr(w io.Writer, p uintptr) { // Null pointer. num := uint64(p) if num == 0 { w.Write(nilAngleBytes) return } // Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix buf := make([]byte, 18) // It's simpler to construct the hex string right to left. base := uint64(16) i := len(buf) - 1 for num >= base { buf[i] = hexDigits[num%base] num /= base i-- } buf[i] = hexDigits[num] // Add '0x' prefix. i-- buf[i] = 'x' i-- buf[i] = '0' // Strip unused leading bytes. buf = buf[i:] w.Write(buf) } // valuesSorter implements sort.Interface to allow a slice of reflect.Value // elements to be sorted. type valuesSorter struct { values []reflect.Value strings []string // either nil or same len and values cs *ConfigState } // newValuesSorter initializes a valuesSorter instance, which holds a set of // surrogate keys on which the data should be sorted. It uses flags in // ConfigState to decide if and how to populate those surrogate keys. func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface { vs := &valuesSorter{values: values, cs: cs} if canSortSimply(vs.values[0].Kind()) { return vs } if !cs.DisableMethods { vs.strings = make([]string, len(values)) for i := range vs.values { b := bytes.Buffer{} if !handleMethods(cs, &b, vs.values[i]) { vs.strings = nil break } vs.strings[i] = b.String() } } if vs.strings == nil && cs.SpewKeys { vs.strings = make([]string, len(values)) for i := range vs.values { vs.strings[i] = Sprintf("%#v", vs.values[i].Interface()) } } return vs } // canSortSimply tests whether a reflect.Kind is a primitive that can be sorted // directly, or whether it should be considered for sorting by surrogate keys // (if the ConfigState allows it). func canSortSimply(kind reflect.Kind) bool { // This switch parallels valueSortLess, except for the default case. switch kind { case reflect.Bool: return true case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: return true case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: return true case reflect.Float32, reflect.Float64: return true case reflect.String: return true case reflect.Uintptr: return true case reflect.Array: return true } return false } // Len returns the number of values in the slice. It is part of the // sort.Interface implementation. func (s *valuesSorter) Len() int { return len(s.values) } // Swap swaps the values at the passed indices. It is part of the // sort.Interface implementation. func (s *valuesSorter) Swap(i, j int) { s.values[i], s.values[j] = s.values[j], s.values[i] if s.strings != nil { s.strings[i], s.strings[j] = s.strings[j], s.strings[i] } } // valueSortLess returns whether the first value should sort before the second // value. It is used by valueSorter.Less as part of the sort.Interface // implementation. func valueSortLess(a, b reflect.Value) bool { switch a.Kind() { case reflect.Bool: return !a.Bool() && b.Bool() case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: return a.Int() < b.Int() case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: return a.Uint() < b.Uint() case reflect.Float32, reflect.Float64: return a.Float() < b.Float() case reflect.String: return a.String() < b.String() case reflect.Uintptr: return a.Uint() < b.Uint() case reflect.Array: // Compare the contents of both arrays. l := a.Len() for i := 0; i < l; i++ { av := a.Index(i) bv := b.Index(i) if av.Interface() == bv.Interface() { continue } return valueSortLess(av, bv) } } return a.String() < b.String() } // Less returns whether the value at index i should sort before the // value at index j. It is part of the sort.Interface implementation. func (s *valuesSorter) Less(i, j int) bool { if s.strings == nil { return valueSortLess(s.values[i], s.values[j]) } return s.strings[i] < s.strings[j] } // sortValues is a sort function that handles both native types and any type that // can be converted to error or Stringer. Other inputs are sorted according to // their Value.String() value to ensure display stability. func sortValues(values []reflect.Value, cs *ConfigState) { if len(values) == 0 { return } sort.Sort(newValuesSorter(values, cs)) } ================================================ FILE: vendor/github.com/davecgh/go-spew/spew/config.go ================================================ /* * Copyright (c) 2013-2016 Dave Collins * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ package spew import ( "bytes" "fmt" "io" "os" ) // ConfigState houses the configuration options used by spew to format and // display values. There is a global instance, Config, that is used to control // all top-level Formatter and Dump functionality. Each ConfigState instance // provides methods equivalent to the top-level functions. // // The zero value for ConfigState provides no indentation. You would typically // want to set it to a space or a tab. // // Alternatively, you can use NewDefaultConfig to get a ConfigState instance // with default settings. See the documentation of NewDefaultConfig for default // values. type ConfigState struct { // Indent specifies the string to use for each indentation level. The // global config instance that all top-level functions use set this to a // single space by default. If you would like more indentation, you might // set this to a tab with "\t" or perhaps two spaces with " ". Indent string // MaxDepth controls the maximum number of levels to descend into nested // data structures. The default, 0, means there is no limit. // // NOTE: Circular data structures are properly detected, so it is not // necessary to set this value unless you specifically want to limit deeply // nested data structures. MaxDepth int // DisableMethods specifies whether or not error and Stringer interfaces are // invoked for types that implement them. DisableMethods bool // DisablePointerMethods specifies whether or not to check for and invoke // error and Stringer interfaces on types which only accept a pointer // receiver when the current type is not a pointer. // // NOTE: This might be an unsafe action since calling one of these methods // with a pointer receiver could technically mutate the value, however, // in practice, types which choose to satisify an error or Stringer // interface with a pointer receiver should not be mutating their state // inside these interface methods. As a result, this option relies on // access to the unsafe package, so it will not have any effect when // running in environments without access to the unsafe package such as // Google App Engine or with the "safe" build tag specified. DisablePointerMethods bool // DisablePointerAddresses specifies whether to disable the printing of // pointer addresses. This is useful when diffing data structures in tests. DisablePointerAddresses bool // DisableCapacities specifies whether to disable the printing of capacities // for arrays, slices, maps and channels. This is useful when diffing // data structures in tests. DisableCapacities bool // ContinueOnMethod specifies whether or not recursion should continue once // a custom error or Stringer interface is invoked. The default, false, // means it will print the results of invoking the custom error or Stringer // interface and return immediately instead of continuing to recurse into // the internals of the data type. // // NOTE: This flag does not have any effect if method invocation is disabled // via the DisableMethods or DisablePointerMethods options. ContinueOnMethod bool // SortKeys specifies map keys should be sorted before being printed. Use // this to have a more deterministic, diffable output. Note that only // native types (bool, int, uint, floats, uintptr and string) and types // that support the error or Stringer interfaces (if methods are // enabled) are supported, with other types sorted according to the // reflect.Value.String() output which guarantees display stability. SortKeys bool // SpewKeys specifies that, as a last resort attempt, map keys should // be spewed to strings and sorted by those strings. This is only // considered if SortKeys is true. SpewKeys bool } // Config is the active configuration of the top-level functions. // The configuration can be changed by modifying the contents of spew.Config. var Config = ConfigState{Indent: " "} // Errorf is a wrapper for fmt.Errorf that treats each argument as if it were // passed with a Formatter interface returned by c.NewFormatter. It returns // the formatted string as a value that satisfies error. See NewFormatter // for formatting details. // // This function is shorthand for the following syntax: // // fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b)) func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) { return fmt.Errorf(format, c.convertArgs(a)...) } // Fprint is a wrapper for fmt.Fprint that treats each argument as if it were // passed with a Formatter interface returned by c.NewFormatter. It returns // the number of bytes written and any write error encountered. See // NewFormatter for formatting details. // // This function is shorthand for the following syntax: // // fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b)) func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) { return fmt.Fprint(w, c.convertArgs(a)...) } // Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were // passed with a Formatter interface returned by c.NewFormatter. It returns // the number of bytes written and any write error encountered. See // NewFormatter for formatting details. // // This function is shorthand for the following syntax: // // fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b)) func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { return fmt.Fprintf(w, format, c.convertArgs(a)...) } // Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it // passed with a Formatter interface returned by c.NewFormatter. See // NewFormatter for formatting details. // // This function is shorthand for the following syntax: // // fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b)) func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) { return fmt.Fprintln(w, c.convertArgs(a)...) } // Print is a wrapper for fmt.Print that treats each argument as if it were // passed with a Formatter interface returned by c.NewFormatter. It returns // the number of bytes written and any write error encountered. See // NewFormatter for formatting details. // // This function is shorthand for the following syntax: // // fmt.Print(c.NewFormatter(a), c.NewFormatter(b)) func (c *ConfigState) Print(a ...interface{}) (n int, err error) { return fmt.Print(c.convertArgs(a)...) } // Printf is a wrapper for fmt.Printf that treats each argument as if it were // passed with a Formatter interface returned by c.NewFormatter. It returns // the number of bytes written and any write error encountered. See // NewFormatter for formatting details. // // This function is shorthand for the following syntax: // // fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b)) func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) { return fmt.Printf(format, c.convertArgs(a)...) } // Println is a wrapper for fmt.Println that treats each argument as if it were // passed with a Formatter interface returned by c.NewFormatter. It returns // the number of bytes written and any write error encountered. See // NewFormatter for formatting details. // // This function is shorthand for the following syntax: // // fmt.Println(c.NewFormatter(a), c.NewFormatter(b)) func (c *ConfigState) Println(a ...interface{}) (n int, err error) { return fmt.Println(c.convertArgs(a)...) } // Sprint is a wrapper for fmt.Sprint that treats each argument as if it were // passed with a Formatter interface returned by c.NewFormatter. It returns // the resulting string. See NewFormatter for formatting details. // // This function is shorthand for the following syntax: // // fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b)) func (c *ConfigState) Sprint(a ...interface{}) string { return fmt.Sprint(c.convertArgs(a)...) } // Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were // passed with a Formatter interface returned by c.NewFormatter. It returns // the resulting string. See NewFormatter for formatting details. // // This function is shorthand for the following syntax: // // fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b)) func (c *ConfigState) Sprintf(format string, a ...interface{}) string { return fmt.Sprintf(format, c.convertArgs(a)...) } // Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it // were passed with a Formatter interface returned by c.NewFormatter. It // returns the resulting string. See NewFormatter for formatting details. // // This function is shorthand for the following syntax: // // fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b)) func (c *ConfigState) Sprintln(a ...interface{}) string { return fmt.Sprintln(c.convertArgs(a)...) } /* NewFormatter returns a custom formatter that satisfies the fmt.Formatter interface. As a result, it integrates cleanly with standard fmt package printing functions. The formatter is useful for inline printing of smaller data types similar to the standard %v format specifier. The custom formatter only responds to the %v (most compact), %+v (adds pointer addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb combinations. Any other verbs such as %x and %q will be sent to the the standard fmt package for formatting. In addition, the custom formatter ignores the width and precision arguments (however they will still work on the format specifiers not handled by the custom formatter). Typically this function shouldn't be called directly. It is much easier to make use of the custom formatter by calling one of the convenience functions such as c.Printf, c.Println, or c.Printf. */ func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter { return newFormatter(c, v) } // Fdump formats and displays the passed arguments to io.Writer w. It formats // exactly the same as Dump. func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) { fdump(c, w, a...) } /* Dump displays the passed parameters to standard out with newlines, customizable indentation, and additional debug information such as complete types and all pointer addresses used to indirect to the final value. It provides the following features over the built-in printing facilities provided by the fmt package: * Pointers are dereferenced and followed * Circular data structures are detected and handled properly * Custom Stringer/error interfaces are optionally invoked, including on unexported types * Custom types which only implement the Stringer/error interfaces via a pointer receiver are optionally invoked when passing non-pointer variables * Byte arrays and slices are dumped like the hexdump -C command which includes offsets, byte values in hex, and ASCII output The configuration options are controlled by modifying the public members of c. See ConfigState for options documentation. See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to get the formatted result as a string. */ func (c *ConfigState) Dump(a ...interface{}) { fdump(c, os.Stdout, a...) } // Sdump returns a string with the passed arguments formatted exactly the same // as Dump. func (c *ConfigState) Sdump(a ...interface{}) string { var buf bytes.Buffer fdump(c, &buf, a...) return buf.String() } // convertArgs accepts a slice of arguments and returns a slice of the same // length with each argument converted to a spew Formatter interface using // the ConfigState associated with s. func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) { formatters = make([]interface{}, len(args)) for index, arg := range args { formatters[index] = newFormatter(c, arg) } return formatters } // NewDefaultConfig returns a ConfigState with the following default settings. // // Indent: " " // MaxDepth: 0 // DisableMethods: false // DisablePointerMethods: false // ContinueOnMethod: false // SortKeys: false func NewDefaultConfig() *ConfigState { return &ConfigState{Indent: " "} } ================================================ FILE: vendor/github.com/davecgh/go-spew/spew/doc.go ================================================ /* * Copyright (c) 2013-2016 Dave Collins * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* Package spew implements a deep pretty printer for Go data structures to aid in debugging. A quick overview of the additional features spew provides over the built-in printing facilities for Go data types are as follows: * Pointers are dereferenced and followed * Circular data structures are detected and handled properly * Custom Stringer/error interfaces are optionally invoked, including on unexported types * Custom types which only implement the Stringer/error interfaces via a pointer receiver are optionally invoked when passing non-pointer variables * Byte arrays and slices are dumped like the hexdump -C command which includes offsets, byte values in hex, and ASCII output (only when using Dump style) There are two different approaches spew allows for dumping Go data structures: * Dump style which prints with newlines, customizable indentation, and additional debug information such as types and all pointer addresses used to indirect to the final value * A custom Formatter interface that integrates cleanly with the standard fmt package and replaces %v, %+v, %#v, and %#+v to provide inline printing similar to the default %v while providing the additional functionality outlined above and passing unsupported format verbs such as %x and %q along to fmt Quick Start This section demonstrates how to quickly get started with spew. See the sections below for further details on formatting and configuration options. To dump a variable with full newlines, indentation, type, and pointer information use Dump, Fdump, or Sdump: spew.Dump(myVar1, myVar2, ...) spew.Fdump(someWriter, myVar1, myVar2, ...) str := spew.Sdump(myVar1, myVar2, ...) Alternatively, if you would prefer to use format strings with a compacted inline printing style, use the convenience wrappers Printf, Fprintf, etc with %v (most compact), %+v (adds pointer addresses), %#v (adds types), or %#+v (adds types and pointer addresses): spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2) spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2) spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) Configuration Options Configuration of spew is handled by fields in the ConfigState type. For convenience, all of the top-level functions use a global state available via the spew.Config global. It is also possible to create a ConfigState instance that provides methods equivalent to the top-level functions. This allows concurrent configuration options. See the ConfigState documentation for more details. The following configuration options are available: * Indent String to use for each indentation level for Dump functions. It is a single space by default. A popular alternative is "\t". * MaxDepth Maximum number of levels to descend into nested data structures. There is no limit by default. * DisableMethods Disables invocation of error and Stringer interface methods. Method invocation is enabled by default. * DisablePointerMethods Disables invocation of error and Stringer interface methods on types which only accept pointer receivers from non-pointer variables. Pointer method invocation is enabled by default. * DisablePointerAddresses DisablePointerAddresses specifies whether to disable the printing of pointer addresses. This is useful when diffing data structures in tests. * DisableCapacities DisableCapacities specifies whether to disable the printing of capacities for arrays, slices, maps and channels. This is useful when diffing data structures in tests. * ContinueOnMethod Enables recursion into types after invoking error and Stringer interface methods. Recursion after method invocation is disabled by default. * SortKeys Specifies map keys should be sorted before being printed. Use this to have a more deterministic, diffable output. Note that only native types (bool, int, uint, floats, uintptr and string) and types which implement error or Stringer interfaces are supported with other types sorted according to the reflect.Value.String() output which guarantees display stability. Natural map order is used by default. * SpewKeys Specifies that, as a last resort attempt, map keys should be spewed to strings and sorted by those strings. This is only considered if SortKeys is true. Dump Usage Simply call spew.Dump with a list of variables you want to dump: spew.Dump(myVar1, myVar2, ...) You may also call spew.Fdump if you would prefer to output to an arbitrary io.Writer. For example, to dump to standard error: spew.Fdump(os.Stderr, myVar1, myVar2, ...) A third option is to call spew.Sdump to get the formatted output as a string: str := spew.Sdump(myVar1, myVar2, ...) Sample Dump Output See the Dump example for details on the setup of the types and variables being shown here. (main.Foo) { unexportedField: (*main.Bar)(0xf84002e210)({ flag: (main.Flag) flagTwo, data: (uintptr) }), ExportedField: (map[interface {}]interface {}) (len=1) { (string) (len=3) "one": (bool) true } } Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C command as shown. ([]uint8) (len=32 cap=32) { 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... | 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0| 00000020 31 32 |12| } Custom Formatter Spew provides a custom formatter that implements the fmt.Formatter interface so that it integrates cleanly with standard fmt package printing functions. The formatter is useful for inline printing of smaller data types similar to the standard %v format specifier. The custom formatter only responds to the %v (most compact), %+v (adds pointer addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb combinations. Any other verbs such as %x and %q will be sent to the the standard fmt package for formatting. In addition, the custom formatter ignores the width and precision arguments (however they will still work on the format specifiers not handled by the custom formatter). Custom Formatter Usage The simplest way to make use of the spew custom formatter is to call one of the convenience functions such as spew.Printf, spew.Println, or spew.Printf. The functions have syntax you are most likely already familiar with: spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2) spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) spew.Println(myVar, myVar2) spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2) spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) See the Index for the full list convenience functions. Sample Formatter Output Double pointer to a uint8: %v: <**>5 %+v: <**>(0xf8400420d0->0xf8400420c8)5 %#v: (**uint8)5 %#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5 Pointer to circular struct with a uint8 field and a pointer to itself: %v: <*>{1 <*>} %+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)} %#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)} %#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)} See the Printf example for details on the setup of variables being shown here. Errors Since it is possible for custom Stringer/error interfaces to panic, spew detects them and handles them internally by printing the panic information inline with the output. Since spew is intended to provide deep pretty printing capabilities on structures, it intentionally does not return any errors. */ package spew ================================================ FILE: vendor/github.com/davecgh/go-spew/spew/dump.go ================================================ /* * Copyright (c) 2013-2016 Dave Collins * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ package spew import ( "bytes" "encoding/hex" "fmt" "io" "os" "reflect" "regexp" "strconv" "strings" ) var ( // uint8Type is a reflect.Type representing a uint8. It is used to // convert cgo types to uint8 slices for hexdumping. uint8Type = reflect.TypeOf(uint8(0)) // cCharRE is a regular expression that matches a cgo char. // It is used to detect character arrays to hexdump them. cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`) // cUnsignedCharRE is a regular expression that matches a cgo unsigned // char. It is used to detect unsigned character arrays to hexdump // them. cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`) // cUint8tCharRE is a regular expression that matches a cgo uint8_t. // It is used to detect uint8_t arrays to hexdump them. cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`) ) // dumpState contains information about the state of a dump operation. type dumpState struct { w io.Writer depth int pointers map[uintptr]int ignoreNextType bool ignoreNextIndent bool cs *ConfigState } // indent performs indentation according to the depth level and cs.Indent // option. func (d *dumpState) indent() { if d.ignoreNextIndent { d.ignoreNextIndent = false return } d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth)) } // unpackValue returns values inside of non-nil interfaces when possible. // This is useful for data types like structs, arrays, slices, and maps which // can contain varying types packed inside an interface. func (d *dumpState) unpackValue(v reflect.Value) reflect.Value { if v.Kind() == reflect.Interface && !v.IsNil() { v = v.Elem() } return v } // dumpPtr handles formatting of pointers by indirecting them as necessary. func (d *dumpState) dumpPtr(v reflect.Value) { // Remove pointers at or below the current depth from map used to detect // circular refs. for k, depth := range d.pointers { if depth >= d.depth { delete(d.pointers, k) } } // Keep list of all dereferenced pointers to show later. pointerChain := make([]uintptr, 0) // Figure out how many levels of indirection there are by dereferencing // pointers and unpacking interfaces down the chain while detecting circular // references. nilFound := false cycleFound := false indirects := 0 ve := v for ve.Kind() == reflect.Ptr { if ve.IsNil() { nilFound = true break } indirects++ addr := ve.Pointer() pointerChain = append(pointerChain, addr) if pd, ok := d.pointers[addr]; ok && pd < d.depth { cycleFound = true indirects-- break } d.pointers[addr] = d.depth ve = ve.Elem() if ve.Kind() == reflect.Interface { if ve.IsNil() { nilFound = true break } ve = ve.Elem() } } // Display type information. d.w.Write(openParenBytes) d.w.Write(bytes.Repeat(asteriskBytes, indirects)) d.w.Write([]byte(ve.Type().String())) d.w.Write(closeParenBytes) // Display pointer information. if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 { d.w.Write(openParenBytes) for i, addr := range pointerChain { if i > 0 { d.w.Write(pointerChainBytes) } printHexPtr(d.w, addr) } d.w.Write(closeParenBytes) } // Display dereferenced value. d.w.Write(openParenBytes) switch { case nilFound: d.w.Write(nilAngleBytes) case cycleFound: d.w.Write(circularBytes) default: d.ignoreNextType = true d.dump(ve) } d.w.Write(closeParenBytes) } // dumpSlice handles formatting of arrays and slices. Byte (uint8 under // reflection) arrays and slices are dumped in hexdump -C fashion. func (d *dumpState) dumpSlice(v reflect.Value) { // Determine whether this type should be hex dumped or not. Also, // for types which should be hexdumped, try to use the underlying data // first, then fall back to trying to convert them to a uint8 slice. var buf []uint8 doConvert := false doHexDump := false numEntries := v.Len() if numEntries > 0 { vt := v.Index(0).Type() vts := vt.String() switch { // C types that need to be converted. case cCharRE.MatchString(vts): fallthrough case cUnsignedCharRE.MatchString(vts): fallthrough case cUint8tCharRE.MatchString(vts): doConvert = true // Try to use existing uint8 slices and fall back to converting // and copying if that fails. case vt.Kind() == reflect.Uint8: // We need an addressable interface to convert the type // to a byte slice. However, the reflect package won't // give us an interface on certain things like // unexported struct fields in order to enforce // visibility rules. We use unsafe, when available, to // bypass these restrictions since this package does not // mutate the values. vs := v if !vs.CanInterface() || !vs.CanAddr() { vs = unsafeReflectValue(vs) } if !UnsafeDisabled { vs = vs.Slice(0, numEntries) // Use the existing uint8 slice if it can be // type asserted. iface := vs.Interface() if slice, ok := iface.([]uint8); ok { buf = slice doHexDump = true break } } // The underlying data needs to be converted if it can't // be type asserted to a uint8 slice. doConvert = true } // Copy and convert the underlying type if needed. if doConvert && vt.ConvertibleTo(uint8Type) { // Convert and copy each element into a uint8 byte // slice. buf = make([]uint8, numEntries) for i := 0; i < numEntries; i++ { vv := v.Index(i) buf[i] = uint8(vv.Convert(uint8Type).Uint()) } doHexDump = true } } // Hexdump the entire slice as needed. if doHexDump { indent := strings.Repeat(d.cs.Indent, d.depth) str := indent + hex.Dump(buf) str = strings.Replace(str, "\n", "\n"+indent, -1) str = strings.TrimRight(str, d.cs.Indent) d.w.Write([]byte(str)) return } // Recursively call dump for each item. for i := 0; i < numEntries; i++ { d.dump(d.unpackValue(v.Index(i))) if i < (numEntries - 1) { d.w.Write(commaNewlineBytes) } else { d.w.Write(newlineBytes) } } } // dump is the main workhorse for dumping a value. It uses the passed reflect // value to figure out what kind of object we are dealing with and formats it // appropriately. It is a recursive function, however circular data structures // are detected and handled properly. func (d *dumpState) dump(v reflect.Value) { // Handle invalid reflect values immediately. kind := v.Kind() if kind == reflect.Invalid { d.w.Write(invalidAngleBytes) return } // Handle pointers specially. if kind == reflect.Ptr { d.indent() d.dumpPtr(v) return } // Print type information unless already handled elsewhere. if !d.ignoreNextType { d.indent() d.w.Write(openParenBytes) d.w.Write([]byte(v.Type().String())) d.w.Write(closeParenBytes) d.w.Write(spaceBytes) } d.ignoreNextType = false // Display length and capacity if the built-in len and cap functions // work with the value's kind and the len/cap itself is non-zero. valueLen, valueCap := 0, 0 switch v.Kind() { case reflect.Array, reflect.Slice, reflect.Chan: valueLen, valueCap = v.Len(), v.Cap() case reflect.Map, reflect.String: valueLen = v.Len() } if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 { d.w.Write(openParenBytes) if valueLen != 0 { d.w.Write(lenEqualsBytes) printInt(d.w, int64(valueLen), 10) } if !d.cs.DisableCapacities && valueCap != 0 { if valueLen != 0 { d.w.Write(spaceBytes) } d.w.Write(capEqualsBytes) printInt(d.w, int64(valueCap), 10) } d.w.Write(closeParenBytes) d.w.Write(spaceBytes) } // Call Stringer/error interfaces if they exist and the handle methods flag // is enabled if !d.cs.DisableMethods { if (kind != reflect.Invalid) && (kind != reflect.Interface) { if handled := handleMethods(d.cs, d.w, v); handled { return } } } switch kind { case reflect.Invalid: // Do nothing. We should never get here since invalid has already // been handled above. case reflect.Bool: printBool(d.w, v.Bool()) case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: printInt(d.w, v.Int(), 10) case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: printUint(d.w, v.Uint(), 10) case reflect.Float32: printFloat(d.w, v.Float(), 32) case reflect.Float64: printFloat(d.w, v.Float(), 64) case reflect.Complex64: printComplex(d.w, v.Complex(), 32) case reflect.Complex128: printComplex(d.w, v.Complex(), 64) case reflect.Slice: if v.IsNil() { d.w.Write(nilAngleBytes) break } fallthrough case reflect.Array: d.w.Write(openBraceNewlineBytes) d.depth++ if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { d.indent() d.w.Write(maxNewlineBytes) } else { d.dumpSlice(v) } d.depth-- d.indent() d.w.Write(closeBraceBytes) case reflect.String: d.w.Write([]byte(strconv.Quote(v.String()))) case reflect.Interface: // The only time we should get here is for nil interfaces due to // unpackValue calls. if v.IsNil() { d.w.Write(nilAngleBytes) } case reflect.Ptr: // Do nothing. We should never get here since pointers have already // been handled above. case reflect.Map: // nil maps should be indicated as different than empty maps if v.IsNil() { d.w.Write(nilAngleBytes) break } d.w.Write(openBraceNewlineBytes) d.depth++ if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { d.indent() d.w.Write(maxNewlineBytes) } else { numEntries := v.Len() keys := v.MapKeys() if d.cs.SortKeys { sortValues(keys, d.cs) } for i, key := range keys { d.dump(d.unpackValue(key)) d.w.Write(colonSpaceBytes) d.ignoreNextIndent = true d.dump(d.unpackValue(v.MapIndex(key))) if i < (numEntries - 1) { d.w.Write(commaNewlineBytes) } else { d.w.Write(newlineBytes) } } } d.depth-- d.indent() d.w.Write(closeBraceBytes) case reflect.Struct: d.w.Write(openBraceNewlineBytes) d.depth++ if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { d.indent() d.w.Write(maxNewlineBytes) } else { vt := v.Type() numFields := v.NumField() for i := 0; i < numFields; i++ { d.indent() vtf := vt.Field(i) d.w.Write([]byte(vtf.Name)) d.w.Write(colonSpaceBytes) d.ignoreNextIndent = true d.dump(d.unpackValue(v.Field(i))) if i < (numFields - 1) { d.w.Write(commaNewlineBytes) } else { d.w.Write(newlineBytes) } } } d.depth-- d.indent() d.w.Write(closeBraceBytes) case reflect.Uintptr: printHexPtr(d.w, uintptr(v.Uint())) case reflect.UnsafePointer, reflect.Chan, reflect.Func: printHexPtr(d.w, v.Pointer()) // There were not any other types at the time this code was written, but // fall back to letting the default fmt package handle it in case any new // types are added. default: if v.CanInterface() { fmt.Fprintf(d.w, "%v", v.Interface()) } else { fmt.Fprintf(d.w, "%v", v.String()) } } } // fdump is a helper function to consolidate the logic from the various public // methods which take varying writers and config states. func fdump(cs *ConfigState, w io.Writer, a ...interface{}) { for _, arg := range a { if arg == nil { w.Write(interfaceBytes) w.Write(spaceBytes) w.Write(nilAngleBytes) w.Write(newlineBytes) continue } d := dumpState{w: w, cs: cs} d.pointers = make(map[uintptr]int) d.dump(reflect.ValueOf(arg)) d.w.Write(newlineBytes) } } // Fdump formats and displays the passed arguments to io.Writer w. It formats // exactly the same as Dump. func Fdump(w io.Writer, a ...interface{}) { fdump(&Config, w, a...) } // Sdump returns a string with the passed arguments formatted exactly the same // as Dump. func Sdump(a ...interface{}) string { var buf bytes.Buffer fdump(&Config, &buf, a...) return buf.String() } /* Dump displays the passed parameters to standard out with newlines, customizable indentation, and additional debug information such as complete types and all pointer addresses used to indirect to the final value. It provides the following features over the built-in printing facilities provided by the fmt package: * Pointers are dereferenced and followed * Circular data structures are detected and handled properly * Custom Stringer/error interfaces are optionally invoked, including on unexported types * Custom types which only implement the Stringer/error interfaces via a pointer receiver are optionally invoked when passing non-pointer variables * Byte arrays and slices are dumped like the hexdump -C command which includes offsets, byte values in hex, and ASCII output The configuration options are controlled by an exported package global, spew.Config. See ConfigState for options documentation. See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to get the formatted result as a string. */ func Dump(a ...interface{}) { fdump(&Config, os.Stdout, a...) } ================================================ FILE: vendor/github.com/davecgh/go-spew/spew/format.go ================================================ /* * Copyright (c) 2013-2016 Dave Collins * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ package spew import ( "bytes" "fmt" "reflect" "strconv" "strings" ) // supportedFlags is a list of all the character flags supported by fmt package. const supportedFlags = "0-+# " // formatState implements the fmt.Formatter interface and contains information // about the state of a formatting operation. The NewFormatter function can // be used to get a new Formatter which can be used directly as arguments // in standard fmt package printing calls. type formatState struct { value interface{} fs fmt.State depth int pointers map[uintptr]int ignoreNextType bool cs *ConfigState } // buildDefaultFormat recreates the original format string without precision // and width information to pass in to fmt.Sprintf in the case of an // unrecognized type. Unless new types are added to the language, this // function won't ever be called. func (f *formatState) buildDefaultFormat() (format string) { buf := bytes.NewBuffer(percentBytes) for _, flag := range supportedFlags { if f.fs.Flag(int(flag)) { buf.WriteRune(flag) } } buf.WriteRune('v') format = buf.String() return format } // constructOrigFormat recreates the original format string including precision // and width information to pass along to the standard fmt package. This allows // automatic deferral of all format strings this package doesn't support. func (f *formatState) constructOrigFormat(verb rune) (format string) { buf := bytes.NewBuffer(percentBytes) for _, flag := range supportedFlags { if f.fs.Flag(int(flag)) { buf.WriteRune(flag) } } if width, ok := f.fs.Width(); ok { buf.WriteString(strconv.Itoa(width)) } if precision, ok := f.fs.Precision(); ok { buf.Write(precisionBytes) buf.WriteString(strconv.Itoa(precision)) } buf.WriteRune(verb) format = buf.String() return format } // unpackValue returns values inside of non-nil interfaces when possible and // ensures that types for values which have been unpacked from an interface // are displayed when the show types flag is also set. // This is useful for data types like structs, arrays, slices, and maps which // can contain varying types packed inside an interface. func (f *formatState) unpackValue(v reflect.Value) reflect.Value { if v.Kind() == reflect.Interface { f.ignoreNextType = false if !v.IsNil() { v = v.Elem() } } return v } // formatPtr handles formatting of pointers by indirecting them as necessary. func (f *formatState) formatPtr(v reflect.Value) { // Display nil if top level pointer is nil. showTypes := f.fs.Flag('#') if v.IsNil() && (!showTypes || f.ignoreNextType) { f.fs.Write(nilAngleBytes) return } // Remove pointers at or below the current depth from map used to detect // circular refs. for k, depth := range f.pointers { if depth >= f.depth { delete(f.pointers, k) } } // Keep list of all dereferenced pointers to possibly show later. pointerChain := make([]uintptr, 0) // Figure out how many levels of indirection there are by derferencing // pointers and unpacking interfaces down the chain while detecting circular // references. nilFound := false cycleFound := false indirects := 0 ve := v for ve.Kind() == reflect.Ptr { if ve.IsNil() { nilFound = true break } indirects++ addr := ve.Pointer() pointerChain = append(pointerChain, addr) if pd, ok := f.pointers[addr]; ok && pd < f.depth { cycleFound = true indirects-- break } f.pointers[addr] = f.depth ve = ve.Elem() if ve.Kind() == reflect.Interface { if ve.IsNil() { nilFound = true break } ve = ve.Elem() } } // Display type or indirection level depending on flags. if showTypes && !f.ignoreNextType { f.fs.Write(openParenBytes) f.fs.Write(bytes.Repeat(asteriskBytes, indirects)) f.fs.Write([]byte(ve.Type().String())) f.fs.Write(closeParenBytes) } else { if nilFound || cycleFound { indirects += strings.Count(ve.Type().String(), "*") } f.fs.Write(openAngleBytes) f.fs.Write([]byte(strings.Repeat("*", indirects))) f.fs.Write(closeAngleBytes) } // Display pointer information depending on flags. if f.fs.Flag('+') && (len(pointerChain) > 0) { f.fs.Write(openParenBytes) for i, addr := range pointerChain { if i > 0 { f.fs.Write(pointerChainBytes) } printHexPtr(f.fs, addr) } f.fs.Write(closeParenBytes) } // Display dereferenced value. switch { case nilFound: f.fs.Write(nilAngleBytes) case cycleFound: f.fs.Write(circularShortBytes) default: f.ignoreNextType = true f.format(ve) } } // format is the main workhorse for providing the Formatter interface. It // uses the passed reflect value to figure out what kind of object we are // dealing with and formats it appropriately. It is a recursive function, // however circular data structures are detected and handled properly. func (f *formatState) format(v reflect.Value) { // Handle invalid reflect values immediately. kind := v.Kind() if kind == reflect.Invalid { f.fs.Write(invalidAngleBytes) return } // Handle pointers specially. if kind == reflect.Ptr { f.formatPtr(v) return } // Print type information unless already handled elsewhere. if !f.ignoreNextType && f.fs.Flag('#') { f.fs.Write(openParenBytes) f.fs.Write([]byte(v.Type().String())) f.fs.Write(closeParenBytes) } f.ignoreNextType = false // Call Stringer/error interfaces if they exist and the handle methods // flag is enabled. if !f.cs.DisableMethods { if (kind != reflect.Invalid) && (kind != reflect.Interface) { if handled := handleMethods(f.cs, f.fs, v); handled { return } } } switch kind { case reflect.Invalid: // Do nothing. We should never get here since invalid has already // been handled above. case reflect.Bool: printBool(f.fs, v.Bool()) case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: printInt(f.fs, v.Int(), 10) case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: printUint(f.fs, v.Uint(), 10) case reflect.Float32: printFloat(f.fs, v.Float(), 32) case reflect.Float64: printFloat(f.fs, v.Float(), 64) case reflect.Complex64: printComplex(f.fs, v.Complex(), 32) case reflect.Complex128: printComplex(f.fs, v.Complex(), 64) case reflect.Slice: if v.IsNil() { f.fs.Write(nilAngleBytes) break } fallthrough case reflect.Array: f.fs.Write(openBracketBytes) f.depth++ if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { f.fs.Write(maxShortBytes) } else { numEntries := v.Len() for i := 0; i < numEntries; i++ { if i > 0 { f.fs.Write(spaceBytes) } f.ignoreNextType = true f.format(f.unpackValue(v.Index(i))) } } f.depth-- f.fs.Write(closeBracketBytes) case reflect.String: f.fs.Write([]byte(v.String())) case reflect.Interface: // The only time we should get here is for nil interfaces due to // unpackValue calls. if v.IsNil() { f.fs.Write(nilAngleBytes) } case reflect.Ptr: // Do nothing. We should never get here since pointers have already // been handled above. case reflect.Map: // nil maps should be indicated as different than empty maps if v.IsNil() { f.fs.Write(nilAngleBytes) break } f.fs.Write(openMapBytes) f.depth++ if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { f.fs.Write(maxShortBytes) } else { keys := v.MapKeys() if f.cs.SortKeys { sortValues(keys, f.cs) } for i, key := range keys { if i > 0 { f.fs.Write(spaceBytes) } f.ignoreNextType = true f.format(f.unpackValue(key)) f.fs.Write(colonBytes) f.ignoreNextType = true f.format(f.unpackValue(v.MapIndex(key))) } } f.depth-- f.fs.Write(closeMapBytes) case reflect.Struct: numFields := v.NumField() f.fs.Write(openBraceBytes) f.depth++ if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { f.fs.Write(maxShortBytes) } else { vt := v.Type() for i := 0; i < numFields; i++ { if i > 0 { f.fs.Write(spaceBytes) } vtf := vt.Field(i) if f.fs.Flag('+') || f.fs.Flag('#') { f.fs.Write([]byte(vtf.Name)) f.fs.Write(colonBytes) } f.format(f.unpackValue(v.Field(i))) } } f.depth-- f.fs.Write(closeBraceBytes) case reflect.Uintptr: printHexPtr(f.fs, uintptr(v.Uint())) case reflect.UnsafePointer, reflect.Chan, reflect.Func: printHexPtr(f.fs, v.Pointer()) // There were not any other types at the time this code was written, but // fall back to letting the default fmt package handle it if any get added. default: format := f.buildDefaultFormat() if v.CanInterface() { fmt.Fprintf(f.fs, format, v.Interface()) } else { fmt.Fprintf(f.fs, format, v.String()) } } } // Format satisfies the fmt.Formatter interface. See NewFormatter for usage // details. func (f *formatState) Format(fs fmt.State, verb rune) { f.fs = fs // Use standard formatting for verbs that are not v. if verb != 'v' { format := f.constructOrigFormat(verb) fmt.Fprintf(fs, format, f.value) return } if f.value == nil { if fs.Flag('#') { fs.Write(interfaceBytes) } fs.Write(nilAngleBytes) return } f.format(reflect.ValueOf(f.value)) } // newFormatter is a helper function to consolidate the logic from the various // public methods which take varying config states. func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter { fs := &formatState{value: v, cs: cs} fs.pointers = make(map[uintptr]int) return fs } /* NewFormatter returns a custom formatter that satisfies the fmt.Formatter interface. As a result, it integrates cleanly with standard fmt package printing functions. The formatter is useful for inline printing of smaller data types similar to the standard %v format specifier. The custom formatter only responds to the %v (most compact), %+v (adds pointer addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb combinations. Any other verbs such as %x and %q will be sent to the the standard fmt package for formatting. In addition, the custom formatter ignores the width and precision arguments (however they will still work on the format specifiers not handled by the custom formatter). Typically this function shouldn't be called directly. It is much easier to make use of the custom formatter by calling one of the convenience functions such as Printf, Println, or Fprintf. */ func NewFormatter(v interface{}) fmt.Formatter { return newFormatter(&Config, v) } ================================================ FILE: vendor/github.com/davecgh/go-spew/spew/spew.go ================================================ /* * Copyright (c) 2013-2016 Dave Collins * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ package spew import ( "fmt" "io" ) // Errorf is a wrapper for fmt.Errorf that treats each argument as if it were // passed with a default Formatter interface returned by NewFormatter. It // returns the formatted string as a value that satisfies error. See // NewFormatter for formatting details. // // This function is shorthand for the following syntax: // // fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b)) func Errorf(format string, a ...interface{}) (err error) { return fmt.Errorf(format, convertArgs(a)...) } // Fprint is a wrapper for fmt.Fprint that treats each argument as if it were // passed with a default Formatter interface returned by NewFormatter. It // returns the number of bytes written and any write error encountered. See // NewFormatter for formatting details. // // This function is shorthand for the following syntax: // // fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b)) func Fprint(w io.Writer, a ...interface{}) (n int, err error) { return fmt.Fprint(w, convertArgs(a)...) } // Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were // passed with a default Formatter interface returned by NewFormatter. It // returns the number of bytes written and any write error encountered. See // NewFormatter for formatting details. // // This function is shorthand for the following syntax: // // fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b)) func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { return fmt.Fprintf(w, format, convertArgs(a)...) } // Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it // passed with a default Formatter interface returned by NewFormatter. See // NewFormatter for formatting details. // // This function is shorthand for the following syntax: // // fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b)) func Fprintln(w io.Writer, a ...interface{}) (n int, err error) { return fmt.Fprintln(w, convertArgs(a)...) } // Print is a wrapper for fmt.Print that treats each argument as if it were // passed with a default Formatter interface returned by NewFormatter. It // returns the number of bytes written and any write error encountered. See // NewFormatter for formatting details. // // This function is shorthand for the following syntax: // // fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b)) func Print(a ...interface{}) (n int, err error) { return fmt.Print(convertArgs(a)...) } // Printf is a wrapper for fmt.Printf that treats each argument as if it were // passed with a default Formatter interface returned by NewFormatter. It // returns the number of bytes written and any write error encountered. See // NewFormatter for formatting details. // // This function is shorthand for the following syntax: // // fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b)) func Printf(format string, a ...interface{}) (n int, err error) { return fmt.Printf(format, convertArgs(a)...) } // Println is a wrapper for fmt.Println that treats each argument as if it were // passed with a default Formatter interface returned by NewFormatter. It // returns the number of bytes written and any write error encountered. See // NewFormatter for formatting details. // // This function is shorthand for the following syntax: // // fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b)) func Println(a ...interface{}) (n int, err error) { return fmt.Println(convertArgs(a)...) } // Sprint is a wrapper for fmt.Sprint that treats each argument as if it were // passed with a default Formatter interface returned by NewFormatter. It // returns the resulting string. See NewFormatter for formatting details. // // This function is shorthand for the following syntax: // // fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b)) func Sprint(a ...interface{}) string { return fmt.Sprint(convertArgs(a)...) } // Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were // passed with a default Formatter interface returned by NewFormatter. It // returns the resulting string. See NewFormatter for formatting details. // // This function is shorthand for the following syntax: // // fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b)) func Sprintf(format string, a ...interface{}) string { return fmt.Sprintf(format, convertArgs(a)...) } // Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it // were passed with a default Formatter interface returned by NewFormatter. It // returns the resulting string. See NewFormatter for formatting details. // // This function is shorthand for the following syntax: // // fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b)) func Sprintln(a ...interface{}) string { return fmt.Sprintln(convertArgs(a)...) } // convertArgs accepts a slice of arguments and returns a slice of the same // length with each argument converted to a default spew Formatter interface. func convertArgs(args []interface{}) (formatters []interface{}) { formatters = make([]interface{}, len(args)) for index, arg := range args { formatters[index] = NewFormatter(arg) } return formatters } ================================================ FILE: vendor/github.com/distribution/reference/.gitattributes ================================================ *.go text eol=lf ================================================ FILE: vendor/github.com/distribution/reference/.gitignore ================================================ # Cover profiles *.out ================================================ FILE: vendor/github.com/distribution/reference/.golangci.yml ================================================ linters: enable: - bodyclose - dupword # Checks for duplicate words in the source code - gofmt - goimports - ineffassign - misspell - revive - staticcheck - unconvert - unused - vet disable: - errcheck run: deadline: 2m ================================================ FILE: vendor/github.com/distribution/reference/CODE-OF-CONDUCT.md ================================================ # Code of Conduct We follow the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). Please contact the [CNCF Code of Conduct Committee](mailto:conduct@cncf.io) in order to report violations of the Code of Conduct. ================================================ FILE: vendor/github.com/distribution/reference/CONTRIBUTING.md ================================================ # Contributing to the reference library ## Community help If you need help, please ask in the [#distribution](https://cloud-native.slack.com/archives/C01GVR8SY4R) channel on CNCF community slack. [Click here for an invite to the CNCF community slack](https://slack.cncf.io/) ## Reporting security issues The maintainers take security seriously. If you discover a security issue, please bring it to their attention right away! Please **DO NOT** file a public issue, instead send your report privately to [cncf-distribution-security@lists.cncf.io](mailto:cncf-distribution-security@lists.cncf.io). ## Reporting an issue properly By following these simple rules you will get better and faster feedback on your issue. - search the bugtracker for an already reported issue ### If you found an issue that describes your problem: - please read other user comments first, and confirm this is the same issue: a given error condition might be indicative of different problems - you may also find a workaround in the comments - please refrain from adding "same thing here" or "+1" comments - you don't need to comment on an issue to get notified of updates: just hit the "subscribe" button - comment if you have some new, technical and relevant information to add to the case - __DO NOT__ comment on closed issues or merged PRs. If you think you have a related problem, open up a new issue and reference the PR or issue. ### If you have not found an existing issue that describes your problem: 1. create a new issue, with a succinct title that describes your issue: - bad title: "It doesn't work with my docker" - good title: "Private registry push fail: 400 error with E_INVALID_DIGEST" 2. copy the output of (or similar for other container tools): - `docker version` - `docker info` - `docker exec registry --version` 3. copy the command line you used to launch your Registry 4. restart your docker daemon in debug mode (add `-D` to the daemon launch arguments) 5. reproduce your problem and get your docker daemon logs showing the error 6. if relevant, copy your registry logs that show the error 7. provide any relevant detail about your specific Registry configuration (e.g., storage backend used) 8. indicate if you are using an enterprise proxy, Nginx, or anything else between you and your Registry ## Contributing Code Contributions should be made via pull requests. Pull requests will be reviewed by one or more maintainers or reviewers and merged when acceptable. You should follow the basic GitHub workflow: 1. Use your own [fork](https://help.github.com/en/articles/about-forks) 2. Create your [change](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#successful-changes) 3. Test your code 4. [Commit](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#commit-messages) your work, always [sign your commits](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#commit-messages) 5. Push your change to your fork and create a [Pull Request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork) Refer to [containerd's contribution guide](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#successful-changes) for tips on creating a successful contribution. ## Sign your work The sign-off is a simple line at the end of the explanation for the patch. Your signature certifies that you wrote the patch or otherwise have the right to pass it on as an open-source patch. The rules are pretty simple: if you can certify the below (from [developercertificate.org](http://developercertificate.org/)): ``` Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 660 York Street, Suite 102, San Francisco, CA 94110 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. ``` Then you just add a line to every git commit message: Signed-off-by: Joe Smith Use your real name (sorry, no pseudonyms or anonymous contributions.) If you set your `user.name` and `user.email` git configs, you can sign your commit automatically with `git commit -s`. ================================================ FILE: vendor/github.com/distribution/reference/GOVERNANCE.md ================================================ # distribution/reference Project Governance Distribution [Code of Conduct](./CODE-OF-CONDUCT.md) can be found here. For specific guidance on practical contribution steps please see our [CONTRIBUTING.md](./CONTRIBUTING.md) guide. ## Maintainership There are different types of maintainers, with different responsibilities, but all maintainers have 3 things in common: 1) They share responsibility in the project's success. 2) They have made a long-term, recurring time investment to improve the project. 3) They spend that time doing whatever needs to be done, not necessarily what is the most interesting or fun. Maintainers are often under-appreciated, because their work is harder to appreciate. It's easy to appreciate a really cool and technically advanced feature. It's harder to appreciate the absence of bugs, the slow but steady improvement in stability, or the reliability of a release process. But those things distinguish a good project from a great one. ## Reviewers A reviewer is a core role within the project. They share in reviewing issues and pull requests and their LGTM counts towards the required LGTM count to merge a code change into the project. Reviewers are part of the organization but do not have write access. Becoming a reviewer is a core aspect in the journey to becoming a maintainer. ## Adding maintainers Maintainers are first and foremost contributors that have shown they are committed to the long term success of a project. Contributors wanting to become maintainers are expected to be deeply involved in contributing code, pull request review, and triage of issues in the project for more than three months. Just contributing does not make you a maintainer, it is about building trust with the current maintainers of the project and being a person that they can depend on and trust to make decisions in the best interest of the project. Periodically, the existing maintainers curate a list of contributors that have shown regular activity on the project over the prior months. From this list, maintainer candidates are selected and proposed in a pull request or a maintainers communication channel. After a candidate has been announced to the maintainers, the existing maintainers are given five business days to discuss the candidate, raise objections and cast their vote. Votes may take place on the communication channel or via pull request comment. Candidates must be approved by at least 66% of the current maintainers by adding their vote on the mailing list. The reviewer role has the same process but only requires 33% of current maintainers. Only maintainers of the repository that the candidate is proposed for are allowed to vote. If a candidate is approved, a maintainer will contact the candidate to invite the candidate to open a pull request that adds the contributor to the MAINTAINERS file. The voting process may take place inside a pull request if a maintainer has already discussed the candidacy with the candidate and a maintainer is willing to be a sponsor by opening the pull request. The candidate becomes a maintainer once the pull request is merged. ## Stepping down policy Life priorities, interests, and passions can change. If you're a maintainer but feel you must remove yourself from the list, inform other maintainers that you intend to step down, and if possible, help find someone to pick up your work. At the very least, ensure your work can be continued where you left off. After you've informed other maintainers, create a pull request to remove yourself from the MAINTAINERS file. ## Removal of inactive maintainers Similar to the procedure for adding new maintainers, existing maintainers can be removed from the list if they do not show significant activity on the project. Periodically, the maintainers review the list of maintainers and their activity over the last three months. If a maintainer has shown insufficient activity over this period, a neutral person will contact the maintainer to ask if they want to continue being a maintainer. If the maintainer decides to step down as a maintainer, they open a pull request to be removed from the MAINTAINERS file. If the maintainer wants to remain a maintainer, but is unable to perform the required duties they can be removed with a vote of at least 66% of the current maintainers. In this case, maintainers should first propose the change to maintainers via the maintainers communication channel, then open a pull request for voting. The voting period is five business days. The voting pull request should not come as a surpise to any maintainer and any discussion related to performance must not be discussed on the pull request. ## How are decisions made? Docker distribution is an open-source project with an open design philosophy. This means that the repository is the source of truth for EVERY aspect of the project, including its philosophy, design, road map, and APIs. *If it's part of the project, it's in the repo. If it's in the repo, it's part of the project.* As a result, all decisions can be expressed as changes to the repository. An implementation change is a change to the source code. An API change is a change to the API specification. A philosophy change is a change to the philosophy manifesto, and so on. All decisions affecting distribution, big and small, follow the same 3 steps: * Step 1: Open a pull request. Anyone can do this. * Step 2: Discuss the pull request. Anyone can do this. * Step 3: Merge or refuse the pull request. Who does this depends on the nature of the pull request and which areas of the project it affects. ## Helping contributors with the DCO The [DCO or `Sign your work`](./CONTRIBUTING.md#sign-your-work) requirement is not intended as a roadblock or speed bump. Some contributors are not as familiar with `git`, or have used a web based editor, and thus asking them to `git commit --amend -s` is not the best way forward. In this case, maintainers can update the commits based on clause (c) of the DCO. The most trivial way for a contributor to allow the maintainer to do this, is to add a DCO signature in a pull requests's comment, or a maintainer can simply note that the change is sufficiently trivial that it does not substantially change the existing contribution - i.e., a spelling change. When you add someone's DCO, please also add your own to keep a log. ## I'm a maintainer. Should I make pull requests too? Yes. Nobody should ever push to master directly. All changes should be made through a pull request. ## Conflict Resolution If you have a technical dispute that you feel has reached an impasse with a subset of the community, any contributor may open an issue, specifically calling for a resolution vote of the current core maintainers to resolve the dispute. The same voting quorums required (2/3) for adding and removing maintainers will apply to conflict resolution. ================================================ FILE: vendor/github.com/distribution/reference/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: vendor/github.com/distribution/reference/MAINTAINERS ================================================ # Distribution project maintainers & reviewers # # See GOVERNANCE.md for maintainer versus reviewer roles # # MAINTAINERS (cncf-distribution-maintainers@lists.cncf.io) # GitHub ID, Name, Email address "chrispat","Chris Patterson","chrispat@github.com" "clarkbw","Bryan Clark","clarkbw@github.com" "corhere","Cory Snider","csnider@mirantis.com" "deleteriousEffect","Hayley Swimelar","hswimelar@gitlab.com" "heww","He Weiwei","hweiwei@vmware.com" "joaodrp","João Pereira","jpereira@gitlab.com" "justincormack","Justin Cormack","justin.cormack@docker.com" "squizzi","Kyle Squizzato","ksquizzato@mirantis.com" "milosgajdos","Milos Gajdos","milosthegajdos@gmail.com" "sargun","Sargun Dhillon","sargun@sargun.me" "wy65701436","Wang Yan","wangyan@vmware.com" "stevelasker","Steve Lasker","steve.lasker@microsoft.com" # # REVIEWERS # GitHub ID, Name, Email address "dmcgowan","Derek McGowan","derek@mcgstyle.net" "stevvooe","Stephen Day","stevvooe@gmail.com" "thajeztah","Sebastiaan van Stijn","github@gone.nl" "DavidSpek", "David van der Spek", "vanderspek.david@gmail.com" "Jamstah", "James Hewitt", "james.hewitt@gmail.com" ================================================ FILE: vendor/github.com/distribution/reference/Makefile ================================================ # Project packages. PACKAGES=$(shell go list ./...) # Flags passed to `go test` BUILDFLAGS ?= TESTFLAGS ?= .PHONY: all build test coverage .DEFAULT: all all: build build: ## no binaries to build, so just check compilation suceeds go build ${BUILDFLAGS} ./... test: ## run tests go test ${TESTFLAGS} ./... coverage: ## generate coverprofiles from the unit tests rm -f coverage.txt go test ${TESTFLAGS} -cover -coverprofile=cover.out ./... .PHONY: help help: @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_\/%-]+:.*?##/ { printf " \033[36m%-27s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) ================================================ FILE: vendor/github.com/distribution/reference/README.md ================================================ # Distribution reference Go library to handle references to container images. [![Build Status](https://github.com/distribution/reference/actions/workflows/test.yml/badge.svg?branch=main&event=push)](https://github.com/distribution/reference/actions?query=workflow%3ACI) [![GoDoc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/distribution/reference) [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE) [![codecov](https://codecov.io/gh/distribution/reference/branch/main/graph/badge.svg)](https://codecov.io/gh/distribution/reference) [![FOSSA Status](https://app.fossa.com/api/projects/custom%2B162%2Fgithub.com%2Fdistribution%2Freference.svg?type=shield)](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Fdistribution%2Freference?ref=badge_shield) This repository contains a library for handling references to container images held in container registries. Please see [godoc](https://pkg.go.dev/github.com/distribution/reference) for details. ## Contribution Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute issues, fixes, and patches to this project. ## Communication For async communication and long running discussions please use issues and pull requests on the github repo. This will be the best place to discuss design and implementation. For sync communication we have a #distribution channel in the [CNCF Slack](https://slack.cncf.io/) that everyone is welcome to join and chat about development. ## Licenses The distribution codebase is released under the [Apache 2.0 license](LICENSE). ================================================ FILE: vendor/github.com/distribution/reference/SECURITY.md ================================================ # Security Policy ## Reporting a Vulnerability The maintainers take security seriously. If you discover a security issue, please bring it to their attention right away! Please DO NOT file a public issue, instead send your report privately to cncf-distribution-security@lists.cncf.io. ================================================ FILE: vendor/github.com/distribution/reference/helpers.go ================================================ package reference import "path" // IsNameOnly returns true if reference only contains a repo name. func IsNameOnly(ref Named) bool { if _, ok := ref.(NamedTagged); ok { return false } if _, ok := ref.(Canonical); ok { return false } return true } // FamiliarName returns the familiar name string // for the given named, familiarizing if needed. func FamiliarName(ref Named) string { if nn, ok := ref.(normalizedNamed); ok { return nn.Familiar().Name() } return ref.Name() } // FamiliarString returns the familiar string representation // for the given reference, familiarizing if needed. func FamiliarString(ref Reference) string { if nn, ok := ref.(normalizedNamed); ok { return nn.Familiar().String() } return ref.String() } // FamiliarMatch reports whether ref matches the specified pattern. // See [path.Match] for supported patterns. func FamiliarMatch(pattern string, ref Reference) (bool, error) { matched, err := path.Match(pattern, FamiliarString(ref)) if namedRef, isNamed := ref.(Named); isNamed && !matched { matched, _ = path.Match(pattern, FamiliarName(namedRef)) } return matched, err } ================================================ FILE: vendor/github.com/distribution/reference/normalize.go ================================================ package reference import ( "fmt" "strings" "github.com/opencontainers/go-digest" ) const ( // legacyDefaultDomain is the legacy domain for Docker Hub (which was // originally named "the Docker Index"). This domain is still used for // authentication and image search, which were part of the "v1" Docker // registry specification. // // This domain will continue to be supported, but there are plans to consolidate // legacy domains to new "canonical" domains. Once those domains are decided // on, we must update the normalization functions, but preserve compatibility // with existing installs, clients, and user configuration. legacyDefaultDomain = "index.docker.io" // defaultDomain is the default domain used for images on Docker Hub. // It is used to normalize "familiar" names to canonical names, for example, // to convert "ubuntu" to "docker.io/library/ubuntu:latest". // // Note that actual domain of Docker Hub's registry is registry-1.docker.io. // This domain will continue to be supported, but there are plans to consolidate // legacy domains to new "canonical" domains. Once those domains are decided // on, we must update the normalization functions, but preserve compatibility // with existing installs, clients, and user configuration. defaultDomain = "docker.io" // officialRepoPrefix is the namespace used for official images on Docker Hub. // It is used to normalize "familiar" names to canonical names, for example, // to convert "ubuntu" to "docker.io/library/ubuntu:latest". officialRepoPrefix = "library/" // defaultTag is the default tag if no tag is provided. defaultTag = "latest" ) // normalizedNamed represents a name which has been // normalized and has a familiar form. A familiar name // is what is used in Docker UI. An example normalized // name is "docker.io/library/ubuntu" and corresponding // familiar name of "ubuntu". type normalizedNamed interface { Named Familiar() Named } // ParseNormalizedNamed parses a string into a named reference // transforming a familiar name from Docker UI to a fully // qualified reference. If the value may be an identifier // use ParseAnyReference. func ParseNormalizedNamed(s string) (Named, error) { if ok := anchoredIdentifierRegexp.MatchString(s); ok { return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s) } domain, remainder := splitDockerDomain(s) var remote string if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 { remote = remainder[:tagSep] } else { remote = remainder } if strings.ToLower(remote) != remote { return nil, fmt.Errorf("invalid reference format: repository name (%s) must be lowercase", remote) } ref, err := Parse(domain + "/" + remainder) if err != nil { return nil, err } named, isNamed := ref.(Named) if !isNamed { return nil, fmt.Errorf("reference %s has no name", ref.String()) } return named, nil } // namedTaggedDigested is a reference that has both a tag and a digest. type namedTaggedDigested interface { NamedTagged Digested } // ParseDockerRef normalizes the image reference following the docker convention, // which allows for references to contain both a tag and a digest. It returns a // reference that is either tagged or digested. For references containing both // a tag and a digest, it returns a digested reference. For example, the following // reference: // // docker.io/library/busybox:latest@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa // // Is returned as a digested reference (with the ":latest" tag removed): // // docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa // // References that are already "tagged" or "digested" are returned unmodified: // // // Already a digested reference // docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa // // // Already a named reference // docker.io/library/busybox:latest func ParseDockerRef(ref string) (Named, error) { named, err := ParseNormalizedNamed(ref) if err != nil { return nil, err } if canonical, ok := named.(namedTaggedDigested); ok { // The reference is both tagged and digested; only return digested. newNamed, err := WithName(canonical.Name()) if err != nil { return nil, err } return WithDigest(newNamed, canonical.Digest()) } return TagNameOnly(named), nil } // splitDockerDomain splits a repository name to domain and remote-name. // If no valid domain is found, the default domain is used. Repository name // needs to be already validated before. func splitDockerDomain(name string) (domain, remoteName string) { maybeDomain, maybeRemoteName, ok := strings.Cut(name, "/") if !ok { // Fast-path for single element ("familiar" names), such as "ubuntu" // or "ubuntu:latest". Familiar names must be handled separately, to // prevent them from being handled as "hostname:port". // // Canonicalize them as "docker.io/library/name[:tag]" // FIXME(thaJeztah): account for bare "localhost" or "example.com" names, which SHOULD be considered a domain. return defaultDomain, officialRepoPrefix + name } switch { case maybeDomain == localhost: // localhost is a reserved namespace and always considered a domain. domain, remoteName = maybeDomain, maybeRemoteName case maybeDomain == legacyDefaultDomain: // canonicalize the Docker Hub and legacy "Docker Index" domains. domain, remoteName = defaultDomain, maybeRemoteName case strings.ContainsAny(maybeDomain, ".:"): // Likely a domain or IP-address: // // - contains a "." (e.g., "example.com" or "127.0.0.1") // - contains a ":" (e.g., "example:5000", "::1", or "[::1]:5000") domain, remoteName = maybeDomain, maybeRemoteName case strings.ToLower(maybeDomain) != maybeDomain: // Uppercase namespaces are not allowed, so if the first element // is not lowercase, we assume it to be a domain-name. domain, remoteName = maybeDomain, maybeRemoteName default: // None of the above: it's not a domain, so use the default, and // use the name input the remote-name. domain, remoteName = defaultDomain, name } if domain == defaultDomain && !strings.ContainsRune(remoteName, '/') { // Canonicalize "familiar" names, but only on Docker Hub, not // on other domains: // // "docker.io/ubuntu[:tag]" => "docker.io/library/ubuntu[:tag]" remoteName = officialRepoPrefix + remoteName } return domain, remoteName } // familiarizeName returns a shortened version of the name familiar // to the Docker UI. Familiar names have the default domain // "docker.io" and "library/" repository prefix removed. // For example, "docker.io/library/redis" will have the familiar // name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp". // Returns a familiarized named only reference. func familiarizeName(named namedRepository) repository { repo := repository{ domain: named.Domain(), path: named.Path(), } if repo.domain == defaultDomain { repo.domain = "" // Handle official repositories which have the pattern "library/" if strings.HasPrefix(repo.path, officialRepoPrefix) { // TODO(thaJeztah): this check may be too strict, as it assumes the // "library/" namespace does not have nested namespaces. While this // is true (currently), technically it would be possible for Docker // Hub to use those (e.g. "library/distros/ubuntu:latest"). // See https://github.com/distribution/distribution/pull/3769#issuecomment-1302031785. if remainder := strings.TrimPrefix(repo.path, officialRepoPrefix); !strings.ContainsRune(remainder, '/') { repo.path = remainder } } } return repo } func (r reference) Familiar() Named { return reference{ namedRepository: familiarizeName(r.namedRepository), tag: r.tag, digest: r.digest, } } func (r repository) Familiar() Named { return familiarizeName(r) } func (t taggedReference) Familiar() Named { return taggedReference{ namedRepository: familiarizeName(t.namedRepository), tag: t.tag, } } func (c canonicalReference) Familiar() Named { return canonicalReference{ namedRepository: familiarizeName(c.namedRepository), digest: c.digest, } } // TagNameOnly adds the default tag "latest" to a reference if it only has // a repo name. func TagNameOnly(ref Named) Named { if IsNameOnly(ref) { namedTagged, err := WithTag(ref, defaultTag) if err != nil { // Default tag must be valid, to create a NamedTagged // type with non-validated input the WithTag function // should be used instead panic(err) } return namedTagged } return ref } // ParseAnyReference parses a reference string as a possible identifier, // full digest, or familiar name. func ParseAnyReference(ref string) (Reference, error) { if ok := anchoredIdentifierRegexp.MatchString(ref); ok { return digestReference("sha256:" + ref), nil } if dgst, err := digest.Parse(ref); err == nil { return digestReference(dgst), nil } return ParseNormalizedNamed(ref) } ================================================ FILE: vendor/github.com/distribution/reference/reference.go ================================================ // Package reference provides a general type to represent any way of referencing images within the registry. // Its main purpose is to abstract tags and digests (content-addressable hash). // // Grammar // // reference := name [ ":" tag ] [ "@" digest ] // name := [domain '/'] remote-name // domain := host [':' port-number] // host := domain-name | IPv4address | \[ IPv6address \] ; rfc3986 appendix-A // domain-name := domain-component ['.' domain-component]* // domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/ // port-number := /[0-9]+/ // path-component := alpha-numeric [separator alpha-numeric]* // path (or "remote-name") := path-component ['/' path-component]* // alpha-numeric := /[a-z0-9]+/ // separator := /[_.]|__|[-]*/ // // tag := /[\w][\w.-]{0,127}/ // // digest := digest-algorithm ":" digest-hex // digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]* // digest-algorithm-separator := /[+.-_]/ // digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/ // digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value // // identifier := /[a-f0-9]{64}/ package reference import ( "errors" "fmt" "strings" "github.com/opencontainers/go-digest" ) const ( // RepositoryNameTotalLengthMax is the maximum total number of characters in a repository name. RepositoryNameTotalLengthMax = 255 // NameTotalLengthMax is the maximum total number of characters in a repository name. // // Deprecated: use [RepositoryNameTotalLengthMax] instead. NameTotalLengthMax = RepositoryNameTotalLengthMax ) var ( // ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference. ErrReferenceInvalidFormat = errors.New("invalid reference format") // ErrTagInvalidFormat represents an error while trying to parse a string as a tag. ErrTagInvalidFormat = errors.New("invalid tag format") // ErrDigestInvalidFormat represents an error while trying to parse a string as a tag. ErrDigestInvalidFormat = errors.New("invalid digest format") // ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters. ErrNameContainsUppercase = errors.New("repository name must be lowercase") // ErrNameEmpty is returned for empty, invalid repository names. ErrNameEmpty = errors.New("repository name must have at least one component") // ErrNameTooLong is returned when a repository name is longer than RepositoryNameTotalLengthMax. ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", RepositoryNameTotalLengthMax) // ErrNameNotCanonical is returned when a name is not canonical. ErrNameNotCanonical = errors.New("repository name must be canonical") ) // Reference is an opaque object reference identifier that may include // modifiers such as a hostname, name, tag, and digest. type Reference interface { // String returns the full reference String() string } // Field provides a wrapper type for resolving correct reference types when // working with encoding. type Field struct { reference Reference } // AsField wraps a reference in a Field for encoding. func AsField(reference Reference) Field { return Field{reference} } // Reference unwraps the reference type from the field to // return the Reference object. This object should be // of the appropriate type to further check for different // reference types. func (f Field) Reference() Reference { return f.reference } // MarshalText serializes the field to byte text which // is the string of the reference. func (f Field) MarshalText() (p []byte, err error) { return []byte(f.reference.String()), nil } // UnmarshalText parses text bytes by invoking the // reference parser to ensure the appropriately // typed reference object is wrapped by field. func (f *Field) UnmarshalText(p []byte) error { r, err := Parse(string(p)) if err != nil { return err } f.reference = r return nil } // Named is an object with a full name type Named interface { Reference Name() string } // Tagged is an object which has a tag type Tagged interface { Reference Tag() string } // NamedTagged is an object including a name and tag. type NamedTagged interface { Named Tag() string } // Digested is an object which has a digest // in which it can be referenced by type Digested interface { Reference Digest() digest.Digest } // Canonical reference is an object with a fully unique // name including a name with domain and digest type Canonical interface { Named Digest() digest.Digest } // namedRepository is a reference to a repository with a name. // A namedRepository has both domain and path components. type namedRepository interface { Named Domain() string Path() string } // Domain returns the domain part of the [Named] reference. func Domain(named Named) string { if r, ok := named.(namedRepository); ok { return r.Domain() } domain, _ := splitDomain(named.Name()) return domain } // Path returns the name without the domain part of the [Named] reference. func Path(named Named) (name string) { if r, ok := named.(namedRepository); ok { return r.Path() } _, path := splitDomain(named.Name()) return path } // splitDomain splits a named reference into a hostname and path string. // If no valid hostname is found, the hostname is empty and the full value // is returned as name func splitDomain(name string) (string, string) { match := anchoredNameRegexp.FindStringSubmatch(name) if len(match) != 3 { return "", name } return match[1], match[2] } // Parse parses s and returns a syntactically valid Reference. // If an error was encountered it is returned, along with a nil Reference. func Parse(s string) (Reference, error) { matches := ReferenceRegexp.FindStringSubmatch(s) if matches == nil { if s == "" { return nil, ErrNameEmpty } if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil { return nil, ErrNameContainsUppercase } return nil, ErrReferenceInvalidFormat } var repo repository nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1]) if len(nameMatch) == 3 { repo.domain = nameMatch[1] repo.path = nameMatch[2] } else { repo.domain = "" repo.path = matches[1] } if len(repo.path) > RepositoryNameTotalLengthMax { return nil, ErrNameTooLong } ref := reference{ namedRepository: repo, tag: matches[2], } if matches[3] != "" { var err error ref.digest, err = digest.Parse(matches[3]) if err != nil { return nil, err } } r := getBestReferenceType(ref) if r == nil { return nil, ErrNameEmpty } return r, nil } // ParseNamed parses s and returns a syntactically valid reference implementing // the Named interface. The reference must have a name and be in the canonical // form, otherwise an error is returned. // If an error was encountered it is returned, along with a nil Reference. func ParseNamed(s string) (Named, error) { named, err := ParseNormalizedNamed(s) if err != nil { return nil, err } if named.String() != s { return nil, ErrNameNotCanonical } return named, nil } // WithName returns a named object representing the given string. If the input // is invalid ErrReferenceInvalidFormat will be returned. func WithName(name string) (Named, error) { match := anchoredNameRegexp.FindStringSubmatch(name) if match == nil || len(match) != 3 { return nil, ErrReferenceInvalidFormat } if len(match[2]) > RepositoryNameTotalLengthMax { return nil, ErrNameTooLong } return repository{ domain: match[1], path: match[2], }, nil } // WithTag combines the name from "name" and the tag from "tag" to form a // reference incorporating both the name and the tag. func WithTag(name Named, tag string) (NamedTagged, error) { if !anchoredTagRegexp.MatchString(tag) { return nil, ErrTagInvalidFormat } var repo repository if r, ok := name.(namedRepository); ok { repo.domain = r.Domain() repo.path = r.Path() } else { repo.path = name.Name() } if canonical, ok := name.(Canonical); ok { return reference{ namedRepository: repo, tag: tag, digest: canonical.Digest(), }, nil } return taggedReference{ namedRepository: repo, tag: tag, }, nil } // WithDigest combines the name from "name" and the digest from "digest" to form // a reference incorporating both the name and the digest. func WithDigest(name Named, digest digest.Digest) (Canonical, error) { if !anchoredDigestRegexp.MatchString(digest.String()) { return nil, ErrDigestInvalidFormat } var repo repository if r, ok := name.(namedRepository); ok { repo.domain = r.Domain() repo.path = r.Path() } else { repo.path = name.Name() } if tagged, ok := name.(Tagged); ok { return reference{ namedRepository: repo, tag: tagged.Tag(), digest: digest, }, nil } return canonicalReference{ namedRepository: repo, digest: digest, }, nil } // TrimNamed removes any tag or digest from the named reference. func TrimNamed(ref Named) Named { repo := repository{} if r, ok := ref.(namedRepository); ok { repo.domain, repo.path = r.Domain(), r.Path() } else { repo.domain, repo.path = splitDomain(ref.Name()) } return repo } func getBestReferenceType(ref reference) Reference { if ref.Name() == "" { // Allow digest only references if ref.digest != "" { return digestReference(ref.digest) } return nil } if ref.tag == "" { if ref.digest != "" { return canonicalReference{ namedRepository: ref.namedRepository, digest: ref.digest, } } return ref.namedRepository } if ref.digest == "" { return taggedReference{ namedRepository: ref.namedRepository, tag: ref.tag, } } return ref } type reference struct { namedRepository tag string digest digest.Digest } func (r reference) String() string { return r.Name() + ":" + r.tag + "@" + r.digest.String() } func (r reference) Tag() string { return r.tag } func (r reference) Digest() digest.Digest { return r.digest } type repository struct { domain string path string } func (r repository) String() string { return r.Name() } func (r repository) Name() string { if r.domain == "" { return r.path } return r.domain + "/" + r.path } func (r repository) Domain() string { return r.domain } func (r repository) Path() string { return r.path } type digestReference digest.Digest func (d digestReference) String() string { return digest.Digest(d).String() } func (d digestReference) Digest() digest.Digest { return digest.Digest(d) } type taggedReference struct { namedRepository tag string } func (t taggedReference) String() string { return t.Name() + ":" + t.tag } func (t taggedReference) Tag() string { return t.tag } type canonicalReference struct { namedRepository digest digest.Digest } func (c canonicalReference) String() string { return c.Name() + "@" + c.digest.String() } func (c canonicalReference) Digest() digest.Digest { return c.digest } ================================================ FILE: vendor/github.com/distribution/reference/regexp.go ================================================ package reference import ( "regexp" "strings" ) // DigestRegexp matches well-formed digests, including algorithm (e.g. "sha256:"). var DigestRegexp = regexp.MustCompile(digestPat) // DomainRegexp matches hostname or IP-addresses, optionally including a port // number. It defines the structure of potential domain components that may be // part of image names. This is purposely a subset of what is allowed by DNS to // ensure backwards compatibility with Docker image names. It may be a subset of // DNS domain name, an IPv4 address in decimal format, or an IPv6 address between // square brackets (excluding zone identifiers as defined by [RFC 6874] or special // addresses such as IPv4-Mapped). // // [RFC 6874]: https://www.rfc-editor.org/rfc/rfc6874. var DomainRegexp = regexp.MustCompile(domainAndPort) // IdentifierRegexp is the format for string identifier used as a // content addressable identifier using sha256. These identifiers // are like digests without the algorithm, since sha256 is used. var IdentifierRegexp = regexp.MustCompile(identifier) // NameRegexp is the format for the name component of references, including // an optional domain and port, but without tag or digest suffix. var NameRegexp = regexp.MustCompile(namePat) // ReferenceRegexp is the full supported format of a reference. The regexp // is anchored and has capturing groups for name, tag, and digest // components. var ReferenceRegexp = regexp.MustCompile(referencePat) // TagRegexp matches valid tag names. From [docker/docker:graph/tags.go]. // // [docker/docker:graph/tags.go]: https://github.com/moby/moby/blob/v1.6.0/graph/tags.go#L26-L28 var TagRegexp = regexp.MustCompile(tag) const ( // alphanumeric defines the alphanumeric atom, typically a // component of names. This only allows lower case characters and digits. alphanumeric = `[a-z0-9]+` // separator defines the separators allowed to be embedded in name // components. This allows one period, one or two underscore and multiple // dashes. Repeated dashes and underscores are intentionally treated // differently. In order to support valid hostnames as name components, // supporting repeated dash was added. Additionally double underscore is // now allowed as a separator to loosen the restriction for previously // supported names. separator = `(?:[._]|__|[-]+)` // localhost is treated as a special value for domain-name. Any other // domain-name without a "." or a ":port" are considered a path component. localhost = `localhost` // domainNameComponent restricts the registry domain component of a // repository name to start with a component as defined by DomainRegexp. domainNameComponent = `(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])` // optionalPort matches an optional port-number including the port separator // (e.g. ":80"). optionalPort = `(?::[0-9]+)?` // tag matches valid tag names. From docker/docker:graph/tags.go. tag = `[\w][\w.-]{0,127}` // digestPat matches well-formed digests, including algorithm (e.g. "sha256:"). // // TODO(thaJeztah): this should follow the same rules as https://pkg.go.dev/github.com/opencontainers/go-digest@v1.0.0#DigestRegexp // so that go-digest defines the canonical format. Note that the go-digest is // more relaxed: // - it allows multiple algorithms (e.g. "sha256+b64:") to allow // future expansion of supported algorithms. // - it allows the "" value to use urlsafe base64 encoding as defined // in [rfc4648, section 5]. // // [rfc4648, section 5]: https://www.rfc-editor.org/rfc/rfc4648#section-5. digestPat = `[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}` // identifier is the format for a content addressable identifier using sha256. // These identifiers are like digests without the algorithm, since sha256 is used. identifier = `([a-f0-9]{64})` // ipv6address are enclosed between square brackets and may be represented // in many ways, see rfc5952. Only IPv6 in compressed or uncompressed format // are allowed, IPv6 zone identifiers (rfc6874) or Special addresses such as // IPv4-Mapped are deliberately excluded. ipv6address = `\[(?:[a-fA-F0-9:]+)\]` ) var ( // domainName defines the structure of potential domain components // that may be part of image names. This is purposely a subset of what is // allowed by DNS to ensure backwards compatibility with Docker image // names. This includes IPv4 addresses on decimal format. domainName = domainNameComponent + anyTimes(`\.`+domainNameComponent) // host defines the structure of potential domains based on the URI // Host subcomponent on rfc3986. It may be a subset of DNS domain name, // or an IPv4 address in decimal format, or an IPv6 address between square // brackets (excluding zone identifiers as defined by rfc6874 or special // addresses such as IPv4-Mapped). host = `(?:` + domainName + `|` + ipv6address + `)` // allowed by the URI Host subcomponent on rfc3986 to ensure backwards // compatibility with Docker image names. domainAndPort = host + optionalPort // anchoredTagRegexp matches valid tag names, anchored at the start and // end of the matched string. anchoredTagRegexp = regexp.MustCompile(anchored(tag)) // anchoredDigestRegexp matches valid digests, anchored at the start and // end of the matched string. anchoredDigestRegexp = regexp.MustCompile(anchored(digestPat)) // pathComponent restricts path-components to start with an alphanumeric // character, with following parts able to be separated by a separator // (one period, one or two underscore and multiple dashes). pathComponent = alphanumeric + anyTimes(separator+alphanumeric) // remoteName matches the remote-name of a repository. It consists of one // or more forward slash (/) delimited path-components: // // pathComponent[[/pathComponent] ...] // e.g., "library/ubuntu" remoteName = pathComponent + anyTimes(`/`+pathComponent) namePat = optional(domainAndPort+`/`) + remoteName // anchoredNameRegexp is used to parse a name value, capturing the // domain and trailing components. anchoredNameRegexp = regexp.MustCompile(anchored(optional(capture(domainAndPort), `/`), capture(remoteName))) referencePat = anchored(capture(namePat), optional(`:`, capture(tag)), optional(`@`, capture(digestPat))) // anchoredIdentifierRegexp is used to check or match an // identifier value, anchored at start and end of string. anchoredIdentifierRegexp = regexp.MustCompile(anchored(identifier)) ) // optional wraps the expression in a non-capturing group and makes the // production optional. func optional(res ...string) string { return `(?:` + strings.Join(res, "") + `)?` } // anyTimes wraps the expression in a non-capturing group that can occur // any number of times. func anyTimes(res ...string) string { return `(?:` + strings.Join(res, "") + `)*` } // capture wraps the expression in a capturing group. func capture(res ...string) string { return `(` + strings.Join(res, "") + `)` } // anchored anchors the regular expression by adding start and end delimiters. func anchored(res ...string) string { return `^` + strings.Join(res, "") + `$` } ================================================ FILE: vendor/github.com/distribution/reference/sort.go ================================================ /* Copyright The containerd Authors. 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. */ package reference import ( "sort" ) // Sort sorts string references preferring higher information references. // // The precedence is as follows: // // 1. [Named] + [Tagged] + [Digested] (e.g., "docker.io/library/busybox:latest@sha256:") // 2. [Named] + [Tagged] (e.g., "docker.io/library/busybox:latest") // 3. [Named] + [Digested] (e.g., "docker.io/library/busybo@sha256:") // 4. [Named] (e.g., "docker.io/library/busybox") // 5. [Digested] (e.g., "docker.io@sha256:") // 6. Parse error func Sort(references []string) []string { var prefs []Reference var bad []string for _, ref := range references { pref, err := ParseAnyReference(ref) if err != nil { bad = append(bad, ref) } else { prefs = append(prefs, pref) } } sort.Slice(prefs, func(a, b int) bool { ar := refRank(prefs[a]) br := refRank(prefs[b]) if ar == br { return prefs[a].String() < prefs[b].String() } return ar < br }) sort.Strings(bad) var refs []string for _, pref := range prefs { refs = append(refs, pref.String()) } return append(refs, bad...) } func refRank(ref Reference) uint8 { if _, ok := ref.(Named); ok { if _, ok = ref.(Tagged); ok { if _, ok = ref.(Digested); ok { return 1 } return 2 } if _, ok = ref.(Digested); ok { return 3 } return 4 } return 5 } ================================================ FILE: vendor/github.com/docker/docker/AUTHORS ================================================ # File @generated by hack/generate-authors.sh. DO NOT EDIT. # This file lists all contributors to the repository. # See hack/generate-authors.sh to make modifications. 7sunarni <710720732@qq.com> Aanand Prasad Aarni Koskela Aaron Davidson Aaron Feng Aaron Hnatiw Aaron Huslage Aaron L. Xu Aaron Lehmann Aaron Welch Aaron Yoshitake Abdur Rehman Abel Muiño Abhijeet Kasurde Abhinandan Prativadi Abhinav Ajgaonkar Abhishek Chanda Abhishek Sharma Abin Shahab Abirdcfly Ada Mancini Adam Avilla Adam Dobrawy Adam Eijdenberg Adam Kunk Adam Lamers Adam Miller Adam Mills Adam Pointer Adam Simon Adam Singer Adam Thornton Adam Walz Adam Williams AdamKorcz Addam Hardy Aditi Rajagopal Aditya Adnan Khan Adolfo Ochagavía Adria Casas Adrian Moisey Adrian Mouat Adrian Oprea Adrien Folie Adrien Gallouët Ahmed Kamal Ahmet Alp Balkan Aidan Feldman Aidan Hobson Sayers AJ Bowen Ajey Charantimath ajneu Akash Gupta Akhil Mohan Akihiro Matsushima Akihiro Suda Akim Demaille Akira Koyasu Akshay Karle Akshay Moghe Al Tobey alambike Alan Hoyle Alan Scherger Alan Thompson Alano Terblanche Albert Callarisa Albert Zhang Albin Kerouanton Alec Benson Alejandro González Hevia Aleksa Sarai Aleksandr Chebotov Aleksandrs Fadins Alena Prokharchyk Alessandro Boch Alessio Biancalana Alex Chan Alex Chen Alex Coventry Alex Crawford Alex Ellis Alex Gaynor Alex Goodman Alex Nordlund Alex Olshansky Alex Samorukov Alex Stockinger Alex Warhawk Alexander Artemenko Alexander Boyd Alexander Larsson Alexander Midlash Alexander Morozov Alexander Polakov Alexander Shopov Alexandre Beslic Alexandre Garnier Alexandre González Alexandre Jomin Alexandru Sfirlogea Alexei Margasov Alexey Guskov Alexey Kotlyarov Alexey Shamrin Alexis Ries Alexis Thomas Alfred Landrum Ali Dehghani Alicia Lauerman Alihan Demir Allen Madsen Allen Sun almoehi Alvaro Saurin Alvin Deng Alvin Richards amangoel Amen Belayneh Ameya Gawde Amir Goldstein AmirBuddy Amit Bakshi Amit Krishnan Amit Shukla Amr Gawish Amy Lindburg Anand Patil AnandkumarPatel Anatoly Borodin Anca Iordache Anchal Agrawal Anda Xu Anders Janmyr Andre Dublin <81dublin@gmail.com> Andre Granovsky Andrea Denisse Gómez Andrea Luzzardi Andrea Turli Andreas Elvers Andreas Köhler Andreas Savvides Andreas Tiefenthaler Andrei Gherzan Andrei Ushakov Andrei Vagin Andrew Baxter <423qpsxzhh8k3h@s.rendaw.me> Andrew C. Bodine Andrew Clay Shafer Andrew Duckworth Andrew France Andrew Gerrand Andrew Guenther Andrew He Andrew Hsu Andrew Kim Andrew Kuklewicz Andrew Macgregor Andrew Macpherson Andrew Martin Andrew McDonnell Andrew Munsell Andrew Pennebaker Andrew Po Andrew Weiss Andrew Williams Andrews Medina Andrey Kolomentsev Andrey Petrov Andrey Stolbovsky André Martins Andrés Maldonado Andy Chambers andy diller Andy Goldstein Andy Kipp Andy Lindeman Andy Rothfusz Andy Smith Andy Wilson Andy Zhang Aneesh Kulkarni Anes Hasicic Angel Velazquez Anil Belur Anil Madhavapeddy Ankit Jain Ankush Agarwal Anonmily Anran Qiao Anshul Pundir Anthon van der Neut Anthony Baire Anthony Bishopric Anthony Dahanne Anthony Sottile Anton Löfgren Anton Nikitin Anton Polonskiy Anton Tiurin Antonio Aguilar Antonio Murdaca Antonis Kalipetis Antony Messerli Anuj Bahuguna Anuj Varma Anusha Ragunathan Anyu Wang apocas Arash Deshmeh arcosx ArikaChen Arko Dasgupta Arnaud Lefebvre Arnaud Porterie Arnaud Rebillout Artem Khramov Arthur Barr Arthur Gautier Artur Meyster Arun Gupta Asad Saeeduddin Asbjørn Enge Ashly Mathew Austin Vazquez averagehuman Avi Das Avi Kivity Avi Miller Avi Vaid Azat Khuyiyakhmetov Bao Yonglei Bardia Keyoumarsi Barnaby Gray Barry Allard Bartłomiej Piotrowski Bastiaan Bakker Bastien Pascard bdevloed Bearice Ren Ben Bonnefoy Ben Firshman Ben Golub Ben Gould Ben Hall Ben Langfeld Ben Lovy Ben Sargent Ben Severson Ben Toews Ben Wiklund Benjamin Atkin Benjamin Baker Benjamin Boudreau Benjamin Böhmke Benjamin Wang Benjamin Yolken Benny Ng Benoit Chesneau Bernerd Schaefer Bernhard M. Wiedemann Bert Goethals Bertrand Roussel Bevisy Zhang Bharath Thiruveedula Bhiraj Butala Bhumika Bayani Bilal Amarni Bill Wang Billy Ridgway Bily Zhang Bin Liu Bingshen Wang Bjorn Neergaard Blake Geno Boaz Shuster bobby abbott Bojun Zhu Boqin Qin Boris Pruessmann Boshi Lian Bouke Haarsma Boyd Hemphill boynux Bradley Cicenas Bradley Wright Brandon Liu Brandon Philips Brandon Rhodes Brendan Dixon Brennan Kinney <5098581+polarathene@users.noreply.github.com> Brent Salisbury Brett Higgins Brett Kochendorfer Brett Milford Brett Randall Brian (bex) Exelbierd Brian Bland Brian DeHamer Brian Dorsey Brian Flad Brian Goff Brian McCallister Brian Olsen Brian Schwind Brian Shumate Brian Torres-Gil Brian Trump Brice Jaglin Briehan Lombaard Brielle Broder Bruno Bigras Bruno Binet Bruno Gazzera Bruno Renié Bruno Tavares Bryan Bess Bryan Boreham Bryan Matsuo Bryan Murphy Burke Libbey Byung Kang Caleb Spare Calen Pennington Calvin Liu Cameron Boehmer Cameron Sparr Cameron Spear Campbell Allen Candid Dauth Cao Weiwei Carl Henrik Lunde Carl Loa Odin Carl X. Su Carlo Mion Carlos Alexandro Becker Carlos de Paula Carlos Sanchez Carol Fager-Higgins Cary Casey Bisson Catalin Pirvu Ce Gao Cedric Davies Cezar Sa Espinola Chad Swenson Chance Zibolski Chander Govindarajan Chanhun Jeong Chao Wang Charity Kathure Charles Chan Charles Hooper Charles Law Charles Lindsay Charles Merriam Charles Sarrazin Charles Smith Charlie Drage Charlie Lewis Chase Bolt ChaYoung You Chee Hau Lim Chen Chao Chen Chuanliang Chen Hanxiao Chen Min Chen Mingjie Chen Qiu Cheng-mean Liu Chengfei Shang Chengguang Xu Chentianze Chenyang Yan chenyuzhu Chetan Birajdar Chewey Chia-liang Kao Chiranjeevi Tirunagari chli Cholerae Hu Chris Alfonso Chris Armstrong Chris Dias Chris Dituri Chris Fordham Chris Gavin Chris Gibson Chris Khoo Chris Kreussling (Flatbush Gardener) Chris McKinnel Chris McKinnel Chris Price Chris Seto Chris Snow Chris St. Pierre Chris Stivers Chris Swan Chris Telfer Chris Wahl Chris Weyl Chris White Christian Becker Christian Berendt Christian Brauner Christian Böhme Christian Muehlhaeuser Christian Persson Christian Rotzoll Christian Simon Christian Stefanescu Christoph Ziebuhr Christophe Mehay Christophe Troestler Christophe Vidal Christopher Biscardi Christopher Crone Christopher Currie Christopher Jones Christopher Latham Christopher Petito Christopher Rigor Christy Norman Chun Chen Ciro S. Costa Clayton Coleman Clint Armstrong Clinton Kitson clubby789 Cody Roseborough Coenraad Loubser Colin Dunklau Colin Hebert Colin Panisset Colin Rice Colin Walters Collin Guarino Colm Hally companycy Conor Evans Corbin Coleman Corey Farrell Cory Forsyth Cory Snider cressie176 Cristian Ariza Cristian Staretu cristiano balducci Cristina Yenyxe Gonzalez Garcia Cruceru Calin-Cristian cui fliter CUI Wei Cuong Manh Le Cyprian Gracz Cyril F Da McGrady Daan van Berkel Daehyeok Mun Dafydd Crosby dalanlan Damian Smyth Damien Nadé Damien Nozay Damjan Georgievski Dan Anolik Dan Buch Dan Cotora Dan Feldman Dan Griffin Dan Hirsch Dan Keder Dan Levy Dan McPherson Dan Plamadeala Dan Stine Dan Williams Dani Hodovic Dani Louca Daniel Antlinger Daniel Black Daniel Dao Daniel Exner Daniel Farrell Daniel Garcia Daniel Gasienica Daniel Grunwell Daniel Guns Daniel Helfand Daniel Hiltgen Daniel J Walsh Daniel Menet Daniel Mizyrycki Daniel Nephin Daniel Norberg Daniel Nordberg Daniel P. Berrangé Daniel Robinson Daniel S Daniel Sweet Daniel Von Fange Daniel Watkins Daniel X Moore Daniel YC Lin Daniel Zhang Daniele Rondina Danny Berger Danny Milosavljevic Danny Yates Danyal Khaliq Darren Coxall Darren Shepherd Darren Stahl Dattatraya Kumbhar Davanum Srinivas Dave Barboza Dave Goodchild Dave Henderson Dave MacDonald Dave Tucker David Anderson David Bellotti David Calavera David Chung David Corking David Cramer David Currie David Davis David Dooling David Gageot David Gebler David Glasser David Karlsson <35727626+dvdksn@users.noreply.github.com> David Lawrence David Lechner David M. Karr David Mackey David Manouchehri David Mat David Mcanulty David McKay David O'Rourke David P Hilton David Pelaez David R. Jenni David Röthlisberger David Sheets David Sissitka David Trott David Wang <00107082@163.com> David Williamson David Xia David Young Davide Ceretti Dawn Chen dbdd dcylabs Debayan De Deborah Gertrude Digges deed02392 Deep Debroy Deng Guangxing Deni Bertovic Denis Defreyne Denis Gladkikh Denis Ollier Dennis Chen Dennis Chen Dennis Docter Derek Derek Derek Ch Derek McGowan Deric Crago Deshi Xiao Devon Estes Devvyn Murphy Dharmit Shah Dhawal Yogesh Bhanushali Dhilip Kumars Diego Romero Diego Siqueira Dieter Reuter Dillon Dixon Dima Stopel Dimitri John Ledkov Dimitris Mandalidis Dimitris Rozakis Dimitry Andric Dinesh Subhraveti Ding Fei dingwei Diogo Monica DiuDiugirl Djibril Koné Djordje Lukic dkumor Dmitri Logvinenko Dmitri Shuralyov Dmitry Demeshchuk Dmitry Gusev Dmitry Kononenko Dmitry Sharshakov Dmitry Shyshkin Dmitry Smirnov Dmitry V. Krivenok Dmitry Vorobev Dmytro Iakovliev docker-unir[bot] Dolph Mathews Dominic Tubach Dominic Yin Dominik Dingel Dominik Finkbeiner Dominik Honnef Don Kirkby Don Kjer Don Spaulding Donald Huang Dong Chen Donghwa Kim Donovan Jones Dorin Geman Doron Podoleanu Doug Davis Doug MacEachern Doug Tangren Douglas Curtis Dr Nic Williams dragon788 Dražen Lučanin Drew Erny Drew Hubl Dustin Sallings Ed Costello Edmund Wagner Eiichi Tsukata Eike Herzbach Eivin Giske Skaaren Eivind Uggedal Elan Ruusamäe Elango Sivanandam Elena Morozova Eli Uriegas Elias Faxö Elias Koromilas Elias Probst Elijah Zupancic eluck Elvir Kuric Emil Davtyan Emil Hernvall Emily Maier Emily Rose Emir Ozer Eng Zer Jun Enguerran Enrico Weigelt, metux IT consult Eohyung Lee epeterso er0k Eric Barch Eric Curtin Eric G. Noriega Eric Hanchrow Eric Lee Eric Mountain Eric Myhre Eric Paris Eric Rafaloff Eric Rosenberg Eric Sage Eric Soderstrom Eric Yang Eric-Olivier Lamey Erica Windisch Erich Cordoba Erik Bray Erik Dubbelboer Erik Hollensbe Erik Inge Bolsø Erik Kristensen Erik Sipsma Erik Sjölund Erik St. Martin Erik Weathers Erno Hopearuoho Erwin van der Koogh Espen Suenson Ethan Bell Ethan Mosbaugh Euan Harris Euan Kemp Eugen Krizo Eugene Yakubovich Evan Allrich Evan Carmi Evan Hazlett Evan Krall Evan Lezar Evan Phoenix Evan Wies Evelyn Xu Everett Toews Evgeniy Makhrov Evgeny Shmarnev Evgeny Vereshchagin Ewa Czechowska Eystein Måløy Stenberg ezbercih Ezra Silvera Fabian Kramm Fabian Lauer Fabian Raetz Fabiano Rosas Fabio Falci Fabio Kung Fabio Rapposelli Fabio Rehm Fabrizio Regini Fabrizio Soppelsa Faiz Khan falmp Fangming Fang Fangyuan Gao <21551127@zju.edu.cn> fanjiyun Fareed Dudhia Fathi Boudra Federico Gimenez Felipe Oliveira Felipe Ruhland Felix Abecassis Felix Geisendörfer Felix Hupfeld Felix Rabe Felix Ruess Felix Schindler Feng Yan Fengtu Wang Ferenc Szabo Fernando Fero Volar Feroz Salam Ferran Rodenas Filipe Brandenburger Filipe Oliveira Filipe Pina Flavio Castelli Flavio Crisciani Florian Florian Klein Florian Maier Florian Noeding Florian Schmaus Florian Weingarten Florin Asavoaie Florin Patan fonglh Foysal Iqbal Francesc Campoy Francesco Degrassi Francesco Mari Francis Chuang Francisco Carriedo Francisco Souza Frank Groeneveld Frank Herrmann Frank Macreery Frank Rosquin Frank Villaro-Dixon Frank Yang François Scala Fred Lifton Frederick F. Kautz IV Frederico F. de Oliveira Frederik Loeffert Frederik Nordahl Jul Sabroe Freek Kalter Frieder Bluemle frobnicaty <92033765+frobnicaty@users.noreply.github.com> Frédéric Dalleau Fu JinLin Félix Baylac-Jacqué Félix Cantournet Gabe Rosenhouse Gabor Nagy Gabriel Adrian Samfira Gabriel Goller Gabriel L. Somlo Gabriel Linder Gabriel Monroy Gabriel Nicolas Avellaneda Gabriel Tomitsuka Gaetan de Villele Galen Sampson Gang Qiao Gareth Rushgrove Garrett Barboza Gary Schaetz Gaurav Gaurav Singh Gaël PORTAY Genki Takiuchi GennadySpb Geoff Levand Geoffrey Bachelet Geon Kim George Adams George Kontridze George Ma George MacRorie George Xie Georgi Hristozov Georgy Yakovlev Gereon Frey German DZ Gert van Valkenhoef Gerwim Feiken Ghislain Bourgeois Giampaolo Mancini Gianluca Borello Gildas Cuisinier Giovan Isa Musthofa gissehel Giuseppe Mazzotta Giuseppe Scrivano Gleb Fotengauer-Malinovskiy Gleb M Borisov Glyn Normington GoBella Goffert van Gool Goldwyn Rodrigues Gopikannan Venugopalsamy Gosuke Miyashita Gou Rao Govinda Fichtner Grace Choi Grant Millar Grant Reaber Graydon Hoare Greg Fausak Greg Pflaum Greg Stephens Greg Thornton Grzegorz Jaśkiewicz Guilhem Lettron Guilherme Salgado Guillaume Dufour Guillaume J. Charmes Gunadhya S. <6939749+gunadhya@users.noreply.github.com> Guoqiang QI guoxiuyan Guri Gurjeet Singh Guruprasad Gustav Sinder gwx296173 Günter Zöchbauer Haichao Yang haikuoliu haining.cao Hakan Özler Hamish Hutchings Hannes Ljungberg Hans Kristian Flaatten Hans Rødtang Hao Shu Wei Hao Zhang <21521210@zju.edu.cn> Harald Albers Harald Niesche Harley Laue Harold Cooper Harrison Turton Harry Zhang Harshal Patil Harshal Patil He Simei He Xiaoxi He Xin heartlock <21521209@zju.edu.cn> Hector Castro Helen Xie Henning Sprang Hiroshi Hatake Hiroyuki Sasagawa Hobofan Hollie Teal Hong Xu Hongbin Lu Hongxu Jia Honza Pokorny Hsing-Hui Hsu Hsing-Yu (David) Chen hsinko <21551195@zju.edu.cn> Hu Keping Hu Tao Huajin Tong huang-jl <1046678590@qq.com> HuanHuan Ye Huanzhong Zhang Huayi Zhang Hugo Barrera Hugo Duncan Hugo Marisco <0x6875676f@gmail.com> Hui Kang Hunter Blanks huqun Huu Nguyen Hyeongkyu Lee Hyzhou Zhy Iago López Galeiras Ian Bishop Ian Bull Ian Calvert Ian Campbell Ian Chen Ian Lee Ian Main Ian Philpot Ian Truslove Iavael Icaro Seara Ignacio Capurro Igor Dolzhikov Igor Karpovich Iliana Weller Ilkka Laukkanen Illia Antypenko Illo Abdulrahim Ilya Dmitrichenko Ilya Gusev Ilya Khlopotov imalasong <2879499479@qq.com> imre Fitos inglesp Ingo Gottwald Innovimax Isaac Dupree Isabel Jimenez Isaiah Grace Isao Jonas Iskander Sharipov Ivan Babrou Ivan Fraixedes Ivan Grcic Ivan Markin J Bruni J. Nunn Jack Danger Canty Jack Laxson Jack Walker <90711509+j2walker@users.noreply.github.com> Jacob Atzen Jacob Edelman Jacob Tomlinson Jacob Vallejo Jacob Wen Jaime Cepeda Jaivish Kothari Jake Champlin Jake Moshenko Jake Sanders Jakub Drahos Jakub Guzik James Allen James Carey James Carr James DeFelice James Harrison Fisher James Kyburz James Kyle James Lal James Mills James Nesbitt James Nugent James Sanders James Turnbull James Watkins-Harvey Jameson Hyde Jamie Hannaford Jamshid Afshar Jan Breig Jan Chren Jan Garcia Jan Götte Jan Keromnes Jan Koprowski Jan Pazdziora Jan Toebes Jan-Gerd Tenberge Jan-Jaap Driessen Jana Radhakrishnan Jannick Fahlbusch Januar Wayong Jared Biel Jared Hocutt Jaroslav Jindrak Jaroslaw Zabiello Jasmine Hegman Jason A. Donenfeld Jason Divock Jason Giedymin Jason Green Jason Hall Jason Heiss Jason Livesay Jason McVetta Jason Plum Jason Shepherd Jason Smith Jason Sommer Jason Stangroome Jasper Siepkes Javier Bassi jaxgeller Jay Jay Kamat Jay Lim Jean Rouge Jean-Baptiste Barth Jean-Baptiste Dalido Jean-Christophe Berthon Jean-Michel Rouet Jean-Paul Calderone Jean-Pierre Huynh Jean-Tiare Le Bigot Jeeva S. Chelladhurai Jeff Anderson Jeff Hajewski Jeff Johnston Jeff Lindsay Jeff Mickey Jeff Minard Jeff Nickoloff Jeff Silberman Jeff Welch Jeff Zvier Jeffrey Bolle Jeffrey Morgan Jeffrey van Gogh Jenny Gebske Jeongseok Kang Jeremy Chambers Jeremy Grosser Jeremy Huntwork Jeremy Price Jeremy Qian Jeremy Unruh Jeremy Yallop Jeroen Franse Jeroen Jacobs Jesse Dearing Jesse Dubay Jessica Frazelle Jeyanthinath Muthuram Jezeniel Zapanta Jhon Honce Ji.Zhilong Jian Liao Jian Zeng Jian Zhang Jiang Jinyang Jianyong Wu Jie Luo Jie Ma Jihyun Hwang Jilles Oldenbeuving Jim Alateras Jim Carroll Jim Ehrismann Jim Galasyn Jim Lin Jim Minter Jim Perrin Jimmy Cuadra Jimmy Puckett Jimmy Song jinjiadu Jinsoo Park Jintao Zhang Jiri Appl Jiri Popelka Jiuyue Ma Jiří Župka jjimbo137 <115816493+jjimbo137@users.noreply.github.com> Joakim Roubert Joan Grau Joao Fernandes Joao Trindade Joe Beda Joe Doliner Joe Ferguson Joe Gordon Joe Shaw Joe Van Dyk Joel Friedly Joel Handwell Joel Hansson Joel Wurtz Joey Geiger Joey Geiger Joey Gibson Joffrey F Johan Euphrosine Johan Rydberg Johanan Lieberman Johannes 'fish' Ziemke John Costa John Feminella John Gardiner Myers John Gossman John Harris John Howard John Laswell John Maguire John Mulhausen John OBrien III John Starks John Stephens John Tims John V. Martinez John Warwick John Willis Jon Johnson Jon Surrell Jon Wedaman Jonas Dohse Jonas Geiler Jonas Heinrich Jonas Pfenniger Jonathan A. Schweder Jonathan A. Sternberg Jonathan Boulle Jonathan Camp Jonathan Choy Jonathan Dowland Jonathan Lebon Jonathan Lomas Jonathan McCrohan Jonathan Mueller Jonathan Pares Jonathan Rudenberg Jonathan Stoppani Jonh Wendell Joni Sar Joost Cassee Jordan Arentsen Jordan Jennings Jordan Sissel Jordi Massaguer Pla Jorge Marin Jorit Kleine-Möllhoff Jose Diaz-Gonzalez Joseph Anthony Pasquale Holsten Joseph Hager Joseph Kern Joseph Rothrock Josh Josh Bodah Josh Bonczkowski Josh Chorlton Josh Eveleth Josh Hawn Josh Horwitz Josh Poimboeuf Josh Soref Josh Wilson Josiah Kiehl José Tomás Albornoz Joyce Jang JP JSchltggr Julian Taylor Julien Barbier Julien Bisconti Julien Bordellier Julien Dubois Julien Kassar Julien Maitrehenry Julien Pervillé Julien Pivotto Julio Guerra Julio Montes Jun Du Jun-Ru Chang junxu Jussi Nummelin Justas Brazauskas Justen Martin Justin Chadwell Justin Cormack Justin Force Justin Keller <85903732+jk-vb@users.noreply.github.com> Justin Menga Justin Plock Justin Simonelis Justin Terry Justyn Temme Jyrki Puttonen Jérémy Leherpeur Jérôme Petazzoni Jörg Thalheim K. Heller Kai Blin Kai Qiang Wu (Kennan) Kaijie Chen Kamil Domański Kamjar Gerami Kanstantsin Shautsou Kara Alexandra Karan Lyons Kareem Khazem kargakis Karl Grzeszczak Karol Duleba Karthik Karanth Karthik Nayak Kasper Fabæch Brandt Kate Heddleston Katie McLaughlin Kato Kazuyoshi Katrina Owen Kawsar Saiyeed Kay Yan kayrus Kazuhiro Sera Kazuyoshi Kato Ke Li Ke Xu Kei Ohmura Keith Hudgins Keli Hu Ken Bannister Ken Cochrane Ken Herner Ken ICHIKAWA Ken Reese Kenfe-Mickaël Laventure Kenjiro Nakayama Kent Johnson Kenta Tada Kevin "qwazerty" Houdebert Kevin Alvarez Kevin Burke Kevin Clark Kevin Feyrer Kevin J. Lynagh Kevin Jing Qiu Kevin Kern Kevin Menard Kevin Meredith Kevin P. Kucharczyk Kevin Parsons Kevin Richardson Kevin Shi Kevin Wallace Kevin Yap Keyvan Fatehi kies Kim BKC Carlbacker Kim Eik Kimbro Staken Kir Kolyshkin Kiran Gangadharan Kirill SIbirev Kirk Easterson knappe Kohei Tsuruta Koichi Shiraishi Konrad Kleine Konrad Ponichtera Konstantin Gribov Konstantin L Konstantin Pelykh Kostadin Plachkov kpcyrd Krasi Georgiev Krasimir Georgiev Kris-Mikael Krister Kristian Haugene Kristina Zabunova Krystian Wojcicki Kunal Kushwaha Kunal Tyagi Kyle Conroy Kyle Linden Kyle Squizzato Kyle Wuolle kyu Lachlan Coote Lai Jiangshan Lajos Papp Lakshan Perera Lalatendu Mohanty Lance Chen Lance Kinley Lars Andringa Lars Butler Lars Kellogg-Stedman Lars R. Damerow Lars-Magnus Skog Laszlo Meszaros Laura Brehm Laura Frank Laurent Bernaille Laurent Erignoux Laurent Goderre Laurie Voss Leandro Motta Barros Leandro Siqueira Lee Calcote Lee Chao <932819864@qq.com> Lee, Meng-Han Lei Gong Lei Jitang Leiiwang Len Weincier Lennie Leo Gallucci Leonardo Nodari Leonardo Taccari Leszek Kowalski Levi Blackstone Levi Gross Levi Harrison Lewis Daly Lewis Marshall Lewis Peckover Li Yi Liam Macgillavry Liana Lo Liang Mingqiang Liang-Chi Hsieh liangwei Liao Qingwei Lifubang Lihua Tang Lily Guo limeidan Lin Lu LingFaKe Linus Heckemann Liran Tal Liron Levin Liu Bo Liu Hua liwenqi lixiaobing10051267 Liz Zhang LIZAO LI Lizzie Dixon <_@lizzie.io> Lloyd Dewolf Lokesh Mandvekar longliqiang88 <394564827@qq.com> Lorenz Leutgeb Lorenzo Fontana Lotus Fenn Louis Delossantos Louis Opter Luboslav Pivarc Luca Favatella Luca Marturana Luca Orlandi Luca-Bogdan Grigorescu Lucas Chan Lucas Chi Lucas Molas Lucas Silvestre Luciano Mores Luis Henrique Mulinari Luis Martínez de Bartolomé Izquierdo Luiz Svoboda Lukas Heeren Lukas Waslowski lukaspustina Lukasz Zajaczkowski Luke Marsden Lyn Lynda O'Leary Lénaïc Huard Ma Müller Ma Shimiao Mabin Madhan Raj Mookkandy Madhav Puri Madhu Venugopal Mageee maggie44 <64841595+maggie44@users.noreply.github.com> Mahesh Tiyyagura malnick Malte Janduda Manfred Touron Manfred Zabarauskas Manjunath A Kumatagi Mansi Nahar Manuel Meurer Manuel Rüger Manuel Woelker mapk0y Marat Radchenko Marc Abramowitz Marc Kuo Marc Tamsky Marcel Edmund Franke Marcelo Horacio Fortino Marcelo Salazar Marco Hennings Marcus Cobden Marcus Farkas Marcus Linke Marcus Martins Marcus Ramberg Marek Goldmann Marian Marinov Marianna Tessel Mario Loriedo Marius Gundersen Marius Sturm Marius Voila Mark Allen Mark Feit Mark Jeromin Mark McGranaghan Mark McKinstry Mark Milstein Mark Oates Mark Parker Mark Vainomaa Mark West Markan Patel Marko Mikulicic Marko Tibold Markus Fix Markus Kortlang Martijn Dwars Martijn van Oosterhout Martin Braun Martin Dojcak Martin Honermeyer Martin Jirku Martin Kelly Martin Mosegaard Amdisen Martin Muzatko Martin Redmond Maru Newby Mary Anthony Masahito Zembutsu Masato Ohba Masayuki Morita Mason Malone Mateusz Sulima Mathias Monnerville Mathieu Champlon Mathieu Le Marec - Pasquet Mathieu Parent Mathieu Paturel Matt Apperson Matt Bachmann Matt Bajor Matt Bentley Matt Haggard Matt Hoyle Matt McCormick Matt Moore Matt Morrison <3maven@gmail.com> Matt Richardson Matt Rickard Matt Robenolt Matt Schurenko Matt Williams Matthew Heon Matthew Lapworth Matthew Mayer Matthew Mosesohn Matthew Mueller Matthew Riley Matthias Klumpp Matthias Kühnle Matthias Rampke Matthieu Fronton Matthieu Hauglustaine Mattias Jernberg Mauricio Garavaglia mauriyouth Max Harmathy Max Shytikov Max Timchenko Maxim Fedchyshyn Maxim Ivanov Maxim Kulkin Maxim Treskin Maxime Petazzoni Maximiliano Maccanti Maxwell Meaglith Ma meejah Megan Kostick Mehul Kar Mei ChunTao Mengdi Gao Menghui Chen Mert Yazıcıoğlu mgniu Micah Zoltu Michael A. Smith Michael Beskin Michael Bridgen Michael Brown Michael Chiang Michael Crosby Michael Currie Michael Friis Michael Gorsuch Michael Grauer Michael Holzheu Michael Hudson-Doyle Michael Huettermann Michael Irwin Michael Kebe Michael Kuehn Michael Käufl Michael Neale Michael Nussbaum Michael Prokop Michael Scharf Michael Spetsiotis Michael Stapelberg Michael Steinert Michael Thies Michael Weidmann Michael West Michael Zhao Michal Fojtik Michal Gebauer Michal Jemala Michal Kostrzewa Michal Minář Michal Rostecki Michal Wieczorek Michaël Pailloncy Michał Czeraszkiewicz Michał Gryko Michał Kosek Michiel de Jong Mickaël Fortunato Mickaël Remars Miguel Angel Fernández Miguel Morales Miguel Perez Mihai Borobocea Mihuleacc Sergiu Mikael Davranche Mike Brown Mike Bush Mike Casas Mike Chelen Mike Danese Mike Dillon Mike Dougherty Mike Estes Mike Gaffney Mike Goelzer Mike Leone Mike Lundy Mike MacCana Mike Naberezny Mike Snitzer Mike Sul mikelinjie <294893458@qq.com> Mikhail Sobolev Miklos Szegedi Milas Bowman Milind Chawre Miloslav Trmač mingqing Mingzhen Feng Misty Stanley-Jones Mitch Capper Mizuki Urushida mlarcher Mohammad Banikazemi Mohammad Nasirifar Mohammed Aaqib Ansari Mohd Sadiq Mohit Soni Moorthy RS Morgan Bauer Morgante Pell Morgy93 Morten Siebuhr Morton Fox Moysés Borges mrfly Mrunal Patel Muayyad Alsadi Muhammad Zohaib Aslam Mustafa Akın Muthukumar R Myeongjoon Kim Máximo Cuadros Médi-Rémi Hashim Nace Oroz Nahum Shalman Nakul Pathak Nalin Dahyabhai Nan Monnand Deng Naoki Orii Natalie Parker Natanael Copa Natasha Jarus Nate Brennand Nate Eagleson Nate Jones Nathan Baulch Nathan Carlson Nathan Herald Nathan Hsieh Nathan Kleyn Nathan LeClaire Nathan McCauley Nathan Williams Naveed Jamil Neal McBurnett Neil Horman Neil Peterson Nelson Chen Neyazul Haque Nghia Tran Niall O'Higgins Nicholas E. Rabenau Nick Adcock Nick DeCoursin Nick Irvine Nick Neisen Nick Parker Nick Payne Nick Russo Nick Santos Nick Stenning Nick Stinemates Nick Wood NickrenREN Nicola Kabar Nicolas Borboën Nicolas De Loof Nicolas Dudebout Nicolas Goy Nicolas Kaiser Nicolas Sterchele Nicolas V Castet Nicolás Hock Isaza Niel Drummond Nigel Poulton Nik Nyby Nikhil Chawla NikolaMandic Nikolas Garofil Nikolay Edigaryev Nikolay Milovanov ningmingxiao Nirmal Mehta Nishant Totla NIWA Hideyuki Noah Meyerhans Noah Treuhaft NobodyOnSE noducks Nolan Darilek Nolan Miles Noriki Nakamura nponeccop Nurahmadie Nuutti Kotivuori nzwsch O.S. Tezer objectified Octol1ttle Odin Ugedal Oguz Bilgic Oh Jinkyun Ohad Schneider ohmystack Ole Reifschneider Oliver Neal Oliver Reason Olivier Gambier Olle Jonsson Olli Janatuinen Olly Pomeroy Omri Shiv Onur Filiz Oriol Francès Oscar Bonilla <6f6231@gmail.com> oscar.chen <2972789494@qq.com> Oskar Niburski Otto Kekäläinen Ouyang Liduo Ovidio Mallo Panagiotis Moustafellos Paolo G. Giarrusso Pascal Pascal Bach Pascal Borreli Pascal Hartig Patrick Böänziger Patrick Devine Patrick Haas Patrick Hemmer Patrick St. laurent Patrick Stapleton Patrik Cyvoct pattichen Paul "TBBle" Hampson Paul paul Paul Annesley Paul Bellamy Paul Bowsher Paul Furtado Paul Hammond Paul Jimenez Paul Kehrer Paul Lietar Paul Liljenberg Paul Morie Paul Nasrat Paul Seiffert Paul Weaver Paulo Gomes Paulo Ribeiro Pavel Lobashov Pavel Matěja Pavel Pletenev Pavel Pospisil Pavel Sutyrin Pavel Tikhomirov Pavlos Ratis Pavol Vargovcik Pawel Konczalski Paweł Gronowski payall4u Peeyush Gupta Peggy Li Pei Su Peng Tao Penghan Wang Per Weijnitz perhapszzy@sina.com Pete Woods Peter Bourgon Peter Braden Peter Bücker Peter Choi Peter Dave Hello Peter Edge Peter Ericson Peter Esbensen Peter Jaffe Peter Kang Peter Malmgren Peter Salvatore Peter Volpe Peter Waller Petr Švihlík Petros Angelatos Phil Phil Estes Phil Sphicas Phil Spitler Philip Alexander Etling Philip K. Warren Philip Monroe Philipp Fruck Philipp Gillé Philipp Wahala Philipp Weissensteiner Phillip Alexander phineas pidster Piergiuliano Bossi Pierre Pierre Carrier Pierre Dal-Pra Pierre Wacrenier Pierre-Alain RIVIERE pinglanlu Piotr Bogdan Piotr Karbowski Porjo Poul Kjeldager Sørensen Pradeep Chhetri Pradip Dhara Pradipta Kr. Banerjee Prasanna Gautam Pratik Karki Prayag Verma Priya Wadhwa Projjol Banerji Przemek Hejman Puneet Pruthi Pure White pysqz Qiang Huang Qin TianHuan Qinglan Peng Quan Tian qudongfang Quentin Brossard Quentin Perez Quentin Tayssier r0n22 Rachit Sharma Radostin Stoyanov Rafael Fernández López Rafal Jeczalik Rafe Colton Raghavendra K T Raghuram Devarakonda Raja Sami Rajat Pandit Rajdeep Dua Ralf Sippl Ralle Ralph Bean Ramkumar Ramachandra Ramon Brooker Ramon van Alteren RaviTeja Pothana Ray Tsang ReadmeCritic realityone Recursive Madman Reficul Regan McCooey Remi Rampin Remy Suen Renato Riccieri Santos Zannon Renaud Gaubert Rhys Hiltner Ri Xu Ricardo N Feliciano Rich Horwood Rich Moyse Rich Seymour Richard Burnison Richard Hansen Richard Harvey Richard Mathie Richard Metzler Richard Scothern Richo Healey Rick Bradley Rick van de Loo Rick Wieman Rik Nijessen Riku Voipio Riley Guerin Ritesh H Shukla Riyaz Faizullabhoy Rob Cowsill <42620235+rcowsill@users.noreply.github.com> Rob Gulewich Rob Murray Rob Vesse Robert Bachmann Robert Bittle Robert Obryk Robert Schneider Robert Shade Robert Stern Robert Terhaar Robert Wallis Robert Wang Roberto G. Hashioka Roberto Muñoz Fernández Robin Naundorf Robin Schneider Robin Speekenbrink Robin Thoni robpc Rodolfo Carvalho Rodrigo Campos Rodrigo Vaz Roel Van Nyen Roger Peppe Rohit Jnagal Rohit Kadam Rohit Kapur Rojin George Roland Huß Roland Kammerer Roland Moriz Roma Sokolov Roman Dudin Roman Mazur Roman Strashkin Roman Volosatovs Roman Zabaluev Ron Smits Ron Williams Rong Gao Rong Zhang Rongxiang Song Rony Weng root root root root Rory Hunter Rory McCune Ross Boucher Rovanion Luckey Roy Reznik Royce Remer Rozhnov Alexandr Rudolph Gottesheim Rui Cao Rui JingAn Rui Lopes Ruilin Li Runshen Zhu Russ Magee Ryan Abrams Ryan Anderson Ryan Aslett Ryan Barry Ryan Belgrave Ryan Campbell Ryan Detzel Ryan Fowler Ryan Liu Ryan McLaughlin Ryan O'Donnell Ryan Seto Ryan Shea Ryan Simmen Ryan Stelly Ryan Thomas Ryan Trauntvein Ryan Wallner Ryan Zhang ryancooper7 RyanDeng Ryo Nakao Ryoga Saito Régis Behmo Rémy Greinhofer s. rannou Sabin Basyal Sachin Joshi Sagar Hani Sainath Grandhi Sakeven Jiang Salahuddin Khan Sally O'Malley Sam Abed Sam Alba Sam Bailey Sam J Sharpe Sam Neirinck Sam Reis Sam Rijs Sam Thibault Sam Whited Sambuddha Basu Sami Wagiaalla Samuel Andaya Samuel Dion-Girardeau Samuel Karp Samuel PHAN sanchayanghosh Sandeep Bansal Sankar சங்கர் Sanket Saurav Santhosh Manohar sapphiredev Sargun Dhillon Sascha Andres Sascha Grunert SataQiu Satnam Singh Satoshi Amemiya Satoshi Tagomori Scott Bessler Scott Collier Scott Johnston Scott Moser Scott Percival Scott Stamp Scott Walls sdreyesg Sean Christopherson Sean Cronin Sean Lee Sean McIntyre Sean OMeara Sean P. Kane Sean Rodman Sebastiaan van Steenis Sebastiaan van Stijn Sebastian Höffner Sebastian Radloff Sebastian Thomschke Sebastien Goasguen Senthil Kumar Selvaraj Senthil Kumaran SeongJae Park Seongyeol Lim Serge Hallyn Sergey Alekseev Sergey Evstifeev Sergii Kabashniuk Sergio Lopez Serhat Gülçiçek Serhii Nakon SeungUkLee Sevki Hasirci Shane Canon Shane da Silva Shaun Kaasten Shaun Thompson shaunol Shawn Landden Shawn Siefkas shawnhe Shayan Pooya Shayne Wang Shekhar Gulati Sheng Yang Shengbo Song Shengjing Zhu Shev Yan Shih-Yuan Lee Shihao Xia Shijiang Wei Shijun Qin Shishir Mahajan Shoubhik Bose Shourya Sarcar Shreenidhi Shedi Shu-Wai Chow shuai-z Shukui Yang Sian Lerk Lau Siarhei Rasiukevich Sidhartha Mani sidharthamani Silas Sewell Silvan Jegen Simão Reis Simon Barendse Simon Eskildsen Simon Ferquel Simon Leinen Simon Menke Simon Taranto Simon Vikstrom Sindhu S Sjoerd Langkemper skanehira Smark Meng Solganik Alexander Solomon Hykes Song Gao Soshi Katsuta Sotiris Salloumis Soulou Spencer Brown Spencer Smith Spike Curtis Sridatta Thatipamala Sridhar Ratnakumar Srini Brahmaroutu Srinivasan Srivatsan Staf Wagemakers Stanislav Bondarenko Stanislav Levin Steeve Morin Stefan Berger Stefan Gehrig Stefan J. Wernli Stefan Praszalowicz Stefan S. Stefan Scherer Stefan Staudenmeyer Stefan Weil Steffen Butzer Stephan Henningsen Stephan Spindler Stephen Benjamin Stephen Crosby Stephen Day Stephen Drake Stephen Rust Steve Desmond Steve Dougherty Steve Durrheimer Steve Francia Steve Koch Steven Burgess Steven Erenst Steven Hartland Steven Iveson Steven Merrill Steven Richards Steven Taylor Stéphane Este-Gracias Stig Larsson Su Wang Subhajit Ghosh Sujith Haridasan Sun Gengze <690388648@qq.com> Sun Jianbo Sune Keller Sunny Gogoi Suryakumar Sudar Sven Dowideit Swapnil Daingade Sylvain Baubeau Sylvain Bellemare Sébastien Sébastien HOUZÉ Sébastien Luttringer Sébastien Stormacq Sören Tempel Tabakhase Tadej Janež Tadeusz Dudkiewicz Takuto Sato tang0th Tangi Colin Tatsuki Sugiura Tatsushi Inagaki Taylan Isikdemir Taylor Jones tcpdumppy <847462026@qq.com> Ted M. Young Tehmasp Chaudhri Tejaswini Duggaraju Tejesh Mehta Terry Chu terryding77 <550147740@qq.com> Thatcher Peskens theadactyl Thell 'Bo' Fowler Thermionix Thiago Alves Silva Thijs Terlouw Thomas Bikeev Thomas Frössman Thomas Gazagnaire Thomas Graf Thomas Grainger Thomas Hansen Thomas Ledos Thomas Leonard Thomas Léveil Thomas Orozco Thomas Riccardi Thomas Schroeter Thomas Sjögren Thomas Swift Thomas Tanaka Thomas Texier Ti Zhou Tiago Seabra Tianon Gravi Tianyi Wang Tibor Vass Tiffany Jernigan Tiffany Low Till Claassen Till Wegmüller Tim Tim Bart Tim Bosse Tim Dettrick Tim Düsterhus Tim Hockin Tim Potter Tim Ruffles Tim Smith Tim Terhorst Tim Wagner Tim Wang Tim Waugh Tim Wraight Tim Zju <21651152@zju.edu.cn> timchenxiaoyu <837829664@qq.com> timfeirg Timo Rothenpieler Timothy Hobbs tjwebb123 tobe Tobias Bieniek Tobias Bradtke Tobias Gesellchen Tobias Klauser Tobias Munk Tobias Pfandzelter Tobias Schmidt Tobias Schwab Todd Crane Todd Lunter Todd Whiteman Toli Kuznets Tom Barlow Tom Booth Tom Denham Tom Fotherby Tom Howe Tom Hulihan Tom Maaswinkel Tom Parker Tom Sweeney Tom Wilkie Tom X. Tobin Tom Zhao Tomas Janousek Tomas Kral Tomas Tomecek Tomasz Kopczynski Tomasz Lipinski Tomasz Nurkiewicz Tomek Mańko Tommaso Visconti Tomoya Tabuchi Tomáš Hrčka Tomáš Virtus tonic Tonny Xu Tony Abboud Tony Daws Tony Miller toogley Torstein Husebø Toshiaki Makita Tõnis Tiigi Trace Andreason tracylihui <793912329@qq.com> Trapier Marshall Travis Cline Travis Thieman Trent Ogren Trevor Trevor Pounds Trevor Sullivan Trishna Guha Tristan Carel Troy Denton Tudor Brindus Ty Alexander Tycho Andersen Tyler Brock Tyler Brown Tzu-Jung Lee uhayate Ulysse Carion Umesh Yadav Utz Bacher vagrant Vaidas Jablonskis Valentin Kulesh vanderliang Velko Ivanov Veres Lajos Victor Algaze Victor Coisne Victor Costan Victor I. Wood Victor Lyuboslavsky Victor Marmol Victor Palma Victor Toni Victor Vieux Victoria Bialas Vijaya Kumar K Vikas Choudhary Vikram bir Singh Viktor Stanchev Viktor Vojnovski VinayRaghavanKS Vincent Batts Vincent Bernat Vincent Boulineau Vincent Demeester Vincent Giersch Vincent Mayers Vincent Woo Vinod Kulkarni Vishal Doshi Vishnu Kannan Vitaly Ostrosablin Vitor Anjos Vitor Monteiro Vivek Agarwal Vivek Dasgupta Vivek Goyal Vladimir Bulyga Vladimir Kirillov Vladimir Pouzanov Vladimir Rutsky Vladimir Varankin VladimirAus Vladislav Kolesnikov Vlastimil Zeman Vojtech Vitek (V-Teq) voloder <110066198+voloder@users.noreply.github.com> Walter Leibbrandt Walter Stanish Wang Chao Wang Guoliang Wang Jie Wang Long Wang Ping Wang Xing Wang Yuexiao Wang Yumu <37442693@qq.com> wanghuaiqing Ward Vandewege WarheadsSE Wassim Dhif Wataru Ishida Wayne Chang Wayne Song weebney Weerasak Chongnguluam Wei Fu Wei Wu Wei-Ting Kuo weipeng weiyan Weiyang Zhu Wen Cheng Ma Wendel Fleming Wenjun Tang Wenkai Yin wenlxie Wenxuan Zhao Wenyu You <21551128@zju.edu.cn> Wenzhi Liang Wes Morgan Wesley Pettit Wewang Xiaorenfine Wiktor Kwapisiewicz Will Dietz Will Rouesnel Will Weaver willhf William Delanoue William Henry William Hubbs William Martin William Riancho William Thurston Wilson Júnior Wing-Kam Wong WiseTrem Wolfgang Nagele Wolfgang Powisch Wonjun Kim WuLonghui xamyzhao Xia Wu Xian Chaobo Xianglin Gao Xianjie Xianlu Bird Xiao YongBiao Xiao Zhang XiaoBing Jiang Xiaodong Liu Xiaodong Zhang Xiaohua Ding Xiaoxi He Xiaoxu Chen Xiaoyu Zhang xichengliudui <1693291525@qq.com> xiekeyang Ximo Guanter Gonzálbez xin.li Xinbo Weng Xinfeng Liu Xinzi Zhou Xiuming Chen Xuecong Liao xuzhaokui Yadnyawalkya Tale Yahya yalpul YAMADA Tsuyoshi Yamasaki Masahide Yamazaki Masashi Yan Feng Yan Zhu Yang Bai Yang Li Yang Pengfei yangchenliang Yann Autissier Yanqiang Miao Yao Zaiyong Yash Murty Yassine Tijani Yasunori Mahata Yazhong Liu Yestin Sun Yi EungJun Yibai Zhang Yihang Ho Ying Li Yohei Ueda Yong Tang Yongxin Li Yongzhi Pan Yosef Fertel You-Sheng Yang (楊有勝) youcai Youcef YEKHLEF Youfu Zhang YR Chen Yu Changchun Yu Chengxia Yu Peng Yu-Ju Hong Yuan Sun Yuanhong Peng Yue Zhang Yufei Xiong Yuhao Fang Yuichiro Kaneko YujiOshima Yunxiang Huang Yurii Rashkovskii Yusuf Tarık Günaydın Yves Blusseau <90z7oey02@sneakemail.com> Yves Junqueira Zac Dover Zach Borboa Zach Gershman Zachary Jaffee Zain Memon Zaiste! Zane DeGraffenried Zefan Li Zen Lin(Zhinan Lin) Zhang Kun Zhang Wei Zhang Wentao zhangguanzhang ZhangHang zhangxianwei Zhenan Ye <21551168@zju.edu.cn> zhenghenghuo Zhenhai Gao Zhenkun Bi ZhiPeng Lu zhipengzuo Zhou Hao Zhoulin Xie Zhu Guihua Zhu Kunjia Zhuoyun Wei Ziheng Liu Zilin Du zimbatm Ziming Dong ZJUshuaizhou <21551191@zju.edu.cn> zmarouf Zoltan Tombol Zou Yu zqh Zuhayr Elahi Zunayed Ali Álvaro Lázaro Átila Camurça Alves 吴小白 <296015668@qq.com> 尹吉峰 屈骏 徐俊杰 慕陶 搏通 黄艳红00139573 정재영 ================================================ FILE: vendor/github.com/docker/docker/LICENSE ================================================ Apache License Version 2.0, January 2004 https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Copyright 2013-2018 Docker, Inc. 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 https://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. ================================================ FILE: vendor/github.com/docker/docker/NOTICE ================================================ Docker Copyright 2012-2017 Docker, Inc. This product includes software developed at Docker, Inc. (https://www.docker.com). This product contains software (https://github.com/creack/pty) developed by Keith Rarick, licensed under the MIT License. The following is courtesy of our legal counsel: Use and transfer of Docker may be subject to certain restrictions by the United States and other governments. It is your responsibility to ensure that your use and/or transfer does not violate applicable laws. For more information, please see https://www.bis.doc.gov See also https://www.apache.org/dev/crypto.html and/or seek legal counsel. ================================================ FILE: vendor/github.com/docker/docker/api/README.md ================================================ # Working on the Engine API The Engine API is an HTTP API used by the command-line client to communicate with the daemon. It can also be used by third-party software to control the daemon. It consists of various components in this repository: - `api/swagger.yaml` A Swagger definition of the API. - `api/types/` Types shared by both the client and server, representing various objects, options, responses, etc. Most are written manually, but some are automatically generated from the Swagger definition. See [#27919](https://github.com/docker/docker/issues/27919) for progress on this. - `cli/` The command-line client. - `client/` The Go client used by the command-line client. It can also be used by third-party Go programs. - `daemon/` The daemon, which serves the API. ## Swagger definition The API is defined by the [Swagger](http://swagger.io/specification/) definition in `api/swagger.yaml`. This definition can be used to: 1. Automatically generate documentation. 2. Automatically generate the Go server and client. (A work-in-progress.) 3. Provide a machine readable version of the API for introspecting what it can do, automatically generating clients for other languages, etc. ## Updating the API documentation The API documentation is generated entirely from `api/swagger.yaml`. If you make updates to the API, edit this file to represent the change in the documentation. The file is split into two main sections: - `definitions`, which defines re-usable objects used in requests and responses - `paths`, which defines the API endpoints (and some inline objects which don't need to be reusable) To make an edit, first look for the endpoint you want to edit under `paths`, then make the required edits. Endpoints may reference reusable objects with `$ref`, which can be found in the `definitions` section. There is hopefully enough example material in the file for you to copy a similar pattern from elsewhere in the file (e.g. adding new fields or endpoints), but for the full reference, see the [Swagger specification](https://github.com/docker/docker/issues/27919). `swagger.yaml` is validated by `hack/validate/swagger` to ensure it is a valid Swagger definition. This is useful when making edits to ensure you are doing the right thing. ## Viewing the API documentation When you make edits to `swagger.yaml`, you may want to check the generated API documentation to ensure it renders correctly. Run `make swagger-docs` and a preview will be running at `http://localhost:9000`. Some of the styling may be incorrect, but you'll be able to ensure that it is generating the correct documentation. The production documentation is generated by vendoring `swagger.yaml` into [docker/docker.github.io](https://github.com/docker/docker.github.io). ================================================ FILE: vendor/github.com/docker/docker/api/common.go ================================================ package api // import "github.com/docker/docker/api" // Common constants for daemon and client. const ( // DefaultVersion of the current REST API. DefaultVersion = "1.48" // MinSupportedAPIVersion is the minimum API version that can be supported // by the API server, specified as "major.minor". Note that the daemon // may be configured with a different minimum API version, as returned // in [github.com/docker/docker/api/types.Version.MinAPIVersion]. // // API requests for API versions lower than the configured version produce // an error. MinSupportedAPIVersion = "1.24" // NoBaseImageSpecifier is the symbol used by the FROM // command to specify that no base image is to be used. NoBaseImageSpecifier = "scratch" ) ================================================ FILE: vendor/github.com/docker/docker/api/swagger-gen.yaml ================================================ layout: models: - name: definition source: asset:model target: "{{ joinFilePath .Target .ModelPackage }}" file_name: "{{ (snakize (pascalize .Name)) }}.go" operations: - name: handler source: asset:serverOperation target: "{{ joinFilePath .Target .APIPackage .Package }}" file_name: "{{ (snakize (pascalize .Name)) }}.go" ================================================ FILE: vendor/github.com/docker/docker/api/swagger.yaml ================================================ # A Swagger 2.0 (a.k.a. OpenAPI) definition of the Engine API. # # This is used for generating API documentation and the types used by the # client/server. See api/README.md for more information. # # Some style notes: # - This file is used by ReDoc, which allows GitHub Flavored Markdown in # descriptions. # - There is no maximum line length, for ease of editing and pretty diffs. # - operationIds are in the format "NounVerb", with a singular noun. swagger: "2.0" schemes: - "http" - "https" produces: - "application/json" - "text/plain" consumes: - "application/json" - "text/plain" basePath: "/v1.48" info: title: "Docker Engine API" version: "1.48" x-logo: url: "https://docs.docker.com/assets/images/logo-docker-main.png" description: | The Engine API is an HTTP API served by Docker Engine. It is the API the Docker client uses to communicate with the Engine, so everything the Docker client can do can be done with the API. Most of the client's commands map directly to API endpoints (e.g. `docker ps` is `GET /containers/json`). The notable exception is running containers, which consists of several API calls. # Errors The API uses standard HTTP status codes to indicate the success or failure of the API call. The body of the response will be JSON in the following format: ``` { "message": "page not found" } ``` # Versioning The API is usually changed in each release, so API calls are versioned to ensure that clients don't break. To lock to a specific version of the API, you prefix the URL with its version, for example, call `/v1.30/info` to use the v1.30 version of the `/info` endpoint. If the API version specified in the URL is not supported by the daemon, a HTTP `400 Bad Request` error message is returned. If you omit the version-prefix, the current version of the API (v1.48) is used. For example, calling `/info` is the same as calling `/v1.48/info`. Using the API without a version-prefix is deprecated and will be removed in a future release. Engine releases in the near future should support this version of the API, so your client will continue to work even if it is talking to a newer Engine. The API uses an open schema model, which means the server may add extra properties to responses. Likewise, the server will ignore any extra query parameters and request body properties. When you write clients, you need to ignore additional properties in responses to ensure they do not break when talking to newer daemons. # Authentication Authentication for registries is handled client side. The client has to send authentication details to various endpoints that need to communicate with registries, such as `POST /images/(name)/push`. These are sent as `X-Registry-Auth` header as a [base64url encoded](https://tools.ietf.org/html/rfc4648#section-5) (JSON) string with the following structure: ``` { "username": "string", "password": "string", "email": "string", "serveraddress": "string" } ``` The `serveraddress` is a domain/IP without a protocol. Throughout this structure, double quotes are required. If you have already got an identity token from the [`/auth` endpoint](#operation/SystemAuth), you can just pass this instead of credentials: ``` { "identitytoken": "9cbaf023786cd7..." } ``` # The tags on paths define the menu sections in the ReDoc documentation, so # the usage of tags must make sense for that: # - They should be singular, not plural. # - There should not be too many tags, or the menu becomes unwieldy. For # example, it is preferable to add a path to the "System" tag instead of # creating a tag with a single path in it. # - The order of tags in this list defines the order in the menu. tags: # Primary objects - name: "Container" x-displayName: "Containers" description: | Create and manage containers. - name: "Image" x-displayName: "Images" - name: "Network" x-displayName: "Networks" description: | Networks are user-defined networks that containers can be attached to. See the [networking documentation](https://docs.docker.com/network/) for more information. - name: "Volume" x-displayName: "Volumes" description: | Create and manage persistent storage that can be attached to containers. - name: "Exec" x-displayName: "Exec" description: | Run new commands inside running containers. Refer to the [command-line reference](https://docs.docker.com/engine/reference/commandline/exec/) for more information. To exec a command in a container, you first need to create an exec instance, then start it. These two API endpoints are wrapped up in a single command-line command, `docker exec`. # Swarm things - name: "Swarm" x-displayName: "Swarm" description: | Engines can be clustered together in a swarm. Refer to the [swarm mode documentation](https://docs.docker.com/engine/swarm/) for more information. - name: "Node" x-displayName: "Nodes" description: | Nodes are instances of the Engine participating in a swarm. Swarm mode must be enabled for these endpoints to work. - name: "Service" x-displayName: "Services" description: | Services are the definitions of tasks to run on a swarm. Swarm mode must be enabled for these endpoints to work. - name: "Task" x-displayName: "Tasks" description: | A task is a container running on a swarm. It is the atomic scheduling unit of swarm. Swarm mode must be enabled for these endpoints to work. - name: "Secret" x-displayName: "Secrets" description: | Secrets are sensitive data that can be used by services. Swarm mode must be enabled for these endpoints to work. - name: "Config" x-displayName: "Configs" description: | Configs are application configurations that can be used by services. Swarm mode must be enabled for these endpoints to work. # System things - name: "Plugin" x-displayName: "Plugins" - name: "System" x-displayName: "System" definitions: Port: type: "object" description: "An open port on a container" required: [PrivatePort, Type] properties: IP: type: "string" format: "ip-address" description: "Host IP address that the container's port is mapped to" PrivatePort: type: "integer" format: "uint16" x-nullable: false description: "Port on the container" PublicPort: type: "integer" format: "uint16" description: "Port exposed on the host" Type: type: "string" x-nullable: false enum: ["tcp", "udp", "sctp"] example: PrivatePort: 8080 PublicPort: 80 Type: "tcp" MountPoint: type: "object" description: | MountPoint represents a mount point configuration inside the container. This is used for reporting the mountpoints in use by a container. properties: Type: description: | The mount type: - `bind` a mount of a file or directory from the host into the container. - `volume` a docker volume with the given `Name`. - `image` a docker image - `tmpfs` a `tmpfs`. - `npipe` a named pipe from the host into the container. - `cluster` a Swarm cluster volume type: "string" enum: - "bind" - "volume" - "image" - "tmpfs" - "npipe" - "cluster" example: "volume" Name: description: | Name is the name reference to the underlying data defined by `Source` e.g., the volume name. type: "string" example: "myvolume" Source: description: | Source location of the mount. For volumes, this contains the storage location of the volume (within `/var/lib/docker/volumes/`). For bind-mounts, and `npipe`, this contains the source (host) part of the bind-mount. For `tmpfs` mount points, this field is empty. type: "string" example: "/var/lib/docker/volumes/myvolume/_data" Destination: description: | Destination is the path relative to the container root (`/`) where the `Source` is mounted inside the container. type: "string" example: "/usr/share/nginx/html/" Driver: description: | Driver is the volume driver used to create the volume (if it is a volume). type: "string" example: "local" Mode: description: | Mode is a comma separated list of options supplied by the user when creating the bind/volume mount. The default is platform-specific (`"z"` on Linux, empty on Windows). type: "string" example: "z" RW: description: | Whether the mount is mounted writable (read-write). type: "boolean" example: true Propagation: description: | Propagation describes how mounts are propagated from the host into the mount point, and vice-versa. Refer to the [Linux kernel documentation](https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt) for details. This field is not used on Windows. type: "string" example: "" DeviceMapping: type: "object" description: "A device mapping between the host and container" properties: PathOnHost: type: "string" PathInContainer: type: "string" CgroupPermissions: type: "string" example: PathOnHost: "/dev/deviceName" PathInContainer: "/dev/deviceName" CgroupPermissions: "mrw" DeviceRequest: type: "object" description: "A request for devices to be sent to device drivers" properties: Driver: type: "string" example: "nvidia" Count: type: "integer" example: -1 DeviceIDs: type: "array" items: type: "string" example: - "0" - "1" - "GPU-fef8089b-4820-abfc-e83e-94318197576e" Capabilities: description: | A list of capabilities; an OR list of AND lists of capabilities. type: "array" items: type: "array" items: type: "string" example: # gpu AND nvidia AND compute - ["gpu", "nvidia", "compute"] Options: description: | Driver-specific options, specified as a key/value pairs. These options are passed directly to the driver. type: "object" additionalProperties: type: "string" ThrottleDevice: type: "object" properties: Path: description: "Device path" type: "string" Rate: description: "Rate" type: "integer" format: "int64" minimum: 0 Mount: type: "object" properties: Target: description: "Container path." type: "string" Source: description: "Mount source (e.g. a volume name, a host path)." type: "string" Type: description: | The mount type. Available types: - `bind` Mounts a file or directory from the host into the container. Must exist prior to creating the container. - `volume` Creates a volume with the given name and options (or uses a pre-existing volume with the same name and options). These are **not** removed when the container is removed. - `image` Mounts an image. - `tmpfs` Create a tmpfs with the given options. The mount source cannot be specified for tmpfs. - `npipe` Mounts a named pipe from the host into the container. Must exist prior to creating the container. - `cluster` a Swarm cluster volume type: "string" enum: - "bind" - "volume" - "image" - "tmpfs" - "npipe" - "cluster" ReadOnly: description: "Whether the mount should be read-only." type: "boolean" Consistency: description: "The consistency requirement for the mount: `default`, `consistent`, `cached`, or `delegated`." type: "string" BindOptions: description: "Optional configuration for the `bind` type." type: "object" properties: Propagation: description: "A propagation mode with the value `[r]private`, `[r]shared`, or `[r]slave`." type: "string" enum: - "private" - "rprivate" - "shared" - "rshared" - "slave" - "rslave" NonRecursive: description: "Disable recursive bind mount." type: "boolean" default: false CreateMountpoint: description: "Create mount point on host if missing" type: "boolean" default: false ReadOnlyNonRecursive: description: | Make the mount non-recursively read-only, but still leave the mount recursive (unless NonRecursive is set to `true` in conjunction). Added in v1.44, before that version all read-only mounts were non-recursive by default. To match the previous behaviour this will default to `true` for clients on versions prior to v1.44. type: "boolean" default: false ReadOnlyForceRecursive: description: "Raise an error if the mount cannot be made recursively read-only." type: "boolean" default: false VolumeOptions: description: "Optional configuration for the `volume` type." type: "object" properties: NoCopy: description: "Populate volume with data from the target." type: "boolean" default: false Labels: description: "User-defined key/value metadata." type: "object" additionalProperties: type: "string" DriverConfig: description: "Map of driver specific options" type: "object" properties: Name: description: "Name of the driver to use to create the volume." type: "string" Options: description: "key/value map of driver specific options." type: "object" additionalProperties: type: "string" Subpath: description: "Source path inside the volume. Must be relative without any back traversals." type: "string" example: "dir-inside-volume/subdirectory" ImageOptions: description: "Optional configuration for the `image` type." type: "object" properties: Subpath: description: "Source path inside the image. Must be relative without any back traversals." type: "string" example: "dir-inside-image/subdirectory" TmpfsOptions: description: "Optional configuration for the `tmpfs` type." type: "object" properties: SizeBytes: description: "The size for the tmpfs mount in bytes." type: "integer" format: "int64" Mode: description: "The permission mode for the tmpfs mount in an integer." type: "integer" Options: description: | The options to be passed to the tmpfs mount. An array of arrays. Flag options should be provided as 1-length arrays. Other types should be provided as as 2-length arrays, where the first item is the key and the second the value. type: "array" items: type: "array" minItems: 1 maxItems: 2 items: type: "string" example: [["noexec"]] RestartPolicy: description: | The behavior to apply when the container exits. The default is not to restart. An ever increasing delay (double the previous delay, starting at 100ms) is added before each restart to prevent flooding the server. type: "object" properties: Name: type: "string" description: | - Empty string means not to restart - `no` Do not automatically restart - `always` Always restart - `unless-stopped` Restart always except when the user has manually stopped the container - `on-failure` Restart only when the container exit code is non-zero enum: - "" - "no" - "always" - "unless-stopped" - "on-failure" MaximumRetryCount: type: "integer" description: | If `on-failure` is used, the number of times to retry before giving up. Resources: description: "A container's resources (cgroups config, ulimits, etc)" type: "object" properties: # Applicable to all platforms CpuShares: description: | An integer value representing this container's relative CPU weight versus other containers. type: "integer" Memory: description: "Memory limit in bytes." type: "integer" format: "int64" default: 0 # Applicable to UNIX platforms CgroupParent: description: | Path to `cgroups` under which the container's `cgroup` is created. If the path is not absolute, the path is considered to be relative to the `cgroups` path of the init process. Cgroups are created if they do not already exist. type: "string" BlkioWeight: description: "Block IO weight (relative weight)." type: "integer" minimum: 0 maximum: 1000 BlkioWeightDevice: description: | Block IO weight (relative device weight) in the form: ``` [{"Path": "device_path", "Weight": weight}] ``` type: "array" items: type: "object" properties: Path: type: "string" Weight: type: "integer" minimum: 0 BlkioDeviceReadBps: description: | Limit read rate (bytes per second) from a device, in the form: ``` [{"Path": "device_path", "Rate": rate}] ``` type: "array" items: $ref: "#/definitions/ThrottleDevice" BlkioDeviceWriteBps: description: | Limit write rate (bytes per second) to a device, in the form: ``` [{"Path": "device_path", "Rate": rate}] ``` type: "array" items: $ref: "#/definitions/ThrottleDevice" BlkioDeviceReadIOps: description: | Limit read rate (IO per second) from a device, in the form: ``` [{"Path": "device_path", "Rate": rate}] ``` type: "array" items: $ref: "#/definitions/ThrottleDevice" BlkioDeviceWriteIOps: description: | Limit write rate (IO per second) to a device, in the form: ``` [{"Path": "device_path", "Rate": rate}] ``` type: "array" items: $ref: "#/definitions/ThrottleDevice" CpuPeriod: description: "The length of a CPU period in microseconds." type: "integer" format: "int64" CpuQuota: description: | Microseconds of CPU time that the container can get in a CPU period. type: "integer" format: "int64" CpuRealtimePeriod: description: | The length of a CPU real-time period in microseconds. Set to 0 to allocate no time allocated to real-time tasks. type: "integer" format: "int64" CpuRealtimeRuntime: description: | The length of a CPU real-time runtime in microseconds. Set to 0 to allocate no time allocated to real-time tasks. type: "integer" format: "int64" CpusetCpus: description: | CPUs in which to allow execution (e.g., `0-3`, `0,1`). type: "string" example: "0-3" CpusetMems: description: | Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. type: "string" Devices: description: "A list of devices to add to the container." type: "array" items: $ref: "#/definitions/DeviceMapping" DeviceCgroupRules: description: "a list of cgroup rules to apply to the container" type: "array" items: type: "string" example: "c 13:* rwm" DeviceRequests: description: | A list of requests for devices to be sent to device drivers. type: "array" items: $ref: "#/definitions/DeviceRequest" KernelMemoryTCP: description: | Hard limit for kernel TCP buffer memory (in bytes). Depending on the OCI runtime in use, this option may be ignored. It is no longer supported by the default (runc) runtime. This field is omitted when empty. type: "integer" format: "int64" MemoryReservation: description: "Memory soft limit in bytes." type: "integer" format: "int64" MemorySwap: description: | Total memory limit (memory + swap). Set as `-1` to enable unlimited swap. type: "integer" format: "int64" MemorySwappiness: description: | Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100. type: "integer" format: "int64" minimum: 0 maximum: 100 NanoCpus: description: "CPU quota in units of 10-9 CPUs." type: "integer" format: "int64" OomKillDisable: description: "Disable OOM Killer for the container." type: "boolean" Init: description: | Run an init inside the container that forwards signals and reaps processes. This field is omitted if empty, and the default (as configured on the daemon) is used. type: "boolean" x-nullable: true PidsLimit: description: | Tune a container's PIDs limit. Set `0` or `-1` for unlimited, or `null` to not change. type: "integer" format: "int64" x-nullable: true Ulimits: description: | A list of resource limits to set in the container. For example: ``` {"Name": "nofile", "Soft": 1024, "Hard": 2048} ``` type: "array" items: type: "object" properties: Name: description: "Name of ulimit" type: "string" Soft: description: "Soft limit" type: "integer" Hard: description: "Hard limit" type: "integer" # Applicable to Windows CpuCount: description: | The number of usable CPUs (Windows only). On Windows Server containers, the processor resource controls are mutually exclusive. The order of precedence is `CPUCount` first, then `CPUShares`, and `CPUPercent` last. type: "integer" format: "int64" CpuPercent: description: | The usable percentage of the available CPUs (Windows only). On Windows Server containers, the processor resource controls are mutually exclusive. The order of precedence is `CPUCount` first, then `CPUShares`, and `CPUPercent` last. type: "integer" format: "int64" IOMaximumIOps: description: "Maximum IOps for the container system drive (Windows only)" type: "integer" format: "int64" IOMaximumBandwidth: description: | Maximum IO in bytes per second for the container system drive (Windows only). type: "integer" format: "int64" Limit: description: | An object describing a limit on resources which can be requested by a task. type: "object" properties: NanoCPUs: type: "integer" format: "int64" example: 4000000000 MemoryBytes: type: "integer" format: "int64" example: 8272408576 Pids: description: | Limits the maximum number of PIDs in the container. Set `0` for unlimited. type: "integer" format: "int64" default: 0 example: 100 ResourceObject: description: | An object describing the resources which can be advertised by a node and requested by a task. type: "object" properties: NanoCPUs: type: "integer" format: "int64" example: 4000000000 MemoryBytes: type: "integer" format: "int64" example: 8272408576 GenericResources: $ref: "#/definitions/GenericResources" GenericResources: description: | User-defined resources can be either Integer resources (e.g, `SSD=3`) or String resources (e.g, `GPU=UUID1`). type: "array" items: type: "object" properties: NamedResourceSpec: type: "object" properties: Kind: type: "string" Value: type: "string" DiscreteResourceSpec: type: "object" properties: Kind: type: "string" Value: type: "integer" format: "int64" example: - DiscreteResourceSpec: Kind: "SSD" Value: 3 - NamedResourceSpec: Kind: "GPU" Value: "UUID1" - NamedResourceSpec: Kind: "GPU" Value: "UUID2" HealthConfig: description: "A test to perform to check that the container is healthy." type: "object" properties: Test: description: | The test to perform. Possible values are: - `[]` inherit healthcheck from image or parent image - `["NONE"]` disable healthcheck - `["CMD", args...]` exec arguments directly - `["CMD-SHELL", command]` run command with system's default shell type: "array" items: type: "string" Interval: description: | The time to wait between checks in nanoseconds. It should be 0 or at least 1000000 (1 ms). 0 means inherit. type: "integer" format: "int64" Timeout: description: | The time to wait before considering the check to have hung. It should be 0 or at least 1000000 (1 ms). 0 means inherit. type: "integer" format: "int64" Retries: description: | The number of consecutive failures needed to consider a container as unhealthy. 0 means inherit. type: "integer" StartPeriod: description: | Start period for the container to initialize before starting health-retries countdown in nanoseconds. It should be 0 or at least 1000000 (1 ms). 0 means inherit. type: "integer" format: "int64" StartInterval: description: | The time to wait between checks in nanoseconds during the start period. It should be 0 or at least 1000000 (1 ms). 0 means inherit. type: "integer" format: "int64" Health: description: | Health stores information about the container's healthcheck results. type: "object" x-nullable: true properties: Status: description: | Status is one of `none`, `starting`, `healthy` or `unhealthy` - "none" Indicates there is no healthcheck - "starting" Starting indicates that the container is not yet ready - "healthy" Healthy indicates that the container is running correctly - "unhealthy" Unhealthy indicates that the container has a problem type: "string" enum: - "none" - "starting" - "healthy" - "unhealthy" example: "healthy" FailingStreak: description: "FailingStreak is the number of consecutive failures" type: "integer" example: 0 Log: type: "array" description: | Log contains the last few results (oldest first) items: $ref: "#/definitions/HealthcheckResult" HealthcheckResult: description: | HealthcheckResult stores information about a single run of a healthcheck probe type: "object" x-nullable: true properties: Start: description: | Date and time at which this check started in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. type: "string" format: "date-time" example: "2020-01-04T10:44:24.496525531Z" End: description: | Date and time at which this check ended in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. type: "string" format: "dateTime" example: "2020-01-04T10:45:21.364524523Z" ExitCode: description: | ExitCode meanings: - `0` healthy - `1` unhealthy - `2` reserved (considered unhealthy) - other values: error running probe type: "integer" example: 0 Output: description: "Output from last check" type: "string" HostConfig: description: "Container configuration that depends on the host we are running on" allOf: - $ref: "#/definitions/Resources" - type: "object" properties: # Applicable to all platforms Binds: type: "array" description: | A list of volume bindings for this container. Each volume binding is a string in one of these forms: - `host-src:container-dest[:options]` to bind-mount a host path into the container. Both `host-src`, and `container-dest` must be an _absolute_ path. - `volume-name:container-dest[:options]` to bind-mount a volume managed by a volume driver into the container. `container-dest` must be an _absolute_ path. `options` is an optional, comma-delimited list of: - `nocopy` disables automatic copying of data from the container path to the volume. The `nocopy` flag only applies to named volumes. - `[ro|rw]` mounts a volume read-only or read-write, respectively. If omitted or set to `rw`, volumes are mounted read-write. - `[z|Z]` applies SELinux labels to allow or deny multiple containers to read and write to the same volume. - `z`: a _shared_ content label is applied to the content. This label indicates that multiple containers can share the volume content, for both reading and writing. - `Z`: a _private unshared_ label is applied to the content. This label indicates that only the current container can use a private volume. Labeling systems such as SELinux require proper labels to be placed on volume content that is mounted into a container. Without a label, the security system can prevent a container's processes from using the content. By default, the labels set by the host operating system are not modified. - `[[r]shared|[r]slave|[r]private]` specifies mount [propagation behavior](https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt). This only applies to bind-mounted volumes, not internal volumes or named volumes. Mount propagation requires the source mount point (the location where the source directory is mounted in the host operating system) to have the correct propagation properties. For shared volumes, the source mount point must be set to `shared`. For slave volumes, the mount must be set to either `shared` or `slave`. items: type: "string" ContainerIDFile: type: "string" description: "Path to a file where the container ID is written" example: "" LogConfig: type: "object" description: "The logging configuration for this container" properties: Type: description: |- Name of the logging driver used for the container or "none" if logging is disabled. type: "string" enum: - "local" - "json-file" - "syslog" - "journald" - "gelf" - "fluentd" - "awslogs" - "splunk" - "etwlogs" - "none" Config: description: |- Driver-specific configuration options for the logging driver. type: "object" additionalProperties: type: "string" example: "max-file": "5" "max-size": "10m" NetworkMode: type: "string" description: | Network mode to use for this container. Supported standard values are: `bridge`, `host`, `none`, and `container:`. Any other value is taken as a custom network's name to which this container should connect to. PortBindings: $ref: "#/definitions/PortMap" RestartPolicy: $ref: "#/definitions/RestartPolicy" AutoRemove: type: "boolean" description: | Automatically remove the container when the container's process exits. This has no effect if `RestartPolicy` is set. VolumeDriver: type: "string" description: "Driver that this container uses to mount volumes." VolumesFrom: type: "array" description: | A list of volumes to inherit from another container, specified in the form `[:]`. items: type: "string" Mounts: description: | Specification for mounts to be added to the container. type: "array" items: $ref: "#/definitions/Mount" ConsoleSize: type: "array" description: | Initial console size, as an `[height, width]` array. x-nullable: true minItems: 2 maxItems: 2 items: type: "integer" minimum: 0 example: [80, 64] Annotations: type: "object" description: | Arbitrary non-identifying metadata attached to container and provided to the runtime when the container is started. additionalProperties: type: "string" # Applicable to UNIX platforms CapAdd: type: "array" description: | A list of kernel capabilities to add to the container. Conflicts with option 'Capabilities'. items: type: "string" CapDrop: type: "array" description: | A list of kernel capabilities to drop from the container. Conflicts with option 'Capabilities'. items: type: "string" CgroupnsMode: type: "string" enum: - "private" - "host" description: | cgroup namespace mode for the container. Possible values are: - `"private"`: the container runs in its own private cgroup namespace - `"host"`: use the host system's cgroup namespace If not specified, the daemon default is used, which can either be `"private"` or `"host"`, depending on daemon version, kernel support and configuration. Dns: type: "array" description: "A list of DNS servers for the container to use." items: type: "string" DnsOptions: type: "array" description: "A list of DNS options." items: type: "string" DnsSearch: type: "array" description: "A list of DNS search domains." items: type: "string" ExtraHosts: type: "array" description: | A list of hostnames/IP mappings to add to the container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. items: type: "string" GroupAdd: type: "array" description: | A list of additional groups that the container process will run as. items: type: "string" IpcMode: type: "string" description: | IPC sharing mode for the container. Possible values are: - `"none"`: own private IPC namespace, with /dev/shm not mounted - `"private"`: own private IPC namespace - `"shareable"`: own private IPC namespace, with a possibility to share it with other containers - `"container:"`: join another (shareable) container's IPC namespace - `"host"`: use the host system's IPC namespace If not specified, daemon default is used, which can either be `"private"` or `"shareable"`, depending on daemon version and configuration. Cgroup: type: "string" description: "Cgroup to use for the container." Links: type: "array" description: | A list of links for the container in the form `container_name:alias`. items: type: "string" OomScoreAdj: type: "integer" description: | An integer value containing the score given to the container in order to tune OOM killer preferences. example: 500 PidMode: type: "string" description: | Set the PID (Process) Namespace mode for the container. It can be either: - `"container:"`: joins another container's PID namespace - `"host"`: use the host's PID namespace inside the container Privileged: type: "boolean" description: |- Gives the container full access to the host. PublishAllPorts: type: "boolean" description: | Allocates an ephemeral host port for all of a container's exposed ports. Ports are de-allocated when the container stops and allocated when the container starts. The allocated port might be changed when restarting the container. The port is selected from the ephemeral port range that depends on the kernel. For example, on Linux the range is defined by `/proc/sys/net/ipv4/ip_local_port_range`. ReadonlyRootfs: type: "boolean" description: "Mount the container's root filesystem as read only." SecurityOpt: type: "array" description: | A list of string values to customize labels for MLS systems, such as SELinux. items: type: "string" StorageOpt: type: "object" description: | Storage driver options for this container, in the form `{"size": "120G"}`. additionalProperties: type: "string" Tmpfs: type: "object" description: | A map of container directories which should be replaced by tmpfs mounts, and their corresponding mount options. For example: ``` { "/run": "rw,noexec,nosuid,size=65536k" } ``` additionalProperties: type: "string" UTSMode: type: "string" description: "UTS namespace to use for the container." UsernsMode: type: "string" description: | Sets the usernamespace mode for the container when usernamespace remapping option is enabled. ShmSize: type: "integer" format: "int64" description: | Size of `/dev/shm` in bytes. If omitted, the system uses 64MB. minimum: 0 Sysctls: type: "object" x-nullable: true description: |- A list of kernel parameters (sysctls) to set in the container. This field is omitted if not set. additionalProperties: type: "string" example: "net.ipv4.ip_forward": "1" Runtime: type: "string" x-nullable: true description: |- Runtime to use with this container. # Applicable to Windows Isolation: type: "string" description: | Isolation technology of the container. (Windows only) enum: - "default" - "process" - "hyperv" - "" MaskedPaths: type: "array" description: | The list of paths to be masked inside the container (this overrides the default set of paths). items: type: "string" example: - "/proc/asound" - "/proc/acpi" - "/proc/kcore" - "/proc/keys" - "/proc/latency_stats" - "/proc/timer_list" - "/proc/timer_stats" - "/proc/sched_debug" - "/proc/scsi" - "/sys/firmware" - "/sys/devices/virtual/powercap" ReadonlyPaths: type: "array" description: | The list of paths to be set as read-only inside the container (this overrides the default set of paths). items: type: "string" example: - "/proc/bus" - "/proc/fs" - "/proc/irq" - "/proc/sys" - "/proc/sysrq-trigger" ContainerConfig: description: | Configuration for a container that is portable between hosts. type: "object" properties: Hostname: description: | The hostname to use for the container, as a valid RFC 1123 hostname. type: "string" example: "439f4e91bd1d" Domainname: description: | The domain name to use for the container. type: "string" User: description: |- Commands run as this user inside the container. If omitted, commands run as the user specified in the image the container was started from. Can be either user-name or UID, and optional group-name or GID, separated by a colon (`[<:group-name|GID>]`). type: "string" example: "123:456" AttachStdin: description: "Whether to attach to `stdin`." type: "boolean" default: false AttachStdout: description: "Whether to attach to `stdout`." type: "boolean" default: true AttachStderr: description: "Whether to attach to `stderr`." type: "boolean" default: true ExposedPorts: description: | An object mapping ports to an empty object in the form: `{"/": {}}` type: "object" x-nullable: true additionalProperties: type: "object" enum: - {} default: {} example: { "80/tcp": {}, "443/tcp": {} } Tty: description: | Attach standard streams to a TTY, including `stdin` if it is not closed. type: "boolean" default: false OpenStdin: description: "Open `stdin`" type: "boolean" default: false StdinOnce: description: "Close `stdin` after one attached client disconnects" type: "boolean" default: false Env: description: | A list of environment variables to set inside the container in the form `["VAR=value", ...]`. A variable without `=` is removed from the environment, rather than to have an empty value. type: "array" items: type: "string" example: - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" Cmd: description: | Command to run specified as a string or an array of strings. type: "array" items: type: "string" example: ["/bin/sh"] Healthcheck: $ref: "#/definitions/HealthConfig" ArgsEscaped: description: "Command is already escaped (Windows only)" type: "boolean" default: false example: false x-nullable: true Image: description: | The name (or reference) of the image to use when creating the container, or which was used when the container was created. type: "string" example: "example-image:1.0" Volumes: description: | An object mapping mount point paths inside the container to empty objects. type: "object" additionalProperties: type: "object" enum: - {} default: {} WorkingDir: description: "The working directory for commands to run in." type: "string" example: "/public/" Entrypoint: description: | The entry point for the container as a string or an array of strings. If the array consists of exactly one empty string (`[""]`) then the entry point is reset to system default (i.e., the entry point used by docker when there is no `ENTRYPOINT` instruction in the `Dockerfile`). type: "array" items: type: "string" example: [] NetworkDisabled: description: "Disable networking for the container." type: "boolean" x-nullable: true MacAddress: description: | MAC address of the container. Deprecated: this field is deprecated in API v1.44 and up. Use EndpointSettings.MacAddress instead. type: "string" x-nullable: true OnBuild: description: | `ONBUILD` metadata that were defined in the image's `Dockerfile`. type: "array" x-nullable: true items: type: "string" example: [] Labels: description: "User-defined key/value metadata." type: "object" additionalProperties: type: "string" example: com.example.some-label: "some-value" com.example.some-other-label: "some-other-value" StopSignal: description: | Signal to stop a container as a string or unsigned integer. type: "string" example: "SIGTERM" x-nullable: true StopTimeout: description: "Timeout to stop a container in seconds." type: "integer" default: 10 x-nullable: true Shell: description: | Shell for when `RUN`, `CMD`, and `ENTRYPOINT` uses a shell. type: "array" x-nullable: true items: type: "string" example: ["/bin/sh", "-c"] ImageConfig: description: | Configuration of the image. These fields are used as defaults when starting a container from the image. type: "object" properties: Hostname: description: | The hostname to use for the container, as a valid RFC 1123 hostname.


> **Deprecated**: this field is not part of the image specification and is > always empty. It must not be used, and will be removed in API v1.48. type: "string" example: "" Domainname: description: | The domain name to use for the container.


> **Deprecated**: this field is not part of the image specification and is > always empty. It must not be used, and will be removed in API v1.48. type: "string" example: "" User: description: "The user that commands are run as inside the container." type: "string" example: "web:web" AttachStdin: description: | Whether to attach to `stdin`.


> **Deprecated**: this field is not part of the image specification and is > always false. It must not be used, and will be removed in API v1.48. type: "boolean" default: false example: false AttachStdout: description: | Whether to attach to `stdout`.


> **Deprecated**: this field is not part of the image specification and is > always false. It must not be used, and will be removed in API v1.48. type: "boolean" default: false example: false AttachStderr: description: | Whether to attach to `stderr`.


> **Deprecated**: this field is not part of the image specification and is > always false. It must not be used, and will be removed in API v1.48. type: "boolean" default: false example: false ExposedPorts: description: | An object mapping ports to an empty object in the form: `{"/": {}}` type: "object" x-nullable: true additionalProperties: type: "object" enum: - {} default: {} example: { "80/tcp": {}, "443/tcp": {} } Tty: description: | Attach standard streams to a TTY, including `stdin` if it is not closed.


> **Deprecated**: this field is not part of the image specification and is > always false. It must not be used, and will be removed in API v1.48. type: "boolean" default: false example: false OpenStdin: description: | Open `stdin`


> **Deprecated**: this field is not part of the image specification and is > always false. It must not be used, and will be removed in API v1.48. type: "boolean" default: false example: false StdinOnce: description: | Close `stdin` after one attached client disconnects.


> **Deprecated**: this field is not part of the image specification and is > always false. It must not be used, and will be removed in API v1.48. type: "boolean" default: false example: false Env: description: | A list of environment variables to set inside the container in the form `["VAR=value", ...]`. A variable without `=` is removed from the environment, rather than to have an empty value. type: "array" items: type: "string" example: - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" Cmd: description: | Command to run specified as a string or an array of strings. type: "array" items: type: "string" example: ["/bin/sh"] Healthcheck: $ref: "#/definitions/HealthConfig" ArgsEscaped: description: "Command is already escaped (Windows only)" type: "boolean" default: false example: false x-nullable: true Image: description: | The name (or reference) of the image to use when creating the container, or which was used when the container was created.


> **Deprecated**: this field is not part of the image specification and is > always empty. It must not be used, and will be removed in API v1.48. type: "string" default: "" example: "" Volumes: description: | An object mapping mount point paths inside the container to empty objects. type: "object" additionalProperties: type: "object" enum: - {} default: {} example: "/app/data": {} "/app/config": {} WorkingDir: description: "The working directory for commands to run in." type: "string" example: "/public/" Entrypoint: description: | The entry point for the container as a string or an array of strings. If the array consists of exactly one empty string (`[""]`) then the entry point is reset to system default (i.e., the entry point used by docker when there is no `ENTRYPOINT` instruction in the `Dockerfile`). type: "array" items: type: "string" example: [] NetworkDisabled: description: | Disable networking for the container.


> **Deprecated**: this field is not part of the image specification and is > always omitted. It must not be used, and will be removed in API v1.48. type: "boolean" default: false example: false x-nullable: true MacAddress: description: | MAC address of the container.


> **Deprecated**: this field is not part of the image specification and is > always omitted. It must not be used, and will be removed in API v1.48. type: "string" default: "" example: "" x-nullable: true OnBuild: description: | `ONBUILD` metadata that were defined in the image's `Dockerfile`. type: "array" x-nullable: true items: type: "string" example: [] Labels: description: "User-defined key/value metadata." type: "object" additionalProperties: type: "string" example: com.example.some-label: "some-value" com.example.some-other-label: "some-other-value" StopSignal: description: | Signal to stop a container as a string or unsigned integer. type: "string" example: "SIGTERM" x-nullable: true StopTimeout: description: | Timeout to stop a container in seconds.


> **Deprecated**: this field is not part of the image specification and is > always omitted. It must not be used, and will be removed in API v1.48. type: "integer" default: 10 x-nullable: true Shell: description: | Shell for when `RUN`, `CMD`, and `ENTRYPOINT` uses a shell. type: "array" x-nullable: true items: type: "string" example: ["/bin/sh", "-c"] # FIXME(thaJeztah): temporarily using a full example to remove some "omitempty" fields. Remove once the fields are removed. example: "Hostname": "" "Domainname": "" "User": "web:web" "AttachStdin": false "AttachStdout": false "AttachStderr": false "ExposedPorts": { "80/tcp": {}, "443/tcp": {} } "Tty": false "OpenStdin": false "StdinOnce": false "Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"] "Cmd": ["/bin/sh"] "Healthcheck": { "Test": ["string"], "Interval": 0, "Timeout": 0, "Retries": 0, "StartPeriod": 0, "StartInterval": 0 } "ArgsEscaped": true "Image": "" "Volumes": { "/app/data": {}, "/app/config": {} } "WorkingDir": "/public/" "Entrypoint": [] "OnBuild": [] "Labels": { "com.example.some-label": "some-value", "com.example.some-other-label": "some-other-value" } "StopSignal": "SIGTERM" "Shell": ["/bin/sh", "-c"] NetworkingConfig: description: | NetworkingConfig represents the container's networking configuration for each of its interfaces. It is used for the networking configs specified in the `docker create` and `docker network connect` commands. type: "object" properties: EndpointsConfig: description: | A mapping of network name to endpoint configuration for that network. The endpoint configuration can be left empty to connect to that network with no particular endpoint configuration. type: "object" additionalProperties: $ref: "#/definitions/EndpointSettings" example: # putting an example here, instead of using the example values from # /definitions/EndpointSettings, because EndpointSettings contains # operational data returned when inspecting a container that we don't # accept here. EndpointsConfig: isolated_nw: IPAMConfig: IPv4Address: "172.20.30.33" IPv6Address: "2001:db8:abcd::3033" LinkLocalIPs: - "169.254.34.68" - "fe80::3468" MacAddress: "02:42:ac:12:05:02" Links: - "container_1" - "container_2" Aliases: - "server_x" - "server_y" database_nw: {} NetworkSettings: description: "NetworkSettings exposes the network settings in the API" type: "object" properties: Bridge: description: | Name of the default bridge interface when dockerd's --bridge flag is set. type: "string" example: "docker0" SandboxID: description: SandboxID uniquely represents a container's network stack. type: "string" example: "9d12daf2c33f5959c8bf90aa513e4f65b561738661003029ec84830cd503a0c3" HairpinMode: description: | Indicates if hairpin NAT should be enabled on the virtual interface. Deprecated: This field is never set and will be removed in a future release. type: "boolean" example: false LinkLocalIPv6Address: description: | IPv6 unicast address using the link-local prefix. Deprecated: This field is never set and will be removed in a future release. type: "string" example: "" LinkLocalIPv6PrefixLen: description: | Prefix length of the IPv6 unicast address. Deprecated: This field is never set and will be removed in a future release. type: "integer" example: "" Ports: $ref: "#/definitions/PortMap" SandboxKey: description: SandboxKey is the full path of the netns handle type: "string" example: "/var/run/docker/netns/8ab54b426c38" SecondaryIPAddresses: description: "Deprecated: This field is never set and will be removed in a future release." type: "array" items: $ref: "#/definitions/Address" x-nullable: true SecondaryIPv6Addresses: description: "Deprecated: This field is never set and will be removed in a future release." type: "array" items: $ref: "#/definitions/Address" x-nullable: true # TODO properties below are part of DefaultNetworkSettings, which is # marked as deprecated since Docker 1.9 and to be removed in Docker v17.12 EndpointID: description: | EndpointID uniquely represents a service endpoint in a Sandbox.


> **Deprecated**: This field is only propagated when attached to the > default "bridge" network. Use the information from the "bridge" > network inside the `Networks` map instead, which contains the same > information. This field was deprecated in Docker 1.9 and is scheduled > to be removed in Docker 17.12.0 type: "string" example: "b88f5b905aabf2893f3cbc4ee42d1ea7980bbc0a92e2c8922b1e1795298afb0b" Gateway: description: | Gateway address for the default "bridge" network.


> **Deprecated**: This field is only propagated when attached to the > default "bridge" network. Use the information from the "bridge" > network inside the `Networks` map instead, which contains the same > information. This field was deprecated in Docker 1.9 and is scheduled > to be removed in Docker 17.12.0 type: "string" example: "172.17.0.1" GlobalIPv6Address: description: | Global IPv6 address for the default "bridge" network.


> **Deprecated**: This field is only propagated when attached to the > default "bridge" network. Use the information from the "bridge" > network inside the `Networks` map instead, which contains the same > information. This field was deprecated in Docker 1.9 and is scheduled > to be removed in Docker 17.12.0 type: "string" example: "2001:db8::5689" GlobalIPv6PrefixLen: description: | Mask length of the global IPv6 address.


> **Deprecated**: This field is only propagated when attached to the > default "bridge" network. Use the information from the "bridge" > network inside the `Networks` map instead, which contains the same > information. This field was deprecated in Docker 1.9 and is scheduled > to be removed in Docker 17.12.0 type: "integer" example: 64 IPAddress: description: | IPv4 address for the default "bridge" network.


> **Deprecated**: This field is only propagated when attached to the > default "bridge" network. Use the information from the "bridge" > network inside the `Networks` map instead, which contains the same > information. This field was deprecated in Docker 1.9 and is scheduled > to be removed in Docker 17.12.0 type: "string" example: "172.17.0.4" IPPrefixLen: description: | Mask length of the IPv4 address.


> **Deprecated**: This field is only propagated when attached to the > default "bridge" network. Use the information from the "bridge" > network inside the `Networks` map instead, which contains the same > information. This field was deprecated in Docker 1.9 and is scheduled > to be removed in Docker 17.12.0 type: "integer" example: 16 IPv6Gateway: description: | IPv6 gateway address for this network.


> **Deprecated**: This field is only propagated when attached to the > default "bridge" network. Use the information from the "bridge" > network inside the `Networks` map instead, which contains the same > information. This field was deprecated in Docker 1.9 and is scheduled > to be removed in Docker 17.12.0 type: "string" example: "2001:db8:2::100" MacAddress: description: | MAC address for the container on the default "bridge" network.


> **Deprecated**: This field is only propagated when attached to the > default "bridge" network. Use the information from the "bridge" > network inside the `Networks` map instead, which contains the same > information. This field was deprecated in Docker 1.9 and is scheduled > to be removed in Docker 17.12.0 type: "string" example: "02:42:ac:11:00:04" Networks: description: | Information about all networks that the container is connected to. type: "object" additionalProperties: $ref: "#/definitions/EndpointSettings" Address: description: Address represents an IPv4 or IPv6 IP address. type: "object" properties: Addr: description: IP address. type: "string" PrefixLen: description: Mask length of the IP address. type: "integer" PortMap: description: | PortMap describes the mapping of container ports to host ports, using the container's port-number and protocol as key in the format `/`, for example, `80/udp`. If a container's port is mapped for multiple protocols, separate entries are added to the mapping table. type: "object" additionalProperties: type: "array" x-nullable: true items: $ref: "#/definitions/PortBinding" example: "443/tcp": - HostIp: "127.0.0.1" HostPort: "4443" "80/tcp": - HostIp: "0.0.0.0" HostPort: "80" - HostIp: "0.0.0.0" HostPort: "8080" "80/udp": - HostIp: "0.0.0.0" HostPort: "80" "53/udp": - HostIp: "0.0.0.0" HostPort: "53" "2377/tcp": null PortBinding: description: | PortBinding represents a binding between a host IP address and a host port. type: "object" properties: HostIp: description: "Host IP address that the container's port is mapped to." type: "string" example: "127.0.0.1" HostPort: description: "Host port number that the container's port is mapped to." type: "string" example: "4443" DriverData: description: | Information about the storage driver used to store the container's and image's filesystem. type: "object" required: [Name, Data] properties: Name: description: "Name of the storage driver." type: "string" x-nullable: false example: "overlay2" Data: description: | Low-level storage metadata, provided as key/value pairs. This information is driver-specific, and depends on the storage-driver in use, and should be used for informational purposes only. type: "object" x-nullable: false additionalProperties: type: "string" example: { "MergedDir": "/var/lib/docker/overlay2/ef749362d13333e65fc95c572eb525abbe0052e16e086cb64bc3b98ae9aa6d74/merged", "UpperDir": "/var/lib/docker/overlay2/ef749362d13333e65fc95c572eb525abbe0052e16e086cb64bc3b98ae9aa6d74/diff", "WorkDir": "/var/lib/docker/overlay2/ef749362d13333e65fc95c572eb525abbe0052e16e086cb64bc3b98ae9aa6d74/work" } FilesystemChange: description: | Change in the container's filesystem. type: "object" required: [Path, Kind] properties: Path: description: | Path to file or directory that has changed. type: "string" x-nullable: false Kind: $ref: "#/definitions/ChangeType" ChangeType: description: | Kind of change Can be one of: - `0`: Modified ("C") - `1`: Added ("A") - `2`: Deleted ("D") type: "integer" format: "uint8" enum: [0, 1, 2] x-nullable: false ImageInspect: description: | Information about an image in the local image cache. type: "object" properties: Id: description: | ID is the content-addressable ID of an image. This identifier is a content-addressable digest calculated from the image's configuration (which includes the digests of layers used by the image). Note that this digest differs from the `RepoDigests` below, which holds digests of image manifests that reference the image. type: "string" x-nullable: false example: "sha256:ec3f0931a6e6b6855d76b2d7b0be30e81860baccd891b2e243280bf1cd8ad710" Descriptor: description: | Descriptor is an OCI descriptor of the image target. In case of a multi-platform image, this descriptor points to the OCI index or a manifest list. This field is only present if the daemon provides a multi-platform image store. WARNING: This is experimental and may change at any time without any backward compatibility. x-nullable: true $ref: "#/definitions/OCIDescriptor" Manifests: description: | Manifests is a list of image manifests available in this image. It provides a more detailed view of the platform-specific image manifests or other image-attached data like build attestations. Only available if the daemon provides a multi-platform image store and the `manifests` option is set in the inspect request. WARNING: This is experimental and may change at any time without any backward compatibility. type: "array" x-nullable: true items: $ref: "#/definitions/ImageManifestSummary" RepoTags: description: | List of image names/tags in the local image cache that reference this image. Multiple image tags can refer to the same image, and this list may be empty if no tags reference the image, in which case the image is "untagged", in which case it can still be referenced by its ID. type: "array" items: type: "string" example: - "example:1.0" - "example:latest" - "example:stable" - "internal.registry.example.com:5000/example:1.0" RepoDigests: description: | List of content-addressable digests of locally available image manifests that the image is referenced from. Multiple manifests can refer to the same image. These digests are usually only available if the image was either pulled from a registry, or if the image was pushed to a registry, which is when the manifest is generated and its digest calculated. type: "array" items: type: "string" example: - "example@sha256:afcc7f1ac1b49db317a7196c902e61c6c3c4607d63599ee1a82d702d249a0ccb" - "internal.registry.example.com:5000/example@sha256:b69959407d21e8a062e0416bf13405bb2b71ed7a84dde4158ebafacfa06f5578" Parent: description: | ID of the parent image. Depending on how the image was created, this field may be empty and is only set for images that were built/created locally. This field is empty if the image was pulled from an image registry. type: "string" x-nullable: false example: "" Comment: description: | Optional message that was set when committing or importing the image. type: "string" x-nullable: false example: "" Created: description: | Date and time at which the image was created, formatted in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. This information is only available if present in the image, and omitted otherwise. type: "string" format: "dateTime" x-nullable: true example: "2022-02-04T21:20:12.497794809Z" DockerVersion: description: | The version of Docker that was used to build the image. Depending on how the image was created, this field may be empty. type: "string" x-nullable: false example: "27.0.1" Author: description: | Name of the author that was specified when committing the image, or as specified through MAINTAINER (deprecated) in the Dockerfile. type: "string" x-nullable: false example: "" Config: $ref: "#/definitions/ImageConfig" Architecture: description: | Hardware CPU architecture that the image runs on. type: "string" x-nullable: false example: "arm" Variant: description: | CPU architecture variant (presently ARM-only). type: "string" x-nullable: true example: "v7" Os: description: | Operating System the image is built to run on. type: "string" x-nullable: false example: "linux" OsVersion: description: | Operating System version the image is built to run on (especially for Windows). type: "string" example: "" x-nullable: true Size: description: | Total size of the image including all layers it is composed of. type: "integer" format: "int64" x-nullable: false example: 1239828 VirtualSize: description: | Total size of the image including all layers it is composed of. Deprecated: this field is omitted in API v1.44, but kept for backward compatibility. Use Size instead. type: "integer" format: "int64" example: 1239828 GraphDriver: $ref: "#/definitions/DriverData" RootFS: description: | Information about the image's RootFS, including the layer IDs. type: "object" required: [Type] properties: Type: type: "string" x-nullable: false example: "layers" Layers: type: "array" items: type: "string" example: - "sha256:1834950e52ce4d5a88a1bbd131c537f4d0e56d10ff0dd69e66be3b7dfa9df7e6" - "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" Metadata: description: | Additional metadata of the image in the local cache. This information is local to the daemon, and not part of the image itself. type: "object" properties: LastTagTime: description: | Date and time at which the image was last tagged in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. This information is only available if the image was tagged locally, and omitted otherwise. type: "string" format: "dateTime" example: "2022-02-28T14:40:02.623929178Z" x-nullable: true ImageSummary: type: "object" x-go-name: "Summary" required: - Id - ParentId - RepoTags - RepoDigests - Created - Size - SharedSize - Labels - Containers properties: Id: description: | ID is the content-addressable ID of an image. This identifier is a content-addressable digest calculated from the image's configuration (which includes the digests of layers used by the image). Note that this digest differs from the `RepoDigests` below, which holds digests of image manifests that reference the image. type: "string" x-nullable: false example: "sha256:ec3f0931a6e6b6855d76b2d7b0be30e81860baccd891b2e243280bf1cd8ad710" ParentId: description: | ID of the parent image. Depending on how the image was created, this field may be empty and is only set for images that were built/created locally. This field is empty if the image was pulled from an image registry. type: "string" x-nullable: false example: "" RepoTags: description: | List of image names/tags in the local image cache that reference this image. Multiple image tags can refer to the same image, and this list may be empty if no tags reference the image, in which case the image is "untagged", in which case it can still be referenced by its ID. type: "array" x-nullable: false items: type: "string" example: - "example:1.0" - "example:latest" - "example:stable" - "internal.registry.example.com:5000/example:1.0" RepoDigests: description: | List of content-addressable digests of locally available image manifests that the image is referenced from. Multiple manifests can refer to the same image. These digests are usually only available if the image was either pulled from a registry, or if the image was pushed to a registry, which is when the manifest is generated and its digest calculated. type: "array" x-nullable: false items: type: "string" example: - "example@sha256:afcc7f1ac1b49db317a7196c902e61c6c3c4607d63599ee1a82d702d249a0ccb" - "internal.registry.example.com:5000/example@sha256:b69959407d21e8a062e0416bf13405bb2b71ed7a84dde4158ebafacfa06f5578" Created: description: | Date and time at which the image was created as a Unix timestamp (number of seconds since EPOCH). type: "integer" x-nullable: false example: "1644009612" Size: description: | Total size of the image including all layers it is composed of. type: "integer" format: "int64" x-nullable: false example: 172064416 SharedSize: description: | Total size of image layers that are shared between this image and other images. This size is not calculated by default. `-1` indicates that the value has not been set / calculated. type: "integer" format: "int64" x-nullable: false example: 1239828 VirtualSize: description: |- Total size of the image including all layers it is composed of. Deprecated: this field is omitted in API v1.44, but kept for backward compatibility. Use Size instead. type: "integer" format: "int64" example: 172064416 Labels: description: "User-defined key/value metadata." type: "object" x-nullable: false additionalProperties: type: "string" example: com.example.some-label: "some-value" com.example.some-other-label: "some-other-value" Containers: description: | Number of containers using this image. Includes both stopped and running containers. This size is not calculated by default, and depends on which API endpoint is used. `-1` indicates that the value has not been set / calculated. x-nullable: false type: "integer" example: 2 Manifests: description: | Manifests is a list of manifests available in this image. It provides a more detailed view of the platform-specific image manifests or other image-attached data like build attestations. WARNING: This is experimental and may change at any time without any backward compatibility. type: "array" x-nullable: false x-omitempty: true items: $ref: "#/definitions/ImageManifestSummary" Descriptor: description: | Descriptor is an OCI descriptor of the image target. In case of a multi-platform image, this descriptor points to the OCI index or a manifest list. This field is only present if the daemon provides a multi-platform image store. WARNING: This is experimental and may change at any time without any backward compatibility. x-nullable: true $ref: "#/definitions/OCIDescriptor" AuthConfig: type: "object" properties: username: type: "string" password: type: "string" email: type: "string" serveraddress: type: "string" example: username: "hannibal" password: "xxxx" serveraddress: "https://index.docker.io/v1/" ProcessConfig: type: "object" properties: privileged: type: "boolean" user: type: "string" tty: type: "boolean" entrypoint: type: "string" arguments: type: "array" items: type: "string" Volume: type: "object" required: [Name, Driver, Mountpoint, Labels, Scope, Options] properties: Name: type: "string" description: "Name of the volume." x-nullable: false example: "tardis" Driver: type: "string" description: "Name of the volume driver used by the volume." x-nullable: false example: "custom" Mountpoint: type: "string" description: "Mount path of the volume on the host." x-nullable: false example: "/var/lib/docker/volumes/tardis" CreatedAt: type: "string" format: "dateTime" description: "Date/Time the volume was created." example: "2016-06-07T20:31:11.853781916Z" Status: type: "object" description: | Low-level details about the volume, provided by the volume driver. Details are returned as a map with key/value pairs: `{"key":"value","key2":"value2"}`. The `Status` field is optional, and is omitted if the volume driver does not support this feature. additionalProperties: type: "object" example: hello: "world" Labels: type: "object" description: "User-defined key/value metadata." x-nullable: false additionalProperties: type: "string" example: com.example.some-label: "some-value" com.example.some-other-label: "some-other-value" Scope: type: "string" description: | The level at which the volume exists. Either `global` for cluster-wide, or `local` for machine level. default: "local" x-nullable: false enum: ["local", "global"] example: "local" ClusterVolume: $ref: "#/definitions/ClusterVolume" Options: type: "object" description: | The driver specific options used when creating the volume. additionalProperties: type: "string" example: device: "tmpfs" o: "size=100m,uid=1000" type: "tmpfs" UsageData: type: "object" x-nullable: true x-go-name: "UsageData" required: [Size, RefCount] description: | Usage details about the volume. This information is used by the `GET /system/df` endpoint, and omitted in other endpoints. properties: Size: type: "integer" format: "int64" default: -1 description: | Amount of disk space used by the volume (in bytes). This information is only available for volumes created with the `"local"` volume driver. For volumes created with other volume drivers, this field is set to `-1` ("not available") x-nullable: false RefCount: type: "integer" format: "int64" default: -1 description: | The number of containers referencing this volume. This field is set to `-1` if the reference-count is not available. x-nullable: false VolumeCreateOptions: description: "Volume configuration" type: "object" title: "VolumeConfig" x-go-name: "CreateOptions" properties: Name: description: | The new volume's name. If not specified, Docker generates a name. type: "string" x-nullable: false example: "tardis" Driver: description: "Name of the volume driver to use." type: "string" default: "local" x-nullable: false example: "custom" DriverOpts: description: | A mapping of driver options and values. These options are passed directly to the driver and are driver specific. type: "object" additionalProperties: type: "string" example: device: "tmpfs" o: "size=100m,uid=1000" type: "tmpfs" Labels: description: "User-defined key/value metadata." type: "object" additionalProperties: type: "string" example: com.example.some-label: "some-value" com.example.some-other-label: "some-other-value" ClusterVolumeSpec: $ref: "#/definitions/ClusterVolumeSpec" VolumeListResponse: type: "object" title: "VolumeListResponse" x-go-name: "ListResponse" description: "Volume list response" properties: Volumes: type: "array" description: "List of volumes" items: $ref: "#/definitions/Volume" Warnings: type: "array" description: | Warnings that occurred when fetching the list of volumes. items: type: "string" example: [] Network: type: "object" properties: Name: description: | Name of the network. type: "string" example: "my_network" Id: description: | ID that uniquely identifies a network on a single machine. type: "string" example: "7d86d31b1478e7cca9ebed7e73aa0fdeec46c5ca29497431d3007d2d9e15ed99" Created: description: | Date and time at which the network was created in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. type: "string" format: "dateTime" example: "2016-10-19T04:33:30.360899459Z" Scope: description: | The level at which the network exists (e.g. `swarm` for cluster-wide or `local` for machine level) type: "string" example: "local" Driver: description: | The name of the driver used to create the network (e.g. `bridge`, `overlay`). type: "string" example: "overlay" EnableIPv4: description: | Whether the network was created with IPv4 enabled. type: "boolean" example: true EnableIPv6: description: | Whether the network was created with IPv6 enabled. type: "boolean" example: false IPAM: $ref: "#/definitions/IPAM" Internal: description: | Whether the network is created to only allow internal networking connectivity. type: "boolean" default: false example: false Attachable: description: | Whether a global / swarm scope network is manually attachable by regular containers from workers in swarm mode. type: "boolean" default: false example: false Ingress: description: | Whether the network is providing the routing-mesh for the swarm cluster. type: "boolean" default: false example: false ConfigFrom: $ref: "#/definitions/ConfigReference" ConfigOnly: description: | Whether the network is a config-only network. Config-only networks are placeholder networks for network configurations to be used by other networks. Config-only networks cannot be used directly to run containers or services. type: "boolean" default: false Containers: description: | Contains endpoints attached to the network. type: "object" additionalProperties: $ref: "#/definitions/NetworkContainer" example: 19a4d5d687db25203351ed79d478946f861258f018fe384f229f2efa4b23513c: Name: "test" EndpointID: "628cadb8bcb92de107b2a1e516cbffe463e321f548feb37697cce00ad694f21a" MacAddress: "02:42:ac:13:00:02" IPv4Address: "172.19.0.2/16" IPv6Address: "" Options: description: | Network-specific options uses when creating the network. type: "object" additionalProperties: type: "string" example: com.docker.network.bridge.default_bridge: "true" com.docker.network.bridge.enable_icc: "true" com.docker.network.bridge.enable_ip_masquerade: "true" com.docker.network.bridge.host_binding_ipv4: "0.0.0.0" com.docker.network.bridge.name: "docker0" com.docker.network.driver.mtu: "1500" Labels: description: "User-defined key/value metadata." type: "object" additionalProperties: type: "string" example: com.example.some-label: "some-value" com.example.some-other-label: "some-other-value" Peers: description: | List of peer nodes for an overlay network. This field is only present for overlay networks, and omitted for other network types. type: "array" items: $ref: "#/definitions/PeerInfo" x-nullable: true # TODO: Add Services (only present when "verbose" is set). ConfigReference: description: | The config-only network source to provide the configuration for this network. type: "object" properties: Network: description: | The name of the config-only network that provides the network's configuration. The specified network must be an existing config-only network. Only network names are allowed, not network IDs. type: "string" example: "config_only_network_01" IPAM: type: "object" properties: Driver: description: "Name of the IPAM driver to use." type: "string" default: "default" example: "default" Config: description: | List of IPAM configuration options, specified as a map: ``` {"Subnet": , "IPRange": , "Gateway": , "AuxAddress": } ``` type: "array" items: $ref: "#/definitions/IPAMConfig" Options: description: "Driver-specific options, specified as a map." type: "object" additionalProperties: type: "string" example: foo: "bar" IPAMConfig: type: "object" properties: Subnet: type: "string" example: "172.20.0.0/16" IPRange: type: "string" example: "172.20.10.0/24" Gateway: type: "string" example: "172.20.10.11" AuxiliaryAddresses: type: "object" additionalProperties: type: "string" NetworkContainer: type: "object" properties: Name: type: "string" example: "container_1" EndpointID: type: "string" example: "628cadb8bcb92de107b2a1e516cbffe463e321f548feb37697cce00ad694f21a" MacAddress: type: "string" example: "02:42:ac:13:00:02" IPv4Address: type: "string" example: "172.19.0.2/16" IPv6Address: type: "string" example: "" PeerInfo: description: | PeerInfo represents one peer of an overlay network. type: "object" properties: Name: description: ID of the peer-node in the Swarm cluster. type: "string" example: "6869d7c1732b" IP: description: IP-address of the peer-node in the Swarm cluster. type: "string" example: "10.133.77.91" NetworkCreateResponse: description: "OK response to NetworkCreate operation" type: "object" title: "NetworkCreateResponse" x-go-name: "CreateResponse" required: [Id, Warning] properties: Id: description: "The ID of the created network." type: "string" x-nullable: false example: "b5c4fc71e8022147cd25de22b22173de4e3b170134117172eb595cb91b4e7e5d" Warning: description: "Warnings encountered when creating the container" type: "string" x-nullable: false example: "" BuildInfo: type: "object" properties: id: type: "string" stream: type: "string" error: type: "string" x-nullable: true description: |- errors encountered during the operation. > **Deprecated**: This field is deprecated since API v1.4, and will be omitted in a future API version. Use the information in errorDetail instead. errorDetail: $ref: "#/definitions/ErrorDetail" status: type: "string" progress: type: "string" x-nullable: true description: |- Progress is a pre-formatted presentation of progressDetail. > **Deprecated**: This field is deprecated since API v1.8, and will be omitted in a future API version. Use the information in progressDetail instead. progressDetail: $ref: "#/definitions/ProgressDetail" aux: $ref: "#/definitions/ImageID" BuildCache: type: "object" description: | BuildCache contains information about a build cache record. properties: ID: type: "string" description: | Unique ID of the build cache record. example: "ndlpt0hhvkqcdfkputsk4cq9c" Parent: description: | ID of the parent build cache record. > **Deprecated**: This field is deprecated, and omitted if empty. type: "string" x-nullable: true example: "" Parents: description: | List of parent build cache record IDs. type: "array" items: type: "string" x-nullable: true example: ["hw53o5aio51xtltp5xjp8v7fx"] Type: type: "string" description: | Cache record type. example: "regular" # see https://github.com/moby/buildkit/blob/fce4a32258dc9d9664f71a4831d5de10f0670677/client/diskusage.go#L75-L84 enum: - "internal" - "frontend" - "source.local" - "source.git.checkout" - "exec.cachemount" - "regular" Description: type: "string" description: | Description of the build-step that produced the build cache. example: "mount / from exec /bin/sh -c echo 'Binary::apt::APT::Keep-Downloaded-Packages \"true\";' > /etc/apt/apt.conf.d/keep-cache" InUse: type: "boolean" description: | Indicates if the build cache is in use. example: false Shared: type: "boolean" description: | Indicates if the build cache is shared. example: true Size: description: | Amount of disk space used by the build cache (in bytes). type: "integer" example: 51 CreatedAt: description: | Date and time at which the build cache was created in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. type: "string" format: "dateTime" example: "2016-08-18T10:44:24.496525531Z" LastUsedAt: description: | Date and time at which the build cache was last used in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. type: "string" format: "dateTime" x-nullable: true example: "2017-08-09T07:09:37.632105588Z" UsageCount: type: "integer" example: 26 ImageID: type: "object" description: "Image ID or Digest" properties: ID: type: "string" example: ID: "sha256:85f05633ddc1c50679be2b16a0479ab6f7637f8884e0cfe0f4d20e1ebb3d6e7c" CreateImageInfo: type: "object" properties: id: type: "string" error: type: "string" x-nullable: true description: |- errors encountered during the operation. > **Deprecated**: This field is deprecated since API v1.4, and will be omitted in a future API version. Use the information in errorDetail instead. errorDetail: $ref: "#/definitions/ErrorDetail" status: type: "string" progress: type: "string" x-nullable: true description: |- Progress is a pre-formatted presentation of progressDetail. > **Deprecated**: This field is deprecated since API v1.8, and will be omitted in a future API version. Use the information in progressDetail instead. progressDetail: $ref: "#/definitions/ProgressDetail" PushImageInfo: type: "object" properties: error: type: "string" x-nullable: true description: |- errors encountered during the operation. > **Deprecated**: This field is deprecated since API v1.4, and will be omitted in a future API version. Use the information in errorDetail instead. errorDetail: $ref: "#/definitions/ErrorDetail" status: type: "string" progress: type: "string" x-nullable: true description: |- Progress is a pre-formatted presentation of progressDetail. > **Deprecated**: This field is deprecated since API v1.8, and will be omitted in a future API version. Use the information in progressDetail instead. progressDetail: $ref: "#/definitions/ProgressDetail" ErrorDetail: type: "object" properties: code: type: "integer" message: type: "string" ProgressDetail: type: "object" properties: current: type: "integer" total: type: "integer" ErrorResponse: description: "Represents an error." type: "object" required: ["message"] properties: message: description: "The error message." type: "string" x-nullable: false example: message: "Something went wrong." IDResponse: description: "Response to an API call that returns just an Id" type: "object" x-go-name: "IDResponse" required: ["Id"] properties: Id: description: "The id of the newly created object." type: "string" x-nullable: false EndpointSettings: description: "Configuration for a network endpoint." type: "object" properties: # Configurations IPAMConfig: $ref: "#/definitions/EndpointIPAMConfig" Links: type: "array" items: type: "string" example: - "container_1" - "container_2" MacAddress: description: | MAC address for the endpoint on this network. The network driver might ignore this parameter. type: "string" example: "02:42:ac:11:00:04" Aliases: type: "array" items: type: "string" example: - "server_x" - "server_y" DriverOpts: description: | DriverOpts is a mapping of driver options and values. These options are passed directly to the driver and are driver specific. type: "object" x-nullable: true additionalProperties: type: "string" example: com.example.some-label: "some-value" com.example.some-other-label: "some-other-value" GwPriority: description: | This property determines which endpoint will provide the default gateway for a container. The endpoint with the highest priority will be used. If multiple endpoints have the same priority, endpoints are lexicographically sorted based on their network name, and the one that sorts first is picked. type: "number" example: - 10 # Operational data NetworkID: description: | Unique ID of the network. type: "string" example: "08754567f1f40222263eab4102e1c733ae697e8e354aa9cd6e18d7402835292a" EndpointID: description: | Unique ID for the service endpoint in a Sandbox. type: "string" example: "b88f5b905aabf2893f3cbc4ee42d1ea7980bbc0a92e2c8922b1e1795298afb0b" Gateway: description: | Gateway address for this network. type: "string" example: "172.17.0.1" IPAddress: description: | IPv4 address. type: "string" example: "172.17.0.4" IPPrefixLen: description: | Mask length of the IPv4 address. type: "integer" example: 16 IPv6Gateway: description: | IPv6 gateway address. type: "string" example: "2001:db8:2::100" GlobalIPv6Address: description: | Global IPv6 address. type: "string" example: "2001:db8::5689" GlobalIPv6PrefixLen: description: | Mask length of the global IPv6 address. type: "integer" format: "int64" example: 64 DNSNames: description: | List of all DNS names an endpoint has on a specific network. This list is based on the container name, network aliases, container short ID, and hostname. These DNS names are non-fully qualified but can contain several dots. You can get fully qualified DNS names by appending `.`. For instance, if container name is `my.ctr` and the network is named `testnet`, `DNSNames` will contain `my.ctr` and the FQDN will be `my.ctr.testnet`. type: array items: type: string example: ["foobar", "server_x", "server_y", "my.ctr"] EndpointIPAMConfig: description: | EndpointIPAMConfig represents an endpoint's IPAM configuration. type: "object" x-nullable: true properties: IPv4Address: type: "string" example: "172.20.30.33" IPv6Address: type: "string" example: "2001:db8:abcd::3033" LinkLocalIPs: type: "array" items: type: "string" example: - "169.254.34.68" - "fe80::3468" PluginMount: type: "object" x-nullable: false required: [Name, Description, Settable, Source, Destination, Type, Options] properties: Name: type: "string" x-nullable: false example: "some-mount" Description: type: "string" x-nullable: false example: "This is a mount that's used by the plugin." Settable: type: "array" items: type: "string" Source: type: "string" example: "/var/lib/docker/plugins/" Destination: type: "string" x-nullable: false example: "/mnt/state" Type: type: "string" x-nullable: false example: "bind" Options: type: "array" items: type: "string" example: - "rbind" - "rw" PluginDevice: type: "object" required: [Name, Description, Settable, Path] x-nullable: false properties: Name: type: "string" x-nullable: false Description: type: "string" x-nullable: false Settable: type: "array" items: type: "string" Path: type: "string" example: "/dev/fuse" PluginEnv: type: "object" x-nullable: false required: [Name, Description, Settable, Value] properties: Name: x-nullable: false type: "string" Description: x-nullable: false type: "string" Settable: type: "array" items: type: "string" Value: type: "string" PluginInterfaceType: type: "object" x-nullable: false required: [Prefix, Capability, Version] properties: Prefix: type: "string" x-nullable: false Capability: type: "string" x-nullable: false Version: type: "string" x-nullable: false PluginPrivilege: description: | Describes a permission the user has to accept upon installing the plugin. type: "object" x-go-name: "PluginPrivilege" properties: Name: type: "string" example: "network" Description: type: "string" Value: type: "array" items: type: "string" example: - "host" Plugin: description: "A plugin for the Engine API" type: "object" required: [Settings, Enabled, Config, Name] properties: Id: type: "string" example: "5724e2c8652da337ab2eedd19fc6fc0ec908e4bd907c7421bf6a8dfc70c4c078" Name: type: "string" x-nullable: false example: "tiborvass/sample-volume-plugin" Enabled: description: True if the plugin is running. False if the plugin is not running, only installed. type: "boolean" x-nullable: false example: true Settings: description: "Settings that can be modified by users." type: "object" x-nullable: false required: [Args, Devices, Env, Mounts] properties: Mounts: type: "array" items: $ref: "#/definitions/PluginMount" Env: type: "array" items: type: "string" example: - "DEBUG=0" Args: type: "array" items: type: "string" Devices: type: "array" items: $ref: "#/definitions/PluginDevice" PluginReference: description: "plugin remote reference used to push/pull the plugin" type: "string" x-nullable: false example: "localhost:5000/tiborvass/sample-volume-plugin:latest" Config: description: "The config of a plugin." type: "object" x-nullable: false required: - Description - Documentation - Interface - Entrypoint - WorkDir - Network - Linux - PidHost - PropagatedMount - IpcHost - Mounts - Env - Args properties: DockerVersion: description: "Docker Version used to create the plugin" type: "string" x-nullable: false example: "17.06.0-ce" Description: type: "string" x-nullable: false example: "A sample volume plugin for Docker" Documentation: type: "string" x-nullable: false example: "https://docs.docker.com/engine/extend/plugins/" Interface: description: "The interface between Docker and the plugin" x-nullable: false type: "object" required: [Types, Socket] properties: Types: type: "array" items: $ref: "#/definitions/PluginInterfaceType" example: - "docker.volumedriver/1.0" Socket: type: "string" x-nullable: false example: "plugins.sock" ProtocolScheme: type: "string" example: "some.protocol/v1.0" description: "Protocol to use for clients connecting to the plugin." enum: - "" - "moby.plugins.http/v1" Entrypoint: type: "array" items: type: "string" example: - "/usr/bin/sample-volume-plugin" - "/data" WorkDir: type: "string" x-nullable: false example: "/bin/" User: type: "object" x-nullable: false properties: UID: type: "integer" format: "uint32" example: 1000 GID: type: "integer" format: "uint32" example: 1000 Network: type: "object" x-nullable: false required: [Type] properties: Type: x-nullable: false type: "string" example: "host" Linux: type: "object" x-nullable: false required: [Capabilities, AllowAllDevices, Devices] properties: Capabilities: type: "array" items: type: "string" example: - "CAP_SYS_ADMIN" - "CAP_SYSLOG" AllowAllDevices: type: "boolean" x-nullable: false example: false Devices: type: "array" items: $ref: "#/definitions/PluginDevice" PropagatedMount: type: "string" x-nullable: false example: "/mnt/volumes" IpcHost: type: "boolean" x-nullable: false example: false PidHost: type: "boolean" x-nullable: false example: false Mounts: type: "array" items: $ref: "#/definitions/PluginMount" Env: type: "array" items: $ref: "#/definitions/PluginEnv" example: - Name: "DEBUG" Description: "If set, prints debug messages" Settable: null Value: "0" Args: type: "object" x-nullable: false required: [Name, Description, Settable, Value] properties: Name: x-nullable: false type: "string" example: "args" Description: x-nullable: false type: "string" example: "command line arguments" Settable: type: "array" items: type: "string" Value: type: "array" items: type: "string" rootfs: type: "object" properties: type: type: "string" example: "layers" diff_ids: type: "array" items: type: "string" example: - "sha256:675532206fbf3030b8458f88d6e26d4eb1577688a25efec97154c94e8b6b4887" - "sha256:e216a057b1cb1efc11f8a268f37ef62083e70b1b38323ba252e25ac88904a7e8" ObjectVersion: description: | The version number of the object such as node, service, etc. This is needed to avoid conflicting writes. The client must send the version number along with the modified specification when updating these objects. This approach ensures safe concurrency and determinism in that the change on the object may not be applied if the version number has changed from the last read. In other words, if two update requests specify the same base version, only one of the requests can succeed. As a result, two separate update requests that happen at the same time will not unintentionally overwrite each other. type: "object" properties: Index: type: "integer" format: "uint64" example: 373531 NodeSpec: type: "object" properties: Name: description: "Name for the node." type: "string" example: "my-node" Labels: description: "User-defined key/value metadata." type: "object" additionalProperties: type: "string" Role: description: "Role of the node." type: "string" enum: - "worker" - "manager" example: "manager" Availability: description: "Availability of the node." type: "string" enum: - "active" - "pause" - "drain" example: "active" example: Availability: "active" Name: "node-name" Role: "manager" Labels: foo: "bar" Node: type: "object" properties: ID: type: "string" example: "24ifsmvkjbyhk" Version: $ref: "#/definitions/ObjectVersion" CreatedAt: description: | Date and time at which the node was added to the swarm in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. type: "string" format: "dateTime" example: "2016-08-18T10:44:24.496525531Z" UpdatedAt: description: | Date and time at which the node was last updated in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. type: "string" format: "dateTime" example: "2017-08-09T07:09:37.632105588Z" Spec: $ref: "#/definitions/NodeSpec" Description: $ref: "#/definitions/NodeDescription" Status: $ref: "#/definitions/NodeStatus" ManagerStatus: $ref: "#/definitions/ManagerStatus" NodeDescription: description: | NodeDescription encapsulates the properties of the Node as reported by the agent. type: "object" properties: Hostname: type: "string" example: "bf3067039e47" Platform: $ref: "#/definitions/Platform" Resources: $ref: "#/definitions/ResourceObject" Engine: $ref: "#/definitions/EngineDescription" TLSInfo: $ref: "#/definitions/TLSInfo" Platform: description: | Platform represents the platform (Arch/OS). type: "object" properties: Architecture: description: | Architecture represents the hardware architecture (for example, `x86_64`). type: "string" example: "x86_64" OS: description: | OS represents the Operating System (for example, `linux` or `windows`). type: "string" example: "linux" EngineDescription: description: "EngineDescription provides information about an engine." type: "object" properties: EngineVersion: type: "string" example: "17.06.0" Labels: type: "object" additionalProperties: type: "string" example: foo: "bar" Plugins: type: "array" items: type: "object" properties: Type: type: "string" Name: type: "string" example: - Type: "Log" Name: "awslogs" - Type: "Log" Name: "fluentd" - Type: "Log" Name: "gcplogs" - Type: "Log" Name: "gelf" - Type: "Log" Name: "journald" - Type: "Log" Name: "json-file" - Type: "Log" Name: "splunk" - Type: "Log" Name: "syslog" - Type: "Network" Name: "bridge" - Type: "Network" Name: "host" - Type: "Network" Name: "ipvlan" - Type: "Network" Name: "macvlan" - Type: "Network" Name: "null" - Type: "Network" Name: "overlay" - Type: "Volume" Name: "local" - Type: "Volume" Name: "localhost:5000/vieux/sshfs:latest" - Type: "Volume" Name: "vieux/sshfs:latest" TLSInfo: description: | Information about the issuer of leaf TLS certificates and the trusted root CA certificate. type: "object" properties: TrustRoot: description: | The root CA certificate(s) that are used to validate leaf TLS certificates. type: "string" CertIssuerSubject: description: The base64-url-safe-encoded raw subject bytes of the issuer. type: "string" CertIssuerPublicKey: description: | The base64-url-safe-encoded raw public key bytes of the issuer. type: "string" example: TrustRoot: | -----BEGIN CERTIFICATE----- MIIBajCCARCgAwIBAgIUbYqrLSOSQHoxD8CwG6Bi2PJi9c8wCgYIKoZIzj0EAwIw EzERMA8GA1UEAxMIc3dhcm0tY2EwHhcNMTcwNDI0MjE0MzAwWhcNMzcwNDE5MjE0 MzAwWjATMREwDwYDVQQDEwhzd2FybS1jYTBZMBMGByqGSM49AgEGCCqGSM49AwEH A0IABJk/VyMPYdaqDXJb/VXh5n/1Yuv7iNrxV3Qb3l06XD46seovcDWs3IZNV1lf 3Skyr0ofcchipoiHkXBODojJydSjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB Af8EBTADAQH/MB0GA1UdDgQWBBRUXxuRcnFjDfR/RIAUQab8ZV/n4jAKBggqhkjO PQQDAgNIADBFAiAy+JTe6Uc3KyLCMiqGl2GyWGQqQDEcO3/YG36x7om65AIhAJvz pxv6zFeVEkAEEkqIYi0omA9+CjanB/6Bz4n1uw8H -----END CERTIFICATE----- CertIssuerSubject: "MBMxETAPBgNVBAMTCHN3YXJtLWNh" CertIssuerPublicKey: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmT9XIw9h1qoNclv9VeHmf/Vi6/uI2vFXdBveXTpcPjqx6i9wNazchk1XWV/dKTKvSh9xyGKmiIeRcE4OiMnJ1A==" NodeStatus: description: | NodeStatus represents the status of a node. It provides the current status of the node, as seen by the manager. type: "object" properties: State: $ref: "#/definitions/NodeState" Message: type: "string" example: "" Addr: description: "IP address of the node." type: "string" example: "172.17.0.2" NodeState: description: "NodeState represents the state of a node." type: "string" enum: - "unknown" - "down" - "ready" - "disconnected" example: "ready" ManagerStatus: description: | ManagerStatus represents the status of a manager. It provides the current status of a node's manager component, if the node is a manager. x-nullable: true type: "object" properties: Leader: type: "boolean" default: false example: true Reachability: $ref: "#/definitions/Reachability" Addr: description: | The IP address and port at which the manager is reachable. type: "string" example: "10.0.0.46:2377" Reachability: description: "Reachability represents the reachability of a node." type: "string" enum: - "unknown" - "unreachable" - "reachable" example: "reachable" SwarmSpec: description: "User modifiable swarm configuration." type: "object" properties: Name: description: "Name of the swarm." type: "string" example: "default" Labels: description: "User-defined key/value metadata." type: "object" additionalProperties: type: "string" example: com.example.corp.type: "production" com.example.corp.department: "engineering" Orchestration: description: "Orchestration configuration." type: "object" x-nullable: true properties: TaskHistoryRetentionLimit: description: | The number of historic tasks to keep per instance or node. If negative, never remove completed or failed tasks. type: "integer" format: "int64" example: 10 Raft: description: "Raft configuration." type: "object" properties: SnapshotInterval: description: "The number of log entries between snapshots." type: "integer" format: "uint64" example: 10000 KeepOldSnapshots: description: | The number of snapshots to keep beyond the current snapshot. type: "integer" format: "uint64" LogEntriesForSlowFollowers: description: | The number of log entries to keep around to sync up slow followers after a snapshot is created. type: "integer" format: "uint64" example: 500 ElectionTick: description: | The number of ticks that a follower will wait for a message from the leader before becoming a candidate and starting an election. `ElectionTick` must be greater than `HeartbeatTick`. A tick currently defaults to one second, so these translate directly to seconds currently, but this is NOT guaranteed. type: "integer" example: 3 HeartbeatTick: description: | The number of ticks between heartbeats. Every HeartbeatTick ticks, the leader will send a heartbeat to the followers. A tick currently defaults to one second, so these translate directly to seconds currently, but this is NOT guaranteed. type: "integer" example: 1 Dispatcher: description: "Dispatcher configuration." type: "object" x-nullable: true properties: HeartbeatPeriod: description: | The delay for an agent to send a heartbeat to the dispatcher. type: "integer" format: "int64" example: 5000000000 CAConfig: description: "CA configuration." type: "object" x-nullable: true properties: NodeCertExpiry: description: "The duration node certificates are issued for." type: "integer" format: "int64" example: 7776000000000000 ExternalCAs: description: | Configuration for forwarding signing requests to an external certificate authority. type: "array" items: type: "object" properties: Protocol: description: | Protocol for communication with the external CA (currently only `cfssl` is supported). type: "string" enum: - "cfssl" default: "cfssl" URL: description: | URL where certificate signing requests should be sent. type: "string" Options: description: | An object with key/value pairs that are interpreted as protocol-specific options for the external CA driver. type: "object" additionalProperties: type: "string" CACert: description: | The root CA certificate (in PEM format) this external CA uses to issue TLS certificates (assumed to be to the current swarm root CA certificate if not provided). type: "string" SigningCACert: description: | The desired signing CA certificate for all swarm node TLS leaf certificates, in PEM format. type: "string" SigningCAKey: description: | The desired signing CA key for all swarm node TLS leaf certificates, in PEM format. type: "string" ForceRotate: description: | An integer whose purpose is to force swarm to generate a new signing CA certificate and key, if none have been specified in `SigningCACert` and `SigningCAKey` format: "uint64" type: "integer" EncryptionConfig: description: "Parameters related to encryption-at-rest." type: "object" properties: AutoLockManagers: description: | If set, generate a key and use it to lock data stored on the managers. type: "boolean" example: false TaskDefaults: description: "Defaults for creating tasks in this cluster." type: "object" properties: LogDriver: description: | The log driver to use for tasks created in the orchestrator if unspecified by a service. Updating this value only affects new tasks. Existing tasks continue to use their previously configured log driver until recreated. type: "object" properties: Name: description: | The log driver to use as a default for new tasks. type: "string" example: "json-file" Options: description: | Driver-specific options for the selected log driver, specified as key/value pairs. type: "object" additionalProperties: type: "string" example: "max-file": "10" "max-size": "100m" # The Swarm information for `GET /info`. It is the same as `GET /swarm`, but # without `JoinTokens`. ClusterInfo: description: | ClusterInfo represents information about the swarm as is returned by the "/info" endpoint. Join-tokens are not included. x-nullable: true type: "object" properties: ID: description: "The ID of the swarm." type: "string" example: "abajmipo7b4xz5ip2nrla6b11" Version: $ref: "#/definitions/ObjectVersion" CreatedAt: description: | Date and time at which the swarm was initialised in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. type: "string" format: "dateTime" example: "2016-08-18T10:44:24.496525531Z" UpdatedAt: description: | Date and time at which the swarm was last updated in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. type: "string" format: "dateTime" example: "2017-08-09T07:09:37.632105588Z" Spec: $ref: "#/definitions/SwarmSpec" TLSInfo: $ref: "#/definitions/TLSInfo" RootRotationInProgress: description: | Whether there is currently a root CA rotation in progress for the swarm type: "boolean" example: false DataPathPort: description: | DataPathPort specifies the data path port number for data traffic. Acceptable port range is 1024 to 49151. If no port is set or is set to 0, the default port (4789) is used. type: "integer" format: "uint32" default: 4789 example: 4789 DefaultAddrPool: description: | Default Address Pool specifies default subnet pools for global scope networks. type: "array" items: type: "string" format: "CIDR" example: ["10.10.0.0/16", "20.20.0.0/16"] SubnetSize: description: | SubnetSize specifies the subnet size of the networks created from the default subnet pool. type: "integer" format: "uint32" maximum: 29 default: 24 example: 24 JoinTokens: description: | JoinTokens contains the tokens workers and managers need to join the swarm. type: "object" properties: Worker: description: | The token workers can use to join the swarm. type: "string" example: "SWMTKN-1-3pu6hszjas19xyp7ghgosyx9k8atbfcr8p2is99znpy26u2lkl-1awxwuwd3z9j1z3puu7rcgdbx" Manager: description: | The token managers can use to join the swarm. type: "string" example: "SWMTKN-1-3pu6hszjas19xyp7ghgosyx9k8atbfcr8p2is99znpy26u2lkl-7p73s1dx5in4tatdymyhg9hu2" Swarm: type: "object" allOf: - $ref: "#/definitions/ClusterInfo" - type: "object" properties: JoinTokens: $ref: "#/definitions/JoinTokens" TaskSpec: description: "User modifiable task configuration." type: "object" properties: PluginSpec: type: "object" description: | Plugin spec for the service. *(Experimental release only.)*


> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are > mutually exclusive. PluginSpec is only used when the Runtime field > is set to `plugin`. NetworkAttachmentSpec is used when the Runtime > field is set to `attachment`. properties: Name: description: "The name or 'alias' to use for the plugin." type: "string" Remote: description: "The plugin image reference to use." type: "string" Disabled: description: "Disable the plugin once scheduled." type: "boolean" PluginPrivilege: type: "array" items: $ref: "#/definitions/PluginPrivilege" ContainerSpec: type: "object" description: | Container spec for the service.


> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are > mutually exclusive. PluginSpec is only used when the Runtime field > is set to `plugin`. NetworkAttachmentSpec is used when the Runtime > field is set to `attachment`. properties: Image: description: "The image name to use for the container" type: "string" Labels: description: "User-defined key/value data." type: "object" additionalProperties: type: "string" Command: description: "The command to be run in the image." type: "array" items: type: "string" Args: description: "Arguments to the command." type: "array" items: type: "string" Hostname: description: | The hostname to use for the container, as a valid [RFC 1123](https://tools.ietf.org/html/rfc1123) hostname. type: "string" Env: description: | A list of environment variables in the form `VAR=value`. type: "array" items: type: "string" Dir: description: "The working directory for commands to run in." type: "string" User: description: "The user inside the container." type: "string" Groups: type: "array" description: | A list of additional groups that the container process will run as. items: type: "string" Privileges: type: "object" description: "Security options for the container" properties: CredentialSpec: type: "object" description: "CredentialSpec for managed service account (Windows only)" properties: Config: type: "string" example: "0bt9dmxjvjiqermk6xrop3ekq" description: | Load credential spec from a Swarm Config with the given ID. The specified config must also be present in the Configs field with the Runtime property set.


> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`, > and `CredentialSpec.Config` are mutually exclusive. File: type: "string" example: "spec.json" description: | Load credential spec from this file. The file is read by the daemon, and must be present in the `CredentialSpecs` subdirectory in the docker data directory, which defaults to `C:\ProgramData\Docker\` on Windows. For example, specifying `spec.json` loads `C:\ProgramData\Docker\CredentialSpecs\spec.json`.


> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`, > and `CredentialSpec.Config` are mutually exclusive. Registry: type: "string" description: | Load credential spec from this value in the Windows registry. The specified registry value must be located in: `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs`


> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`, > and `CredentialSpec.Config` are mutually exclusive. SELinuxContext: type: "object" description: "SELinux labels of the container" properties: Disable: type: "boolean" description: "Disable SELinux" User: type: "string" description: "SELinux user label" Role: type: "string" description: "SELinux role label" Type: type: "string" description: "SELinux type label" Level: type: "string" description: "SELinux level label" Seccomp: type: "object" description: "Options for configuring seccomp on the container" properties: Mode: type: "string" enum: - "default" - "unconfined" - "custom" Profile: description: "The custom seccomp profile as a json object" type: "string" AppArmor: type: "object" description: "Options for configuring AppArmor on the container" properties: Mode: type: "string" enum: - "default" - "disabled" NoNewPrivileges: type: "boolean" description: "Configuration of the no_new_privs bit in the container" TTY: description: "Whether a pseudo-TTY should be allocated." type: "boolean" OpenStdin: description: "Open `stdin`" type: "boolean" ReadOnly: description: "Mount the container's root filesystem as read only." type: "boolean" Mounts: description: | Specification for mounts to be added to containers created as part of the service. type: "array" items: $ref: "#/definitions/Mount" StopSignal: description: "Signal to stop the container." type: "string" StopGracePeriod: description: | Amount of time to wait for the container to terminate before forcefully killing it. type: "integer" format: "int64" HealthCheck: $ref: "#/definitions/HealthConfig" Hosts: type: "array" description: | A list of hostname/IP mappings to add to the container's `hosts` file. The format of extra hosts is specified in the [hosts(5)](http://man7.org/linux/man-pages/man5/hosts.5.html) man page: IP_address canonical_hostname [aliases...] items: type: "string" DNSConfig: description: | Specification for DNS related configurations in resolver configuration file (`resolv.conf`). type: "object" properties: Nameservers: description: "The IP addresses of the name servers." type: "array" items: type: "string" Search: description: "A search list for host-name lookup." type: "array" items: type: "string" Options: description: | A list of internal resolver variables to be modified (e.g., `debug`, `ndots:3`, etc.). type: "array" items: type: "string" Secrets: description: | Secrets contains references to zero or more secrets that will be exposed to the service. type: "array" items: type: "object" properties: File: description: | File represents a specific target that is backed by a file. type: "object" properties: Name: description: | Name represents the final filename in the filesystem. type: "string" UID: description: "UID represents the file UID." type: "string" GID: description: "GID represents the file GID." type: "string" Mode: description: "Mode represents the FileMode of the file." type: "integer" format: "uint32" SecretID: description: | SecretID represents the ID of the specific secret that we're referencing. type: "string" SecretName: description: | SecretName is the name of the secret that this references, but this is just provided for lookup/display purposes. The secret in the reference will be identified by its ID. type: "string" OomScoreAdj: type: "integer" format: "int64" description: | An integer value containing the score given to the container in order to tune OOM killer preferences. example: 0 Configs: description: | Configs contains references to zero or more configs that will be exposed to the service. type: "array" items: type: "object" properties: File: description: | File represents a specific target that is backed by a file.


> **Note**: `Configs.File` and `Configs.Runtime` are mutually exclusive type: "object" properties: Name: description: | Name represents the final filename in the filesystem. type: "string" UID: description: "UID represents the file UID." type: "string" GID: description: "GID represents the file GID." type: "string" Mode: description: "Mode represents the FileMode of the file." type: "integer" format: "uint32" Runtime: description: | Runtime represents a target that is not mounted into the container but is used by the task


> **Note**: `Configs.File` and `Configs.Runtime` are mutually > exclusive type: "object" ConfigID: description: | ConfigID represents the ID of the specific config that we're referencing. type: "string" ConfigName: description: | ConfigName is the name of the config that this references, but this is just provided for lookup/display purposes. The config in the reference will be identified by its ID. type: "string" Isolation: type: "string" description: | Isolation technology of the containers running the service. (Windows only) enum: - "default" - "process" - "hyperv" - "" Init: description: | Run an init inside the container that forwards signals and reaps processes. This field is omitted if empty, and the default (as configured on the daemon) is used. type: "boolean" x-nullable: true Sysctls: description: | Set kernel namedspaced parameters (sysctls) in the container. The Sysctls option on services accepts the same sysctls as the are supported on containers. Note that while the same sysctls are supported, no guarantees or checks are made about their suitability for a clustered environment, and it's up to the user to determine whether a given sysctl will work properly in a Service. type: "object" additionalProperties: type: "string" # This option is not used by Windows containers CapabilityAdd: type: "array" description: | A list of kernel capabilities to add to the default set for the container. items: type: "string" example: - "CAP_NET_RAW" - "CAP_SYS_ADMIN" - "CAP_SYS_CHROOT" - "CAP_SYSLOG" CapabilityDrop: type: "array" description: | A list of kernel capabilities to drop from the default set for the container. items: type: "string" example: - "CAP_NET_RAW" Ulimits: description: | A list of resource limits to set in the container. For example: `{"Name": "nofile", "Soft": 1024, "Hard": 2048}`" type: "array" items: type: "object" properties: Name: description: "Name of ulimit" type: "string" Soft: description: "Soft limit" type: "integer" Hard: description: "Hard limit" type: "integer" NetworkAttachmentSpec: description: | Read-only spec type for non-swarm containers attached to swarm overlay networks.


> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are > mutually exclusive. PluginSpec is only used when the Runtime field > is set to `plugin`. NetworkAttachmentSpec is used when the Runtime > field is set to `attachment`. type: "object" properties: ContainerID: description: "ID of the container represented by this task" type: "string" Resources: description: | Resource requirements which apply to each individual container created as part of the service. type: "object" properties: Limits: description: "Define resources limits." $ref: "#/definitions/Limit" Reservations: description: "Define resources reservation." $ref: "#/definitions/ResourceObject" RestartPolicy: description: | Specification for the restart policy which applies to containers created as part of this service. type: "object" properties: Condition: description: "Condition for restart." type: "string" enum: - "none" - "on-failure" - "any" Delay: description: "Delay between restart attempts." type: "integer" format: "int64" MaxAttempts: description: | Maximum attempts to restart a given container before giving up (default value is 0, which is ignored). type: "integer" format: "int64" default: 0 Window: description: | Windows is the time window used to evaluate the restart policy (default value is 0, which is unbounded). type: "integer" format: "int64" default: 0 Placement: type: "object" properties: Constraints: description: | An array of constraint expressions to limit the set of nodes where a task can be scheduled. Constraint expressions can either use a _match_ (`==`) or _exclude_ (`!=`) rule. Multiple constraints find nodes that satisfy every expression (AND match). Constraints can match node or Docker Engine labels as follows: node attribute | matches | example ---------------------|--------------------------------|----------------------------------------------- `node.id` | Node ID | `node.id==2ivku8v2gvtg4` `node.hostname` | Node hostname | `node.hostname!=node-2` `node.role` | Node role (`manager`/`worker`) | `node.role==manager` `node.platform.os` | Node operating system | `node.platform.os==windows` `node.platform.arch` | Node architecture | `node.platform.arch==x86_64` `node.labels` | User-defined node labels | `node.labels.security==high` `engine.labels` | Docker Engine's labels | `engine.labels.operatingsystem==ubuntu-24.04` `engine.labels` apply to Docker Engine labels like operating system, drivers, etc. Swarm administrators add `node.labels` for operational purposes by using the [`node update endpoint`](#operation/NodeUpdate). type: "array" items: type: "string" example: - "node.hostname!=node3.corp.example.com" - "node.role!=manager" - "node.labels.type==production" - "node.platform.os==linux" - "node.platform.arch==x86_64" Preferences: description: | Preferences provide a way to make the scheduler aware of factors such as topology. They are provided in order from highest to lowest precedence. type: "array" items: type: "object" properties: Spread: type: "object" properties: SpreadDescriptor: description: | label descriptor, such as `engine.labels.az`. type: "string" example: - Spread: SpreadDescriptor: "node.labels.datacenter" - Spread: SpreadDescriptor: "node.labels.rack" MaxReplicas: description: | Maximum number of replicas for per node (default value is 0, which is unlimited) type: "integer" format: "int64" default: 0 Platforms: description: | Platforms stores all the platforms that the service's image can run on. This field is used in the platform filter for scheduling. If empty, then the platform filter is off, meaning there are no scheduling restrictions. type: "array" items: $ref: "#/definitions/Platform" ForceUpdate: description: | A counter that triggers an update even if no relevant parameters have been changed. type: "integer" Runtime: description: | Runtime is the type of runtime specified for the task executor. type: "string" Networks: description: "Specifies which networks the service should attach to." type: "array" items: $ref: "#/definitions/NetworkAttachmentConfig" LogDriver: description: | Specifies the log driver to use for tasks created from this spec. If not present, the default one for the swarm will be used, finally falling back to the engine default if not specified. type: "object" properties: Name: type: "string" Options: type: "object" additionalProperties: type: "string" TaskState: type: "string" enum: - "new" - "allocated" - "pending" - "assigned" - "accepted" - "preparing" - "ready" - "starting" - "running" - "complete" - "shutdown" - "failed" - "rejected" - "remove" - "orphaned" ContainerStatus: type: "object" description: "represents the status of a container." properties: ContainerID: type: "string" PID: type: "integer" ExitCode: type: "integer" PortStatus: type: "object" description: "represents the port status of a task's host ports whose service has published host ports" properties: Ports: type: "array" items: $ref: "#/definitions/EndpointPortConfig" TaskStatus: type: "object" description: "represents the status of a task." properties: Timestamp: type: "string" format: "dateTime" State: $ref: "#/definitions/TaskState" Message: type: "string" Err: type: "string" ContainerStatus: $ref: "#/definitions/ContainerStatus" PortStatus: $ref: "#/definitions/PortStatus" Task: type: "object" properties: ID: description: "The ID of the task." type: "string" Version: $ref: "#/definitions/ObjectVersion" CreatedAt: type: "string" format: "dateTime" UpdatedAt: type: "string" format: "dateTime" Name: description: "Name of the task." type: "string" Labels: description: "User-defined key/value metadata." type: "object" additionalProperties: type: "string" Spec: $ref: "#/definitions/TaskSpec" ServiceID: description: "The ID of the service this task is part of." type: "string" Slot: type: "integer" NodeID: description: "The ID of the node that this task is on." type: "string" AssignedGenericResources: $ref: "#/definitions/GenericResources" Status: $ref: "#/definitions/TaskStatus" DesiredState: $ref: "#/definitions/TaskState" JobIteration: description: | If the Service this Task belongs to is a job-mode service, contains the JobIteration of the Service this Task was created for. Absent if the Task was created for a Replicated or Global Service. $ref: "#/definitions/ObjectVersion" example: ID: "0kzzo1i0y4jz6027t0k7aezc7" Version: Index: 71 CreatedAt: "2016-06-07T21:07:31.171892745Z" UpdatedAt: "2016-06-07T21:07:31.376370513Z" Spec: ContainerSpec: Image: "redis" Resources: Limits: {} Reservations: {} RestartPolicy: Condition: "any" MaxAttempts: 0 Placement: {} ServiceID: "9mnpnzenvg8p8tdbtq4wvbkcz" Slot: 1 NodeID: "60gvrl6tm78dmak4yl7srz94v" Status: Timestamp: "2016-06-07T21:07:31.290032978Z" State: "running" Message: "started" ContainerStatus: ContainerID: "e5d62702a1b48d01c3e02ca1e0212a250801fa8d67caca0b6f35919ebc12f035" PID: 677 DesiredState: "running" NetworksAttachments: - Network: ID: "4qvuz4ko70xaltuqbt8956gd1" Version: Index: 18 CreatedAt: "2016-06-07T20:31:11.912919752Z" UpdatedAt: "2016-06-07T21:07:29.955277358Z" Spec: Name: "ingress" Labels: com.docker.swarm.internal: "true" DriverConfiguration: {} IPAMOptions: Driver: {} Configs: - Subnet: "10.255.0.0/16" Gateway: "10.255.0.1" DriverState: Name: "overlay" Options: com.docker.network.driver.overlay.vxlanid_list: "256" IPAMOptions: Driver: Name: "default" Configs: - Subnet: "10.255.0.0/16" Gateway: "10.255.0.1" Addresses: - "10.255.0.10/16" AssignedGenericResources: - DiscreteResourceSpec: Kind: "SSD" Value: 3 - NamedResourceSpec: Kind: "GPU" Value: "UUID1" - NamedResourceSpec: Kind: "GPU" Value: "UUID2" ServiceSpec: description: "User modifiable configuration for a service." type: object properties: Name: description: "Name of the service." type: "string" Labels: description: "User-defined key/value metadata." type: "object" additionalProperties: type: "string" TaskTemplate: $ref: "#/definitions/TaskSpec" Mode: description: "Scheduling mode for the service." type: "object" properties: Replicated: type: "object" properties: Replicas: type: "integer" format: "int64" Global: type: "object" ReplicatedJob: description: | The mode used for services with a finite number of tasks that run to a completed state. type: "object" properties: MaxConcurrent: description: | The maximum number of replicas to run simultaneously. type: "integer" format: "int64" default: 1 TotalCompletions: description: | The total number of replicas desired to reach the Completed state. If unset, will default to the value of `MaxConcurrent` type: "integer" format: "int64" GlobalJob: description: | The mode used for services which run a task to the completed state on each valid node. type: "object" UpdateConfig: description: "Specification for the update strategy of the service." type: "object" properties: Parallelism: description: | Maximum number of tasks to be updated in one iteration (0 means unlimited parallelism). type: "integer" format: "int64" Delay: description: "Amount of time between updates, in nanoseconds." type: "integer" format: "int64" FailureAction: description: | Action to take if an updated task fails to run, or stops running during the update. type: "string" enum: - "continue" - "pause" - "rollback" Monitor: description: | Amount of time to monitor each updated task for failures, in nanoseconds. type: "integer" format: "int64" MaxFailureRatio: description: | The fraction of tasks that may fail during an update before the failure action is invoked, specified as a floating point number between 0 and 1. type: "number" default: 0 Order: description: | The order of operations when rolling out an updated task. Either the old task is shut down before the new task is started, or the new task is started before the old task is shut down. type: "string" enum: - "stop-first" - "start-first" RollbackConfig: description: "Specification for the rollback strategy of the service." type: "object" properties: Parallelism: description: | Maximum number of tasks to be rolled back in one iteration (0 means unlimited parallelism). type: "integer" format: "int64" Delay: description: | Amount of time between rollback iterations, in nanoseconds. type: "integer" format: "int64" FailureAction: description: | Action to take if an rolled back task fails to run, or stops running during the rollback. type: "string" enum: - "continue" - "pause" Monitor: description: | Amount of time to monitor each rolled back task for failures, in nanoseconds. type: "integer" format: "int64" MaxFailureRatio: description: | The fraction of tasks that may fail during a rollback before the failure action is invoked, specified as a floating point number between 0 and 1. type: "number" default: 0 Order: description: | The order of operations when rolling back a task. Either the old task is shut down before the new task is started, or the new task is started before the old task is shut down. type: "string" enum: - "stop-first" - "start-first" Networks: description: | Specifies which networks the service should attach to. Deprecated: This field is deprecated since v1.44. The Networks field in TaskSpec should be used instead. type: "array" items: $ref: "#/definitions/NetworkAttachmentConfig" EndpointSpec: $ref: "#/definitions/EndpointSpec" EndpointPortConfig: type: "object" properties: Name: type: "string" Protocol: type: "string" enum: - "tcp" - "udp" - "sctp" TargetPort: description: "The port inside the container." type: "integer" PublishedPort: description: "The port on the swarm hosts." type: "integer" PublishMode: description: | The mode in which port is published.


- "ingress" makes the target port accessible on every node, regardless of whether there is a task for the service running on that node or not. - "host" bypasses the routing mesh and publish the port directly on the swarm node where that service is running. type: "string" enum: - "ingress" - "host" default: "ingress" example: "ingress" EndpointSpec: description: "Properties that can be configured to access and load balance a service." type: "object" properties: Mode: description: | The mode of resolution to use for internal load balancing between tasks. type: "string" enum: - "vip" - "dnsrr" default: "vip" Ports: description: | List of exposed ports that this service is accessible on from the outside. Ports can only be provided if `vip` resolution mode is used. type: "array" items: $ref: "#/definitions/EndpointPortConfig" Service: type: "object" properties: ID: type: "string" Version: $ref: "#/definitions/ObjectVersion" CreatedAt: type: "string" format: "dateTime" UpdatedAt: type: "string" format: "dateTime" Spec: $ref: "#/definitions/ServiceSpec" Endpoint: type: "object" properties: Spec: $ref: "#/definitions/EndpointSpec" Ports: type: "array" items: $ref: "#/definitions/EndpointPortConfig" VirtualIPs: type: "array" items: type: "object" properties: NetworkID: type: "string" Addr: type: "string" UpdateStatus: description: "The status of a service update." type: "object" properties: State: type: "string" enum: - "updating" - "paused" - "completed" StartedAt: type: "string" format: "dateTime" CompletedAt: type: "string" format: "dateTime" Message: type: "string" ServiceStatus: description: | The status of the service's tasks. Provided only when requested as part of a ServiceList operation. type: "object" properties: RunningTasks: description: | The number of tasks for the service currently in the Running state. type: "integer" format: "uint64" example: 7 DesiredTasks: description: | The number of tasks for the service desired to be running. For replicated services, this is the replica count from the service spec. For global services, this is computed by taking count of all tasks for the service with a Desired State other than Shutdown. type: "integer" format: "uint64" example: 10 CompletedTasks: description: | The number of tasks for a job that are in the Completed state. This field must be cross-referenced with the service type, as the value of 0 may mean the service is not in a job mode, or it may mean the job-mode service has no tasks yet Completed. type: "integer" format: "uint64" JobStatus: description: | The status of the service when it is in one of ReplicatedJob or GlobalJob modes. Absent on Replicated and Global mode services. The JobIteration is an ObjectVersion, but unlike the Service's version, does not need to be sent with an update request. type: "object" properties: JobIteration: description: | JobIteration is a value increased each time a Job is executed, successfully or otherwise. "Executed", in this case, means the job as a whole has been started, not that an individual Task has been launched. A job is "Executed" when its ServiceSpec is updated. JobIteration can be used to disambiguate Tasks belonging to different executions of a job. Though JobIteration will increase with each subsequent execution, it may not necessarily increase by 1, and so JobIteration should not be used to $ref: "#/definitions/ObjectVersion" LastExecution: description: | The last time, as observed by the server, that this job was started. type: "string" format: "dateTime" example: ID: "9mnpnzenvg8p8tdbtq4wvbkcz" Version: Index: 19 CreatedAt: "2016-06-07T21:05:51.880065305Z" UpdatedAt: "2016-06-07T21:07:29.962229872Z" Spec: Name: "hopeful_cori" TaskTemplate: ContainerSpec: Image: "redis" Resources: Limits: {} Reservations: {} RestartPolicy: Condition: "any" MaxAttempts: 0 Placement: {} ForceUpdate: 0 Mode: Replicated: Replicas: 1 UpdateConfig: Parallelism: 1 Delay: 1000000000 FailureAction: "pause" Monitor: 15000000000 MaxFailureRatio: 0.15 RollbackConfig: Parallelism: 1 Delay: 1000000000 FailureAction: "pause" Monitor: 15000000000 MaxFailureRatio: 0.15 EndpointSpec: Mode: "vip" Ports: - Protocol: "tcp" TargetPort: 6379 PublishedPort: 30001 Endpoint: Spec: Mode: "vip" Ports: - Protocol: "tcp" TargetPort: 6379 PublishedPort: 30001 Ports: - Protocol: "tcp" TargetPort: 6379 PublishedPort: 30001 VirtualIPs: - NetworkID: "4qvuz4ko70xaltuqbt8956gd1" Addr: "10.255.0.2/16" - NetworkID: "4qvuz4ko70xaltuqbt8956gd1" Addr: "10.255.0.3/16" ImageDeleteResponseItem: type: "object" x-go-name: "DeleteResponse" properties: Untagged: description: "The image ID of an image that was untagged" type: "string" Deleted: description: "The image ID of an image that was deleted" type: "string" ServiceCreateResponse: type: "object" description: | contains the information returned to a client on the creation of a new service. properties: ID: description: "The ID of the created service." type: "string" x-nullable: false example: "ak7w3gjqoa3kuz8xcpnyy0pvl" Warnings: description: | Optional warning message. FIXME(thaJeztah): this should have "omitempty" in the generated type. type: "array" x-nullable: true items: type: "string" example: - "unable to pin image doesnotexist:latest to digest: image library/doesnotexist:latest not found" ServiceUpdateResponse: type: "object" properties: Warnings: description: "Optional warning messages" type: "array" items: type: "string" example: Warnings: - "unable to pin image doesnotexist:latest to digest: image library/doesnotexist:latest not found" ContainerInspectResponse: type: "object" title: "ContainerInspectResponse" x-go-name: "InspectResponse" properties: Id: description: |- The ID of this container as a 128-bit (64-character) hexadecimal string (32 bytes). type: "string" x-go-name: "ID" minLength: 64 maxLength: 64 pattern: "^[0-9a-fA-F]{64}$" example: "aa86eacfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf" Created: description: |- Date and time at which the container was created, formatted in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. type: "string" format: "dateTime" x-nullable: true example: "2025-02-17T17:43:39.64001363Z" Path: description: |- The path to the command being run type: "string" example: "/bin/sh" Args: description: "The arguments to the command being run" type: "array" items: type: "string" example: - "-c" - "exit 9" State: $ref: "#/definitions/ContainerState" Image: description: |- The ID (digest) of the image that this container was created from. type: "string" example: "sha256:72297848456d5d37d1262630108ab308d3e9ec7ed1c3286a32fe09856619a782" ResolvConfPath: description: |- Location of the `/etc/resolv.conf` generated for the container on the host. This file is managed through the docker daemon, and should not be accessed or modified by other tools. type: "string" example: "/var/lib/docker/containers/aa86eacfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf/resolv.conf" HostnamePath: description: |- Location of the `/etc/hostname` generated for the container on the host. This file is managed through the docker daemon, and should not be accessed or modified by other tools. type: "string" example: "/var/lib/docker/containers/aa86eacfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf/hostname" HostsPath: description: |- Location of the `/etc/hosts` generated for the container on the host. This file is managed through the docker daemon, and should not be accessed or modified by other tools. type: "string" example: "/var/lib/docker/containers/aa86eacfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf/hosts" LogPath: description: |- Location of the file used to buffer the container's logs. Depending on the logging-driver used for the container, this field may be omitted. This file is managed through the docker daemon, and should not be accessed or modified by other tools. type: "string" x-nullable: true example: "/var/lib/docker/containers/5b7c7e2b992aa426584ce6c47452756066be0e503a08b4516a433a54d2f69e59/5b7c7e2b992aa426584ce6c47452756066be0e503a08b4516a433a54d2f69e59-json.log" Name: description: |- The name associated with this container. For historic reasons, the name may be prefixed with a forward-slash (`/`). type: "string" example: "/funny_chatelet" RestartCount: description: |- Number of times the container was restarted since it was created, or since daemon was started. type: "integer" example: 0 Driver: description: |- The storage-driver used for the container's filesystem (graph-driver or snapshotter). type: "string" example: "overlayfs" Platform: description: |- The platform (operating system) for which the container was created. This field was introduced for the experimental "LCOW" (Linux Containers On Windows) features, which has been removed. In most cases, this field is equal to the host's operating system (`linux` or `windows`). type: "string" example: "linux" ImageManifestDescriptor: $ref: "#/definitions/OCIDescriptor" description: |- OCI descriptor of the platform-specific manifest of the image the container was created from. Note: Only available if the daemon provides a multi-platform image store. MountLabel: description: |- SELinux mount label set for the container. type: "string" example: "" ProcessLabel: description: |- SELinux process label set for the container. type: "string" example: "" AppArmorProfile: description: |- The AppArmor profile set for the container. type: "string" example: "" ExecIDs: description: |- IDs of exec instances that are running in the container. type: "array" items: type: "string" x-nullable: true example: - "b35395de42bc8abd327f9dd65d913b9ba28c74d2f0734eeeae84fa1c616a0fca" - "3fc1232e5cd20c8de182ed81178503dc6437f4e7ef12b52cc5e8de020652f1c4" HostConfig: $ref: "#/definitions/HostConfig" GraphDriver: $ref: "#/definitions/DriverData" SizeRw: description: |- The size of files that have been created or changed by this container. This field is omitted by default, and only set when size is requested in the API request. type: "integer" format: "int64" x-nullable: true example: "122880" SizeRootFs: description: |- The total size of all files in the read-only layers from the image that the container uses. These layers can be shared between containers. This field is omitted by default, and only set when size is requested in the API request. type: "integer" format: "int64" x-nullable: true example: "1653948416" Mounts: description: |- List of mounts used by the container. type: "array" items: $ref: "#/definitions/MountPoint" Config: $ref: "#/definitions/ContainerConfig" NetworkSettings: $ref: "#/definitions/NetworkSettings" ContainerSummary: type: "object" properties: Id: description: |- The ID of this container as a 128-bit (64-character) hexadecimal string (32 bytes). type: "string" x-go-name: "ID" minLength: 64 maxLength: 64 pattern: "^[0-9a-fA-F]{64}$" example: "aa86eacfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf" Names: description: |- The names associated with this container. Most containers have a single name, but when using legacy "links", the container can have multiple names. For historic reasons, names are prefixed with a forward-slash (`/`). type: "array" items: type: "string" example: - "/funny_chatelet" Image: description: |- The name or ID of the image used to create the container. This field shows the image reference as was specified when creating the container, which can be in its canonical form (e.g., `docker.io/library/ubuntu:latest` or `docker.io/library/ubuntu@sha256:72297848456d5d37d1262630108ab308d3e9ec7ed1c3286a32fe09856619a782`), short form (e.g., `ubuntu:latest`)), or the ID(-prefix) of the image (e.g., `72297848456d`). The content of this field can be updated at runtime if the image used to create the container is untagged, in which case the field is updated to contain the the image ID (digest) it was resolved to in its canonical, non-truncated form (e.g., `sha256:72297848456d5d37d1262630108ab308d3e9ec7ed1c3286a32fe09856619a782`). type: "string" example: "docker.io/library/ubuntu:latest" ImageID: description: |- The ID (digest) of the image that this container was created from. type: "string" example: "sha256:72297848456d5d37d1262630108ab308d3e9ec7ed1c3286a32fe09856619a782" ImageManifestDescriptor: $ref: "#/definitions/OCIDescriptor" x-nullable: true description: | OCI descriptor of the platform-specific manifest of the image the container was created from. Note: Only available if the daemon provides a multi-platform image store. This field is not populated in the `GET /system/df` endpoint. Command: description: "Command to run when starting the container" type: "string" example: "/bin/bash" Created: description: |- Date and time at which the container was created as a Unix timestamp (number of seconds since EPOCH). type: "integer" format: "int64" example: "1739811096" Ports: description: |- Port-mappings for the container. type: "array" items: $ref: "#/definitions/Port" SizeRw: description: |- The size of files that have been created or changed by this container. This field is omitted by default, and only set when size is requested in the API request. type: "integer" format: "int64" x-nullable: true example: "122880" SizeRootFs: description: |- The total size of all files in the read-only layers from the image that the container uses. These layers can be shared between containers. This field is omitted by default, and only set when size is requested in the API request. type: "integer" format: "int64" x-nullable: true example: "1653948416" Labels: description: "User-defined key/value metadata." type: "object" additionalProperties: type: "string" example: com.example.vendor: "Acme" com.example.license: "GPL" com.example.version: "1.0" State: description: | The state of this container. type: "string" enum: - "created" - "running" - "paused" - "restarting" - "exited" - "removing" - "dead" example: "running" Status: description: |- Additional human-readable status of this container (e.g. `Exit 0`) type: "string" example: "Up 4 days" HostConfig: type: "object" description: |- Summary of host-specific runtime information of the container. This is a reduced set of information in the container's "HostConfig" as available in the container "inspect" response. properties: NetworkMode: description: |- Networking mode (`host`, `none`, `container:`) or name of the primary network the container is using. This field is primarily for backward compatibility. The container can be connected to multiple networks for which information can be found in the `NetworkSettings.Networks` field, which enumerates settings per network. type: "string" example: "mynetwork" Annotations: description: |- Arbitrary key-value metadata attached to the container. type: "object" x-nullable: true additionalProperties: type: "string" example: io.kubernetes.docker.type: "container" io.kubernetes.sandbox.id: "3befe639bed0fd6afdd65fd1fa84506756f59360ec4adc270b0fdac9be22b4d3" NetworkSettings: description: |- Summary of the container's network settings type: "object" properties: Networks: type: "object" description: |- Summary of network-settings for each network the container is attached to. additionalProperties: $ref: "#/definitions/EndpointSettings" Mounts: type: "array" description: |- List of mounts used by the container. items: $ref: "#/definitions/MountPoint" Driver: description: "Driver represents a driver (network, logging, secrets)." type: "object" required: [Name] properties: Name: description: "Name of the driver." type: "string" x-nullable: false example: "some-driver" Options: description: "Key/value map of driver-specific options." type: "object" x-nullable: false additionalProperties: type: "string" example: OptionA: "value for driver-specific option A" OptionB: "value for driver-specific option B" SecretSpec: type: "object" properties: Name: description: "User-defined name of the secret." type: "string" Labels: description: "User-defined key/value metadata." type: "object" additionalProperties: type: "string" example: com.example.some-label: "some-value" com.example.some-other-label: "some-other-value" Data: description: | Base64-url-safe-encoded ([RFC 4648](https://tools.ietf.org/html/rfc4648#section-5)) data to store as secret. This field is only used to _create_ a secret, and is not returned by other endpoints. type: "string" example: "" Driver: description: | Name of the secrets driver used to fetch the secret's value from an external secret store. $ref: "#/definitions/Driver" Templating: description: | Templating driver, if applicable Templating controls whether and how to evaluate the config payload as a template. If no driver is set, no templating is used. $ref: "#/definitions/Driver" Secret: type: "object" properties: ID: type: "string" example: "blt1owaxmitz71s9v5zh81zun" Version: $ref: "#/definitions/ObjectVersion" CreatedAt: type: "string" format: "dateTime" example: "2017-07-20T13:55:28.678958722Z" UpdatedAt: type: "string" format: "dateTime" example: "2017-07-20T13:55:28.678958722Z" Spec: $ref: "#/definitions/SecretSpec" ConfigSpec: type: "object" properties: Name: description: "User-defined name of the config." type: "string" Labels: description: "User-defined key/value metadata." type: "object" additionalProperties: type: "string" Data: description: | Base64-url-safe-encoded ([RFC 4648](https://tools.ietf.org/html/rfc4648#section-5)) config data. type: "string" Templating: description: | Templating driver, if applicable Templating controls whether and how to evaluate the config payload as a template. If no driver is set, no templating is used. $ref: "#/definitions/Driver" Config: type: "object" properties: ID: type: "string" Version: $ref: "#/definitions/ObjectVersion" CreatedAt: type: "string" format: "dateTime" UpdatedAt: type: "string" format: "dateTime" Spec: $ref: "#/definitions/ConfigSpec" ContainerState: description: | ContainerState stores container's running state. It's part of ContainerJSONBase and will be returned by the "inspect" command. type: "object" x-nullable: true properties: Status: description: | String representation of the container state. Can be one of "created", "running", "paused", "restarting", "removing", "exited", or "dead". type: "string" enum: ["created", "running", "paused", "restarting", "removing", "exited", "dead"] example: "running" Running: description: | Whether this container is running. Note that a running container can be _paused_. The `Running` and `Paused` booleans are not mutually exclusive: When pausing a container (on Linux), the freezer cgroup is used to suspend all processes in the container. Freezing the process requires the process to be running. As a result, paused containers are both `Running` _and_ `Paused`. Use the `Status` field instead to determine if a container's state is "running". type: "boolean" example: true Paused: description: "Whether this container is paused." type: "boolean" example: false Restarting: description: "Whether this container is restarting." type: "boolean" example: false OOMKilled: description: | Whether a process within this container has been killed because it ran out of memory since the container was last started. type: "boolean" example: false Dead: type: "boolean" example: false Pid: description: "The process ID of this container" type: "integer" example: 1234 ExitCode: description: "The last exit code of this container" type: "integer" example: 0 Error: type: "string" StartedAt: description: "The time when this container was last started." type: "string" example: "2020-01-06T09:06:59.461876391Z" FinishedAt: description: "The time when this container last exited." type: "string" example: "2020-01-06T09:07:59.461876391Z" Health: $ref: "#/definitions/Health" ContainerCreateResponse: description: "OK response to ContainerCreate operation" type: "object" title: "ContainerCreateResponse" x-go-name: "CreateResponse" required: [Id, Warnings] properties: Id: description: "The ID of the created container" type: "string" x-nullable: false example: "ede54ee1afda366ab42f824e8a5ffd195155d853ceaec74a927f249ea270c743" Warnings: description: "Warnings encountered when creating the container" type: "array" x-nullable: false items: type: "string" example: [] ContainerUpdateResponse: type: "object" title: "ContainerUpdateResponse" x-go-name: "UpdateResponse" description: |- Response for a successful container-update. properties: Warnings: type: "array" description: |- Warnings encountered when updating the container. items: type: "string" example: ["Published ports are discarded when using host network mode"] ContainerStatsResponse: description: | Statistics sample for a container. type: "object" x-go-name: "StatsResponse" title: "ContainerStatsResponse" properties: name: description: "Name of the container" type: "string" x-nullable: true example: "boring_wozniak" id: description: "ID of the container" type: "string" x-nullable: true example: "ede54ee1afda366ab42f824e8a5ffd195155d853ceaec74a927f249ea270c743" read: description: | Date and time at which this sample was collected. The value is formatted as [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) with nano-seconds. type: "string" format: "date-time" example: "2025-01-16T13:55:22.165243637Z" preread: description: | Date and time at which this first sample was collected. This field is not propagated if the "one-shot" option is set. If the "one-shot" option is set, this field may be omitted, empty, or set to a default date (`0001-01-01T00:00:00Z`). The value is formatted as [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) with nano-seconds. type: "string" format: "date-time" example: "2025-01-16T13:55:21.160452595Z" pids_stats: $ref: "#/definitions/ContainerPidsStats" blkio_stats: $ref: "#/definitions/ContainerBlkioStats" num_procs: description: | The number of processors on the system. This field is Windows-specific and always zero for Linux containers. type: "integer" format: "uint32" example: 16 storage_stats: $ref: "#/definitions/ContainerStorageStats" cpu_stats: $ref: "#/definitions/ContainerCPUStats" precpu_stats: $ref: "#/definitions/ContainerCPUStats" memory_stats: $ref: "#/definitions/ContainerMemoryStats" networks: description: | Network statistics for the container per interface. This field is omitted if the container has no networking enabled. x-nullable: true additionalProperties: $ref: "#/definitions/ContainerNetworkStats" example: eth0: rx_bytes: 5338 rx_dropped: 0 rx_errors: 0 rx_packets: 36 tx_bytes: 648 tx_dropped: 0 tx_errors: 0 tx_packets: 8 eth5: rx_bytes: 4641 rx_dropped: 0 rx_errors: 0 rx_packets: 26 tx_bytes: 690 tx_dropped: 0 tx_errors: 0 tx_packets: 9 ContainerBlkioStats: description: | BlkioStats stores all IO service stats for data read and write. This type is Linux-specific and holds many fields that are specific to cgroups v1. On a cgroup v2 host, all fields other than `io_service_bytes_recursive` are omitted or `null`. This type is only populated on Linux and omitted for Windows containers. type: "object" x-go-name: "BlkioStats" x-nullable: true properties: io_service_bytes_recursive: type: "array" items: $ref: "#/definitions/ContainerBlkioStatEntry" io_serviced_recursive: description: | This field is only available when using Linux containers with cgroups v1. It is omitted or `null` when using cgroups v2. x-nullable: true type: "array" items: $ref: "#/definitions/ContainerBlkioStatEntry" io_queue_recursive: description: | This field is only available when using Linux containers with cgroups v1. It is omitted or `null` when using cgroups v2. x-nullable: true type: "array" items: $ref: "#/definitions/ContainerBlkioStatEntry" io_service_time_recursive: description: | This field is only available when using Linux containers with cgroups v1. It is omitted or `null` when using cgroups v2. x-nullable: true type: "array" items: $ref: "#/definitions/ContainerBlkioStatEntry" io_wait_time_recursive: description: | This field is only available when using Linux containers with cgroups v1. It is omitted or `null` when using cgroups v2. x-nullable: true type: "array" items: $ref: "#/definitions/ContainerBlkioStatEntry" io_merged_recursive: description: | This field is only available when using Linux containers with cgroups v1. It is omitted or `null` when using cgroups v2. x-nullable: true type: "array" items: $ref: "#/definitions/ContainerBlkioStatEntry" io_time_recursive: description: | This field is only available when using Linux containers with cgroups v1. It is omitted or `null` when using cgroups v2. x-nullable: true type: "array" items: $ref: "#/definitions/ContainerBlkioStatEntry" sectors_recursive: description: | This field is only available when using Linux containers with cgroups v1. It is omitted or `null` when using cgroups v2. x-nullable: true type: "array" items: $ref: "#/definitions/ContainerBlkioStatEntry" example: io_service_bytes_recursive: [ {"major": 254, "minor": 0, "op": "read", "value": 7593984}, {"major": 254, "minor": 0, "op": "write", "value": 100} ] io_serviced_recursive: null io_queue_recursive: null io_service_time_recursive: null io_wait_time_recursive: null io_merged_recursive: null io_time_recursive: null sectors_recursive: null ContainerBlkioStatEntry: description: | Blkio stats entry. This type is Linux-specific and omitted for Windows containers. type: "object" x-go-name: "BlkioStatEntry" x-nullable: true properties: major: type: "integer" format: "uint64" example: 254 minor: type: "integer" format: "uint64" example: 0 op: type: "string" example: "read" value: type: "integer" format: "uint64" example: 7593984 ContainerCPUStats: description: | CPU related info of the container type: "object" x-go-name: "CPUStats" x-nullable: true properties: cpu_usage: $ref: "#/definitions/ContainerCPUUsage" system_cpu_usage: description: | System Usage. This field is Linux-specific and omitted for Windows containers. type: "integer" format: "uint64" x-nullable: true example: 5 online_cpus: description: | Number of online CPUs. This field is Linux-specific and omitted for Windows containers. type: "integer" format: "uint32" x-nullable: true example: 5 throttling_data: $ref: "#/definitions/ContainerThrottlingData" ContainerCPUUsage: description: | All CPU stats aggregated since container inception. type: "object" x-go-name: "CPUUsage" x-nullable: true properties: total_usage: description: | Total CPU time consumed in nanoseconds (Linux) or 100's of nanoseconds (Windows). type: "integer" format: "uint64" example: 29912000 percpu_usage: description: | Total CPU time (in nanoseconds) consumed per core (Linux). This field is Linux-specific when using cgroups v1. It is omitted when using cgroups v2 and Windows containers. type: "array" x-nullable: true items: type: "integer" format: "uint64" example: 29912000 usage_in_kernelmode: description: | Time (in nanoseconds) spent by tasks of the cgroup in kernel mode (Linux), or time spent (in 100's of nanoseconds) by all container processes in kernel mode (Windows). Not populated for Windows containers using Hyper-V isolation. type: "integer" format: "uint64" example: 21994000 usage_in_usermode: description: | Time (in nanoseconds) spent by tasks of the cgroup in user mode (Linux), or time spent (in 100's of nanoseconds) by all container processes in kernel mode (Windows). Not populated for Windows containers using Hyper-V isolation. type: "integer" format: "uint64" example: 7918000 ContainerPidsStats: description: | PidsStats contains Linux-specific stats of a container's process-IDs (PIDs). This type is Linux-specific and omitted for Windows containers. type: "object" x-go-name: "PidsStats" x-nullable: true properties: current: description: | Current is the number of PIDs in the cgroup. type: "integer" format: "uint64" x-nullable: true example: 5 limit: description: | Limit is the hard limit on the number of pids in the cgroup. A "Limit" of 0 means that there is no limit. type: "integer" format: "uint64" x-nullable: true example: 18446744073709551615 ContainerThrottlingData: description: | CPU throttling stats of the container. This type is Linux-specific and omitted for Windows containers. type: "object" x-go-name: "ThrottlingData" x-nullable: true properties: periods: description: | Number of periods with throttling active. type: "integer" format: "uint64" example: 0 throttled_periods: description: | Number of periods when the container hit its throttling limit. type: "integer" format: "uint64" example: 0 throttled_time: description: | Aggregated time (in nanoseconds) the container was throttled for. type: "integer" format: "uint64" example: 0 ContainerMemoryStats: description: | Aggregates all memory stats since container inception on Linux. Windows returns stats for commit and private working set only. type: "object" x-go-name: "MemoryStats" properties: usage: description: | Current `res_counter` usage for memory. This field is Linux-specific and omitted for Windows containers. type: "integer" format: "uint64" x-nullable: true example: 0 max_usage: description: | Maximum usage ever recorded. This field is Linux-specific and only supported on cgroups v1. It is omitted when using cgroups v2 and for Windows containers. type: "integer" format: "uint64" x-nullable: true example: 0 stats: description: | All the stats exported via memory.stat. when using cgroups v2. This field is Linux-specific and omitted for Windows containers. type: "object" additionalProperties: type: "integer" format: "uint64" x-nullable: true example: { "active_anon": 1572864, "active_file": 5115904, "anon": 1572864, "anon_thp": 0, "file": 7626752, "file_dirty": 0, "file_mapped": 2723840, "file_writeback": 0, "inactive_anon": 0, "inactive_file": 2510848, "kernel_stack": 16384, "pgactivate": 0, "pgdeactivate": 0, "pgfault": 2042, "pglazyfree": 0, "pglazyfreed": 0, "pgmajfault": 45, "pgrefill": 0, "pgscan": 0, "pgsteal": 0, "shmem": 0, "slab": 1180928, "slab_reclaimable": 725576, "slab_unreclaimable": 455352, "sock": 0, "thp_collapse_alloc": 0, "thp_fault_alloc": 1, "unevictable": 0, "workingset_activate": 0, "workingset_nodereclaim": 0, "workingset_refault": 0 } failcnt: description: | Number of times memory usage hits limits. This field is Linux-specific and only supported on cgroups v1. It is omitted when using cgroups v2 and for Windows containers. type: "integer" format: "uint64" x-nullable: true example: 0 limit: description: | This field is Linux-specific and omitted for Windows containers. type: "integer" format: "uint64" x-nullable: true example: 8217579520 commitbytes: description: | Committed bytes. This field is Windows-specific and omitted for Linux containers. type: "integer" format: "uint64" x-nullable: true example: 0 commitpeakbytes: description: | Peak committed bytes. This field is Windows-specific and omitted for Linux containers. type: "integer" format: "uint64" x-nullable: true example: 0 privateworkingset: description: | Private working set. This field is Windows-specific and omitted for Linux containers. type: "integer" format: "uint64" x-nullable: true example: 0 ContainerNetworkStats: description: | Aggregates the network stats of one container type: "object" x-go-name: "NetworkStats" x-nullable: true properties: rx_bytes: description: | Bytes received. Windows and Linux. type: "integer" format: "uint64" example: 5338 rx_packets: description: | Packets received. Windows and Linux. type: "integer" format: "uint64" example: 36 rx_errors: description: | Received errors. Not used on Windows. This field is Linux-specific and always zero for Windows containers. type: "integer" format: "uint64" example: 0 rx_dropped: description: | Incoming packets dropped. Windows and Linux. type: "integer" format: "uint64" example: 0 tx_bytes: description: | Bytes sent. Windows and Linux. type: "integer" format: "uint64" example: 1200 tx_packets: description: | Packets sent. Windows and Linux. type: "integer" format: "uint64" example: 12 tx_errors: description: | Sent errors. Not used on Windows. This field is Linux-specific and always zero for Windows containers. type: "integer" format: "uint64" example: 0 tx_dropped: description: | Outgoing packets dropped. Windows and Linux. type: "integer" format: "uint64" example: 0 endpoint_id: description: | Endpoint ID. Not used on Linux. This field is Windows-specific and omitted for Linux containers. type: "string" x-nullable: true instance_id: description: | Instance ID. Not used on Linux. This field is Windows-specific and omitted for Linux containers. type: "string" x-nullable: true ContainerStorageStats: description: | StorageStats is the disk I/O stats for read/write on Windows. This type is Windows-specific and omitted for Linux containers. type: "object" x-go-name: "StorageStats" x-nullable: true properties: read_count_normalized: type: "integer" format: "uint64" x-nullable: true example: 7593984 read_size_bytes: type: "integer" format: "uint64" x-nullable: true example: 7593984 write_count_normalized: type: "integer" format: "uint64" x-nullable: true example: 7593984 write_size_bytes: type: "integer" format: "uint64" x-nullable: true example: 7593984 ContainerTopResponse: type: "object" x-go-name: "TopResponse" title: "ContainerTopResponse" description: |- Container "top" response. properties: Titles: description: "The ps column titles" type: "array" items: type: "string" example: Titles: - "UID" - "PID" - "PPID" - "C" - "STIME" - "TTY" - "TIME" - "CMD" Processes: description: |- Each process running in the container, where each process is an array of values corresponding to the titles. type: "array" items: type: "array" items: type: "string" example: Processes: - - "root" - "13642" - "882" - "0" - "17:03" - "pts/0" - "00:00:00" - "/bin/bash" - - "root" - "13735" - "13642" - "0" - "17:06" - "pts/0" - "00:00:00" - "sleep 10" ContainerWaitResponse: description: "OK response to ContainerWait operation" type: "object" x-go-name: "WaitResponse" title: "ContainerWaitResponse" required: [StatusCode] properties: StatusCode: description: "Exit code of the container" type: "integer" format: "int64" x-nullable: false Error: $ref: "#/definitions/ContainerWaitExitError" ContainerWaitExitError: description: "container waiting error, if any" type: "object" x-go-name: "WaitExitError" properties: Message: description: "Details of an error" type: "string" SystemVersion: type: "object" description: | Response of Engine API: GET "/version" properties: Platform: type: "object" required: [Name] properties: Name: type: "string" Components: type: "array" description: | Information about system components items: type: "object" x-go-name: ComponentVersion required: [Name, Version] properties: Name: description: | Name of the component type: "string" example: "Engine" Version: description: | Version of the component type: "string" x-nullable: false example: "27.0.1" Details: description: | Key/value pairs of strings with additional information about the component. These values are intended for informational purposes only, and their content is not defined, and not part of the API specification. These messages can be printed by the client as information to the user. type: "object" x-nullable: true Version: description: "The version of the daemon" type: "string" example: "27.0.1" ApiVersion: description: | The default (and highest) API version that is supported by the daemon type: "string" example: "1.47" MinAPIVersion: description: | The minimum API version that is supported by the daemon type: "string" example: "1.24" GitCommit: description: | The Git commit of the source code that was used to build the daemon type: "string" example: "48a66213fe" GoVersion: description: | The version Go used to compile the daemon, and the version of the Go runtime in use. type: "string" example: "go1.22.7" Os: description: | The operating system that the daemon is running on ("linux" or "windows") type: "string" example: "linux" Arch: description: | The architecture that the daemon is running on type: "string" example: "amd64" KernelVersion: description: | The kernel version (`uname -r`) that the daemon is running on. This field is omitted when empty. type: "string" example: "6.8.0-31-generic" Experimental: description: | Indicates if the daemon is started with experimental features enabled. This field is omitted when empty / false. type: "boolean" example: true BuildTime: description: | The date and time that the daemon was compiled. type: "string" example: "2020-06-22T15:49:27.000000000+00:00" SystemInfo: type: "object" properties: ID: description: | Unique identifier of the daemon.


> **Note**: The format of the ID itself is not part of the API, and > should not be considered stable. type: "string" example: "7TRN:IPZB:QYBB:VPBQ:UMPP:KARE:6ZNR:XE6T:7EWV:PKF4:ZOJD:TPYS" Containers: description: "Total number of containers on the host." type: "integer" example: 14 ContainersRunning: description: | Number of containers with status `"running"`. type: "integer" example: 3 ContainersPaused: description: | Number of containers with status `"paused"`. type: "integer" example: 1 ContainersStopped: description: | Number of containers with status `"stopped"`. type: "integer" example: 10 Images: description: | Total number of images on the host. Both _tagged_ and _untagged_ (dangling) images are counted. type: "integer" example: 508 Driver: description: "Name of the storage driver in use." type: "string" example: "overlay2" DriverStatus: description: | Information specific to the storage driver, provided as "label" / "value" pairs. This information is provided by the storage driver, and formatted in a way consistent with the output of `docker info` on the command line.


> **Note**: The information returned in this field, including the > formatting of values and labels, should not be considered stable, > and may change without notice. type: "array" items: type: "array" items: type: "string" example: - ["Backing Filesystem", "extfs"] - ["Supports d_type", "true"] - ["Native Overlay Diff", "true"] DockerRootDir: description: | Root directory of persistent Docker state. Defaults to `/var/lib/docker` on Linux, and `C:\ProgramData\docker` on Windows. type: "string" example: "/var/lib/docker" Plugins: $ref: "#/definitions/PluginsInfo" MemoryLimit: description: "Indicates if the host has memory limit support enabled." type: "boolean" example: true SwapLimit: description: "Indicates if the host has memory swap limit support enabled." type: "boolean" example: true KernelMemoryTCP: description: | Indicates if the host has kernel memory TCP limit support enabled. This field is omitted if not supported. Kernel memory TCP limits are not supported when using cgroups v2, which does not support the corresponding `memory.kmem.tcp.limit_in_bytes` cgroup. type: "boolean" example: true CpuCfsPeriod: description: | Indicates if CPU CFS(Completely Fair Scheduler) period is supported by the host. type: "boolean" example: true CpuCfsQuota: description: | Indicates if CPU CFS(Completely Fair Scheduler) quota is supported by the host. type: "boolean" example: true CPUShares: description: | Indicates if CPU Shares limiting is supported by the host. type: "boolean" example: true CPUSet: description: | Indicates if CPUsets (cpuset.cpus, cpuset.mems) are supported by the host. See [cpuset(7)](https://www.kernel.org/doc/Documentation/cgroup-v1/cpusets.txt) type: "boolean" example: true PidsLimit: description: "Indicates if the host kernel has PID limit support enabled." type: "boolean" example: true OomKillDisable: description: "Indicates if OOM killer disable is supported on the host." type: "boolean" IPv4Forwarding: description: "Indicates IPv4 forwarding is enabled." type: "boolean" example: true BridgeNfIptables: description: | Indicates if `bridge-nf-call-iptables` is available on the host when the daemon was started.


> **Deprecated**: netfilter module is now loaded on-demand and no longer > during daemon startup, making this field obsolete. This field is always > `false` and will be removed in a API v1.49. type: "boolean" example: false BridgeNfIp6tables: description: | Indicates if `bridge-nf-call-ip6tables` is available on the host.


> **Deprecated**: netfilter module is now loaded on-demand, and no longer > during daemon startup, making this field obsolete. This field is always > `false` and will be removed in a API v1.49. type: "boolean" example: false Debug: description: | Indicates if the daemon is running in debug-mode / with debug-level logging enabled. type: "boolean" example: true NFd: description: | The total number of file Descriptors in use by the daemon process. This information is only returned if debug-mode is enabled. type: "integer" example: 64 NGoroutines: description: | The number of goroutines that currently exist. This information is only returned if debug-mode is enabled. type: "integer" example: 174 SystemTime: description: | Current system-time in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. type: "string" example: "2017-08-08T20:28:29.06202363Z" LoggingDriver: description: | The logging driver to use as a default for new containers. type: "string" CgroupDriver: description: | The driver to use for managing cgroups. type: "string" enum: ["cgroupfs", "systemd", "none"] default: "cgroupfs" example: "cgroupfs" CgroupVersion: description: | The version of the cgroup. type: "string" enum: ["1", "2"] default: "1" example: "1" NEventsListener: description: "Number of event listeners subscribed." type: "integer" example: 30 KernelVersion: description: | Kernel version of the host. On Linux, this information obtained from `uname`. On Windows this information is queried from the HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ registry value, for example _"10.0 14393 (14393.1198.amd64fre.rs1_release_sec.170427-1353)"_. type: "string" example: "6.8.0-31-generic" OperatingSystem: description: | Name of the host's operating system, for example: "Ubuntu 24.04 LTS" or "Windows Server 2016 Datacenter" type: "string" example: "Ubuntu 24.04 LTS" OSVersion: description: | Version of the host's operating system


> **Note**: The information returned in this field, including its > very existence, and the formatting of values, should not be considered > stable, and may change without notice. type: "string" example: "24.04" OSType: description: | Generic type of the operating system of the host, as returned by the Go runtime (`GOOS`). Currently returned values are "linux" and "windows". A full list of possible values can be found in the [Go documentation](https://go.dev/doc/install/source#environment). type: "string" example: "linux" Architecture: description: | Hardware architecture of the host, as returned by the Go runtime (`GOARCH`). A full list of possible values can be found in the [Go documentation](https://go.dev/doc/install/source#environment). type: "string" example: "x86_64" NCPU: description: | The number of logical CPUs usable by the daemon. The number of available CPUs is checked by querying the operating system when the daemon starts. Changes to operating system CPU allocation after the daemon is started are not reflected. type: "integer" example: 4 MemTotal: description: | Total amount of physical memory available on the host, in bytes. type: "integer" format: "int64" example: 2095882240 IndexServerAddress: description: | Address / URL of the index server that is used for image search, and as a default for user authentication for Docker Hub and Docker Cloud. default: "https://index.docker.io/v1/" type: "string" example: "https://index.docker.io/v1/" RegistryConfig: $ref: "#/definitions/RegistryServiceConfig" GenericResources: $ref: "#/definitions/GenericResources" HttpProxy: description: | HTTP-proxy configured for the daemon. This value is obtained from the [`HTTP_PROXY`](https://www.gnu.org/software/wget/manual/html_node/Proxies.html) environment variable. Credentials ([user info component](https://tools.ietf.org/html/rfc3986#section-3.2.1)) in the proxy URL are masked in the API response. Containers do not automatically inherit this configuration. type: "string" example: "http://xxxxx:xxxxx@proxy.corp.example.com:8080" HttpsProxy: description: | HTTPS-proxy configured for the daemon. This value is obtained from the [`HTTPS_PROXY`](https://www.gnu.org/software/wget/manual/html_node/Proxies.html) environment variable. Credentials ([user info component](https://tools.ietf.org/html/rfc3986#section-3.2.1)) in the proxy URL are masked in the API response. Containers do not automatically inherit this configuration. type: "string" example: "https://xxxxx:xxxxx@proxy.corp.example.com:4443" NoProxy: description: | Comma-separated list of domain extensions for which no proxy should be used. This value is obtained from the [`NO_PROXY`](https://www.gnu.org/software/wget/manual/html_node/Proxies.html) environment variable. Containers do not automatically inherit this configuration. type: "string" example: "*.local, 169.254/16" Name: description: "Hostname of the host." type: "string" example: "node5.corp.example.com" Labels: description: | User-defined labels (key/value metadata) as set on the daemon.


> **Note**: When part of a Swarm, nodes can both have _daemon_ labels, > set through the daemon configuration, and _node_ labels, set from a > manager node in the Swarm. Node labels are not included in this > field. Node labels can be retrieved using the `/nodes/(id)` endpoint > on a manager node in the Swarm. type: "array" items: type: "string" example: ["storage=ssd", "production"] ExperimentalBuild: description: | Indicates if experimental features are enabled on the daemon. type: "boolean" example: true ServerVersion: description: | Version string of the daemon. type: "string" example: "27.0.1" Runtimes: description: | List of [OCI compliant](https://github.com/opencontainers/runtime-spec) runtimes configured on the daemon. Keys hold the "name" used to reference the runtime. The Docker daemon relies on an OCI compliant runtime (invoked via the `containerd` daemon) as its interface to the Linux kernel namespaces, cgroups, and SELinux. The default runtime is `runc`, and automatically configured. Additional runtimes can be configured by the user and will be listed here. type: "object" additionalProperties: $ref: "#/definitions/Runtime" default: runc: path: "runc" example: runc: path: "runc" runc-master: path: "/go/bin/runc" custom: path: "/usr/local/bin/my-oci-runtime" runtimeArgs: ["--debug", "--systemd-cgroup=false"] DefaultRuntime: description: | Name of the default OCI runtime that is used when starting containers. The default can be overridden per-container at create time. type: "string" default: "runc" example: "runc" Swarm: $ref: "#/definitions/SwarmInfo" LiveRestoreEnabled: description: | Indicates if live restore is enabled. If enabled, containers are kept running when the daemon is shutdown or upon daemon start if running containers are detected. type: "boolean" default: false example: false Isolation: description: | Represents the isolation technology to use as a default for containers. The supported values are platform-specific. If no isolation value is specified on daemon start, on Windows client, the default is `hyperv`, and on Windows server, the default is `process`. This option is currently not used on other platforms. default: "default" type: "string" enum: - "default" - "hyperv" - "process" - "" InitBinary: description: | Name and, optional, path of the `docker-init` binary. If the path is omitted, the daemon searches the host's `$PATH` for the binary and uses the first result. type: "string" example: "docker-init" ContainerdCommit: $ref: "#/definitions/Commit" RuncCommit: $ref: "#/definitions/Commit" InitCommit: $ref: "#/definitions/Commit" SecurityOptions: description: | List of security features that are enabled on the daemon, such as apparmor, seccomp, SELinux, user-namespaces (userns), rootless and no-new-privileges. Additional configuration options for each security feature may be present, and are included as a comma-separated list of key/value pairs. type: "array" items: type: "string" example: - "name=apparmor" - "name=seccomp,profile=default" - "name=selinux" - "name=userns" - "name=rootless" ProductLicense: description: | Reports a summary of the product license on the daemon. If a commercial license has been applied to the daemon, information such as number of nodes, and expiration are included. type: "string" example: "Community Engine" DefaultAddressPools: description: | List of custom default address pools for local networks, which can be specified in the daemon.json file or dockerd option. Example: a Base "10.10.0.0/16" with Size 24 will define the set of 256 10.10.[0-255].0/24 address pools. type: "array" items: type: "object" properties: Base: description: "The network address in CIDR format" type: "string" example: "10.10.0.0/16" Size: description: "The network pool size" type: "integer" example: "24" Warnings: description: | List of warnings / informational messages about missing features, or issues related to the daemon configuration. These messages can be printed by the client as information to the user. type: "array" items: type: "string" example: - "WARNING: No memory limit support" CDISpecDirs: description: | List of directories where (Container Device Interface) CDI specifications are located. These specifications define vendor-specific modifications to an OCI runtime specification for a container being created. An empty list indicates that CDI device injection is disabled. Note that since using CDI device injection requires the daemon to have experimental enabled. For non-experimental daemons an empty list will always be returned. type: "array" items: type: "string" example: - "/etc/cdi" - "/var/run/cdi" Containerd: $ref: "#/definitions/ContainerdInfo" ContainerdInfo: description: | Information for connecting to the containerd instance that is used by the daemon. This is included for debugging purposes only. type: "object" x-nullable: true properties: Address: description: "The address of the containerd socket." type: "string" example: "/run/containerd/containerd.sock" Namespaces: description: | The namespaces that the daemon uses for running containers and plugins in containerd. These namespaces can be configured in the daemon configuration, and are considered to be used exclusively by the daemon, Tampering with the containerd instance may cause unexpected behavior. As these namespaces are considered to be exclusively accessed by the daemon, it is not recommended to change these values, or to change them to a value that is used by other systems, such as cri-containerd. type: "object" properties: Containers: description: | The default containerd namespace used for containers managed by the daemon. The default namespace for containers is "moby", but will be suffixed with the `.` of the remapped `root` if user-namespaces are enabled and the containerd image-store is used. type: "string" default: "moby" example: "moby" Plugins: description: | The default containerd namespace used for plugins managed by the daemon. The default namespace for plugins is "plugins.moby", but will be suffixed with the `.` of the remapped `root` if user-namespaces are enabled and the containerd image-store is used. type: "string" default: "plugins.moby" example: "plugins.moby" # PluginsInfo is a temp struct holding Plugins name # registered with docker daemon. It is used by Info struct PluginsInfo: description: | Available plugins per type.


> **Note**: Only unmanaged (V1) plugins are included in this list. > V1 plugins are "lazily" loaded, and are not returned in this list > if there is no resource using the plugin. type: "object" properties: Volume: description: "Names of available volume-drivers, and network-driver plugins." type: "array" items: type: "string" example: ["local"] Network: description: "Names of available network-drivers, and network-driver plugins." type: "array" items: type: "string" example: ["bridge", "host", "ipvlan", "macvlan", "null", "overlay"] Authorization: description: "Names of available authorization plugins." type: "array" items: type: "string" example: ["img-authz-plugin", "hbm"] Log: description: "Names of available logging-drivers, and logging-driver plugins." type: "array" items: type: "string" example: ["awslogs", "fluentd", "gcplogs", "gelf", "journald", "json-file", "splunk", "syslog"] RegistryServiceConfig: description: | RegistryServiceConfig stores daemon registry services configuration. type: "object" x-nullable: true properties: AllowNondistributableArtifactsCIDRs: description: | List of IP ranges to which nondistributable artifacts can be pushed, using the CIDR syntax [RFC 4632](https://tools.ietf.org/html/4632).


> **Deprecated**: Pushing nondistributable artifacts is now always enabled > and this field is always `null`. This field will be removed in a API v1.49. type: "array" items: type: "string" example: [] AllowNondistributableArtifactsHostnames: description: | List of registry hostnames to which nondistributable artifacts can be pushed, using the format `[:]` or `[:]`.


> **Deprecated**: Pushing nondistributable artifacts is now always enabled > and this field is always `null`. This field will be removed in a API v1.49. type: "array" items: type: "string" example: [] InsecureRegistryCIDRs: description: | List of IP ranges of insecure registries, using the CIDR syntax ([RFC 4632](https://tools.ietf.org/html/4632)). Insecure registries accept un-encrypted (HTTP) and/or untrusted (HTTPS with certificates from unknown CAs) communication. By default, local registries (`::1/128` and `127.0.0.0/8`) are configured as insecure. All other registries are secure. Communicating with an insecure registry is not possible if the daemon assumes that registry is secure. This configuration override this behavior, insecure communication with registries whose resolved IP address is within the subnet described by the CIDR syntax. Registries can also be marked insecure by hostname. Those registries are listed under `IndexConfigs` and have their `Secure` field set to `false`. > **Warning**: Using this option can be useful when running a local > registry, but introduces security vulnerabilities. This option > should therefore ONLY be used for testing purposes. For increased > security, users should add their CA to their system's list of trusted > CAs instead of enabling this option. type: "array" items: type: "string" example: ["::1/128", "127.0.0.0/8"] IndexConfigs: type: "object" additionalProperties: $ref: "#/definitions/IndexInfo" example: "127.0.0.1:5000": "Name": "127.0.0.1:5000" "Mirrors": [] "Secure": false "Official": false "[2001:db8:a0b:12f0::1]:80": "Name": "[2001:db8:a0b:12f0::1]:80" "Mirrors": [] "Secure": false "Official": false "docker.io": Name: "docker.io" Mirrors: ["https://hub-mirror.corp.example.com:5000/"] Secure: true Official: true "registry.internal.corp.example.com:3000": Name: "registry.internal.corp.example.com:3000" Mirrors: [] Secure: false Official: false Mirrors: description: | List of registry URLs that act as a mirror for the official (`docker.io`) registry. type: "array" items: type: "string" example: - "https://hub-mirror.corp.example.com:5000/" - "https://[2001:db8:a0b:12f0::1]/" IndexInfo: description: IndexInfo contains information about a registry. type: "object" x-nullable: true properties: Name: description: | Name of the registry, such as "docker.io". type: "string" example: "docker.io" Mirrors: description: | List of mirrors, expressed as URIs. type: "array" items: type: "string" example: - "https://hub-mirror.corp.example.com:5000/" - "https://registry-2.docker.io/" - "https://registry-3.docker.io/" Secure: description: | Indicates if the registry is part of the list of insecure registries. If `false`, the registry is insecure. Insecure registries accept un-encrypted (HTTP) and/or untrusted (HTTPS with certificates from unknown CAs) communication. > **Warning**: Insecure registries can be useful when running a local > registry. However, because its use creates security vulnerabilities > it should ONLY be enabled for testing purposes. For increased > security, users should add their CA to their system's list of > trusted CAs instead of enabling this option. type: "boolean" example: true Official: description: | Indicates whether this is an official registry (i.e., Docker Hub / docker.io) type: "boolean" example: true Runtime: description: | Runtime describes an [OCI compliant](https://github.com/opencontainers/runtime-spec) runtime. The runtime is invoked by the daemon via the `containerd` daemon. OCI runtimes act as an interface to the Linux kernel namespaces, cgroups, and SELinux. type: "object" properties: path: description: | Name and, optional, path, of the OCI executable binary. If the path is omitted, the daemon searches the host's `$PATH` for the binary and uses the first result. type: "string" example: "/usr/local/bin/my-oci-runtime" runtimeArgs: description: | List of command-line arguments to pass to the runtime when invoked. type: "array" x-nullable: true items: type: "string" example: ["--debug", "--systemd-cgroup=false"] status: description: | Information specific to the runtime. While this API specification does not define data provided by runtimes, the following well-known properties may be provided by runtimes: `org.opencontainers.runtime-spec.features`: features structure as defined in the [OCI Runtime Specification](https://github.com/opencontainers/runtime-spec/blob/main/features.md), in a JSON string representation.


> **Note**: The information returned in this field, including the > formatting of values and labels, should not be considered stable, > and may change without notice. type: "object" x-nullable: true additionalProperties: type: "string" example: "org.opencontainers.runtime-spec.features": "{\"ociVersionMin\":\"1.0.0\",\"ociVersionMax\":\"1.1.0\",\"...\":\"...\"}" Commit: description: | Commit holds the Git-commit (SHA1) that a binary was built from, as reported in the version-string of external tools, such as `containerd`, or `runC`. type: "object" properties: ID: description: "Actual commit ID of external tool." type: "string" example: "cfb82a876ecc11b5ca0977d1733adbe58599088a" Expected: description: | Commit ID of external tool expected by dockerd as set at build time. **Deprecated**: This field is deprecated and will be omitted in a API v1.49. type: "string" example: "2d41c047c83e09a6d61d464906feb2a2f3c52aa4" SwarmInfo: description: | Represents generic information about swarm. type: "object" properties: NodeID: description: "Unique identifier of for this node in the swarm." type: "string" default: "" example: "k67qz4598weg5unwwffg6z1m1" NodeAddr: description: | IP address at which this node can be reached by other nodes in the swarm. type: "string" default: "" example: "10.0.0.46" LocalNodeState: $ref: "#/definitions/LocalNodeState" ControlAvailable: type: "boolean" default: false example: true Error: type: "string" default: "" RemoteManagers: description: | List of ID's and addresses of other managers in the swarm. type: "array" default: null x-nullable: true items: $ref: "#/definitions/PeerNode" example: - NodeID: "71izy0goik036k48jg985xnds" Addr: "10.0.0.158:2377" - NodeID: "79y6h1o4gv8n120drcprv5nmc" Addr: "10.0.0.159:2377" - NodeID: "k67qz4598weg5unwwffg6z1m1" Addr: "10.0.0.46:2377" Nodes: description: "Total number of nodes in the swarm." type: "integer" x-nullable: true example: 4 Managers: description: "Total number of managers in the swarm." type: "integer" x-nullable: true example: 3 Cluster: $ref: "#/definitions/ClusterInfo" LocalNodeState: description: "Current local status of this node." type: "string" default: "" enum: - "" - "inactive" - "pending" - "active" - "error" - "locked" example: "active" PeerNode: description: "Represents a peer-node in the swarm" type: "object" properties: NodeID: description: "Unique identifier of for this node in the swarm." type: "string" Addr: description: | IP address and ports at which this node can be reached. type: "string" NetworkAttachmentConfig: description: | Specifies how a service should be attached to a particular network. type: "object" properties: Target: description: | The target network for attachment. Must be a network name or ID. type: "string" Aliases: description: | Discoverable alternate names for the service on this network. type: "array" items: type: "string" DriverOpts: description: | Driver attachment options for the network target. type: "object" additionalProperties: type: "string" EventActor: description: | Actor describes something that generates events, like a container, network, or a volume. type: "object" properties: ID: description: "The ID of the object emitting the event" type: "string" example: "ede54ee1afda366ab42f824e8a5ffd195155d853ceaec74a927f249ea270c743" Attributes: description: | Various key/value attributes of the object, depending on its type. type: "object" additionalProperties: type: "string" example: com.example.some-label: "some-label-value" image: "alpine:latest" name: "my-container" EventMessage: description: | EventMessage represents the information an event contains. type: "object" title: "SystemEventsResponse" properties: Type: description: "The type of object emitting the event" type: "string" enum: ["builder", "config", "container", "daemon", "image", "network", "node", "plugin", "secret", "service", "volume"] example: "container" Action: description: "The type of event" type: "string" example: "create" Actor: $ref: "#/definitions/EventActor" scope: description: | Scope of the event. Engine events are `local` scope. Cluster (Swarm) events are `swarm` scope. type: "string" enum: ["local", "swarm"] time: description: "Timestamp of event" type: "integer" format: "int64" example: 1629574695 timeNano: description: "Timestamp of event, with nanosecond accuracy" type: "integer" format: "int64" example: 1629574695515050031 OCIDescriptor: type: "object" x-go-name: Descriptor description: | A descriptor struct containing digest, media type, and size, as defined in the [OCI Content Descriptors Specification](https://github.com/opencontainers/image-spec/blob/v1.0.1/descriptor.md). properties: mediaType: description: | The media type of the object this schema refers to. type: "string" example: "application/vnd.oci.image.manifest.v1+json" digest: description: | The digest of the targeted content. type: "string" example: "sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96" size: description: | The size in bytes of the blob. type: "integer" format: "int64" example: 424 urls: description: |- List of URLs from which this object MAY be downloaded. type: "array" items: type: "string" format: "uri" x-nullable: true annotations: description: |- Arbitrary metadata relating to the targeted content. type: "object" x-nullable: true additionalProperties: type: "string" example: "com.docker.official-images.bashbrew.arch": "amd64" "org.opencontainers.image.base.digest": "sha256:0d0ef5c914d3ea700147da1bd050c59edb8bb12ca312f3800b29d7c8087eabd8" "org.opencontainers.image.base.name": "scratch" "org.opencontainers.image.created": "2025-01-27T00:00:00Z" "org.opencontainers.image.revision": "9fabb4bad5138435b01857e2fe9363e2dc5f6a79" "org.opencontainers.image.source": "https://git.launchpad.net/cloud-images/+oci/ubuntu-base" "org.opencontainers.image.url": "https://hub.docker.com/_/ubuntu" "org.opencontainers.image.version": "24.04" data: type: string x-nullable: true description: |- Data is an embedding of the targeted content. This is encoded as a base64 string when marshalled to JSON (automatically, by encoding/json). If present, Data can be used directly to avoid fetching the targeted content. example: null platform: $ref: "#/definitions/OCIPlatform" artifactType: description: |- ArtifactType is the IANA media type of this artifact. type: "string" x-nullable: true example: null OCIPlatform: type: "object" x-go-name: Platform x-nullable: true description: | Describes the platform which the image in the manifest runs on, as defined in the [OCI Image Index Specification](https://github.com/opencontainers/image-spec/blob/v1.0.1/image-index.md). properties: architecture: description: | The CPU architecture, for example `amd64` or `ppc64`. type: "string" example: "arm" os: description: | The operating system, for example `linux` or `windows`. type: "string" example: "windows" os.version: description: | Optional field specifying the operating system version, for example on Windows `10.0.19041.1165`. type: "string" example: "10.0.19041.1165" os.features: description: | Optional field specifying an array of strings, each listing a required OS feature (for example on Windows `win32k`). type: "array" items: type: "string" example: - "win32k" variant: description: | Optional field specifying a variant of the CPU, for example `v7` to specify ARMv7 when architecture is `arm`. type: "string" example: "v7" DistributionInspect: type: "object" x-go-name: DistributionInspect title: "DistributionInspectResponse" required: [Descriptor, Platforms] description: | Describes the result obtained from contacting the registry to retrieve image metadata. properties: Descriptor: $ref: "#/definitions/OCIDescriptor" Platforms: type: "array" description: | An array containing all platforms supported by the image. items: $ref: "#/definitions/OCIPlatform" ClusterVolume: type: "object" description: | Options and information specific to, and only present on, Swarm CSI cluster volumes. properties: ID: type: "string" description: | The Swarm ID of this volume. Because cluster volumes are Swarm objects, they have an ID, unlike non-cluster volumes. This ID can be used to refer to the Volume instead of the name. Version: $ref: "#/definitions/ObjectVersion" CreatedAt: type: "string" format: "dateTime" UpdatedAt: type: "string" format: "dateTime" Spec: $ref: "#/definitions/ClusterVolumeSpec" Info: type: "object" description: | Information about the global status of the volume. properties: CapacityBytes: type: "integer" format: "int64" description: | The capacity of the volume in bytes. A value of 0 indicates that the capacity is unknown. VolumeContext: type: "object" description: | A map of strings to strings returned from the storage plugin when the volume is created. additionalProperties: type: "string" VolumeID: type: "string" description: | The ID of the volume as returned by the CSI storage plugin. This is distinct from the volume's ID as provided by Docker. This ID is never used by the user when communicating with Docker to refer to this volume. If the ID is blank, then the Volume has not been successfully created in the plugin yet. AccessibleTopology: type: "array" description: | The topology this volume is actually accessible from. items: $ref: "#/definitions/Topology" PublishStatus: type: "array" description: | The status of the volume as it pertains to its publishing and use on specific nodes items: type: "object" properties: NodeID: type: "string" description: | The ID of the Swarm node the volume is published on. State: type: "string" description: | The published state of the volume. * `pending-publish` The volume should be published to this node, but the call to the controller plugin to do so has not yet been successfully completed. * `published` The volume is published successfully to the node. * `pending-node-unpublish` The volume should be unpublished from the node, and the manager is awaiting confirmation from the worker that it has done so. * `pending-controller-unpublish` The volume is successfully unpublished from the node, but has not yet been successfully unpublished on the controller. enum: - "pending-publish" - "published" - "pending-node-unpublish" - "pending-controller-unpublish" PublishContext: type: "object" description: | A map of strings to strings returned by the CSI controller plugin when a volume is published. additionalProperties: type: "string" ClusterVolumeSpec: type: "object" description: | Cluster-specific options used to create the volume. properties: Group: type: "string" description: | Group defines the volume group of this volume. Volumes belonging to the same group can be referred to by group name when creating Services. Referring to a volume by group instructs Swarm to treat volumes in that group interchangeably for the purpose of scheduling. Volumes with an empty string for a group technically all belong to the same, emptystring group. AccessMode: type: "object" description: | Defines how the volume is used by tasks. properties: Scope: type: "string" description: | The set of nodes this volume can be used on at one time. - `single` The volume may only be scheduled to one node at a time. - `multi` the volume may be scheduled to any supported number of nodes at a time. default: "single" enum: ["single", "multi"] x-nullable: false Sharing: type: "string" description: | The number and way that different tasks can use this volume at one time. - `none` The volume may only be used by one task at a time. - `readonly` The volume may be used by any number of tasks, but they all must mount the volume as readonly - `onewriter` The volume may be used by any number of tasks, but only one may mount it as read/write. - `all` The volume may have any number of readers and writers. default: "none" enum: ["none", "readonly", "onewriter", "all"] x-nullable: false MountVolume: type: "object" description: | Options for using this volume as a Mount-type volume. Either MountVolume or BlockVolume, but not both, must be present. properties: FsType: type: "string" description: | Specifies the filesystem type for the mount volume. Optional. MountFlags: type: "array" description: | Flags to pass when mounting the volume. Optional. items: type: "string" BlockVolume: type: "object" description: | Options for using this volume as a Block-type volume. Intentionally empty. Secrets: type: "array" description: | Swarm Secrets that are passed to the CSI storage plugin when operating on this volume. items: type: "object" description: | One cluster volume secret entry. Defines a key-value pair that is passed to the plugin. properties: Key: type: "string" description: | Key is the name of the key of the key-value pair passed to the plugin. Secret: type: "string" description: | Secret is the swarm Secret object from which to read data. This can be a Secret name or ID. The Secret data is retrieved by swarm and used as the value of the key-value pair passed to the plugin. AccessibilityRequirements: type: "object" description: | Requirements for the accessible topology of the volume. These fields are optional. For an in-depth description of what these fields mean, see the CSI specification. properties: Requisite: type: "array" description: | A list of required topologies, at least one of which the volume must be accessible from. items: $ref: "#/definitions/Topology" Preferred: type: "array" description: | A list of topologies that the volume should attempt to be provisioned in. items: $ref: "#/definitions/Topology" CapacityRange: type: "object" description: | The desired capacity that the volume should be created with. If empty, the plugin will decide the capacity. properties: RequiredBytes: type: "integer" format: "int64" description: | The volume must be at least this big. The value of 0 indicates an unspecified minimum LimitBytes: type: "integer" format: "int64" description: | The volume must not be bigger than this. The value of 0 indicates an unspecified maximum. Availability: type: "string" description: | The availability of the volume for use in tasks. - `active` The volume is fully available for scheduling on the cluster - `pause` No new workloads should use the volume, but existing workloads are not stopped. - `drain` All workloads using this volume should be stopped and rescheduled, and no new ones should be started. default: "active" x-nullable: false enum: - "active" - "pause" - "drain" Topology: description: | A map of topological domains to topological segments. For in depth details, see documentation for the Topology object in the CSI specification. type: "object" additionalProperties: type: "string" ImageManifestSummary: x-go-name: "ManifestSummary" description: | ImageManifestSummary represents a summary of an image manifest. type: "object" required: ["ID", "Descriptor", "Available", "Size", "Kind"] properties: ID: description: | ID is the content-addressable ID of an image and is the same as the digest of the image manifest. type: "string" example: "sha256:95869fbcf224d947ace8d61d0e931d49e31bb7fc67fffbbe9c3198c33aa8e93f" Descriptor: $ref: "#/definitions/OCIDescriptor" Available: description: Indicates whether all the child content (image config, layers) is fully available locally. type: "boolean" example: true Size: type: "object" x-nullable: false required: ["Content", "Total"] properties: Total: type: "integer" format: "int64" example: 8213251 description: | Total is the total size (in bytes) of all the locally present data (both distributable and non-distributable) that's related to this manifest and its children. This equal to the sum of [Content] size AND all the sizes in the [Size] struct present in the Kind-specific data struct. For example, for an image kind (Kind == "image") this would include the size of the image content and unpacked image snapshots ([Size.Content] + [ImageData.Size.Unpacked]). Content: description: | Content is the size (in bytes) of all the locally present content in the content store (e.g. image config, layers) referenced by this manifest and its children. This only includes blobs in the content store. type: "integer" format: "int64" example: 3987495 Kind: type: "string" example: "image" enum: - "image" - "attestation" - "unknown" description: | The kind of the manifest. kind | description -------------|----------------------------------------------------------- image | Image manifest that can be used to start a container. attestation | Attestation manifest produced by the Buildkit builder for a specific image manifest. ImageData: description: | The image data for the image manifest. This field is only populated when Kind is "image". type: "object" x-nullable: true x-omitempty: true required: ["Platform", "Containers", "Size", "UnpackedSize"] properties: Platform: $ref: "#/definitions/OCIPlatform" description: | OCI platform of the image. This will be the platform specified in the manifest descriptor from the index/manifest list. If it's not available, it will be obtained from the image config. Containers: description: | The IDs of the containers that are using this image. type: "array" items: type: "string" example: ["ede54ee1fda366ab42f824e8a5ffd195155d853ceaec74a927f249ea270c7430", "abadbce344c096744d8d6071a90d474d28af8f1034b5ea9fb03c3f4bfc6d005e"] Size: type: "object" x-nullable: false required: ["Unpacked"] properties: Unpacked: type: "integer" format: "int64" example: 3987495 description: | Unpacked is the size (in bytes) of the locally unpacked (uncompressed) image content that's directly usable by the containers running this image. It's independent of the distributable content - e.g. the image might still have an unpacked data that's still used by some container even when the distributable/compressed content is already gone. AttestationData: description: | The image data for the attestation manifest. This field is only populated when Kind is "attestation". type: "object" x-nullable: true x-omitempty: true required: ["For"] properties: For: description: | The digest of the image manifest that this attestation is for. type: "string" example: "sha256:95869fbcf224d947ace8d61d0e931d49e31bb7fc67fffbbe9c3198c33aa8e93f" paths: /containers/json: get: summary: "List containers" description: | Returns a list of containers. For details on the format, see the [inspect endpoint](#operation/ContainerInspect). Note that it uses a different, smaller representation of a container than inspecting a single container. For example, the list of linked containers is not propagated . operationId: "ContainerList" produces: - "application/json" parameters: - name: "all" in: "query" description: | Return all containers. By default, only running containers are shown. type: "boolean" default: false - name: "limit" in: "query" description: | Return this number of most recently created containers, including non-running ones. type: "integer" - name: "size" in: "query" description: | Return the size of container as fields `SizeRw` and `SizeRootFs`. type: "boolean" default: false - name: "filters" in: "query" description: | Filters to process on the container list, encoded as JSON (a `map[string][]string`). For example, `{"status": ["paused"]}` will only return paused containers. Available filters: - `ancestor`=(`[:]`, ``, or ``) - `before`=(`` or ``) - `expose`=(`[/]`|`/[]`) - `exited=` containers with exit code of `` - `health`=(`starting`|`healthy`|`unhealthy`|`none`) - `id=` a container's ID - `isolation=`(`default`|`process`|`hyperv`) (Windows daemon only) - `is-task=`(`true`|`false`) - `label=key` or `label="key=value"` of a container label - `name=` a container's name - `network`=(`` or ``) - `publish`=(`[/]`|`/[]`) - `since`=(`` or ``) - `status=`(`created`|`restarting`|`running`|`removing`|`paused`|`exited`|`dead`) - `volume`=(`` or ``) type: "string" responses: 200: description: "no error" schema: type: "array" items: $ref: "#/definitions/ContainerSummary" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" tags: ["Container"] /containers/create: post: summary: "Create a container" operationId: "ContainerCreate" consumes: - "application/json" - "application/octet-stream" produces: - "application/json" parameters: - name: "name" in: "query" description: | Assign the specified name to the container. Must match `/?[a-zA-Z0-9][a-zA-Z0-9_.-]+`. type: "string" pattern: "^/?[a-zA-Z0-9][a-zA-Z0-9_.-]+$" - name: "platform" in: "query" description: | Platform in the format `os[/arch[/variant]]` used for image lookup. When specified, the daemon checks if the requested image is present in the local image cache with the given OS and Architecture, and otherwise returns a `404` status. If the option is not set, the host's native OS and Architecture are used to look up the image in the image cache. However, if no platform is passed and the given image does exist in the local image cache, but its OS or architecture does not match, the container is created with the available image, and a warning is added to the `Warnings` field in the response, for example; WARNING: The requested image's platform (linux/arm64/v8) does not match the detected host platform (linux/amd64) and no specific platform was requested type: "string" default: "" - name: "body" in: "body" description: "Container to create" schema: allOf: - $ref: "#/definitions/ContainerConfig" - type: "object" properties: HostConfig: $ref: "#/definitions/HostConfig" NetworkingConfig: $ref: "#/definitions/NetworkingConfig" example: Hostname: "" Domainname: "" User: "" AttachStdin: false AttachStdout: true AttachStderr: true Tty: false OpenStdin: false StdinOnce: false Env: - "FOO=bar" - "BAZ=quux" Cmd: - "date" Entrypoint: "" Image: "ubuntu" Labels: com.example.vendor: "Acme" com.example.license: "GPL" com.example.version: "1.0" Volumes: /volumes/data: {} WorkingDir: "" NetworkDisabled: false MacAddress: "12:34:56:78:9a:bc" ExposedPorts: 22/tcp: {} StopSignal: "SIGTERM" StopTimeout: 10 HostConfig: Binds: - "/tmp:/tmp" Links: - "redis3:redis" Memory: 0 MemorySwap: 0 MemoryReservation: 0 NanoCpus: 500000 CpuPercent: 80 CpuShares: 512 CpuPeriod: 100000 CpuRealtimePeriod: 1000000 CpuRealtimeRuntime: 10000 CpuQuota: 50000 CpusetCpus: "0,1" CpusetMems: "0,1" MaximumIOps: 0 MaximumIOBps: 0 BlkioWeight: 300 BlkioWeightDevice: - {} BlkioDeviceReadBps: - {} BlkioDeviceReadIOps: - {} BlkioDeviceWriteBps: - {} BlkioDeviceWriteIOps: - {} DeviceRequests: - Driver: "nvidia" Count: -1 DeviceIDs": ["0", "1", "GPU-fef8089b-4820-abfc-e83e-94318197576e"] Capabilities: [["gpu", "nvidia", "compute"]] Options: property1: "string" property2: "string" MemorySwappiness: 60 OomKillDisable: false OomScoreAdj: 500 PidMode: "" PidsLimit: 0 PortBindings: 22/tcp: - HostPort: "11022" PublishAllPorts: false Privileged: false ReadonlyRootfs: false Dns: - "8.8.8.8" DnsOptions: - "" DnsSearch: - "" VolumesFrom: - "parent" - "other:ro" CapAdd: - "NET_ADMIN" CapDrop: - "MKNOD" GroupAdd: - "newgroup" RestartPolicy: Name: "" MaximumRetryCount: 0 AutoRemove: true NetworkMode: "bridge" Devices: [] Ulimits: - {} LogConfig: Type: "json-file" Config: {} SecurityOpt: [] StorageOpt: {} CgroupParent: "" VolumeDriver: "" ShmSize: 67108864 NetworkingConfig: EndpointsConfig: isolated_nw: IPAMConfig: IPv4Address: "172.20.30.33" IPv6Address: "2001:db8:abcd::3033" LinkLocalIPs: - "169.254.34.68" - "fe80::3468" Links: - "container_1" - "container_2" Aliases: - "server_x" - "server_y" database_nw: {} required: true responses: 201: description: "Container created successfully" schema: $ref: "#/definitions/ContainerCreateResponse" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 404: description: "no such image" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such image: c2ada9df5af8" 409: description: "conflict" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" tags: ["Container"] /containers/{id}/json: get: summary: "Inspect a container" description: "Return low-level information about a container." operationId: "ContainerInspect" produces: - "application/json" responses: 200: description: "no error" schema: $ref: "#/definitions/ContainerInspectResponse" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "size" in: "query" type: "boolean" default: false description: "Return the size of container as fields `SizeRw` and `SizeRootFs`" tags: ["Container"] /containers/{id}/top: get: summary: "List processes running inside a container" description: | On Unix systems, this is done by running the `ps` command. This endpoint is not supported on Windows. operationId: "ContainerTop" responses: 200: description: "no error" schema: $ref: "#/definitions/ContainerTopResponse" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "ps_args" in: "query" description: "The arguments to pass to `ps`. For example, `aux`" type: "string" default: "-ef" tags: ["Container"] /containers/{id}/logs: get: summary: "Get container logs" description: | Get `stdout` and `stderr` logs from a container. Note: This endpoint works only for containers with the `json-file` or `journald` logging driver. produces: - "application/vnd.docker.raw-stream" - "application/vnd.docker.multiplexed-stream" operationId: "ContainerLogs" responses: 200: description: | logs returned as a stream in response body. For the stream format, [see the documentation for the attach endpoint](#operation/ContainerAttach). Note that unlike the attach endpoint, the logs endpoint does not upgrade the connection and does not set Content-Type. schema: type: "string" format: "binary" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "follow" in: "query" description: "Keep connection after returning logs." type: "boolean" default: false - name: "stdout" in: "query" description: "Return logs from `stdout`" type: "boolean" default: false - name: "stderr" in: "query" description: "Return logs from `stderr`" type: "boolean" default: false - name: "since" in: "query" description: "Only return logs since this time, as a UNIX timestamp" type: "integer" default: 0 - name: "until" in: "query" description: "Only return logs before this time, as a UNIX timestamp" type: "integer" default: 0 - name: "timestamps" in: "query" description: "Add timestamps to every log line" type: "boolean" default: false - name: "tail" in: "query" description: | Only return this number of log lines from the end of the logs. Specify as an integer or `all` to output all log lines. type: "string" default: "all" tags: ["Container"] /containers/{id}/changes: get: summary: "Get changes on a container’s filesystem" description: | Returns which files in a container's filesystem have been added, deleted, or modified. The `Kind` of modification can be one of: - `0`: Modified ("C") - `1`: Added ("A") - `2`: Deleted ("D") operationId: "ContainerChanges" produces: ["application/json"] responses: 200: description: "The list of changes" schema: type: "array" items: $ref: "#/definitions/FilesystemChange" examples: application/json: - Path: "/dev" Kind: 0 - Path: "/dev/kmsg" Kind: 1 - Path: "/test" Kind: 1 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" tags: ["Container"] /containers/{id}/export: get: summary: "Export a container" description: "Export the contents of a container as a tarball." operationId: "ContainerExport" produces: - "application/octet-stream" responses: 200: description: "no error" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" tags: ["Container"] /containers/{id}/stats: get: summary: "Get container stats based on resource usage" description: | This endpoint returns a live stream of a container’s resource usage statistics. The `precpu_stats` is the CPU statistic of the *previous* read, and is used to calculate the CPU usage percentage. It is not an exact copy of the `cpu_stats` field. If either `precpu_stats.online_cpus` or `cpu_stats.online_cpus` is nil then for compatibility with older daemons the length of the corresponding `cpu_usage.percpu_usage` array should be used. On a cgroup v2 host, the following fields are not set * `blkio_stats`: all fields other than `io_service_bytes_recursive` * `cpu_stats`: `cpu_usage.percpu_usage` * `memory_stats`: `max_usage` and `failcnt` Also, `memory_stats.stats` fields are incompatible with cgroup v1. To calculate the values shown by the `stats` command of the docker cli tool the following formulas can be used: * used_memory = `memory_stats.usage - memory_stats.stats.cache` * available_memory = `memory_stats.limit` * Memory usage % = `(used_memory / available_memory) * 100.0` * cpu_delta = `cpu_stats.cpu_usage.total_usage - precpu_stats.cpu_usage.total_usage` * system_cpu_delta = `cpu_stats.system_cpu_usage - precpu_stats.system_cpu_usage` * number_cpus = `length(cpu_stats.cpu_usage.percpu_usage)` or `cpu_stats.online_cpus` * CPU usage % = `(cpu_delta / system_cpu_delta) * number_cpus * 100.0` operationId: "ContainerStats" produces: ["application/json"] responses: 200: description: "no error" schema: $ref: "#/definitions/ContainerStatsResponse" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "stream" in: "query" description: | Stream the output. If false, the stats will be output once and then it will disconnect. type: "boolean" default: true - name: "one-shot" in: "query" description: | Only get a single stat instead of waiting for 2 cycles. Must be used with `stream=false`. type: "boolean" default: false tags: ["Container"] /containers/{id}/resize: post: summary: "Resize a container TTY" description: "Resize the TTY for a container." operationId: "ContainerResize" consumes: - "application/octet-stream" produces: - "text/plain" responses: 200: description: "no error" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "cannot resize container" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "h" in: "query" required: true description: "Height of the TTY session in characters" type: "integer" - name: "w" in: "query" required: true description: "Width of the TTY session in characters" type: "integer" tags: ["Container"] /containers/{id}/start: post: summary: "Start a container" operationId: "ContainerStart" responses: 204: description: "no error" 304: description: "container already started" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "detachKeys" in: "query" description: | Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-` where `` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`. type: "string" tags: ["Container"] /containers/{id}/stop: post: summary: "Stop a container" operationId: "ContainerStop" responses: 204: description: "no error" 304: description: "container already stopped" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "signal" in: "query" description: | Signal to send to the container as an integer or string (e.g. `SIGINT`). type: "string" - name: "t" in: "query" description: "Number of seconds to wait before killing the container" type: "integer" tags: ["Container"] /containers/{id}/restart: post: summary: "Restart a container" operationId: "ContainerRestart" responses: 204: description: "no error" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "signal" in: "query" description: | Signal to send to the container as an integer or string (e.g. `SIGINT`). type: "string" - name: "t" in: "query" description: "Number of seconds to wait before killing the container" type: "integer" tags: ["Container"] /containers/{id}/kill: post: summary: "Kill a container" description: | Send a POSIX signal to a container, defaulting to killing to the container. operationId: "ContainerKill" responses: 204: description: "no error" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 409: description: "container is not running" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "Container d37cde0fe4ad63c3a7252023b2f9800282894247d145cb5933ddf6e52cc03a28 is not running" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "signal" in: "query" description: | Signal to send to the container as an integer or string (e.g. `SIGINT`). type: "string" default: "SIGKILL" tags: ["Container"] /containers/{id}/update: post: summary: "Update a container" description: | Change various configuration options of a container without having to recreate it. operationId: "ContainerUpdate" consumes: ["application/json"] produces: ["application/json"] responses: 200: description: "The container has been updated." schema: $ref: "#/definitions/ContainerUpdateResponse" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "update" in: "body" required: true schema: allOf: - $ref: "#/definitions/Resources" - type: "object" properties: RestartPolicy: $ref: "#/definitions/RestartPolicy" example: BlkioWeight: 300 CpuShares: 512 CpuPeriod: 100000 CpuQuota: 50000 CpuRealtimePeriod: 1000000 CpuRealtimeRuntime: 10000 CpusetCpus: "0,1" CpusetMems: "0" Memory: 314572800 MemorySwap: 514288000 MemoryReservation: 209715200 RestartPolicy: MaximumRetryCount: 4 Name: "on-failure" tags: ["Container"] /containers/{id}/rename: post: summary: "Rename a container" operationId: "ContainerRename" responses: 204: description: "no error" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 409: description: "name already in use" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "name" in: "query" required: true description: "New name for the container" type: "string" tags: ["Container"] /containers/{id}/pause: post: summary: "Pause a container" description: | Use the freezer cgroup to suspend all processes in a container. Traditionally, when suspending a process the `SIGSTOP` signal is used, which is observable by the process being suspended. With the freezer cgroup the process is unaware, and unable to capture, that it is being suspended, and subsequently resumed. operationId: "ContainerPause" responses: 204: description: "no error" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" tags: ["Container"] /containers/{id}/unpause: post: summary: "Unpause a container" description: "Resume a container which has been paused." operationId: "ContainerUnpause" responses: 204: description: "no error" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" tags: ["Container"] /containers/{id}/attach: post: summary: "Attach to a container" description: | Attach to a container to read its output or send it input. You can attach to the same container multiple times and you can reattach to containers that have been detached. Either the `stream` or `logs` parameter must be `true` for this endpoint to do anything. See the [documentation for the `docker attach` command](https://docs.docker.com/engine/reference/commandline/attach/) for more details. ### Hijacking This endpoint hijacks the HTTP connection to transport `stdin`, `stdout`, and `stderr` on the same socket. This is the response from the daemon for an attach request: ``` HTTP/1.1 200 OK Content-Type: application/vnd.docker.raw-stream [STREAM] ``` After the headers and two new lines, the TCP connection can now be used for raw, bidirectional communication between the client and server. To hint potential proxies about connection hijacking, the Docker client can also optionally send connection upgrade headers. For example, the client sends this request to upgrade the connection: ``` POST /containers/16253994b7c4/attach?stream=1&stdout=1 HTTP/1.1 Upgrade: tcp Connection: Upgrade ``` The Docker daemon will respond with a `101 UPGRADED` response, and will similarly follow with the raw stream: ``` HTTP/1.1 101 UPGRADED Content-Type: application/vnd.docker.raw-stream Connection: Upgrade Upgrade: tcp [STREAM] ``` ### Stream format When the TTY setting is disabled in [`POST /containers/create`](#operation/ContainerCreate), the HTTP Content-Type header is set to application/vnd.docker.multiplexed-stream and the stream over the hijacked connected is multiplexed to separate out `stdout` and `stderr`. The stream consists of a series of frames, each containing a header and a payload. The header contains the information which the stream writes (`stdout` or `stderr`). It also contains the size of the associated frame encoded in the last four bytes (`uint32`). It is encoded on the first eight bytes like this: ```go header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4} ``` `STREAM_TYPE` can be: - 0: `stdin` (is written on `stdout`) - 1: `stdout` - 2: `stderr` `SIZE1, SIZE2, SIZE3, SIZE4` are the four bytes of the `uint32` size encoded as big endian. Following the header is the payload, which is the specified number of bytes of `STREAM_TYPE`. The simplest way to implement this protocol is the following: 1. Read 8 bytes. 2. Choose `stdout` or `stderr` depending on the first byte. 3. Extract the frame size from the last four bytes. 4. Read the extracted size and output it on the correct output. 5. Goto 1. ### Stream format when using a TTY When the TTY setting is enabled in [`POST /containers/create`](#operation/ContainerCreate), the stream is not multiplexed. The data exchanged over the hijacked connection is simply the raw data from the process PTY and client's `stdin`. operationId: "ContainerAttach" produces: - "application/vnd.docker.raw-stream" - "application/vnd.docker.multiplexed-stream" responses: 101: description: "no error, hints proxy about hijacking" 200: description: "no error, no upgrade header found" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "detachKeys" in: "query" description: | Override the key sequence for detaching a container.Format is a single character `[a-Z]` or `ctrl-` where `` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`. type: "string" - name: "logs" in: "query" description: | Replay previous logs from the container. This is useful for attaching to a container that has started and you want to output everything since the container started. If `stream` is also enabled, once all the previous output has been returned, it will seamlessly transition into streaming current output. type: "boolean" default: false - name: "stream" in: "query" description: | Stream attached streams from the time the request was made onwards. type: "boolean" default: false - name: "stdin" in: "query" description: "Attach to `stdin`" type: "boolean" default: false - name: "stdout" in: "query" description: "Attach to `stdout`" type: "boolean" default: false - name: "stderr" in: "query" description: "Attach to `stderr`" type: "boolean" default: false tags: ["Container"] /containers/{id}/attach/ws: get: summary: "Attach to a container via a websocket" operationId: "ContainerAttachWebsocket" responses: 101: description: "no error, hints proxy about hijacking" 200: description: "no error, no upgrade header found" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "detachKeys" in: "query" description: | Override the key sequence for detaching a container.Format is a single character `[a-Z]` or `ctrl-` where `` is one of: `a-z`, `@`, `^`, `[`, `,`, or `_`. type: "string" - name: "logs" in: "query" description: "Return logs" type: "boolean" default: false - name: "stream" in: "query" description: "Return stream" type: "boolean" default: false - name: "stdin" in: "query" description: "Attach to `stdin`" type: "boolean" default: false - name: "stdout" in: "query" description: "Attach to `stdout`" type: "boolean" default: false - name: "stderr" in: "query" description: "Attach to `stderr`" type: "boolean" default: false tags: ["Container"] /containers/{id}/wait: post: summary: "Wait for a container" description: "Block until a container stops, then returns the exit code." operationId: "ContainerWait" produces: ["application/json"] responses: 200: description: "The container has exit." schema: $ref: "#/definitions/ContainerWaitResponse" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "condition" in: "query" description: | Wait until a container state reaches the given condition. Defaults to `not-running` if omitted or empty. type: "string" enum: - "not-running" - "next-exit" - "removed" default: "not-running" tags: ["Container"] /containers/{id}: delete: summary: "Remove a container" operationId: "ContainerDelete" responses: 204: description: "no error" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 409: description: "conflict" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: | You cannot remove a running container: c2ada9df5af8. Stop the container before attempting removal or force remove 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "v" in: "query" description: "Remove anonymous volumes associated with the container." type: "boolean" default: false - name: "force" in: "query" description: "If the container is running, kill it before removing it." type: "boolean" default: false - name: "link" in: "query" description: "Remove the specified link associated with the container." type: "boolean" default: false tags: ["Container"] /containers/{id}/archive: head: summary: "Get information about files in a container" description: | A response header `X-Docker-Container-Path-Stat` is returned, containing a base64 - encoded JSON object with some filesystem header information about the path. operationId: "ContainerArchiveInfo" responses: 200: description: "no error" headers: X-Docker-Container-Path-Stat: type: "string" description: | A base64 - encoded JSON object with some filesystem header information about the path 400: description: "Bad parameter" schema: $ref: "#/definitions/ErrorResponse" 404: description: "Container or path does not exist" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "path" in: "query" required: true description: "Resource in the container’s filesystem to archive." type: "string" tags: ["Container"] get: summary: "Get an archive of a filesystem resource in a container" description: "Get a tar archive of a resource in the filesystem of container id." operationId: "ContainerArchive" produces: ["application/x-tar"] responses: 200: description: "no error" 400: description: "Bad parameter" schema: $ref: "#/definitions/ErrorResponse" 404: description: "Container or path does not exist" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "path" in: "query" required: true description: "Resource in the container’s filesystem to archive." type: "string" tags: ["Container"] put: summary: "Extract an archive of files or folders to a directory in a container" description: | Upload a tar archive to be extracted to a path in the filesystem of container id. `path` parameter is asserted to be a directory. If it exists as a file, 400 error will be returned with message "not a directory". operationId: "PutContainerArchive" consumes: ["application/x-tar", "application/octet-stream"] responses: 200: description: "The content was extracted successfully" 400: description: "Bad parameter" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "not a directory" 403: description: "Permission denied, the volume or container rootfs is marked as read-only." schema: $ref: "#/definitions/ErrorResponse" 404: description: "No such container or path does not exist inside the container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "path" in: "query" required: true description: "Path to a directory in the container to extract the archive’s contents into. " type: "string" - name: "noOverwriteDirNonDir" in: "query" description: | If `1`, `true`, or `True` then it will be an error if unpacking the given content would cause an existing directory to be replaced with a non-directory and vice versa. type: "string" - name: "copyUIDGID" in: "query" description: | If `1`, `true`, then it will copy UID/GID maps to the dest file or dir type: "string" - name: "inputStream" in: "body" required: true description: | The input stream must be a tar archive compressed with one of the following algorithms: `identity` (no compression), `gzip`, `bzip2`, or `xz`. schema: type: "string" format: "binary" tags: ["Container"] /containers/prune: post: summary: "Delete stopped containers" produces: - "application/json" operationId: "ContainerPrune" parameters: - name: "filters" in: "query" description: | Filters to process on the prune list, encoded as JSON (a `map[string][]string`). Available filters: - `until=` Prune containers created before this timestamp. The `` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed relative to the daemon machine’s time. - `label` (`label=`, `label==`, `label!=`, or `label!==`) Prune containers with (or without, in case `label!=...` is used) the specified labels. type: "string" responses: 200: description: "No error" schema: type: "object" title: "ContainerPruneResponse" properties: ContainersDeleted: description: "Container IDs that were deleted" type: "array" items: type: "string" SpaceReclaimed: description: "Disk space reclaimed in bytes" type: "integer" format: "int64" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" tags: ["Container"] /images/json: get: summary: "List Images" description: "Returns a list of images on the server. Note that it uses a different, smaller representation of an image than inspecting a single image." operationId: "ImageList" produces: - "application/json" responses: 200: description: "Summary image data for the images matching the query" schema: type: "array" items: $ref: "#/definitions/ImageSummary" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "all" in: "query" description: "Show all images. Only images from a final layer (no children) are shown by default." type: "boolean" default: false - name: "filters" in: "query" description: | A JSON encoded value of the filters (a `map[string][]string`) to process on the images list. Available filters: - `before`=(`[:]`, `` or ``) - `dangling=true` - `label=key` or `label="key=value"` of an image label - `reference`=(`[:]`) - `since`=(`[:]`, `` or ``) - `until=` type: "string" - name: "shared-size" in: "query" description: "Compute and show shared size as a `SharedSize` field on each image." type: "boolean" default: false - name: "digests" in: "query" description: "Show digest information as a `RepoDigests` field on each image." type: "boolean" default: false - name: "manifests" in: "query" description: "Include `Manifests` in the image summary." type: "boolean" default: false tags: ["Image"] /build: post: summary: "Build an image" description: | Build an image from a tar archive with a `Dockerfile` in it. The `Dockerfile` specifies how the image is built from the tar archive. It is typically in the archive's root, but can be at a different path or have a different name by specifying the `dockerfile` parameter. [See the `Dockerfile` reference for more information](https://docs.docker.com/engine/reference/builder/). The Docker daemon performs a preliminary validation of the `Dockerfile` before starting the build, and returns an error if the syntax is incorrect. After that, each instruction is run one-by-one until the ID of the new image is output. The build is canceled if the client drops the connection by quitting or being killed. operationId: "ImageBuild" consumes: - "application/octet-stream" produces: - "application/json" parameters: - name: "inputStream" in: "body" description: "A tar archive compressed with one of the following algorithms: identity (no compression), gzip, bzip2, xz." schema: type: "string" format: "binary" - name: "dockerfile" in: "query" description: "Path within the build context to the `Dockerfile`. This is ignored if `remote` is specified and points to an external `Dockerfile`." type: "string" default: "Dockerfile" - name: "t" in: "query" description: "A name and optional tag to apply to the image in the `name:tag` format. If you omit the tag the default `latest` value is assumed. You can provide several `t` parameters." type: "string" - name: "extrahosts" in: "query" description: "Extra hosts to add to /etc/hosts" type: "string" - name: "remote" in: "query" description: "A Git repository URI or HTTP/HTTPS context URI. If the URI points to a single text file, the file’s contents are placed into a file called `Dockerfile` and the image is built from that file. If the URI points to a tarball, the file is downloaded by the daemon and the contents therein used as the context for the build. If the URI points to a tarball and the `dockerfile` parameter is also specified, there must be a file with the corresponding path inside the tarball." type: "string" - name: "q" in: "query" description: "Suppress verbose build output." type: "boolean" default: false - name: "nocache" in: "query" description: "Do not use the cache when building the image." type: "boolean" default: false - name: "cachefrom" in: "query" description: "JSON array of images used for build cache resolution." type: "string" - name: "pull" in: "query" description: "Attempt to pull the image even if an older image exists locally." type: "string" - name: "rm" in: "query" description: "Remove intermediate containers after a successful build." type: "boolean" default: true - name: "forcerm" in: "query" description: "Always remove intermediate containers, even upon failure." type: "boolean" default: false - name: "memory" in: "query" description: "Set memory limit for build." type: "integer" - name: "memswap" in: "query" description: "Total memory (memory + swap). Set as `-1` to disable swap." type: "integer" - name: "cpushares" in: "query" description: "CPU shares (relative weight)." type: "integer" - name: "cpusetcpus" in: "query" description: "CPUs in which to allow execution (e.g., `0-3`, `0,1`)." type: "string" - name: "cpuperiod" in: "query" description: "The length of a CPU period in microseconds." type: "integer" - name: "cpuquota" in: "query" description: "Microseconds of CPU time that the container can get in a CPU period." type: "integer" - name: "buildargs" in: "query" description: > JSON map of string pairs for build-time variables. Users pass these values at build-time. Docker uses the buildargs as the environment context for commands run via the `Dockerfile` RUN instruction, or for variable expansion in other `Dockerfile` instructions. This is not meant for passing secret values. For example, the build arg `FOO=bar` would become `{"FOO":"bar"}` in JSON. This would result in the query parameter `buildargs={"FOO":"bar"}`. Note that `{"FOO":"bar"}` should be URI component encoded. [Read more about the buildargs instruction.](https://docs.docker.com/engine/reference/builder/#arg) type: "string" - name: "shmsize" in: "query" description: "Size of `/dev/shm` in bytes. The size must be greater than 0. If omitted the system uses 64MB." type: "integer" - name: "squash" in: "query" description: "Squash the resulting images layers into a single layer. *(Experimental release only.)*" type: "boolean" - name: "labels" in: "query" description: "Arbitrary key/value labels to set on the image, as a JSON map of string pairs." type: "string" - name: "networkmode" in: "query" description: | Sets the networking mode for the run commands during build. Supported standard values are: `bridge`, `host`, `none`, and `container:`. Any other value is taken as a custom network's name or ID to which this container should connect to. type: "string" - name: "Content-type" in: "header" type: "string" enum: - "application/x-tar" default: "application/x-tar" - name: "X-Registry-Config" in: "header" description: | This is a base64-encoded JSON object with auth configurations for multiple registries that a build may refer to. The key is a registry URL, and the value is an auth configuration object, [as described in the authentication section](#section/Authentication). For example: ``` { "docker.example.com": { "username": "janedoe", "password": "hunter2" }, "https://index.docker.io/v1/": { "username": "mobydock", "password": "conta1n3rize14" } } ``` Only the registry domain name (and port if not the default 443) are required. However, for legacy reasons, the Docker Hub registry must be specified with both a `https://` prefix and a `/v1/` suffix even though Docker will prefer to use the v2 registry API. type: "string" - name: "platform" in: "query" description: "Platform in the format os[/arch[/variant]]" type: "string" default: "" - name: "target" in: "query" description: "Target build stage" type: "string" default: "" - name: "outputs" in: "query" description: "BuildKit output configuration" type: "string" default: "" - name: "version" in: "query" type: "string" default: "1" enum: ["1", "2"] description: | Version of the builder backend to use. - `1` is the first generation classic (deprecated) builder in the Docker daemon (default) - `2` is [BuildKit](https://github.com/moby/buildkit) responses: 200: description: "no error" 400: description: "Bad parameter" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" tags: ["Image"] /build/prune: post: summary: "Delete builder cache" produces: - "application/json" operationId: "BuildPrune" parameters: - name: "keep-storage" in: "query" description: | Amount of disk space in bytes to keep for cache > **Deprecated**: This parameter is deprecated and has been renamed to "reserved-space". > It is kept for backward compatibility and will be removed in API v1.49. type: "integer" format: "int64" - name: "reserved-space" in: "query" description: "Amount of disk space in bytes to keep for cache" type: "integer" format: "int64" - name: "max-used-space" in: "query" description: "Maximum amount of disk space allowed to keep for cache" type: "integer" format: "int64" - name: "min-free-space" in: "query" description: "Target amount of free disk space after pruning" type: "integer" format: "int64" - name: "all" in: "query" type: "boolean" description: "Remove all types of build cache" - name: "filters" in: "query" type: "string" description: | A JSON encoded value of the filters (a `map[string][]string`) to process on the list of build cache objects. Available filters: - `until=` remove cache older than ``. The `` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed relative to the daemon's local time. - `id=` - `parent=` - `type=` - `description=` - `inuse` - `shared` - `private` responses: 200: description: "No error" schema: type: "object" title: "BuildPruneResponse" properties: CachesDeleted: type: "array" items: description: "ID of build cache object" type: "string" SpaceReclaimed: description: "Disk space reclaimed in bytes" type: "integer" format: "int64" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" tags: ["Image"] /images/create: post: summary: "Create an image" description: "Pull or import an image." operationId: "ImageCreate" consumes: - "text/plain" - "application/octet-stream" produces: - "application/json" responses: 200: description: "no error" 404: description: "repository does not exist or no read access" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "fromImage" in: "query" description: | Name of the image to pull. If the name includes a tag or digest, specific behavior applies: - If only `fromImage` includes a tag, that tag is used. - If both `fromImage` and `tag` are provided, `tag` takes precedence. - If `fromImage` includes a digest, the image is pulled by digest, and `tag` is ignored. - If neither a tag nor digest is specified, all tags are pulled. type: "string" - name: "fromSrc" in: "query" description: "Source to import. The value may be a URL from which the image can be retrieved or `-` to read the image from the request body. This parameter may only be used when importing an image." type: "string" - name: "repo" in: "query" description: "Repository name given to an image when it is imported. The repo may include a tag. This parameter may only be used when importing an image." type: "string" - name: "tag" in: "query" description: "Tag or digest. If empty when pulling an image, this causes all tags for the given image to be pulled." type: "string" - name: "message" in: "query" description: "Set commit message for imported image." type: "string" - name: "inputImage" in: "body" description: "Image content if the value `-` has been specified in fromSrc query parameter" schema: type: "string" required: false - name: "X-Registry-Auth" in: "header" description: | A base64url-encoded auth configuration. Refer to the [authentication section](#section/Authentication) for details. type: "string" - name: "changes" in: "query" description: | Apply `Dockerfile` instructions to the image that is created, for example: `changes=ENV DEBUG=true`. Note that `ENV DEBUG=true` should be URI component encoded. Supported `Dockerfile` instructions: `CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`ONBUILD`|`USER`|`VOLUME`|`WORKDIR` type: "array" items: type: "string" - name: "platform" in: "query" description: | Platform in the format os[/arch[/variant]]. When used in combination with the `fromImage` option, the daemon checks if the given image is present in the local image cache with the given OS and Architecture, and otherwise attempts to pull the image. If the option is not set, the host's native OS and Architecture are used. If the given image does not exist in the local image cache, the daemon attempts to pull the image with the host's native OS and Architecture. If the given image does exists in the local image cache, but its OS or architecture does not match, a warning is produced. When used with the `fromSrc` option to import an image from an archive, this option sets the platform information for the imported image. If the option is not set, the host's native OS and Architecture are used for the imported image. type: "string" default: "" tags: ["Image"] /images/{name}/json: get: summary: "Inspect an image" description: "Return low-level information about an image." operationId: "ImageInspect" produces: - "application/json" responses: 200: description: "No error" schema: $ref: "#/definitions/ImageInspect" 404: description: "No such image" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such image: someimage (tag: latest)" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "name" in: "path" description: "Image name or id" type: "string" required: true - name: "manifests" in: "query" description: "Include Manifests in the image summary." type: "boolean" default: false required: false tags: ["Image"] /images/{name}/history: get: summary: "Get the history of an image" description: "Return parent layers of an image." operationId: "ImageHistory" produces: ["application/json"] responses: 200: description: "List of image layers" schema: type: "array" items: type: "object" x-go-name: HistoryResponseItem title: "HistoryResponseItem" description: "individual image layer information in response to ImageHistory operation" required: [Id, Created, CreatedBy, Tags, Size, Comment] properties: Id: type: "string" x-nullable: false Created: type: "integer" format: "int64" x-nullable: false CreatedBy: type: "string" x-nullable: false Tags: type: "array" items: type: "string" Size: type: "integer" format: "int64" x-nullable: false Comment: type: "string" x-nullable: false examples: application/json: - Id: "3db9c44f45209632d6050b35958829c3a2aa256d81b9a7be45b362ff85c54710" Created: 1398108230 CreatedBy: "/bin/sh -c #(nop) ADD file:eb15dbd63394e063b805a3c32ca7bf0266ef64676d5a6fab4801f2e81e2a5148 in /" Tags: - "ubuntu:lucid" - "ubuntu:10.04" Size: 182964289 Comment: "" - Id: "6cfa4d1f33fb861d4d114f43b25abd0ac737509268065cdfd69d544a59c85ab8" Created: 1398108222 CreatedBy: "/bin/sh -c #(nop) MAINTAINER Tianon Gravi - mkimage-debootstrap.sh -i iproute,iputils-ping,ubuntu-minimal -t lucid.tar.xz lucid http://archive.ubuntu.com/ubuntu/" Tags: [] Size: 0 Comment: "" - Id: "511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158" Created: 1371157430 CreatedBy: "" Tags: - "scratch12:latest" - "scratch:latest" Size: 0 Comment: "Imported from -" 404: description: "No such image" schema: $ref: "#/definitions/ErrorResponse" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "name" in: "path" description: "Image name or ID" type: "string" required: true - name: "platform" type: "string" in: "query" description: | JSON-encoded OCI platform to select the platform-variant. If omitted, it defaults to any locally available platform, prioritizing the daemon's host platform. If the daemon provides a multi-platform image store, this selects the platform-variant to show the history for. If the image is a single-platform image, or if the multi-platform image does not provide a variant matching the given platform, an error is returned. Example: `{"os": "linux", "architecture": "arm", "variant": "v5"}` tags: ["Image"] /images/{name}/push: post: summary: "Push an image" description: | Push an image to a registry. If you wish to push an image on to a private registry, that image must already have a tag which references the registry. For example, `registry.example.com/myimage:latest`. The push is cancelled if the HTTP connection is closed. operationId: "ImagePush" consumes: - "application/octet-stream" responses: 200: description: "No error" 404: description: "No such image" schema: $ref: "#/definitions/ErrorResponse" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "name" in: "path" description: | Name of the image to push. For example, `registry.example.com/myimage`. The image must be present in the local image store with the same name. The name should be provided without tag; if a tag is provided, it is ignored. For example, `registry.example.com/myimage:latest` is considered equivalent to `registry.example.com/myimage`. Use the `tag` parameter to specify the tag to push. type: "string" required: true - name: "tag" in: "query" description: | Tag of the image to push. For example, `latest`. If no tag is provided, all tags of the given image that are present in the local image store are pushed. type: "string" - name: "platform" type: "string" in: "query" description: | JSON-encoded OCI platform to select the platform-variant to push. If not provided, all available variants will attempt to be pushed. If the daemon provides a multi-platform image store, this selects the platform-variant to push to the registry. If the image is a single-platform image, or if the multi-platform image does not provide a variant matching the given platform, an error is returned. Example: `{"os": "linux", "architecture": "arm", "variant": "v5"}` - name: "X-Registry-Auth" in: "header" description: | A base64url-encoded auth configuration. Refer to the [authentication section](#section/Authentication) for details. type: "string" required: true tags: ["Image"] /images/{name}/tag: post: summary: "Tag an image" description: "Tag an image so that it becomes part of a repository." operationId: "ImageTag" responses: 201: description: "No error" 400: description: "Bad parameter" schema: $ref: "#/definitions/ErrorResponse" 404: description: "No such image" schema: $ref: "#/definitions/ErrorResponse" 409: description: "Conflict" schema: $ref: "#/definitions/ErrorResponse" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "name" in: "path" description: "Image name or ID to tag." type: "string" required: true - name: "repo" in: "query" description: "The repository to tag in. For example, `someuser/someimage`." type: "string" - name: "tag" in: "query" description: "The name of the new tag." type: "string" tags: ["Image"] /images/{name}: delete: summary: "Remove an image" description: | Remove an image, along with any untagged parent images that were referenced by that image. Images can't be removed if they have descendant images, are being used by a running container or are being used by a build. operationId: "ImageDelete" produces: ["application/json"] responses: 200: description: "The image was deleted successfully" schema: type: "array" items: $ref: "#/definitions/ImageDeleteResponseItem" examples: application/json: - Untagged: "3e2f21a89f" - Deleted: "3e2f21a89f" - Deleted: "53b4f83ac9" 404: description: "No such image" schema: $ref: "#/definitions/ErrorResponse" 409: description: "Conflict" schema: $ref: "#/definitions/ErrorResponse" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "name" in: "path" description: "Image name or ID" type: "string" required: true - name: "force" in: "query" description: "Remove the image even if it is being used by stopped containers or has other tags" type: "boolean" default: false - name: "noprune" in: "query" description: "Do not delete untagged parent images" type: "boolean" default: false tags: ["Image"] /images/search: get: summary: "Search images" description: "Search for an image on Docker Hub." operationId: "ImageSearch" produces: - "application/json" responses: 200: description: "No error" schema: type: "array" items: type: "object" title: "ImageSearchResponseItem" properties: description: type: "string" is_official: type: "boolean" is_automated: description: | Whether this repository has automated builds enabled.


> **Deprecated**: This field is deprecated and will always be "false". type: "boolean" example: false name: type: "string" star_count: type: "integer" examples: application/json: - description: "A minimal Docker image based on Alpine Linux with a complete package index and only 5 MB in size!" is_official: true is_automated: false name: "alpine" star_count: 10093 - description: "Busybox base image." is_official: true is_automated: false name: "Busybox base image." star_count: 3037 - description: "The PostgreSQL object-relational database system provides reliability and data integrity." is_official: true is_automated: false name: "postgres" star_count: 12408 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "term" in: "query" description: "Term to search" type: "string" required: true - name: "limit" in: "query" description: "Maximum number of results to return" type: "integer" - name: "filters" in: "query" description: | A JSON encoded value of the filters (a `map[string][]string`) to process on the images list. Available filters: - `is-official=(true|false)` - `stars=` Matches images that has at least 'number' stars. type: "string" tags: ["Image"] /images/prune: post: summary: "Delete unused images" produces: - "application/json" operationId: "ImagePrune" parameters: - name: "filters" in: "query" description: | Filters to process on the prune list, encoded as JSON (a `map[string][]string`). Available filters: - `dangling=` When set to `true` (or `1`), prune only unused *and* untagged images. When set to `false` (or `0`), all unused images are pruned. - `until=` Prune images created before this timestamp. The `` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed relative to the daemon machine’s time. - `label` (`label=`, `label==`, `label!=`, or `label!==`) Prune images with (or without, in case `label!=...` is used) the specified labels. type: "string" responses: 200: description: "No error" schema: type: "object" title: "ImagePruneResponse" properties: ImagesDeleted: description: "Images that were deleted" type: "array" items: $ref: "#/definitions/ImageDeleteResponseItem" SpaceReclaimed: description: "Disk space reclaimed in bytes" type: "integer" format: "int64" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" tags: ["Image"] /auth: post: summary: "Check auth configuration" description: | Validate credentials for a registry and, if available, get an identity token for accessing the registry without password. operationId: "SystemAuth" consumes: ["application/json"] produces: ["application/json"] responses: 200: description: "An identity token was generated successfully." schema: type: "object" title: "SystemAuthResponse" required: [Status] properties: Status: description: "The status of the authentication" type: "string" x-nullable: false IdentityToken: description: "An opaque token used to authenticate a user after a successful login" type: "string" x-nullable: false examples: application/json: Status: "Login Succeeded" IdentityToken: "9cbaf023786cd7..." 204: description: "No error" 401: description: "Auth error" schema: $ref: "#/definitions/ErrorResponse" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "authConfig" in: "body" description: "Authentication to check" schema: $ref: "#/definitions/AuthConfig" tags: ["System"] /info: get: summary: "Get system information" operationId: "SystemInfo" produces: - "application/json" responses: 200: description: "No error" schema: $ref: "#/definitions/SystemInfo" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" tags: ["System"] /version: get: summary: "Get version" description: "Returns the version of Docker that is running and various information about the system that Docker is running on." operationId: "SystemVersion" produces: ["application/json"] responses: 200: description: "no error" schema: $ref: "#/definitions/SystemVersion" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" tags: ["System"] /_ping: get: summary: "Ping" description: "This is a dummy endpoint you can use to test if the server is accessible." operationId: "SystemPing" produces: ["text/plain"] responses: 200: description: "no error" schema: type: "string" example: "OK" headers: Api-Version: type: "string" description: "Max API Version the server supports" Builder-Version: type: "string" description: | Default version of docker image builder The default on Linux is version "2" (BuildKit), but the daemon can be configured to recommend version "1" (classic Builder). Windows does not yet support BuildKit for native Windows images, and uses "1" (classic builder) as a default. This value is a recommendation as advertised by the daemon, and it is up to the client to choose which builder to use. default: "2" Docker-Experimental: type: "boolean" description: "If the server is running with experimental mode enabled" Swarm: type: "string" enum: ["inactive", "pending", "error", "locked", "active/worker", "active/manager"] description: | Contains information about Swarm status of the daemon, and if the daemon is acting as a manager or worker node. default: "inactive" Cache-Control: type: "string" default: "no-cache, no-store, must-revalidate" Pragma: type: "string" default: "no-cache" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" headers: Cache-Control: type: "string" default: "no-cache, no-store, must-revalidate" Pragma: type: "string" default: "no-cache" tags: ["System"] head: summary: "Ping" description: "This is a dummy endpoint you can use to test if the server is accessible." operationId: "SystemPingHead" produces: ["text/plain"] responses: 200: description: "no error" schema: type: "string" example: "(empty)" headers: Api-Version: type: "string" description: "Max API Version the server supports" Builder-Version: type: "string" description: "Default version of docker image builder" Docker-Experimental: type: "boolean" description: "If the server is running with experimental mode enabled" Swarm: type: "string" enum: ["inactive", "pending", "error", "locked", "active/worker", "active/manager"] description: | Contains information about Swarm status of the daemon, and if the daemon is acting as a manager or worker node. default: "inactive" Cache-Control: type: "string" default: "no-cache, no-store, must-revalidate" Pragma: type: "string" default: "no-cache" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" tags: ["System"] /commit: post: summary: "Create a new image from a container" operationId: "ImageCommit" consumes: - "application/json" produces: - "application/json" responses: 201: description: "no error" schema: $ref: "#/definitions/IDResponse" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "containerConfig" in: "body" description: "The container configuration" schema: $ref: "#/definitions/ContainerConfig" - name: "container" in: "query" description: "The ID or name of the container to commit" type: "string" - name: "repo" in: "query" description: "Repository name for the created image" type: "string" - name: "tag" in: "query" description: "Tag name for the create image" type: "string" - name: "comment" in: "query" description: "Commit message" type: "string" - name: "author" in: "query" description: "Author of the image (e.g., `John Hannibal Smith `)" type: "string" - name: "pause" in: "query" description: "Whether to pause the container before committing" type: "boolean" default: true - name: "changes" in: "query" description: "`Dockerfile` instructions to apply while committing" type: "string" tags: ["Image"] /events: get: summary: "Monitor events" description: | Stream real-time events from the server. Various objects within Docker report events when something happens to them. Containers report these events: `attach`, `commit`, `copy`, `create`, `destroy`, `detach`, `die`, `exec_create`, `exec_detach`, `exec_start`, `exec_die`, `export`, `health_status`, `kill`, `oom`, `pause`, `rename`, `resize`, `restart`, `start`, `stop`, `top`, `unpause`, `update`, and `prune` Images report these events: `create`, `delete`, `import`, `load`, `pull`, `push`, `save`, `tag`, `untag`, and `prune` Volumes report these events: `create`, `mount`, `unmount`, `destroy`, and `prune` Networks report these events: `create`, `connect`, `disconnect`, `destroy`, `update`, `remove`, and `prune` The Docker daemon reports these events: `reload` Services report these events: `create`, `update`, and `remove` Nodes report these events: `create`, `update`, and `remove` Secrets report these events: `create`, `update`, and `remove` Configs report these events: `create`, `update`, and `remove` The Builder reports `prune` events operationId: "SystemEvents" produces: - "application/json" responses: 200: description: "no error" schema: $ref: "#/definitions/EventMessage" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "since" in: "query" description: "Show events created since this timestamp then stream new events." type: "string" - name: "until" in: "query" description: "Show events created until this timestamp then stop streaming." type: "string" - name: "filters" in: "query" description: | A JSON encoded value of filters (a `map[string][]string`) to process on the event list. Available filters: - `config=` config name or ID - `container=` container name or ID - `daemon=` daemon name or ID - `event=` event type - `image=` image name or ID - `label=` image or container label - `network=` network name or ID - `node=` node ID - `plugin`= plugin name or ID - `scope`= local or swarm - `secret=` secret name or ID - `service=` service name or ID - `type=` object to filter by, one of `container`, `image`, `volume`, `network`, `daemon`, `plugin`, `node`, `service`, `secret` or `config` - `volume=` volume name type: "string" tags: ["System"] /system/df: get: summary: "Get data usage information" operationId: "SystemDataUsage" responses: 200: description: "no error" schema: type: "object" title: "SystemDataUsageResponse" properties: LayersSize: type: "integer" format: "int64" Images: type: "array" items: $ref: "#/definitions/ImageSummary" Containers: type: "array" items: $ref: "#/definitions/ContainerSummary" Volumes: type: "array" items: $ref: "#/definitions/Volume" BuildCache: type: "array" items: $ref: "#/definitions/BuildCache" example: LayersSize: 1092588 Images: - Id: "sha256:2b8fd9751c4c0f5dd266fcae00707e67a2545ef34f9a29354585f93dac906749" ParentId: "" RepoTags: - "busybox:latest" RepoDigests: - "busybox@sha256:a59906e33509d14c036c8678d687bd4eec81ed7c4b8ce907b888c607f6a1e0e6" Created: 1466724217 Size: 1092588 SharedSize: 0 Labels: {} Containers: 1 Containers: - Id: "e575172ed11dc01bfce087fb27bee502db149e1a0fad7c296ad300bbff178148" Names: - "/top" Image: "busybox" ImageID: "sha256:2b8fd9751c4c0f5dd266fcae00707e67a2545ef34f9a29354585f93dac906749" Command: "top" Created: 1472592424 Ports: [] SizeRootFs: 1092588 Labels: {} State: "exited" Status: "Exited (0) 56 minutes ago" HostConfig: NetworkMode: "default" NetworkSettings: Networks: bridge: IPAMConfig: null Links: null Aliases: null NetworkID: "d687bc59335f0e5c9ee8193e5612e8aee000c8c62ea170cfb99c098f95899d92" EndpointID: "8ed5115aeaad9abb174f68dcf135b49f11daf597678315231a32ca28441dec6a" Gateway: "172.18.0.1" IPAddress: "172.18.0.2" IPPrefixLen: 16 IPv6Gateway: "" GlobalIPv6Address: "" GlobalIPv6PrefixLen: 0 MacAddress: "02:42:ac:12:00:02" Mounts: [] Volumes: - Name: "my-volume" Driver: "local" Mountpoint: "/var/lib/docker/volumes/my-volume/_data" Labels: null Scope: "local" Options: null UsageData: Size: 10920104 RefCount: 2 BuildCache: - ID: "hw53o5aio51xtltp5xjp8v7fx" Parents: [] Type: "regular" Description: "pulled from docker.io/library/debian@sha256:234cb88d3020898631af0ccbbcca9a66ae7306ecd30c9720690858c1b007d2a0" InUse: false Shared: true Size: 0 CreatedAt: "2021-06-28T13:31:01.474619385Z" LastUsedAt: "2021-07-07T22:02:32.738075951Z" UsageCount: 26 - ID: "ndlpt0hhvkqcdfkputsk4cq9c" Parents: ["ndlpt0hhvkqcdfkputsk4cq9c"] Type: "regular" Description: "mount / from exec /bin/sh -c echo 'Binary::apt::APT::Keep-Downloaded-Packages \"true\";' > /etc/apt/apt.conf.d/keep-cache" InUse: false Shared: true Size: 51 CreatedAt: "2021-06-28T13:31:03.002625487Z" LastUsedAt: "2021-07-07T22:02:32.773909517Z" UsageCount: 26 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "type" in: "query" description: | Object types, for which to compute and return data. type: "array" collectionFormat: multi items: type: "string" enum: ["container", "image", "volume", "build-cache"] tags: ["System"] /images/{name}/get: get: summary: "Export an image" description: | Get a tarball containing all images and metadata for a repository. If `name` is a specific name and tag (e.g. `ubuntu:latest`), then only that image (and its parents) are returned. If `name` is an image ID, similarly only that image (and its parents) are returned, but with the exclusion of the `repositories` file in the tarball, as there were no image names referenced. ### Image tarball format An image tarball contains one directory per image layer (named using its long ID), each containing these files: - `VERSION`: currently `1.0` - the file format version - `json`: detailed layer information, similar to `docker inspect layer_id` - `layer.tar`: A tarfile containing the filesystem changes in this layer The `layer.tar` file contains `aufs` style `.wh..wh.aufs` files and directories for storing attribute changes and deletions. If the tarball defines a repository, the tarball should also include a `repositories` file at the root that contains a list of repository and tag names mapped to layer IDs. ```json { "hello-world": { "latest": "565a9d68a73f6706862bfe8409a7f659776d4d60a8d096eb4a3cbce6999cc2a1" } } ``` operationId: "ImageGet" produces: - "application/x-tar" responses: 200: description: "no error" schema: type: "string" format: "binary" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "name" in: "path" description: "Image name or ID" type: "string" required: true - name: "platform" type: "string" in: "query" description: | JSON encoded OCI platform describing a platform which will be used to select a platform-specific image to be saved if the image is multi-platform. If not provided, the full multi-platform image will be saved. Example: `{"os": "linux", "architecture": "arm", "variant": "v5"}` /images/get: get: summary: "Export several images" description: | Get a tarball containing all images and metadata for several image repositories. For each value of the `names` parameter: if it is a specific name and tag (e.g. `ubuntu:latest`), then only that image (and its parents) are returned; if it is an image ID, similarly only that image (and its parents) are returned and there would be no names referenced in the 'repositories' file for this image ID. For details on the format, see the [export image endpoint](#operation/ImageGet). operationId: "ImageGetAll" produces: - "application/x-tar" responses: 200: description: "no error" schema: type: "string" format: "binary" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "names" in: "query" description: "Image names to filter by" type: "array" items: type: "string" tags: ["Image"] /images/load: post: summary: "Import images" description: | Load a set of images and tags into a repository. For details on the format, see the [export image endpoint](#operation/ImageGet). operationId: "ImageLoad" consumes: - "application/x-tar" produces: - "application/json" responses: 200: description: "no error" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "imagesTarball" in: "body" description: "Tar archive containing images" schema: type: "string" format: "binary" - name: "quiet" in: "query" description: "Suppress progress details during load." type: "boolean" default: false - name: "platform" type: "string" in: "query" description: | JSON encoded OCI platform describing a platform which will be used to select a platform-specific image to be load if the image is multi-platform. If not provided, the full multi-platform image will be loaded. Example: `{"os": "linux", "architecture": "arm", "variant": "v5"}` tags: ["Image"] /containers/{id}/exec: post: summary: "Create an exec instance" description: "Run a command inside a running container." operationId: "ContainerExec" consumes: - "application/json" produces: - "application/json" responses: 201: description: "no error" schema: $ref: "#/definitions/IDResponse" 404: description: "no such container" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such container: c2ada9df5af8" 409: description: "container is paused" schema: $ref: "#/definitions/ErrorResponse" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "execConfig" in: "body" description: "Exec configuration" schema: type: "object" title: "ExecConfig" properties: AttachStdin: type: "boolean" description: "Attach to `stdin` of the exec command." AttachStdout: type: "boolean" description: "Attach to `stdout` of the exec command." AttachStderr: type: "boolean" description: "Attach to `stderr` of the exec command." ConsoleSize: type: "array" description: "Initial console size, as an `[height, width]` array." x-nullable: true minItems: 2 maxItems: 2 items: type: "integer" minimum: 0 example: [80, 64] DetachKeys: type: "string" description: | Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-` where `` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`. Tty: type: "boolean" description: "Allocate a pseudo-TTY." Env: description: | A list of environment variables in the form `["VAR=value", ...]`. type: "array" items: type: "string" Cmd: type: "array" description: "Command to run, as a string or array of strings." items: type: "string" Privileged: type: "boolean" description: "Runs the exec process with extended privileges." default: false User: type: "string" description: | The user, and optionally, group to run the exec process inside the container. Format is one of: `user`, `user:group`, `uid`, or `uid:gid`. WorkingDir: type: "string" description: | The working directory for the exec process inside the container. example: AttachStdin: false AttachStdout: true AttachStderr: true DetachKeys: "ctrl-p,ctrl-q" Tty: false Cmd: - "date" Env: - "FOO=bar" - "BAZ=quux" required: true - name: "id" in: "path" description: "ID or name of container" type: "string" required: true tags: ["Exec"] /exec/{id}/start: post: summary: "Start an exec instance" description: | Starts a previously set up exec instance. If detach is true, this endpoint returns immediately after starting the command. Otherwise, it sets up an interactive session with the command. operationId: "ExecStart" consumes: - "application/json" produces: - "application/vnd.docker.raw-stream" - "application/vnd.docker.multiplexed-stream" responses: 200: description: "No error" 404: description: "No such exec instance" schema: $ref: "#/definitions/ErrorResponse" 409: description: "Container is stopped or paused" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "execStartConfig" in: "body" schema: type: "object" title: "ExecStartConfig" properties: Detach: type: "boolean" description: "Detach from the command." example: false Tty: type: "boolean" description: "Allocate a pseudo-TTY." example: true ConsoleSize: type: "array" description: "Initial console size, as an `[height, width]` array." x-nullable: true minItems: 2 maxItems: 2 items: type: "integer" minimum: 0 example: [80, 64] - name: "id" in: "path" description: "Exec instance ID" required: true type: "string" tags: ["Exec"] /exec/{id}/resize: post: summary: "Resize an exec instance" description: | Resize the TTY session used by an exec instance. This endpoint only works if `tty` was specified as part of creating and starting the exec instance. operationId: "ExecResize" responses: 200: description: "No error" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 404: description: "No such exec instance" schema: $ref: "#/definitions/ErrorResponse" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" description: "Exec instance ID" required: true type: "string" - name: "h" in: "query" required: true description: "Height of the TTY session in characters" type: "integer" - name: "w" in: "query" required: true description: "Width of the TTY session in characters" type: "integer" tags: ["Exec"] /exec/{id}/json: get: summary: "Inspect an exec instance" description: "Return low-level information about an exec instance." operationId: "ExecInspect" produces: - "application/json" responses: 200: description: "No error" schema: type: "object" title: "ExecInspectResponse" properties: CanRemove: type: "boolean" DetachKeys: type: "string" ID: type: "string" Running: type: "boolean" ExitCode: type: "integer" ProcessConfig: $ref: "#/definitions/ProcessConfig" OpenStdin: type: "boolean" OpenStderr: type: "boolean" OpenStdout: type: "boolean" ContainerID: type: "string" Pid: type: "integer" description: "The system process ID for the exec process." examples: application/json: CanRemove: false ContainerID: "b53ee82b53a40c7dca428523e34f741f3abc51d9f297a14ff874bf761b995126" DetachKeys: "" ExitCode: 2 ID: "f33bbfb39f5b142420f4759b2348913bd4a8d1a6d7fd56499cb41a1bb91d7b3b" OpenStderr: true OpenStdin: true OpenStdout: true ProcessConfig: arguments: - "-c" - "exit 2" entrypoint: "sh" privileged: false tty: true user: "1000" Running: false Pid: 42000 404: description: "No such exec instance" schema: $ref: "#/definitions/ErrorResponse" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" description: "Exec instance ID" required: true type: "string" tags: ["Exec"] /volumes: get: summary: "List volumes" operationId: "VolumeList" produces: ["application/json"] responses: 200: description: "Summary volume data that matches the query" schema: $ref: "#/definitions/VolumeListResponse" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "filters" in: "query" description: | JSON encoded value of the filters (a `map[string][]string`) to process on the volumes list. Available filters: - `dangling=` When set to `true` (or `1`), returns all volumes that are not in use by a container. When set to `false` (or `0`), only volumes that are in use by one or more containers are returned. - `driver=` Matches volumes based on their driver. - `label=` or `label=:` Matches volumes based on the presence of a `label` alone or a `label` and a value. - `name=` Matches all or part of a volume name. type: "string" format: "json" tags: ["Volume"] /volumes/create: post: summary: "Create a volume" operationId: "VolumeCreate" consumes: ["application/json"] produces: ["application/json"] responses: 201: description: "The volume was created successfully" schema: $ref: "#/definitions/Volume" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "volumeConfig" in: "body" required: true description: "Volume configuration" schema: $ref: "#/definitions/VolumeCreateOptions" tags: ["Volume"] /volumes/{name}: get: summary: "Inspect a volume" operationId: "VolumeInspect" produces: ["application/json"] responses: 200: description: "No error" schema: $ref: "#/definitions/Volume" 404: description: "No such volume" schema: $ref: "#/definitions/ErrorResponse" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "name" in: "path" required: true description: "Volume name or ID" type: "string" tags: ["Volume"] put: summary: | "Update a volume. Valid only for Swarm cluster volumes" operationId: "VolumeUpdate" consumes: ["application/json"] produces: ["application/json"] responses: 200: description: "no error" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 404: description: "no such volume" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "name" in: "path" description: "The name or ID of the volume" type: "string" required: true - name: "body" in: "body" schema: # though the schema for is an object that contains only a # ClusterVolumeSpec, wrapping the ClusterVolumeSpec in this object # means that if, later on, we support things like changing the # labels, we can do so without duplicating that information to the # ClusterVolumeSpec. type: "object" description: "Volume configuration" properties: Spec: $ref: "#/definitions/ClusterVolumeSpec" description: | The spec of the volume to update. Currently, only Availability may change. All other fields must remain unchanged. - name: "version" in: "query" description: | The version number of the volume being updated. This is required to avoid conflicting writes. Found in the volume's `ClusterVolume` field. type: "integer" format: "int64" required: true tags: ["Volume"] delete: summary: "Remove a volume" description: "Instruct the driver to remove the volume." operationId: "VolumeDelete" responses: 204: description: "The volume was removed" 404: description: "No such volume or volume driver" schema: $ref: "#/definitions/ErrorResponse" 409: description: "Volume is in use and cannot be removed" schema: $ref: "#/definitions/ErrorResponse" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "name" in: "path" required: true description: "Volume name or ID" type: "string" - name: "force" in: "query" description: "Force the removal of the volume" type: "boolean" default: false tags: ["Volume"] /volumes/prune: post: summary: "Delete unused volumes" produces: - "application/json" operationId: "VolumePrune" parameters: - name: "filters" in: "query" description: | Filters to process on the prune list, encoded as JSON (a `map[string][]string`). Available filters: - `label` (`label=`, `label==`, `label!=`, or `label!==`) Prune volumes with (or without, in case `label!=...` is used) the specified labels. - `all` (`all=true`) - Consider all (local) volumes for pruning and not just anonymous volumes. type: "string" responses: 200: description: "No error" schema: type: "object" title: "VolumePruneResponse" properties: VolumesDeleted: description: "Volumes that were deleted" type: "array" items: type: "string" SpaceReclaimed: description: "Disk space reclaimed in bytes" type: "integer" format: "int64" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" tags: ["Volume"] /networks: get: summary: "List networks" description: | Returns a list of networks. For details on the format, see the [network inspect endpoint](#operation/NetworkInspect). Note that it uses a different, smaller representation of a network than inspecting a single network. For example, the list of containers attached to the network is not propagated in API versions 1.28 and up. operationId: "NetworkList" produces: - "application/json" responses: 200: description: "No error" schema: type: "array" items: $ref: "#/definitions/Network" examples: application/json: - Name: "bridge" Id: "f2de39df4171b0dc801e8002d1d999b77256983dfc63041c0f34030aa3977566" Created: "2016-10-19T06:21:00.416543526Z" Scope: "local" Driver: "bridge" EnableIPv4: true EnableIPv6: false Internal: false Attachable: false Ingress: false IPAM: Driver: "default" Config: - Subnet: "172.17.0.0/16" Options: com.docker.network.bridge.default_bridge: "true" com.docker.network.bridge.enable_icc: "true" com.docker.network.bridge.enable_ip_masquerade: "true" com.docker.network.bridge.host_binding_ipv4: "0.0.0.0" com.docker.network.bridge.name: "docker0" com.docker.network.driver.mtu: "1500" - Name: "none" Id: "e086a3893b05ab69242d3c44e49483a3bbbd3a26b46baa8f61ab797c1088d794" Created: "0001-01-01T00:00:00Z" Scope: "local" Driver: "null" EnableIPv4: false EnableIPv6: false Internal: false Attachable: false Ingress: false IPAM: Driver: "default" Config: [] Containers: {} Options: {} - Name: "host" Id: "13e871235c677f196c4e1ecebb9dc733b9b2d2ab589e30c539efeda84a24215e" Created: "0001-01-01T00:00:00Z" Scope: "local" Driver: "host" EnableIPv4: false EnableIPv6: false Internal: false Attachable: false Ingress: false IPAM: Driver: "default" Config: [] Containers: {} Options: {} 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "filters" in: "query" description: | JSON encoded value of the filters (a `map[string][]string`) to process on the networks list. Available filters: - `dangling=` When set to `true` (or `1`), returns all networks that are not in use by a container. When set to `false` (or `0`), only networks that are in use by one or more containers are returned. - `driver=` Matches a network's driver. - `id=` Matches all or part of a network ID. - `label=` or `label==` of a network label. - `name=` Matches all or part of a network name. - `scope=["swarm"|"global"|"local"]` Filters networks by scope (`swarm`, `global`, or `local`). - `type=["custom"|"builtin"]` Filters networks by type. The `custom` keyword returns all user-defined networks. type: "string" tags: ["Network"] /networks/{id}: get: summary: "Inspect a network" operationId: "NetworkInspect" produces: - "application/json" responses: 200: description: "No error" schema: $ref: "#/definitions/Network" 404: description: "Network not found" schema: $ref: "#/definitions/ErrorResponse" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" description: "Network ID or name" required: true type: "string" - name: "verbose" in: "query" description: "Detailed inspect output for troubleshooting" type: "boolean" default: false - name: "scope" in: "query" description: "Filter the network by scope (swarm, global, or local)" type: "string" tags: ["Network"] delete: summary: "Remove a network" operationId: "NetworkDelete" responses: 204: description: "No error" 403: description: "operation not supported for pre-defined networks" schema: $ref: "#/definitions/ErrorResponse" 404: description: "no such network" schema: $ref: "#/definitions/ErrorResponse" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" description: "Network ID or name" required: true type: "string" tags: ["Network"] /networks/create: post: summary: "Create a network" operationId: "NetworkCreate" consumes: - "application/json" produces: - "application/json" responses: 201: description: "Network created successfully" schema: $ref: "#/definitions/NetworkCreateResponse" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 403: description: | Forbidden operation. This happens when trying to create a network named after a pre-defined network, or when trying to create an overlay network on a daemon which is not part of a Swarm cluster. schema: $ref: "#/definitions/ErrorResponse" 404: description: "plugin not found" schema: $ref: "#/definitions/ErrorResponse" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "networkConfig" in: "body" description: "Network configuration" required: true schema: type: "object" title: "NetworkCreateRequest" required: ["Name"] properties: Name: description: "The network's name." type: "string" example: "my_network" Driver: description: "Name of the network driver plugin to use." type: "string" default: "bridge" example: "bridge" Scope: description: | The level at which the network exists (e.g. `swarm` for cluster-wide or `local` for machine level). type: "string" Internal: description: "Restrict external access to the network." type: "boolean" Attachable: description: | Globally scoped network is manually attachable by regular containers from workers in swarm mode. type: "boolean" example: true Ingress: description: | Ingress network is the network which provides the routing-mesh in swarm mode. type: "boolean" example: false ConfigOnly: description: | Creates a config-only network. Config-only networks are placeholder networks for network configurations to be used by other networks. Config-only networks cannot be used directly to run containers or services. type: "boolean" default: false example: false ConfigFrom: description: | Specifies the source which will provide the configuration for this network. The specified network must be an existing config-only network; see ConfigOnly. $ref: "#/definitions/ConfigReference" IPAM: description: "Optional custom IP scheme for the network." $ref: "#/definitions/IPAM" EnableIPv4: description: "Enable IPv4 on the network." type: "boolean" example: true EnableIPv6: description: "Enable IPv6 on the network." type: "boolean" example: true Options: description: "Network specific options to be used by the drivers." type: "object" additionalProperties: type: "string" example: com.docker.network.bridge.default_bridge: "true" com.docker.network.bridge.enable_icc: "true" com.docker.network.bridge.enable_ip_masquerade: "true" com.docker.network.bridge.host_binding_ipv4: "0.0.0.0" com.docker.network.bridge.name: "docker0" com.docker.network.driver.mtu: "1500" Labels: description: "User-defined key/value metadata." type: "object" additionalProperties: type: "string" example: com.example.some-label: "some-value" com.example.some-other-label: "some-other-value" tags: ["Network"] /networks/{id}/connect: post: summary: "Connect a container to a network" description: "The network must be either a local-scoped network or a swarm-scoped network with the `attachable` option set. A network cannot be re-attached to a running container" operationId: "NetworkConnect" consumes: - "application/json" responses: 200: description: "No error" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 403: description: "Operation forbidden" schema: $ref: "#/definitions/ErrorResponse" 404: description: "Network or container not found" schema: $ref: "#/definitions/ErrorResponse" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" description: "Network ID or name" required: true type: "string" - name: "container" in: "body" required: true schema: type: "object" title: "NetworkConnectRequest" properties: Container: type: "string" description: "The ID or name of the container to connect to the network." EndpointConfig: $ref: "#/definitions/EndpointSettings" example: Container: "3613f73ba0e4" EndpointConfig: IPAMConfig: IPv4Address: "172.24.56.89" IPv6Address: "2001:db8::5689" MacAddress: "02:42:ac:12:05:02" Priority: 100 tags: ["Network"] /networks/{id}/disconnect: post: summary: "Disconnect a container from a network" operationId: "NetworkDisconnect" consumes: - "application/json" responses: 200: description: "No error" 403: description: "Operation not supported for swarm scoped networks" schema: $ref: "#/definitions/ErrorResponse" 404: description: "Network or container not found" schema: $ref: "#/definitions/ErrorResponse" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" description: "Network ID or name" required: true type: "string" - name: "container" in: "body" required: true schema: type: "object" title: "NetworkDisconnectRequest" properties: Container: type: "string" description: | The ID or name of the container to disconnect from the network. Force: type: "boolean" description: | Force the container to disconnect from the network. tags: ["Network"] /networks/prune: post: summary: "Delete unused networks" produces: - "application/json" operationId: "NetworkPrune" parameters: - name: "filters" in: "query" description: | Filters to process on the prune list, encoded as JSON (a `map[string][]string`). Available filters: - `until=` Prune networks created before this timestamp. The `` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed relative to the daemon machine’s time. - `label` (`label=`, `label==`, `label!=`, or `label!==`) Prune networks with (or without, in case `label!=...` is used) the specified labels. type: "string" responses: 200: description: "No error" schema: type: "object" title: "NetworkPruneResponse" properties: NetworksDeleted: description: "Networks that were deleted" type: "array" items: type: "string" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" tags: ["Network"] /plugins: get: summary: "List plugins" operationId: "PluginList" description: "Returns information about installed plugins." produces: ["application/json"] responses: 200: description: "No error" schema: type: "array" items: $ref: "#/definitions/Plugin" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "filters" in: "query" type: "string" description: | A JSON encoded value of the filters (a `map[string][]string`) to process on the plugin list. Available filters: - `capability=` - `enable=|` tags: ["Plugin"] /plugins/privileges: get: summary: "Get plugin privileges" operationId: "GetPluginPrivileges" responses: 200: description: "no error" schema: type: "array" items: $ref: "#/definitions/PluginPrivilege" example: - Name: "network" Description: "" Value: - "host" - Name: "mount" Description: "" Value: - "/data" - Name: "device" Description: "" Value: - "/dev/cpu_dma_latency" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "remote" in: "query" description: | The name of the plugin. The `:latest` tag is optional, and is the default if omitted. required: true type: "string" tags: - "Plugin" /plugins/pull: post: summary: "Install a plugin" operationId: "PluginPull" description: | Pulls and installs a plugin. After the plugin is installed, it can be enabled using the [`POST /plugins/{name}/enable` endpoint](#operation/PostPluginsEnable). produces: - "application/json" responses: 204: description: "no error" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "remote" in: "query" description: | Remote reference for plugin to install. The `:latest` tag is optional, and is used as the default if omitted. required: true type: "string" - name: "name" in: "query" description: | Local name for the pulled plugin. The `:latest` tag is optional, and is used as the default if omitted. required: false type: "string" - name: "X-Registry-Auth" in: "header" description: | A base64url-encoded auth configuration to use when pulling a plugin from a registry. Refer to the [authentication section](#section/Authentication) for details. type: "string" - name: "body" in: "body" schema: type: "array" items: $ref: "#/definitions/PluginPrivilege" example: - Name: "network" Description: "" Value: - "host" - Name: "mount" Description: "" Value: - "/data" - Name: "device" Description: "" Value: - "/dev/cpu_dma_latency" tags: ["Plugin"] /plugins/{name}/json: get: summary: "Inspect a plugin" operationId: "PluginInspect" responses: 200: description: "no error" schema: $ref: "#/definitions/Plugin" 404: description: "plugin is not installed" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "name" in: "path" description: | The name of the plugin. The `:latest` tag is optional, and is the default if omitted. required: true type: "string" tags: ["Plugin"] /plugins/{name}: delete: summary: "Remove a plugin" operationId: "PluginDelete" responses: 200: description: "no error" schema: $ref: "#/definitions/Plugin" 404: description: "plugin is not installed" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "name" in: "path" description: | The name of the plugin. The `:latest` tag is optional, and is the default if omitted. required: true type: "string" - name: "force" in: "query" description: | Disable the plugin before removing. This may result in issues if the plugin is in use by a container. type: "boolean" default: false tags: ["Plugin"] /plugins/{name}/enable: post: summary: "Enable a plugin" operationId: "PluginEnable" responses: 200: description: "no error" 404: description: "plugin is not installed" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "name" in: "path" description: | The name of the plugin. The `:latest` tag is optional, and is the default if omitted. required: true type: "string" - name: "timeout" in: "query" description: "Set the HTTP client timeout (in seconds)" type: "integer" default: 0 tags: ["Plugin"] /plugins/{name}/disable: post: summary: "Disable a plugin" operationId: "PluginDisable" responses: 200: description: "no error" 404: description: "plugin is not installed" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "name" in: "path" description: | The name of the plugin. The `:latest` tag is optional, and is the default if omitted. required: true type: "string" - name: "force" in: "query" description: | Force disable a plugin even if still in use. required: false type: "boolean" tags: ["Plugin"] /plugins/{name}/upgrade: post: summary: "Upgrade a plugin" operationId: "PluginUpgrade" responses: 204: description: "no error" 404: description: "plugin not installed" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "name" in: "path" description: | The name of the plugin. The `:latest` tag is optional, and is the default if omitted. required: true type: "string" - name: "remote" in: "query" description: | Remote reference to upgrade to. The `:latest` tag is optional, and is used as the default if omitted. required: true type: "string" - name: "X-Registry-Auth" in: "header" description: | A base64url-encoded auth configuration to use when pulling a plugin from a registry. Refer to the [authentication section](#section/Authentication) for details. type: "string" - name: "body" in: "body" schema: type: "array" items: $ref: "#/definitions/PluginPrivilege" example: - Name: "network" Description: "" Value: - "host" - Name: "mount" Description: "" Value: - "/data" - Name: "device" Description: "" Value: - "/dev/cpu_dma_latency" tags: ["Plugin"] /plugins/create: post: summary: "Create a plugin" operationId: "PluginCreate" consumes: - "application/x-tar" responses: 204: description: "no error" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "name" in: "query" description: | The name of the plugin. The `:latest` tag is optional, and is the default if omitted. required: true type: "string" - name: "tarContext" in: "body" description: "Path to tar containing plugin rootfs and manifest" schema: type: "string" format: "binary" tags: ["Plugin"] /plugins/{name}/push: post: summary: "Push a plugin" operationId: "PluginPush" description: | Push a plugin to the registry. parameters: - name: "name" in: "path" description: | The name of the plugin. The `:latest` tag is optional, and is the default if omitted. required: true type: "string" responses: 200: description: "no error" 404: description: "plugin not installed" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" tags: ["Plugin"] /plugins/{name}/set: post: summary: "Configure a plugin" operationId: "PluginSet" consumes: - "application/json" parameters: - name: "name" in: "path" description: | The name of the plugin. The `:latest` tag is optional, and is the default if omitted. required: true type: "string" - name: "body" in: "body" schema: type: "array" items: type: "string" example: ["DEBUG=1"] responses: 204: description: "No error" 404: description: "Plugin not installed" schema: $ref: "#/definitions/ErrorResponse" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" tags: ["Plugin"] /nodes: get: summary: "List nodes" operationId: "NodeList" responses: 200: description: "no error" schema: type: "array" items: $ref: "#/definitions/Node" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "filters" in: "query" description: | Filters to process on the nodes list, encoded as JSON (a `map[string][]string`). Available filters: - `id=` - `label=` - `membership=`(`accepted`|`pending`)` - `name=` - `node.label=` - `role=`(`manager`|`worker`)` type: "string" tags: ["Node"] /nodes/{id}: get: summary: "Inspect a node" operationId: "NodeInspect" responses: 200: description: "no error" schema: $ref: "#/definitions/Node" 404: description: "no such node" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" description: "The ID or name of the node" type: "string" required: true tags: ["Node"] delete: summary: "Delete a node" operationId: "NodeDelete" responses: 200: description: "no error" 404: description: "no such node" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" description: "The ID or name of the node" type: "string" required: true - name: "force" in: "query" description: "Force remove a node from the swarm" default: false type: "boolean" tags: ["Node"] /nodes/{id}/update: post: summary: "Update a node" operationId: "NodeUpdate" responses: 200: description: "no error" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 404: description: "no such node" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" description: "The ID of the node" type: "string" required: true - name: "body" in: "body" schema: $ref: "#/definitions/NodeSpec" - name: "version" in: "query" description: | The version number of the node object being updated. This is required to avoid conflicting writes. type: "integer" format: "int64" required: true tags: ["Node"] /swarm: get: summary: "Inspect swarm" operationId: "SwarmInspect" responses: 200: description: "no error" schema: $ref: "#/definitions/Swarm" 404: description: "no such swarm" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" tags: ["Swarm"] /swarm/init: post: summary: "Initialize a new swarm" operationId: "SwarmInit" produces: - "application/json" - "text/plain" responses: 200: description: "no error" schema: description: "The node ID" type: "string" example: "7v2t30z9blmxuhnyo6s4cpenp" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is already part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "body" in: "body" required: true schema: type: "object" title: "SwarmInitRequest" properties: ListenAddr: description: | Listen address used for inter-manager communication, as well as determining the networking interface used for the VXLAN Tunnel Endpoint (VTEP). This can either be an address/port combination in the form `192.168.1.1:4567`, or an interface followed by a port number, like `eth0:4567`. If the port number is omitted, the default swarm listening port is used. type: "string" AdvertiseAddr: description: | Externally reachable address advertised to other nodes. This can either be an address/port combination in the form `192.168.1.1:4567`, or an interface followed by a port number, like `eth0:4567`. If the port number is omitted, the port number from the listen address is used. If `AdvertiseAddr` is not specified, it will be automatically detected when possible. type: "string" DataPathAddr: description: | Address or interface to use for data path traffic (format: ``), for example, `192.168.1.1`, or an interface, like `eth0`. If `DataPathAddr` is unspecified, the same address as `AdvertiseAddr` is used. The `DataPathAddr` specifies the address that global scope network drivers will publish towards other nodes in order to reach the containers running on this node. Using this parameter it is possible to separate the container data traffic from the management traffic of the cluster. type: "string" DataPathPort: description: | DataPathPort specifies the data path port number for data traffic. Acceptable port range is 1024 to 49151. if no port is set or is set to 0, default port 4789 will be used. type: "integer" format: "uint32" DefaultAddrPool: description: | Default Address Pool specifies default subnet pools for global scope networks. type: "array" items: type: "string" example: ["10.10.0.0/16", "20.20.0.0/16"] ForceNewCluster: description: "Force creation of a new swarm." type: "boolean" SubnetSize: description: | SubnetSize specifies the subnet size of the networks created from the default subnet pool. type: "integer" format: "uint32" Spec: $ref: "#/definitions/SwarmSpec" example: ListenAddr: "0.0.0.0:2377" AdvertiseAddr: "192.168.1.1:2377" DataPathPort: 4789 DefaultAddrPool: ["10.10.0.0/8", "20.20.0.0/8"] SubnetSize: 24 ForceNewCluster: false Spec: Orchestration: {} Raft: {} Dispatcher: {} CAConfig: {} EncryptionConfig: AutoLockManagers: false tags: ["Swarm"] /swarm/join: post: summary: "Join an existing swarm" operationId: "SwarmJoin" responses: 200: description: "no error" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is already part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "body" in: "body" required: true schema: type: "object" title: "SwarmJoinRequest" properties: ListenAddr: description: | Listen address used for inter-manager communication if the node gets promoted to manager, as well as determining the networking interface used for the VXLAN Tunnel Endpoint (VTEP). type: "string" AdvertiseAddr: description: | Externally reachable address advertised to other nodes. This can either be an address/port combination in the form `192.168.1.1:4567`, or an interface followed by a port number, like `eth0:4567`. If the port number is omitted, the port number from the listen address is used. If `AdvertiseAddr` is not specified, it will be automatically detected when possible. type: "string" DataPathAddr: description: | Address or interface to use for data path traffic (format: ``), for example, `192.168.1.1`, or an interface, like `eth0`. If `DataPathAddr` is unspecified, the same address as `AdvertiseAddr` is used. The `DataPathAddr` specifies the address that global scope network drivers will publish towards other nodes in order to reach the containers running on this node. Using this parameter it is possible to separate the container data traffic from the management traffic of the cluster. type: "string" RemoteAddrs: description: | Addresses of manager nodes already participating in the swarm. type: "array" items: type: "string" JoinToken: description: "Secret token for joining this swarm." type: "string" example: ListenAddr: "0.0.0.0:2377" AdvertiseAddr: "192.168.1.1:2377" DataPathAddr: "192.168.1.1" RemoteAddrs: - "node1:2377" JoinToken: "SWMTKN-1-3pu6hszjas19xyp7ghgosyx9k8atbfcr8p2is99znpy26u2lkl-7p73s1dx5in4tatdymyhg9hu2" tags: ["Swarm"] /swarm/leave: post: summary: "Leave a swarm" operationId: "SwarmLeave" responses: 200: description: "no error" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "force" description: | Force leave swarm, even if this is the last manager or that it will break the cluster. in: "query" type: "boolean" default: false tags: ["Swarm"] /swarm/update: post: summary: "Update a swarm" operationId: "SwarmUpdate" responses: 200: description: "no error" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "body" in: "body" required: true schema: $ref: "#/definitions/SwarmSpec" - name: "version" in: "query" description: | The version number of the swarm object being updated. This is required to avoid conflicting writes. type: "integer" format: "int64" required: true - name: "rotateWorkerToken" in: "query" description: "Rotate the worker join token." type: "boolean" default: false - name: "rotateManagerToken" in: "query" description: "Rotate the manager join token." type: "boolean" default: false - name: "rotateManagerUnlockKey" in: "query" description: "Rotate the manager unlock key." type: "boolean" default: false tags: ["Swarm"] /swarm/unlockkey: get: summary: "Get the unlock key" operationId: "SwarmUnlockkey" consumes: - "application/json" responses: 200: description: "no error" schema: type: "object" title: "UnlockKeyResponse" properties: UnlockKey: description: "The swarm's unlock key." type: "string" example: UnlockKey: "SWMKEY-1-7c37Cc8654o6p38HnroywCi19pllOnGtbdZEgtKxZu8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" tags: ["Swarm"] /swarm/unlock: post: summary: "Unlock a locked manager" operationId: "SwarmUnlock" consumes: - "application/json" produces: - "application/json" parameters: - name: "body" in: "body" required: true schema: type: "object" title: "SwarmUnlockRequest" properties: UnlockKey: description: "The swarm's unlock key." type: "string" example: UnlockKey: "SWMKEY-1-7c37Cc8654o6p38HnroywCi19pllOnGtbdZEgtKxZu8" responses: 200: description: "no error" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" tags: ["Swarm"] /services: get: summary: "List services" operationId: "ServiceList" responses: 200: description: "no error" schema: type: "array" items: $ref: "#/definitions/Service" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "filters" in: "query" type: "string" description: | A JSON encoded value of the filters (a `map[string][]string`) to process on the services list. Available filters: - `id=` - `label=` - `mode=["replicated"|"global"]` - `name=` - name: "status" in: "query" type: "boolean" description: | Include service status, with count of running and desired tasks. tags: ["Service"] /services/create: post: summary: "Create a service" operationId: "ServiceCreate" consumes: - "application/json" produces: - "application/json" responses: 201: description: "no error" schema: $ref: "#/definitions/ServiceCreateResponse" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 403: description: "network is not eligible for services" schema: $ref: "#/definitions/ErrorResponse" 409: description: "name conflicts with an existing service" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "body" in: "body" required: true schema: allOf: - $ref: "#/definitions/ServiceSpec" - type: "object" example: Name: "web" TaskTemplate: ContainerSpec: Image: "nginx:alpine" Mounts: - ReadOnly: true Source: "web-data" Target: "/usr/share/nginx/html" Type: "volume" VolumeOptions: DriverConfig: {} Labels: com.example.something: "something-value" Hosts: ["10.10.10.10 host1", "ABCD:EF01:2345:6789:ABCD:EF01:2345:6789 host2"] User: "33" DNSConfig: Nameservers: ["8.8.8.8"] Search: ["example.org"] Options: ["timeout:3"] Secrets: - File: Name: "www.example.org.key" UID: "33" GID: "33" Mode: 384 SecretID: "fpjqlhnwb19zds35k8wn80lq9" SecretName: "example_org_domain_key" OomScoreAdj: 0 LogDriver: Name: "json-file" Options: max-file: "3" max-size: "10M" Placement: {} Resources: Limits: MemoryBytes: 104857600 Reservations: {} RestartPolicy: Condition: "on-failure" Delay: 10000000000 MaxAttempts: 10 Mode: Replicated: Replicas: 4 UpdateConfig: Parallelism: 2 Delay: 1000000000 FailureAction: "pause" Monitor: 15000000000 MaxFailureRatio: 0.15 RollbackConfig: Parallelism: 1 Delay: 1000000000 FailureAction: "pause" Monitor: 15000000000 MaxFailureRatio: 0.15 EndpointSpec: Ports: - Protocol: "tcp" PublishedPort: 8080 TargetPort: 80 Labels: foo: "bar" - name: "X-Registry-Auth" in: "header" description: | A base64url-encoded auth configuration for pulling from private registries. Refer to the [authentication section](#section/Authentication) for details. type: "string" tags: ["Service"] /services/{id}: get: summary: "Inspect a service" operationId: "ServiceInspect" responses: 200: description: "no error" schema: $ref: "#/definitions/Service" 404: description: "no such service" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" description: "ID or name of service." required: true type: "string" - name: "insertDefaults" in: "query" description: "Fill empty fields with default values." type: "boolean" default: false tags: ["Service"] delete: summary: "Delete a service" operationId: "ServiceDelete" responses: 200: description: "no error" 404: description: "no such service" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" description: "ID or name of service." required: true type: "string" tags: ["Service"] /services/{id}/update: post: summary: "Update a service" operationId: "ServiceUpdate" consumes: ["application/json"] produces: ["application/json"] responses: 200: description: "no error" schema: $ref: "#/definitions/ServiceUpdateResponse" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 404: description: "no such service" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" description: "ID or name of service." required: true type: "string" - name: "body" in: "body" required: true schema: allOf: - $ref: "#/definitions/ServiceSpec" - type: "object" example: Name: "top" TaskTemplate: ContainerSpec: Image: "busybox" Args: - "top" OomScoreAdj: 0 Resources: Limits: {} Reservations: {} RestartPolicy: Condition: "any" MaxAttempts: 0 Placement: {} ForceUpdate: 0 Mode: Replicated: Replicas: 1 UpdateConfig: Parallelism: 2 Delay: 1000000000 FailureAction: "pause" Monitor: 15000000000 MaxFailureRatio: 0.15 RollbackConfig: Parallelism: 1 Delay: 1000000000 FailureAction: "pause" Monitor: 15000000000 MaxFailureRatio: 0.15 EndpointSpec: Mode: "vip" - name: "version" in: "query" description: | The version number of the service object being updated. This is required to avoid conflicting writes. This version number should be the value as currently set on the service *before* the update. You can find the current version by calling `GET /services/{id}` required: true type: "integer" - name: "registryAuthFrom" in: "query" description: | If the `X-Registry-Auth` header is not specified, this parameter indicates where to find registry authorization credentials. type: "string" enum: ["spec", "previous-spec"] default: "spec" - name: "rollback" in: "query" description: | Set to this parameter to `previous` to cause a server-side rollback to the previous service spec. The supplied spec will be ignored in this case. type: "string" - name: "X-Registry-Auth" in: "header" description: | A base64url-encoded auth configuration for pulling from private registries. Refer to the [authentication section](#section/Authentication) for details. type: "string" tags: ["Service"] /services/{id}/logs: get: summary: "Get service logs" description: | Get `stdout` and `stderr` logs from a service. See also [`/containers/{id}/logs`](#operation/ContainerLogs). **Note**: This endpoint works only for services with the `local`, `json-file` or `journald` logging drivers. produces: - "application/vnd.docker.raw-stream" - "application/vnd.docker.multiplexed-stream" operationId: "ServiceLogs" responses: 200: description: "logs returned as a stream in response body" schema: type: "string" format: "binary" 404: description: "no such service" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such service: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the service" type: "string" - name: "details" in: "query" description: "Show service context and extra details provided to logs." type: "boolean" default: false - name: "follow" in: "query" description: "Keep connection after returning logs." type: "boolean" default: false - name: "stdout" in: "query" description: "Return logs from `stdout`" type: "boolean" default: false - name: "stderr" in: "query" description: "Return logs from `stderr`" type: "boolean" default: false - name: "since" in: "query" description: "Only return logs since this time, as a UNIX timestamp" type: "integer" default: 0 - name: "timestamps" in: "query" description: "Add timestamps to every log line" type: "boolean" default: false - name: "tail" in: "query" description: | Only return this number of log lines from the end of the logs. Specify as an integer or `all` to output all log lines. type: "string" default: "all" tags: ["Service"] /tasks: get: summary: "List tasks" operationId: "TaskList" produces: - "application/json" responses: 200: description: "no error" schema: type: "array" items: $ref: "#/definitions/Task" example: - ID: "0kzzo1i0y4jz6027t0k7aezc7" Version: Index: 71 CreatedAt: "2016-06-07T21:07:31.171892745Z" UpdatedAt: "2016-06-07T21:07:31.376370513Z" Spec: ContainerSpec: Image: "redis" Resources: Limits: {} Reservations: {} RestartPolicy: Condition: "any" MaxAttempts: 0 Placement: {} ServiceID: "9mnpnzenvg8p8tdbtq4wvbkcz" Slot: 1 NodeID: "60gvrl6tm78dmak4yl7srz94v" Status: Timestamp: "2016-06-07T21:07:31.290032978Z" State: "running" Message: "started" ContainerStatus: ContainerID: "e5d62702a1b48d01c3e02ca1e0212a250801fa8d67caca0b6f35919ebc12f035" PID: 677 DesiredState: "running" NetworksAttachments: - Network: ID: "4qvuz4ko70xaltuqbt8956gd1" Version: Index: 18 CreatedAt: "2016-06-07T20:31:11.912919752Z" UpdatedAt: "2016-06-07T21:07:29.955277358Z" Spec: Name: "ingress" Labels: com.docker.swarm.internal: "true" DriverConfiguration: {} IPAMOptions: Driver: {} Configs: - Subnet: "10.255.0.0/16" Gateway: "10.255.0.1" DriverState: Name: "overlay" Options: com.docker.network.driver.overlay.vxlanid_list: "256" IPAMOptions: Driver: Name: "default" Configs: - Subnet: "10.255.0.0/16" Gateway: "10.255.0.1" Addresses: - "10.255.0.10/16" - ID: "1yljwbmlr8er2waf8orvqpwms" Version: Index: 30 CreatedAt: "2016-06-07T21:07:30.019104782Z" UpdatedAt: "2016-06-07T21:07:30.231958098Z" Name: "hopeful_cori" Spec: ContainerSpec: Image: "redis" Resources: Limits: {} Reservations: {} RestartPolicy: Condition: "any" MaxAttempts: 0 Placement: {} ServiceID: "9mnpnzenvg8p8tdbtq4wvbkcz" Slot: 1 NodeID: "60gvrl6tm78dmak4yl7srz94v" Status: Timestamp: "2016-06-07T21:07:30.202183143Z" State: "shutdown" Message: "shutdown" ContainerStatus: ContainerID: "1cf8d63d18e79668b0004a4be4c6ee58cddfad2dae29506d8781581d0688a213" DesiredState: "shutdown" NetworksAttachments: - Network: ID: "4qvuz4ko70xaltuqbt8956gd1" Version: Index: 18 CreatedAt: "2016-06-07T20:31:11.912919752Z" UpdatedAt: "2016-06-07T21:07:29.955277358Z" Spec: Name: "ingress" Labels: com.docker.swarm.internal: "true" DriverConfiguration: {} IPAMOptions: Driver: {} Configs: - Subnet: "10.255.0.0/16" Gateway: "10.255.0.1" DriverState: Name: "overlay" Options: com.docker.network.driver.overlay.vxlanid_list: "256" IPAMOptions: Driver: Name: "default" Configs: - Subnet: "10.255.0.0/16" Gateway: "10.255.0.1" Addresses: - "10.255.0.5/16" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "filters" in: "query" type: "string" description: | A JSON encoded value of the filters (a `map[string][]string`) to process on the tasks list. Available filters: - `desired-state=(running | shutdown | accepted)` - `id=` - `label=key` or `label="key=value"` - `name=` - `node=` - `service=` tags: ["Task"] /tasks/{id}: get: summary: "Inspect a task" operationId: "TaskInspect" produces: - "application/json" responses: 200: description: "no error" schema: $ref: "#/definitions/Task" 404: description: "no such task" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" description: "ID of the task" required: true type: "string" tags: ["Task"] /tasks/{id}/logs: get: summary: "Get task logs" description: | Get `stdout` and `stderr` logs from a task. See also [`/containers/{id}/logs`](#operation/ContainerLogs). **Note**: This endpoint works only for services with the `local`, `json-file` or `journald` logging drivers. operationId: "TaskLogs" produces: - "application/vnd.docker.raw-stream" - "application/vnd.docker.multiplexed-stream" responses: 200: description: "logs returned as a stream in response body" schema: type: "string" format: "binary" 404: description: "no such task" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such task: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID of the task" type: "string" - name: "details" in: "query" description: "Show task context and extra details provided to logs." type: "boolean" default: false - name: "follow" in: "query" description: "Keep connection after returning logs." type: "boolean" default: false - name: "stdout" in: "query" description: "Return logs from `stdout`" type: "boolean" default: false - name: "stderr" in: "query" description: "Return logs from `stderr`" type: "boolean" default: false - name: "since" in: "query" description: "Only return logs since this time, as a UNIX timestamp" type: "integer" default: 0 - name: "timestamps" in: "query" description: "Add timestamps to every log line" type: "boolean" default: false - name: "tail" in: "query" description: | Only return this number of log lines from the end of the logs. Specify as an integer or `all` to output all log lines. type: "string" default: "all" tags: ["Task"] /secrets: get: summary: "List secrets" operationId: "SecretList" produces: - "application/json" responses: 200: description: "no error" schema: type: "array" items: $ref: "#/definitions/Secret" example: - ID: "blt1owaxmitz71s9v5zh81zun" Version: Index: 85 CreatedAt: "2017-07-20T13:55:28.678958722Z" UpdatedAt: "2017-07-20T13:55:28.678958722Z" Spec: Name: "mysql-passwd" Labels: some.label: "some.value" Driver: Name: "secret-bucket" Options: OptionA: "value for driver option A" OptionB: "value for driver option B" - ID: "ktnbjxoalbkvbvedmg1urrz8h" Version: Index: 11 CreatedAt: "2016-11-05T01:20:17.327670065Z" UpdatedAt: "2016-11-05T01:20:17.327670065Z" Spec: Name: "app-dev.crt" Labels: foo: "bar" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "filters" in: "query" type: "string" description: | A JSON encoded value of the filters (a `map[string][]string`) to process on the secrets list. Available filters: - `id=` - `label= or label==value` - `name=` - `names=` tags: ["Secret"] /secrets/create: post: summary: "Create a secret" operationId: "SecretCreate" consumes: - "application/json" produces: - "application/json" responses: 201: description: "no error" schema: $ref: "#/definitions/IDResponse" 409: description: "name conflicts with an existing object" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "body" in: "body" schema: allOf: - $ref: "#/definitions/SecretSpec" - type: "object" example: Name: "app-key.crt" Labels: foo: "bar" Data: "VEhJUyBJUyBOT1QgQSBSRUFMIENFUlRJRklDQVRFCg==" Driver: Name: "secret-bucket" Options: OptionA: "value for driver option A" OptionB: "value for driver option B" tags: ["Secret"] /secrets/{id}: get: summary: "Inspect a secret" operationId: "SecretInspect" produces: - "application/json" responses: 200: description: "no error" schema: $ref: "#/definitions/Secret" examples: application/json: ID: "ktnbjxoalbkvbvedmg1urrz8h" Version: Index: 11 CreatedAt: "2016-11-05T01:20:17.327670065Z" UpdatedAt: "2016-11-05T01:20:17.327670065Z" Spec: Name: "app-dev.crt" Labels: foo: "bar" Driver: Name: "secret-bucket" Options: OptionA: "value for driver option A" OptionB: "value for driver option B" 404: description: "secret not found" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true type: "string" description: "ID of the secret" tags: ["Secret"] delete: summary: "Delete a secret" operationId: "SecretDelete" produces: - "application/json" responses: 204: description: "no error" 404: description: "secret not found" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true type: "string" description: "ID of the secret" tags: ["Secret"] /secrets/{id}/update: post: summary: "Update a Secret" operationId: "SecretUpdate" responses: 200: description: "no error" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 404: description: "no such secret" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" description: "The ID or name of the secret" type: "string" required: true - name: "body" in: "body" schema: $ref: "#/definitions/SecretSpec" description: | The spec of the secret to update. Currently, only the Labels field can be updated. All other fields must remain unchanged from the [SecretInspect endpoint](#operation/SecretInspect) response values. - name: "version" in: "query" description: | The version number of the secret object being updated. This is required to avoid conflicting writes. type: "integer" format: "int64" required: true tags: ["Secret"] /configs: get: summary: "List configs" operationId: "ConfigList" produces: - "application/json" responses: 200: description: "no error" schema: type: "array" items: $ref: "#/definitions/Config" example: - ID: "ktnbjxoalbkvbvedmg1urrz8h" Version: Index: 11 CreatedAt: "2016-11-05T01:20:17.327670065Z" UpdatedAt: "2016-11-05T01:20:17.327670065Z" Spec: Name: "server.conf" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "filters" in: "query" type: "string" description: | A JSON encoded value of the filters (a `map[string][]string`) to process on the configs list. Available filters: - `id=` - `label= or label==value` - `name=` - `names=` tags: ["Config"] /configs/create: post: summary: "Create a config" operationId: "ConfigCreate" consumes: - "application/json" produces: - "application/json" responses: 201: description: "no error" schema: $ref: "#/definitions/IDResponse" 409: description: "name conflicts with an existing object" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "body" in: "body" schema: allOf: - $ref: "#/definitions/ConfigSpec" - type: "object" example: Name: "server.conf" Labels: foo: "bar" Data: "VEhJUyBJUyBOT1QgQSBSRUFMIENFUlRJRklDQVRFCg==" tags: ["Config"] /configs/{id}: get: summary: "Inspect a config" operationId: "ConfigInspect" produces: - "application/json" responses: 200: description: "no error" schema: $ref: "#/definitions/Config" examples: application/json: ID: "ktnbjxoalbkvbvedmg1urrz8h" Version: Index: 11 CreatedAt: "2016-11-05T01:20:17.327670065Z" UpdatedAt: "2016-11-05T01:20:17.327670065Z" Spec: Name: "app-dev.crt" 404: description: "config not found" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true type: "string" description: "ID of the config" tags: ["Config"] delete: summary: "Delete a config" operationId: "ConfigDelete" produces: - "application/json" responses: 204: description: "no error" 404: description: "config not found" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" required: true type: "string" description: "ID of the config" tags: ["Config"] /configs/{id}/update: post: summary: "Update a Config" operationId: "ConfigUpdate" responses: 200: description: "no error" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 404: description: "no such config" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" 503: description: "node is not part of a swarm" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "id" in: "path" description: "The ID or name of the config" type: "string" required: true - name: "body" in: "body" schema: $ref: "#/definitions/ConfigSpec" description: | The spec of the config to update. Currently, only the Labels field can be updated. All other fields must remain unchanged from the [ConfigInspect endpoint](#operation/ConfigInspect) response values. - name: "version" in: "query" description: | The version number of the config object being updated. This is required to avoid conflicting writes. type: "integer" format: "int64" required: true tags: ["Config"] /distribution/{name}/json: get: summary: "Get image information from the registry" description: | Return image digest and platform information by contacting the registry. operationId: "DistributionInspect" produces: - "application/json" responses: 200: description: "descriptor and platform information" schema: $ref: "#/definitions/DistributionInspect" 401: description: "Failed authentication or no image found" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: message: "No such image: someimage (tag: latest)" 500: description: "Server error" schema: $ref: "#/definitions/ErrorResponse" parameters: - name: "name" in: "path" description: "Image name or id" type: "string" required: true tags: ["Distribution"] /session: post: summary: "Initialize interactive session" description: | Start a new interactive session with a server. Session allows server to call back to the client for advanced capabilities. ### Hijacking This endpoint hijacks the HTTP connection to HTTP2 transport that allows the client to expose gPRC services on that connection. For example, the client sends this request to upgrade the connection: ``` POST /session HTTP/1.1 Upgrade: h2c Connection: Upgrade ``` The Docker daemon responds with a `101 UPGRADED` response follow with the raw stream: ``` HTTP/1.1 101 UPGRADED Connection: Upgrade Upgrade: h2c ``` operationId: "Session" produces: - "application/vnd.docker.raw-stream" responses: 101: description: "no error, hijacking successful" 400: description: "bad parameter" schema: $ref: "#/definitions/ErrorResponse" 500: description: "server error" schema: $ref: "#/definitions/ErrorResponse" tags: ["Session"] ================================================ FILE: vendor/github.com/docker/docker/api/types/blkiodev/blkio.go ================================================ package blkiodev // import "github.com/docker/docker/api/types/blkiodev" import "fmt" // WeightDevice is a structure that holds device:weight pair type WeightDevice struct { Path string Weight uint16 } func (w *WeightDevice) String() string { return fmt.Sprintf("%s:%d", w.Path, w.Weight) } // ThrottleDevice is a structure that holds device:rate_per_second pair type ThrottleDevice struct { Path string Rate uint64 } func (t *ThrottleDevice) String() string { return fmt.Sprintf("%s:%d", t.Path, t.Rate) } ================================================ FILE: vendor/github.com/docker/docker/api/types/checkpoint/list.go ================================================ package checkpoint // Summary represents the details of a checkpoint when listing endpoints. type Summary struct { // Name is the name of the checkpoint. Name string } ================================================ FILE: vendor/github.com/docker/docker/api/types/checkpoint/options.go ================================================ package checkpoint // CreateOptions holds parameters to create a checkpoint from a container. type CreateOptions struct { CheckpointID string CheckpointDir string Exit bool } // ListOptions holds parameters to list checkpoints for a container. type ListOptions struct { CheckpointDir string } // DeleteOptions holds parameters to delete a checkpoint from a container. type DeleteOptions struct { CheckpointID string CheckpointDir string } ================================================ FILE: vendor/github.com/docker/docker/api/types/client.go ================================================ package types // import "github.com/docker/docker/api/types" import ( "bufio" "context" "io" "net" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/registry" ) // NewHijackedResponse initializes a [HijackedResponse] type. func NewHijackedResponse(conn net.Conn, mediaType string) HijackedResponse { return HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn), mediaType: mediaType} } // HijackedResponse holds connection information for a hijacked request. type HijackedResponse struct { mediaType string Conn net.Conn Reader *bufio.Reader } // Close closes the hijacked connection and reader. func (h *HijackedResponse) Close() { h.Conn.Close() } // MediaType let client know if HijackedResponse hold a raw or multiplexed stream. // returns false if HTTP Content-Type is not relevant, and container must be inspected func (h *HijackedResponse) MediaType() (string, bool) { if h.mediaType == "" { return "", false } return h.mediaType, true } // CloseWriter is an interface that implements structs // that close input streams to prevent from writing. type CloseWriter interface { CloseWrite() error } // CloseWrite closes a readWriter for writing. func (h *HijackedResponse) CloseWrite() error { if conn, ok := h.Conn.(CloseWriter); ok { return conn.CloseWrite() } return nil } // ImageBuildOptions holds the information // necessary to build images. type ImageBuildOptions struct { Tags []string SuppressOutput bool RemoteContext string NoCache bool Remove bool ForceRemove bool PullParent bool Isolation container.Isolation CPUSetCPUs string CPUSetMems string CPUShares int64 CPUQuota int64 CPUPeriod int64 Memory int64 MemorySwap int64 CgroupParent string NetworkMode string ShmSize int64 Dockerfile string Ulimits []*container.Ulimit // BuildArgs needs to be a *string instead of just a string so that // we can tell the difference between "" (empty string) and no value // at all (nil). See the parsing of buildArgs in // api/server/router/build/build_routes.go for even more info. BuildArgs map[string]*string AuthConfigs map[string]registry.AuthConfig Context io.Reader Labels map[string]string // squash the resulting image's layers to the parent // preserves the original image and creates a new one from the parent with all // the changes applied to a single layer Squash bool // CacheFrom specifies images that are used for matching cache. Images // specified here do not need to have a valid parent chain to match cache. CacheFrom []string SecurityOpt []string ExtraHosts []string // List of extra hosts Target string SessionID string Platform string // Version specifies the version of the underlying builder to use Version BuilderVersion // BuildID is an optional identifier that can be passed together with the // build request. The same identifier can be used to gracefully cancel the // build with the cancel request. BuildID string // Outputs defines configurations for exporting build results. Only supported // in BuildKit mode Outputs []ImageBuildOutput } // ImageBuildOutput defines configuration for exporting a build result type ImageBuildOutput struct { Type string Attrs map[string]string } // BuilderVersion sets the version of underlying builder to use type BuilderVersion string const ( // BuilderV1 is the first generation builder in docker daemon BuilderV1 BuilderVersion = "1" // BuilderBuildKit is builder based on moby/buildkit project BuilderBuildKit BuilderVersion = "2" ) // ImageBuildResponse holds information // returned by a server after building // an image. type ImageBuildResponse struct { Body io.ReadCloser OSType string } // NodeListOptions holds parameters to list nodes with. type NodeListOptions struct { Filters filters.Args } // NodeRemoveOptions holds parameters to remove nodes with. type NodeRemoveOptions struct { Force bool } // ServiceCreateOptions contains the options to use when creating a service. type ServiceCreateOptions struct { // EncodedRegistryAuth is the encoded registry authorization credentials to // use when updating the service. // // This field follows the format of the X-Registry-Auth header. EncodedRegistryAuth string // QueryRegistry indicates whether the service update requires // contacting a registry. A registry may be contacted to retrieve // the image digest and manifest, which in turn can be used to update // platform or other information about the service. QueryRegistry bool } // Values for RegistryAuthFrom in ServiceUpdateOptions const ( RegistryAuthFromSpec = "spec" RegistryAuthFromPreviousSpec = "previous-spec" ) // ServiceUpdateOptions contains the options to be used for updating services. type ServiceUpdateOptions struct { // EncodedRegistryAuth is the encoded registry authorization credentials to // use when updating the service. // // This field follows the format of the X-Registry-Auth header. EncodedRegistryAuth string // TODO(stevvooe): Consider moving the version parameter of ServiceUpdate // into this field. While it does open API users up to racy writes, most // users may not need that level of consistency in practice. // RegistryAuthFrom specifies where to find the registry authorization // credentials if they are not given in EncodedRegistryAuth. Valid // values are "spec" and "previous-spec". RegistryAuthFrom string // Rollback indicates whether a server-side rollback should be // performed. When this is set, the provided spec will be ignored. // The valid values are "previous" and "none". An empty value is the // same as "none". Rollback string // QueryRegistry indicates whether the service update requires // contacting a registry. A registry may be contacted to retrieve // the image digest and manifest, which in turn can be used to update // platform or other information about the service. QueryRegistry bool } // ServiceListOptions holds parameters to list services with. type ServiceListOptions struct { Filters filters.Args // Status indicates whether the server should include the service task // count of running and desired tasks. Status bool } // ServiceInspectOptions holds parameters related to the "service inspect" // operation. type ServiceInspectOptions struct { InsertDefaults bool } // TaskListOptions holds parameters to list tasks with. type TaskListOptions struct { Filters filters.Args } // PluginRemoveOptions holds parameters to remove plugins. type PluginRemoveOptions struct { Force bool } // PluginEnableOptions holds parameters to enable plugins. type PluginEnableOptions struct { Timeout int } // PluginDisableOptions holds parameters to disable plugins. type PluginDisableOptions struct { Force bool } // PluginInstallOptions holds parameters to install a plugin. type PluginInstallOptions struct { Disabled bool AcceptAllPermissions bool RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry RemoteRef string // RemoteRef is the plugin name on the registry // PrivilegeFunc is a function that clients can supply to retry operations // after getting an authorization error. This function returns the registry // authentication header value in base64 encoded format, or an error if the // privilege request fails. // // For details, refer to [github.com/docker/docker/api/types/registry.RequestAuthConfig]. PrivilegeFunc func(context.Context) (string, error) AcceptPermissionsFunc func(context.Context, PluginPrivileges) (bool, error) Args []string } // SwarmUnlockKeyResponse contains the response for Engine API: // GET /swarm/unlockkey type SwarmUnlockKeyResponse struct { // UnlockKey is the unlock key in ASCII-armored format. UnlockKey string } // PluginCreateOptions hold all options to plugin create. type PluginCreateOptions struct { RepoName string } ================================================ FILE: vendor/github.com/docker/docker/api/types/common/id_response.go ================================================ package common // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // IDResponse Response to an API call that returns just an Id // swagger:model IDResponse type IDResponse struct { // The id of the newly created object. // Required: true ID string `json:"Id"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/change_type.go ================================================ package container // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // ChangeType Kind of change // // Can be one of: // // - `0`: Modified ("C") // - `1`: Added ("A") // - `2`: Deleted ("D") // // swagger:model ChangeType type ChangeType uint8 ================================================ FILE: vendor/github.com/docker/docker/api/types/container/change_types.go ================================================ package container const ( // ChangeModify represents the modify operation. ChangeModify ChangeType = 0 // ChangeAdd represents the add operation. ChangeAdd ChangeType = 1 // ChangeDelete represents the delete operation. ChangeDelete ChangeType = 2 ) func (ct ChangeType) String() string { switch ct { case ChangeModify: return "C" case ChangeAdd: return "A" case ChangeDelete: return "D" default: return "" } } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/commit.go ================================================ package container import "github.com/docker/docker/api/types/common" // CommitResponse response for the commit API call, containing the ID of the // image that was produced. type CommitResponse = common.IDResponse ================================================ FILE: vendor/github.com/docker/docker/api/types/container/config.go ================================================ package container // import "github.com/docker/docker/api/types/container" import ( "time" "github.com/docker/docker/api/types/strslice" "github.com/docker/go-connections/nat" dockerspec "github.com/moby/docker-image-spec/specs-go/v1" ) // MinimumDuration puts a minimum on user configured duration. // This is to prevent API error on time unit. For example, API may // set 3 as healthcheck interval with intention of 3 seconds, but // Docker interprets it as 3 nanoseconds. const MinimumDuration = 1 * time.Millisecond // StopOptions holds the options to stop or restart a container. type StopOptions struct { // Signal (optional) is the signal to send to the container to (gracefully) // stop it before forcibly terminating the container with SIGKILL after the // timeout expires. If not value is set, the default (SIGTERM) is used. Signal string `json:",omitempty"` // Timeout (optional) is the timeout (in seconds) to wait for the container // to stop gracefully before forcibly terminating it with SIGKILL. // // - Use nil to use the default timeout (10 seconds). // - Use '-1' to wait indefinitely. // - Use '0' to not wait for the container to exit gracefully, and // immediately proceeds to forcibly terminating the container. // - Other positive values are used as timeout (in seconds). Timeout *int `json:",omitempty"` } // HealthConfig holds configuration settings for the HEALTHCHECK feature. type HealthConfig = dockerspec.HealthcheckConfig // Config contains the configuration data about a container. // It should hold only portable information about the container. // Here, "portable" means "independent from the host we are running on". // Non-portable information *should* appear in HostConfig. // All fields added to this struct must be marked `omitempty` to keep getting // predictable hashes from the old `v1Compatibility` configuration. type Config struct { Hostname string // Hostname Domainname string // Domainname User string // User that will run the command(s) inside the container, also support user:group AttachStdin bool // Attach the standard input, makes possible user interaction AttachStdout bool // Attach the standard output AttachStderr bool // Attach the standard error ExposedPorts nat.PortSet `json:",omitempty"` // List of exposed ports Tty bool // Attach standard streams to a tty, including stdin if it is not closed. OpenStdin bool // Open stdin StdinOnce bool // If true, close stdin after the 1 attached client disconnects. Env []string // List of environment variable to set in the container Cmd strslice.StrSlice // Command to run when starting the container Healthcheck *HealthConfig `json:",omitempty"` // Healthcheck describes how to check the container is healthy ArgsEscaped bool `json:",omitempty"` // True if command is already escaped (meaning treat as a command line) (Windows specific). Image string // Name of the image as it was passed by the operator (e.g. could be symbolic) Volumes map[string]struct{} // List of volumes (mounts) used for the container WorkingDir string // Current directory (PWD) in the command will be launched Entrypoint strslice.StrSlice // Entrypoint to run when starting the container NetworkDisabled bool `json:",omitempty"` // Is network disabled // Mac Address of the container. // // Deprecated: this field is deprecated since API v1.44. Use EndpointSettings.MacAddress instead. MacAddress string `json:",omitempty"` OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile Labels map[string]string // List of labels set to this container StopSignal string `json:",omitempty"` // Signal to stop a container StopTimeout *int `json:",omitempty"` // Timeout (in seconds) to stop a container Shell strslice.StrSlice `json:",omitempty"` // Shell for shell-form of RUN, CMD, ENTRYPOINT } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/container.go ================================================ package container import ( "io" "os" "time" "github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/storage" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // ContainerUpdateOKBody OK response to ContainerUpdate operation // // Deprecated: use [UpdateResponse]. This alias will be removed in the next release. type ContainerUpdateOKBody = UpdateResponse // ContainerTopOKBody OK response to ContainerTop operation // // Deprecated: use [TopResponse]. This alias will be removed in the next release. type ContainerTopOKBody = TopResponse // PruneReport contains the response for Engine API: // POST "/containers/prune" type PruneReport struct { ContainersDeleted []string SpaceReclaimed uint64 } // PathStat is used to encode the header from // GET "/containers/{name:.*}/archive" // "Name" is the file or directory name. type PathStat struct { Name string `json:"name"` Size int64 `json:"size"` Mode os.FileMode `json:"mode"` Mtime time.Time `json:"mtime"` LinkTarget string `json:"linkTarget"` } // CopyToContainerOptions holds information // about files to copy into a container type CopyToContainerOptions struct { AllowOverwriteDirWithFile bool CopyUIDGID bool } // StatsResponseReader wraps an io.ReadCloser to read (a stream of) stats // for a container, as produced by the GET "/stats" endpoint. // // The OSType field is set to the server's platform to allow // platform-specific handling of the response. // // TODO(thaJeztah): remove this wrapper, and make OSType part of [StatsResponse]. type StatsResponseReader struct { Body io.ReadCloser `json:"body"` OSType string `json:"ostype"` } // MountPoint represents a mount point configuration inside the container. // This is used for reporting the mountpoints in use by a container. type MountPoint struct { // Type is the type of mount, see `Type` definitions in // github.com/docker/docker/api/types/mount.Type Type mount.Type `json:",omitempty"` // Name is the name reference to the underlying data defined by `Source` // e.g., the volume name. Name string `json:",omitempty"` // Source is the source location of the mount. // // For volumes, this contains the storage location of the volume (within // `/var/lib/docker/volumes/`). For bind-mounts, and `npipe`, this contains // the source (host) part of the bind-mount. For `tmpfs` mount points, this // field is empty. Source string // Destination is the path relative to the container root (`/`) where the // Source is mounted inside the container. Destination string // Driver is the volume driver used to create the volume (if it is a volume). Driver string `json:",omitempty"` // Mode is a comma separated list of options supplied by the user when // creating the bind/volume mount. // // The default is platform-specific (`"z"` on Linux, empty on Windows). Mode string // RW indicates whether the mount is mounted writable (read-write). RW bool // Propagation describes how mounts are propagated from the host into the // mount point, and vice-versa. Refer to the Linux kernel documentation // for details: // https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt // // This field is not used on Windows. Propagation mount.Propagation } // State stores container's running state // it's part of ContainerJSONBase and returned by "inspect" command type State struct { Status string // String representation of the container state. Can be one of "created", "running", "paused", "restarting", "removing", "exited", or "dead" Running bool Paused bool Restarting bool OOMKilled bool Dead bool Pid int ExitCode int Error string StartedAt string FinishedAt string Health *Health `json:",omitempty"` } // Summary contains response of Engine API: // GET "/containers/json" type Summary struct { ID string `json:"Id"` Names []string Image string ImageID string ImageManifestDescriptor *ocispec.Descriptor `json:"ImageManifestDescriptor,omitempty"` Command string Created int64 Ports []Port SizeRw int64 `json:",omitempty"` SizeRootFs int64 `json:",omitempty"` Labels map[string]string State string Status string HostConfig struct { NetworkMode string `json:",omitempty"` Annotations map[string]string `json:",omitempty"` } NetworkSettings *NetworkSettingsSummary Mounts []MountPoint } // ContainerJSONBase contains response of Engine API GET "/containers/{name:.*}/json" // for API version 1.18 and older. // // TODO(thaJeztah): combine ContainerJSONBase and InspectResponse into a single struct. // The split between ContainerJSONBase (ContainerJSONBase) and InspectResponse (InspectResponse) // was done in commit 6deaa58ba5f051039643cedceee97c8695e2af74 (https://github.com/moby/moby/pull/13675). // ContainerJSONBase contained all fields for API < 1.19, and InspectResponse // held fields that were added in API 1.19 and up. Given that the minimum // supported API version is now 1.24, we no longer use the separate type. type ContainerJSONBase struct { ID string `json:"Id"` Created string Path string Args []string State *State Image string ResolvConfPath string HostnamePath string HostsPath string LogPath string Name string RestartCount int Driver string Platform string MountLabel string ProcessLabel string AppArmorProfile string ExecIDs []string HostConfig *HostConfig GraphDriver storage.DriverData SizeRw *int64 `json:",omitempty"` SizeRootFs *int64 `json:",omitempty"` } // InspectResponse is the response for the GET "/containers/{name:.*}/json" // endpoint. type InspectResponse struct { *ContainerJSONBase Mounts []MountPoint Config *Config NetworkSettings *NetworkSettings // ImageManifestDescriptor is the descriptor of a platform-specific manifest of the image used to create the container. ImageManifestDescriptor *ocispec.Descriptor `json:"ImageManifestDescriptor,omitempty"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/create_request.go ================================================ package container import "github.com/docker/docker/api/types/network" // CreateRequest is the request message sent to the server for container // create calls. It is a config wrapper that holds the container [Config] // (portable) and the corresponding [HostConfig] (non-portable) and // [network.NetworkingConfig]. type CreateRequest struct { *Config HostConfig *HostConfig `json:"HostConfig,omitempty"` NetworkingConfig *network.NetworkingConfig `json:"NetworkingConfig,omitempty"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/create_response.go ================================================ package container // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // CreateResponse ContainerCreateResponse // // OK response to ContainerCreate operation // swagger:model CreateResponse type CreateResponse struct { // The ID of the created container // Required: true ID string `json:"Id"` // Warnings encountered when creating the container // Required: true Warnings []string `json:"Warnings"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/errors.go ================================================ package container type errInvalidParameter struct{ error } func (e *errInvalidParameter) InvalidParameter() {} func (e *errInvalidParameter) Unwrap() error { return e.error } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/exec.go ================================================ package container import "github.com/docker/docker/api/types/common" // ExecCreateResponse is the response for a successful exec-create request. // It holds the ID of the exec that was created. // // TODO(thaJeztah): make this a distinct type. type ExecCreateResponse = common.IDResponse // ExecOptions is a small subset of the Config struct that holds the configuration // for the exec feature of docker. type ExecOptions struct { User string // User that will run the command Privileged bool // Is the container in privileged mode Tty bool // Attach standard streams to a tty. ConsoleSize *[2]uint `json:",omitempty"` // Initial console size [height, width] AttachStdin bool // Attach the standard input, makes possible user interaction AttachStderr bool // Attach the standard error AttachStdout bool // Attach the standard output Detach bool // Execute in detach mode DetachKeys string // Escape keys for detach Env []string // Environment variables WorkingDir string // Working directory Cmd []string // Execution commands and args } // ExecStartOptions is a temp struct used by execStart // Config fields is part of ExecConfig in runconfig package type ExecStartOptions struct { // ExecStart will first check if it's detached Detach bool // Check if there's a tty Tty bool // Terminal size [height, width], unused if Tty == false ConsoleSize *[2]uint `json:",omitempty"` } // ExecAttachOptions is a temp struct used by execAttach. // // TODO(thaJeztah): make this a separate type; ContainerExecAttach does not use the Detach option, and cannot run detached. type ExecAttachOptions = ExecStartOptions // ExecInspect holds information returned by exec inspect. type ExecInspect struct { ExecID string `json:"ID"` ContainerID string Running bool ExitCode int Pid int } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/filesystem_change.go ================================================ package container // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // FilesystemChange Change in the container's filesystem. // // swagger:model FilesystemChange type FilesystemChange struct { // kind // Required: true Kind ChangeType `json:"Kind"` // Path to file or directory that has changed. // // Required: true Path string `json:"Path"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/health.go ================================================ package container import "time" // Health states const ( NoHealthcheck = "none" // Indicates there is no healthcheck Starting = "starting" // Starting indicates that the container is not yet ready Healthy = "healthy" // Healthy indicates that the container is running correctly Unhealthy = "unhealthy" // Unhealthy indicates that the container has a problem ) // Health stores information about the container's healthcheck results type Health struct { Status string // Status is one of [Starting], [Healthy] or [Unhealthy]. FailingStreak int // FailingStreak is the number of consecutive failures Log []*HealthcheckResult // Log contains the last few results (oldest first) } // HealthcheckResult stores information about a single run of a healthcheck probe type HealthcheckResult struct { Start time.Time // Start is the time this check started End time.Time // End is the time this check ended ExitCode int // ExitCode meanings: 0=healthy, 1=unhealthy, 2=reserved (considered unhealthy), else=error running probe Output string // Output from last check } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/hostconfig.go ================================================ package container // import "github.com/docker/docker/api/types/container" import ( "errors" "fmt" "strings" "github.com/docker/docker/api/types/blkiodev" "github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/strslice" "github.com/docker/go-connections/nat" "github.com/docker/go-units" ) // CgroupnsMode represents the cgroup namespace mode of the container type CgroupnsMode string // cgroup namespace modes for containers const ( CgroupnsModeEmpty CgroupnsMode = "" CgroupnsModePrivate CgroupnsMode = "private" CgroupnsModeHost CgroupnsMode = "host" ) // IsPrivate indicates whether the container uses its own private cgroup namespace func (c CgroupnsMode) IsPrivate() bool { return c == CgroupnsModePrivate } // IsHost indicates whether the container shares the host's cgroup namespace func (c CgroupnsMode) IsHost() bool { return c == CgroupnsModeHost } // IsEmpty indicates whether the container cgroup namespace mode is unset func (c CgroupnsMode) IsEmpty() bool { return c == CgroupnsModeEmpty } // Valid indicates whether the cgroup namespace mode is valid func (c CgroupnsMode) Valid() bool { return c.IsEmpty() || c.IsPrivate() || c.IsHost() } // Isolation represents the isolation technology of a container. The supported // values are platform specific type Isolation string // Isolation modes for containers const ( IsolationEmpty Isolation = "" // IsolationEmpty is unspecified (same behavior as default) IsolationDefault Isolation = "default" // IsolationDefault is the default isolation mode on current daemon IsolationProcess Isolation = "process" // IsolationProcess is process isolation mode IsolationHyperV Isolation = "hyperv" // IsolationHyperV is HyperV isolation mode ) // IsDefault indicates the default isolation technology of a container. On Linux this // is the native driver. On Windows, this is a Windows Server Container. func (i Isolation) IsDefault() bool { // TODO consider making isolation-mode strict (case-sensitive) v := Isolation(strings.ToLower(string(i))) return v == IsolationDefault || v == IsolationEmpty } // IsHyperV indicates the use of a Hyper-V partition for isolation func (i Isolation) IsHyperV() bool { // TODO consider making isolation-mode strict (case-sensitive) return Isolation(strings.ToLower(string(i))) == IsolationHyperV } // IsProcess indicates the use of process isolation func (i Isolation) IsProcess() bool { // TODO consider making isolation-mode strict (case-sensitive) return Isolation(strings.ToLower(string(i))) == IsolationProcess } // IpcMode represents the container ipc stack. type IpcMode string // IpcMode constants const ( IPCModeNone IpcMode = "none" IPCModeHost IpcMode = "host" IPCModeContainer IpcMode = "container" IPCModePrivate IpcMode = "private" IPCModeShareable IpcMode = "shareable" ) // IsPrivate indicates whether the container uses its own private ipc namespace which can not be shared. func (n IpcMode) IsPrivate() bool { return n == IPCModePrivate } // IsHost indicates whether the container shares the host's ipc namespace. func (n IpcMode) IsHost() bool { return n == IPCModeHost } // IsShareable indicates whether the container's ipc namespace can be shared with another container. func (n IpcMode) IsShareable() bool { return n == IPCModeShareable } // IsContainer indicates whether the container uses another container's ipc namespace. func (n IpcMode) IsContainer() bool { _, ok := containerID(string(n)) return ok } // IsNone indicates whether container IpcMode is set to "none". func (n IpcMode) IsNone() bool { return n == IPCModeNone } // IsEmpty indicates whether container IpcMode is empty func (n IpcMode) IsEmpty() bool { return n == "" } // Valid indicates whether the ipc mode is valid. func (n IpcMode) Valid() bool { // TODO(thaJeztah): align with PidMode, and consider container-mode without a container name/ID to be invalid. return n.IsEmpty() || n.IsNone() || n.IsPrivate() || n.IsHost() || n.IsShareable() || n.IsContainer() } // Container returns the name of the container ipc stack is going to be used. func (n IpcMode) Container() (idOrName string) { idOrName, _ = containerID(string(n)) return idOrName } // NetworkMode represents the container network stack. type NetworkMode string // IsNone indicates whether container isn't using a network stack. func (n NetworkMode) IsNone() bool { return n == network.NetworkNone } // IsDefault indicates whether container uses the default network stack. func (n NetworkMode) IsDefault() bool { return n == network.NetworkDefault } // IsPrivate indicates whether container uses its private network stack. func (n NetworkMode) IsPrivate() bool { return !(n.IsHost() || n.IsContainer()) } // IsContainer indicates whether container uses a container network stack. func (n NetworkMode) IsContainer() bool { _, ok := containerID(string(n)) return ok } // ConnectedContainer is the id of the container which network this container is connected to. func (n NetworkMode) ConnectedContainer() (idOrName string) { idOrName, _ = containerID(string(n)) return idOrName } // UserDefined indicates user-created network func (n NetworkMode) UserDefined() string { if n.IsUserDefined() { return string(n) } return "" } // UsernsMode represents userns mode in the container. type UsernsMode string // IsHost indicates whether the container uses the host's userns. func (n UsernsMode) IsHost() bool { return n == "host" } // IsPrivate indicates whether the container uses the a private userns. func (n UsernsMode) IsPrivate() bool { return !n.IsHost() } // Valid indicates whether the userns is valid. func (n UsernsMode) Valid() bool { return n == "" || n.IsHost() } // CgroupSpec represents the cgroup to use for the container. type CgroupSpec string // IsContainer indicates whether the container is using another container cgroup func (c CgroupSpec) IsContainer() bool { _, ok := containerID(string(c)) return ok } // Valid indicates whether the cgroup spec is valid. func (c CgroupSpec) Valid() bool { // TODO(thaJeztah): align with PidMode, and consider container-mode without a container name/ID to be invalid. return c == "" || c.IsContainer() } // Container returns the ID or name of the container whose cgroup will be used. func (c CgroupSpec) Container() (idOrName string) { idOrName, _ = containerID(string(c)) return idOrName } // UTSMode represents the UTS namespace of the container. type UTSMode string // IsPrivate indicates whether the container uses its private UTS namespace. func (n UTSMode) IsPrivate() bool { return !n.IsHost() } // IsHost indicates whether the container uses the host's UTS namespace. func (n UTSMode) IsHost() bool { return n == "host" } // Valid indicates whether the UTS namespace is valid. func (n UTSMode) Valid() bool { return n == "" || n.IsHost() } // PidMode represents the pid namespace of the container. type PidMode string // IsPrivate indicates whether the container uses its own new pid namespace. func (n PidMode) IsPrivate() bool { return !(n.IsHost() || n.IsContainer()) } // IsHost indicates whether the container uses the host's pid namespace. func (n PidMode) IsHost() bool { return n == "host" } // IsContainer indicates whether the container uses a container's pid namespace. func (n PidMode) IsContainer() bool { _, ok := containerID(string(n)) return ok } // Valid indicates whether the pid namespace is valid. func (n PidMode) Valid() bool { return n == "" || n.IsHost() || validContainer(string(n)) } // Container returns the name of the container whose pid namespace is going to be used. func (n PidMode) Container() (idOrName string) { idOrName, _ = containerID(string(n)) return idOrName } // DeviceRequest represents a request for devices from a device driver. // Used by GPU device drivers. type DeviceRequest struct { Driver string // Name of device driver Count int // Number of devices to request (-1 = All) DeviceIDs []string // List of device IDs as recognizable by the device driver Capabilities [][]string // An OR list of AND lists of device capabilities (e.g. "gpu") Options map[string]string // Options to pass onto the device driver } // DeviceMapping represents the device mapping between the host and the container. type DeviceMapping struct { PathOnHost string PathInContainer string CgroupPermissions string } // RestartPolicy represents the restart policies of the container. type RestartPolicy struct { Name RestartPolicyMode MaximumRetryCount int } type RestartPolicyMode string const ( RestartPolicyDisabled RestartPolicyMode = "no" RestartPolicyAlways RestartPolicyMode = "always" RestartPolicyOnFailure RestartPolicyMode = "on-failure" RestartPolicyUnlessStopped RestartPolicyMode = "unless-stopped" ) // IsNone indicates whether the container has the "no" restart policy. // This means the container will not automatically restart when exiting. func (rp *RestartPolicy) IsNone() bool { return rp.Name == RestartPolicyDisabled || rp.Name == "" } // IsAlways indicates whether the container has the "always" restart policy. // This means the container will automatically restart regardless of the exit status. func (rp *RestartPolicy) IsAlways() bool { return rp.Name == RestartPolicyAlways } // IsOnFailure indicates whether the container has the "on-failure" restart policy. // This means the container will automatically restart of exiting with a non-zero exit status. func (rp *RestartPolicy) IsOnFailure() bool { return rp.Name == RestartPolicyOnFailure } // IsUnlessStopped indicates whether the container has the // "unless-stopped" restart policy. This means the container will // automatically restart unless user has put it to stopped state. func (rp *RestartPolicy) IsUnlessStopped() bool { return rp.Name == RestartPolicyUnlessStopped } // IsSame compares two RestartPolicy to see if they are the same func (rp *RestartPolicy) IsSame(tp *RestartPolicy) bool { return rp.Name == tp.Name && rp.MaximumRetryCount == tp.MaximumRetryCount } // ValidateRestartPolicy validates the given RestartPolicy. func ValidateRestartPolicy(policy RestartPolicy) error { switch policy.Name { case RestartPolicyAlways, RestartPolicyUnlessStopped, RestartPolicyDisabled: if policy.MaximumRetryCount != 0 { msg := "invalid restart policy: maximum retry count can only be used with 'on-failure'" if policy.MaximumRetryCount < 0 { msg += " and cannot be negative" } return &errInvalidParameter{errors.New(msg)} } return nil case RestartPolicyOnFailure: if policy.MaximumRetryCount < 0 { return &errInvalidParameter{errors.New("invalid restart policy: maximum retry count cannot be negative")} } return nil case "": // Versions before v25.0.0 created an empty restart-policy "name" as // default. Allow an empty name with "any" MaximumRetryCount for // backward-compatibility. return nil default: return &errInvalidParameter{fmt.Errorf("invalid restart policy: unknown policy '%s'; use one of '%s', '%s', '%s', or '%s'", policy.Name, RestartPolicyDisabled, RestartPolicyAlways, RestartPolicyOnFailure, RestartPolicyUnlessStopped)} } } // LogMode is a type to define the available modes for logging // These modes affect how logs are handled when log messages start piling up. type LogMode string // Available logging modes const ( LogModeUnset LogMode = "" LogModeBlocking LogMode = "blocking" LogModeNonBlock LogMode = "non-blocking" ) // LogConfig represents the logging configuration of the container. type LogConfig struct { Type string Config map[string]string } // Ulimit is an alias for [units.Ulimit], which may be moving to a different // location or become a local type. This alias is to help transitioning. // // Users are recommended to use this alias instead of using [units.Ulimit] directly. type Ulimit = units.Ulimit // Resources contains container's resources (cgroups config, ulimits...) type Resources struct { // Applicable to all platforms CPUShares int64 `json:"CpuShares"` // CPU shares (relative weight vs. other containers) Memory int64 // Memory limit (in bytes) NanoCPUs int64 `json:"NanoCpus"` // CPU quota in units of 10-9 CPUs. // Applicable to UNIX platforms CgroupParent string // Parent cgroup. BlkioWeight uint16 // Block IO weight (relative weight vs. other containers) BlkioWeightDevice []*blkiodev.WeightDevice BlkioDeviceReadBps []*blkiodev.ThrottleDevice BlkioDeviceWriteBps []*blkiodev.ThrottleDevice BlkioDeviceReadIOps []*blkiodev.ThrottleDevice BlkioDeviceWriteIOps []*blkiodev.ThrottleDevice CPUPeriod int64 `json:"CpuPeriod"` // CPU CFS (Completely Fair Scheduler) period CPUQuota int64 `json:"CpuQuota"` // CPU CFS (Completely Fair Scheduler) quota CPURealtimePeriod int64 `json:"CpuRealtimePeriod"` // CPU real-time period CPURealtimeRuntime int64 `json:"CpuRealtimeRuntime"` // CPU real-time runtime CpusetCpus string // CpusetCpus 0-2, 0,1 CpusetMems string // CpusetMems 0-2, 0,1 Devices []DeviceMapping // List of devices to map inside the container DeviceCgroupRules []string // List of rule to be added to the device cgroup DeviceRequests []DeviceRequest // List of device requests for device drivers // KernelMemory specifies the kernel memory limit (in bytes) for the container. // Deprecated: kernel 5.4 deprecated kmem.limit_in_bytes. KernelMemory int64 `json:",omitempty"` KernelMemoryTCP int64 `json:",omitempty"` // Hard limit for kernel TCP buffer memory (in bytes) MemoryReservation int64 // Memory soft limit (in bytes) MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap MemorySwappiness *int64 // Tuning container memory swappiness behaviour OomKillDisable *bool // Whether to disable OOM Killer or not PidsLimit *int64 // Setting PIDs limit for a container; Set `0` or `-1` for unlimited, or `null` to not change. Ulimits []*Ulimit // List of ulimits to be set in the container // Applicable to Windows CPUCount int64 `json:"CpuCount"` // CPU count CPUPercent int64 `json:"CpuPercent"` // CPU percent IOMaximumIOps uint64 // Maximum IOps for the container system drive IOMaximumBandwidth uint64 // Maximum IO in bytes per second for the container system drive } // UpdateConfig holds the mutable attributes of a Container. // Those attributes can be updated at runtime. type UpdateConfig struct { // Contains container's resources (cgroups, ulimits) Resources RestartPolicy RestartPolicy } // HostConfig the non-portable Config structure of a container. // Here, "non-portable" means "dependent of the host we are running on". // Portable information *should* appear in Config. type HostConfig struct { // Applicable to all platforms Binds []string // List of volume bindings for this container ContainerIDFile string // File (path) where the containerId is written LogConfig LogConfig // Configuration of the logs for this container NetworkMode NetworkMode // Network mode to use for the container PortBindings nat.PortMap // Port mapping between the exposed port (container) and the host RestartPolicy RestartPolicy // Restart policy to be used for the container AutoRemove bool // Automatically remove container when it exits VolumeDriver string // Name of the volume driver used to mount volumes VolumesFrom []string // List of volumes to take from other container ConsoleSize [2]uint // Initial console size (height,width) Annotations map[string]string `json:",omitempty"` // Arbitrary non-identifying metadata attached to container and provided to the runtime // Applicable to UNIX platforms CapAdd strslice.StrSlice // List of kernel capabilities to add to the container CapDrop strslice.StrSlice // List of kernel capabilities to remove from the container CgroupnsMode CgroupnsMode // Cgroup namespace mode to use for the container DNS []string `json:"Dns"` // List of DNS server to lookup DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for ExtraHosts []string // List of extra hosts GroupAdd []string // List of additional groups that the container process will run as IpcMode IpcMode // IPC namespace to use for the container Cgroup CgroupSpec // Cgroup to use for the container Links []string // List of links (in the name:alias form) OomScoreAdj int // Container preference for OOM-killing PidMode PidMode // PID namespace to use for the container Privileged bool // Is the container in privileged mode PublishAllPorts bool // Should docker publish all exposed port for the container ReadonlyRootfs bool // Is the container root filesystem in read-only SecurityOpt []string // List of string values to customize labels for MLS systems, such as SELinux. StorageOpt map[string]string `json:",omitempty"` // Storage driver options per container. Tmpfs map[string]string `json:",omitempty"` // List of tmpfs (mounts) used for the container UTSMode UTSMode // UTS namespace to use for the container UsernsMode UsernsMode // The user namespace to use for the container ShmSize int64 // Total shm memory usage Sysctls map[string]string `json:",omitempty"` // List of Namespaced sysctls used for the container Runtime string `json:",omitempty"` // Runtime to use with this container // Applicable to Windows Isolation Isolation // Isolation technology of the container (e.g. default, hyperv) // Contains container's resources (cgroups, ulimits) Resources // Mounts specs used by the container Mounts []mount.Mount `json:",omitempty"` // MaskedPaths is the list of paths to be masked inside the container (this overrides the default set of paths) MaskedPaths []string // ReadonlyPaths is the list of paths to be set as read-only inside the container (this overrides the default set of paths) ReadonlyPaths []string // Run a custom init inside the container, if null, use the daemon's configured settings Init *bool `json:",omitempty"` } // containerID splits "container:" values. It returns the container // ID or name, and whether an ID/name was found. It returns an empty string and // a "false" if the value does not have a "container:" prefix. Further validation // of the returned, including checking if the value is empty, should be handled // by the caller. func containerID(val string) (idOrName string, ok bool) { k, v, hasSep := strings.Cut(val, ":") if !hasSep || k != "container" { return "", false } return v, true } // validContainer checks if the given value is a "container:" mode with // a non-empty name/ID. func validContainer(val string) bool { id, ok := containerID(val) return ok && id != "" } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/hostconfig_unix.go ================================================ //go:build !windows package container // import "github.com/docker/docker/api/types/container" import "github.com/docker/docker/api/types/network" // IsValid indicates if an isolation technology is valid func (i Isolation) IsValid() bool { return i.IsDefault() } // IsBridge indicates whether container uses the bridge network stack func (n NetworkMode) IsBridge() bool { return n == network.NetworkBridge } // IsHost indicates whether container uses the host network stack. func (n NetworkMode) IsHost() bool { return n == network.NetworkHost } // IsUserDefined indicates user-created network func (n NetworkMode) IsUserDefined() bool { return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer() } // NetworkName returns the name of the network stack. func (n NetworkMode) NetworkName() string { switch { case n.IsDefault(): return network.NetworkDefault case n.IsBridge(): return network.NetworkBridge case n.IsHost(): return network.NetworkHost case n.IsNone(): return network.NetworkNone case n.IsContainer(): return "container" case n.IsUserDefined(): return n.UserDefined() default: return "" } } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/hostconfig_windows.go ================================================ package container // import "github.com/docker/docker/api/types/container" import "github.com/docker/docker/api/types/network" // IsValid indicates if an isolation technology is valid func (i Isolation) IsValid() bool { return i.IsDefault() || i.IsHyperV() || i.IsProcess() } // IsBridge indicates whether container uses the bridge network stack // in windows it is given the name NAT func (n NetworkMode) IsBridge() bool { return n == network.NetworkNat } // IsHost indicates whether container uses the host network stack. // returns false as this is not supported by windows func (n NetworkMode) IsHost() bool { return false } // IsUserDefined indicates user-created network func (n NetworkMode) IsUserDefined() bool { return !n.IsDefault() && !n.IsNone() && !n.IsBridge() && !n.IsContainer() } // NetworkName returns the name of the network stack. func (n NetworkMode) NetworkName() string { switch { case n.IsDefault(): return network.NetworkDefault case n.IsBridge(): return network.NetworkNat case n.IsHost(): // Windows currently doesn't support host network-mode, so // this would currently never happen.. return network.NetworkHost case n.IsNone(): return network.NetworkNone case n.IsContainer(): return "container" case n.IsUserDefined(): return n.UserDefined() default: return "" } } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/network_settings.go ================================================ package container import ( "github.com/docker/docker/api/types/network" "github.com/docker/go-connections/nat" ) // NetworkSettings exposes the network settings in the api type NetworkSettings struct { NetworkSettingsBase DefaultNetworkSettings Networks map[string]*network.EndpointSettings } // NetworkSettingsBase holds networking state for a container when inspecting it. type NetworkSettingsBase struct { Bridge string // Bridge contains the name of the default bridge interface iff it was set through the daemon --bridge flag. SandboxID string // SandboxID uniquely represents a container's network stack SandboxKey string // SandboxKey identifies the sandbox Ports nat.PortMap // Ports is a collection of PortBinding indexed by Port // HairpinMode specifies if hairpin NAT should be enabled on the virtual interface // // Deprecated: This field is never set and will be removed in a future release. HairpinMode bool // LinkLocalIPv6Address is an IPv6 unicast address using the link-local prefix // // Deprecated: This field is never set and will be removed in a future release. LinkLocalIPv6Address string // LinkLocalIPv6PrefixLen is the prefix length of an IPv6 unicast address // // Deprecated: This field is never set and will be removed in a future release. LinkLocalIPv6PrefixLen int SecondaryIPAddresses []network.Address // Deprecated: This field is never set and will be removed in a future release. SecondaryIPv6Addresses []network.Address // Deprecated: This field is never set and will be removed in a future release. } // DefaultNetworkSettings holds network information // during the 2 release deprecation period. // It will be removed in Docker 1.11. type DefaultNetworkSettings struct { EndpointID string // EndpointID uniquely represents a service endpoint in a Sandbox Gateway string // Gateway holds the gateway address for the network GlobalIPv6Address string // GlobalIPv6Address holds network's global IPv6 address GlobalIPv6PrefixLen int // GlobalIPv6PrefixLen represents mask length of network's global IPv6 address IPAddress string // IPAddress holds the IPv4 address for the network IPPrefixLen int // IPPrefixLen represents mask length of network's IPv4 address IPv6Gateway string // IPv6Gateway holds gateway address specific for IPv6 MacAddress string // MacAddress holds the MAC address for the network } // NetworkSettingsSummary provides a summary of container's networks // in /containers/json type NetworkSettingsSummary struct { Networks map[string]*network.EndpointSettings } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/options.go ================================================ package container import "github.com/docker/docker/api/types/filters" // ResizeOptions holds parameters to resize a TTY. // It can be used to resize container TTYs and // exec process TTYs too. type ResizeOptions struct { Height uint Width uint } // AttachOptions holds parameters to attach to a container. type AttachOptions struct { Stream bool Stdin bool Stdout bool Stderr bool DetachKeys string Logs bool } // CommitOptions holds parameters to commit changes into a container. type CommitOptions struct { Reference string Comment string Author string Changes []string Pause bool Config *Config } // RemoveOptions holds parameters to remove containers. type RemoveOptions struct { RemoveVolumes bool RemoveLinks bool Force bool } // StartOptions holds parameters to start containers. type StartOptions struct { CheckpointID string CheckpointDir string } // ListOptions holds parameters to list containers with. type ListOptions struct { Size bool All bool Latest bool Since string Before string Limit int Filters filters.Args } // LogsOptions holds parameters to filter logs with. type LogsOptions struct { ShowStdout bool ShowStderr bool Since string Until string Timestamps bool Follow bool Tail string Details bool } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/port.go ================================================ package container // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // Port An open port on a container // swagger:model Port type Port struct { // Host IP address that the container's port is mapped to IP string `json:"IP,omitempty"` // Port on the container // Required: true PrivatePort uint16 `json:"PrivatePort"` // Port exposed on the host PublicPort uint16 `json:"PublicPort,omitempty"` // type // Required: true Type string `json:"Type"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/stats.go ================================================ package container import "time" // ThrottlingData stores CPU throttling stats of one running container. // Not used on Windows. type ThrottlingData struct { // Number of periods with throttling active Periods uint64 `json:"periods"` // Number of periods when the container hits its throttling limit. ThrottledPeriods uint64 `json:"throttled_periods"` // Aggregate time the container was throttled for in nanoseconds. ThrottledTime uint64 `json:"throttled_time"` } // CPUUsage stores All CPU stats aggregated since container inception. type CPUUsage struct { // Total CPU time consumed. // Units: nanoseconds (Linux) // Units: 100's of nanoseconds (Windows) TotalUsage uint64 `json:"total_usage"` // Total CPU time consumed per core (Linux). Not used on Windows. // Units: nanoseconds. PercpuUsage []uint64 `json:"percpu_usage,omitempty"` // Time spent by tasks of the cgroup in kernel mode (Linux). // Time spent by all container processes in kernel mode (Windows). // Units: nanoseconds (Linux). // Units: 100's of nanoseconds (Windows). Not populated for Hyper-V Containers. UsageInKernelmode uint64 `json:"usage_in_kernelmode"` // Time spent by tasks of the cgroup in user mode (Linux). // Time spent by all container processes in user mode (Windows). // Units: nanoseconds (Linux). // Units: 100's of nanoseconds (Windows). Not populated for Hyper-V Containers UsageInUsermode uint64 `json:"usage_in_usermode"` } // CPUStats aggregates and wraps all CPU related info of container type CPUStats struct { // CPU Usage. Linux and Windows. CPUUsage CPUUsage `json:"cpu_usage"` // System Usage. Linux only. SystemUsage uint64 `json:"system_cpu_usage,omitempty"` // Online CPUs. Linux only. OnlineCPUs uint32 `json:"online_cpus,omitempty"` // Throttling Data. Linux only. ThrottlingData ThrottlingData `json:"throttling_data,omitempty"` } // MemoryStats aggregates all memory stats since container inception on Linux. // Windows returns stats for commit and private working set only. type MemoryStats struct { // Linux Memory Stats // current res_counter usage for memory Usage uint64 `json:"usage,omitempty"` // maximum usage ever recorded. MaxUsage uint64 `json:"max_usage,omitempty"` // TODO(vishh): Export these as stronger types. // all the stats exported via memory.stat. Stats map[string]uint64 `json:"stats,omitempty"` // number of times memory usage hits limits. Failcnt uint64 `json:"failcnt,omitempty"` Limit uint64 `json:"limit,omitempty"` // Windows Memory Stats // See https://technet.microsoft.com/en-us/magazine/ff382715.aspx // committed bytes Commit uint64 `json:"commitbytes,omitempty"` // peak committed bytes CommitPeak uint64 `json:"commitpeakbytes,omitempty"` // private working set PrivateWorkingSet uint64 `json:"privateworkingset,omitempty"` } // BlkioStatEntry is one small entity to store a piece of Blkio stats // Not used on Windows. type BlkioStatEntry struct { Major uint64 `json:"major"` Minor uint64 `json:"minor"` Op string `json:"op"` Value uint64 `json:"value"` } // BlkioStats stores All IO service stats for data read and write. // This is a Linux specific structure as the differences between expressing // block I/O on Windows and Linux are sufficiently significant to make // little sense attempting to morph into a combined structure. type BlkioStats struct { // number of bytes transferred to and from the block device IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive"` IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recursive"` IoQueuedRecursive []BlkioStatEntry `json:"io_queue_recursive"` IoServiceTimeRecursive []BlkioStatEntry `json:"io_service_time_recursive"` IoWaitTimeRecursive []BlkioStatEntry `json:"io_wait_time_recursive"` IoMergedRecursive []BlkioStatEntry `json:"io_merged_recursive"` IoTimeRecursive []BlkioStatEntry `json:"io_time_recursive"` SectorsRecursive []BlkioStatEntry `json:"sectors_recursive"` } // StorageStats is the disk I/O stats for read/write on Windows. type StorageStats struct { ReadCountNormalized uint64 `json:"read_count_normalized,omitempty"` ReadSizeBytes uint64 `json:"read_size_bytes,omitempty"` WriteCountNormalized uint64 `json:"write_count_normalized,omitempty"` WriteSizeBytes uint64 `json:"write_size_bytes,omitempty"` } // NetworkStats aggregates the network stats of one container type NetworkStats struct { // Bytes received. Windows and Linux. RxBytes uint64 `json:"rx_bytes"` // Packets received. Windows and Linux. RxPackets uint64 `json:"rx_packets"` // Received errors. Not used on Windows. Note that we don't `omitempty` this // field as it is expected in the >=v1.21 API stats structure. RxErrors uint64 `json:"rx_errors"` // Incoming packets dropped. Windows and Linux. RxDropped uint64 `json:"rx_dropped"` // Bytes sent. Windows and Linux. TxBytes uint64 `json:"tx_bytes"` // Packets sent. Windows and Linux. TxPackets uint64 `json:"tx_packets"` // Sent errors. Not used on Windows. Note that we don't `omitempty` this // field as it is expected in the >=v1.21 API stats structure. TxErrors uint64 `json:"tx_errors"` // Outgoing packets dropped. Windows and Linux. TxDropped uint64 `json:"tx_dropped"` // Endpoint ID. Not used on Linux. EndpointID string `json:"endpoint_id,omitempty"` // Instance ID. Not used on Linux. InstanceID string `json:"instance_id,omitempty"` } // PidsStats contains the stats of a container's pids type PidsStats struct { // Current is the number of pids in the cgroup Current uint64 `json:"current,omitempty"` // Limit is the hard limit on the number of pids in the cgroup. // A "Limit" of 0 means that there is no limit. Limit uint64 `json:"limit,omitempty"` } // Stats is Ultimate struct aggregating all types of stats of one container // // Deprecated: use [StatsResponse] instead. This type will be removed in the next release. type Stats = StatsResponse // StatsResponse aggregates all types of stats of one container. type StatsResponse struct { Name string `json:"name,omitempty"` ID string `json:"id,omitempty"` // Common stats Read time.Time `json:"read"` PreRead time.Time `json:"preread"` // Linux specific stats, not populated on Windows. PidsStats PidsStats `json:"pids_stats,omitempty"` BlkioStats BlkioStats `json:"blkio_stats,omitempty"` // Windows specific stats, not populated on Linux. NumProcs uint32 `json:"num_procs"` StorageStats StorageStats `json:"storage_stats,omitempty"` // Shared stats CPUStats CPUStats `json:"cpu_stats,omitempty"` PreCPUStats CPUStats `json:"precpu_stats,omitempty"` // "Pre"="Previous" MemoryStats MemoryStats `json:"memory_stats,omitempty"` Networks map[string]NetworkStats `json:"networks,omitempty"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/top_response.go ================================================ package container // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // TopResponse ContainerTopResponse // // Container "top" response. // swagger:model TopResponse type TopResponse struct { // Each process running in the container, where each process // is an array of values corresponding to the titles. Processes [][]string `json:"Processes"` // The ps column titles Titles []string `json:"Titles"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/update_response.go ================================================ package container // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // UpdateResponse ContainerUpdateResponse // // Response for a successful container-update. // swagger:model UpdateResponse type UpdateResponse struct { // Warnings encountered when updating the container. Warnings []string `json:"Warnings"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/wait_exit_error.go ================================================ package container // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // WaitExitError container waiting error, if any // swagger:model WaitExitError type WaitExitError struct { // Details of an error Message string `json:"Message,omitempty"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/wait_response.go ================================================ package container // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // WaitResponse ContainerWaitResponse // // OK response to ContainerWait operation // swagger:model WaitResponse type WaitResponse struct { // error Error *WaitExitError `json:"Error,omitempty"` // Exit code of the container // Required: true StatusCode int64 `json:"StatusCode"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/container/waitcondition.go ================================================ package container // import "github.com/docker/docker/api/types/container" // WaitCondition is a type used to specify a container state for which // to wait. type WaitCondition string // Possible WaitCondition Values. // // WaitConditionNotRunning (default) is used to wait for any of the non-running // states: "created", "exited", "dead", "removing", or "removed". // // WaitConditionNextExit is used to wait for the next time the state changes // to a non-running state. If the state is currently "created" or "exited", // this would cause Wait() to block until either the container runs and exits // or is removed. // // WaitConditionRemoved is used to wait for the container to be removed. const ( WaitConditionNotRunning WaitCondition = "not-running" WaitConditionNextExit WaitCondition = "next-exit" WaitConditionRemoved WaitCondition = "removed" ) ================================================ FILE: vendor/github.com/docker/docker/api/types/error_response.go ================================================ package types // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // ErrorResponse Represents an error. // swagger:model ErrorResponse type ErrorResponse struct { // The error message. // Required: true Message string `json:"message"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/error_response_ext.go ================================================ package types // Error returns the error message func (e ErrorResponse) Error() string { return e.Message } ================================================ FILE: vendor/github.com/docker/docker/api/types/events/events.go ================================================ package events // import "github.com/docker/docker/api/types/events" import "github.com/docker/docker/api/types/filters" // Type is used for event-types. type Type string // List of known event types. const ( BuilderEventType Type = "builder" // BuilderEventType is the event type that the builder generates. ConfigEventType Type = "config" // ConfigEventType is the event type that configs generate. ContainerEventType Type = "container" // ContainerEventType is the event type that containers generate. DaemonEventType Type = "daemon" // DaemonEventType is the event type that daemon generate. ImageEventType Type = "image" // ImageEventType is the event type that images generate. NetworkEventType Type = "network" // NetworkEventType is the event type that networks generate. NodeEventType Type = "node" // NodeEventType is the event type that nodes generate. PluginEventType Type = "plugin" // PluginEventType is the event type that plugins generate. SecretEventType Type = "secret" // SecretEventType is the event type that secrets generate. ServiceEventType Type = "service" // ServiceEventType is the event type that services generate. VolumeEventType Type = "volume" // VolumeEventType is the event type that volumes generate. ) // Action is used for event-actions. type Action string const ( ActionCreate Action = "create" ActionStart Action = "start" ActionRestart Action = "restart" ActionStop Action = "stop" ActionCheckpoint Action = "checkpoint" ActionPause Action = "pause" ActionUnPause Action = "unpause" ActionAttach Action = "attach" ActionDetach Action = "detach" ActionResize Action = "resize" ActionUpdate Action = "update" ActionRename Action = "rename" ActionKill Action = "kill" ActionDie Action = "die" ActionOOM Action = "oom" ActionDestroy Action = "destroy" ActionRemove Action = "remove" ActionCommit Action = "commit" ActionTop Action = "top" ActionCopy Action = "copy" ActionArchivePath Action = "archive-path" ActionExtractToDir Action = "extract-to-dir" ActionExport Action = "export" ActionImport Action = "import" ActionSave Action = "save" ActionLoad Action = "load" ActionTag Action = "tag" ActionUnTag Action = "untag" ActionPush Action = "push" ActionPull Action = "pull" ActionPrune Action = "prune" ActionDelete Action = "delete" ActionEnable Action = "enable" ActionDisable Action = "disable" ActionConnect Action = "connect" ActionDisconnect Action = "disconnect" ActionReload Action = "reload" ActionMount Action = "mount" ActionUnmount Action = "unmount" // ActionExecCreate is the prefix used for exec_create events. These // event-actions are commonly followed by a colon and space (": "), // and the command that's defined for the exec, for example: // // exec_create: /bin/sh -c 'echo hello' // // This is far from ideal; it's a compromise to allow filtering and // to preserve backward-compatibility. ActionExecCreate Action = "exec_create" // ActionExecStart is the prefix used for exec_create events. These // event-actions are commonly followed by a colon and space (": "), // and the command that's defined for the exec, for example: // // exec_start: /bin/sh -c 'echo hello' // // This is far from ideal; it's a compromise to allow filtering and // to preserve backward-compatibility. ActionExecStart Action = "exec_start" ActionExecDie Action = "exec_die" ActionExecDetach Action = "exec_detach" // ActionHealthStatus is the prefix to use for health_status events. // // Health-status events can either have a pre-defined status, in which // case the "health_status" action is followed by a colon, or can be // "free-form", in which case they're followed by the output of the // health-check output. // // This is far form ideal, and a compromise to allow filtering, and // to preserve backward-compatibility. ActionHealthStatus Action = "health_status" ActionHealthStatusRunning Action = "health_status: running" ActionHealthStatusHealthy Action = "health_status: healthy" ActionHealthStatusUnhealthy Action = "health_status: unhealthy" ) // Actor describes something that generates events, // like a container, or a network, or a volume. // It has a defined name and a set of attributes. // The container attributes are its labels, other actors // can generate these attributes from other properties. type Actor struct { ID string Attributes map[string]string } // Message represents the information an event contains type Message struct { // Deprecated information from JSONMessage. // With data only in container events. Status string `json:"status,omitempty"` // Deprecated: use Action instead. ID string `json:"id,omitempty"` // Deprecated: use Actor.ID instead. From string `json:"from,omitempty"` // Deprecated: use Actor.Attributes["image"] instead. Type Type Action Action Actor Actor // Engine events are local scope. Cluster events are swarm scope. Scope string `json:"scope,omitempty"` Time int64 `json:"time,omitempty"` TimeNano int64 `json:"timeNano,omitempty"` } // ListOptions holds parameters to filter events with. type ListOptions struct { Since string Until string Filters filters.Args } ================================================ FILE: vendor/github.com/docker/docker/api/types/filters/errors.go ================================================ package filters import "fmt" // invalidFilter indicates that the provided filter or its value is invalid type invalidFilter struct { Filter string Value []string } func (e invalidFilter) Error() string { msg := "invalid filter" if e.Filter != "" { msg += " '" + e.Filter if e.Value != nil { msg = fmt.Sprintf("%s=%s", msg, e.Value) } msg += "'" } return msg } // InvalidParameter marks this error as ErrInvalidParameter func (e invalidFilter) InvalidParameter() {} ================================================ FILE: vendor/github.com/docker/docker/api/types/filters/parse.go ================================================ /* Package filters provides tools for encoding a mapping of keys to a set of multiple values. */ package filters // import "github.com/docker/docker/api/types/filters" import ( "encoding/json" "regexp" "strings" "github.com/docker/docker/api/types/versions" ) // Args stores a mapping of keys to a set of multiple values. type Args struct { fields map[string]map[string]bool } // KeyValuePair are used to initialize a new Args type KeyValuePair struct { Key string Value string } // Arg creates a new KeyValuePair for initializing Args func Arg(key, value string) KeyValuePair { return KeyValuePair{Key: key, Value: value} } // NewArgs returns a new Args populated with the initial args func NewArgs(initialArgs ...KeyValuePair) Args { args := Args{fields: map[string]map[string]bool{}} for _, arg := range initialArgs { args.Add(arg.Key, arg.Value) } return args } // Keys returns all the keys in list of Args func (args Args) Keys() []string { keys := make([]string, 0, len(args.fields)) for k := range args.fields { keys = append(keys, k) } return keys } // MarshalJSON returns a JSON byte representation of the Args func (args Args) MarshalJSON() ([]byte, error) { if len(args.fields) == 0 { return []byte("{}"), nil } return json.Marshal(args.fields) } // ToJSON returns the Args as a JSON encoded string func ToJSON(a Args) (string, error) { if a.Len() == 0 { return "", nil } buf, err := json.Marshal(a) return string(buf), err } // ToParamWithVersion encodes Args as a JSON string. If version is less than 1.22 // then the encoded format will use an older legacy format where the values are a // list of strings, instead of a set. // // Deprecated: do not use in any new code; use ToJSON instead func ToParamWithVersion(version string, a Args) (string, error) { if a.Len() == 0 { return "", nil } if version != "" && versions.LessThan(version, "1.22") { buf, err := json.Marshal(convertArgsToSlice(a.fields)) return string(buf), err } return ToJSON(a) } // FromJSON decodes a JSON encoded string into Args func FromJSON(p string) (Args, error) { args := NewArgs() if p == "" { return args, nil } raw := []byte(p) err := json.Unmarshal(raw, &args) if err == nil { return args, nil } // Fallback to parsing arguments in the legacy slice format deprecated := map[string][]string{} if legacyErr := json.Unmarshal(raw, &deprecated); legacyErr != nil { return args, &invalidFilter{} } args.fields = deprecatedArgs(deprecated) return args, nil } // UnmarshalJSON populates the Args from JSON encode bytes func (args Args) UnmarshalJSON(raw []byte) error { return json.Unmarshal(raw, &args.fields) } // Get returns the list of values associated with the key func (args Args) Get(key string) []string { values := args.fields[key] if values == nil { return make([]string, 0) } slice := make([]string, 0, len(values)) for key := range values { slice = append(slice, key) } return slice } // Add a new value to the set of values func (args Args) Add(key, value string) { if _, ok := args.fields[key]; ok { args.fields[key][value] = true } else { args.fields[key] = map[string]bool{value: true} } } // Del removes a value from the set func (args Args) Del(key, value string) { if _, ok := args.fields[key]; ok { delete(args.fields[key], value) if len(args.fields[key]) == 0 { delete(args.fields, key) } } } // Len returns the number of keys in the mapping func (args Args) Len() int { return len(args.fields) } // MatchKVList returns true if all the pairs in sources exist as key=value // pairs in the mapping at key, or if there are no values at key. func (args Args) MatchKVList(key string, sources map[string]string) bool { fieldValues := args.fields[key] // do not filter if there is no filter set or cannot determine filter if len(fieldValues) == 0 { return true } if len(sources) == 0 { return false } for value := range fieldValues { testK, testV, hasValue := strings.Cut(value, "=") v, ok := sources[testK] if !ok { return false } if hasValue && testV != v { return false } } return true } // Match returns true if any of the values at key match the source string func (args Args) Match(field, source string) bool { if args.ExactMatch(field, source) { return true } fieldValues := args.fields[field] for name2match := range fieldValues { match, err := regexp.MatchString(name2match, source) if err != nil { continue } if match { return true } } return false } // GetBoolOrDefault returns a boolean value of the key if the key is present // and is interpretable as a boolean value. Otherwise the default value is returned. // Error is not nil only if the filter values are not valid boolean or are conflicting. func (args Args) GetBoolOrDefault(key string, defaultValue bool) (bool, error) { fieldValues, ok := args.fields[key] if !ok { return defaultValue, nil } if len(fieldValues) == 0 { return defaultValue, &invalidFilter{key, nil} } isFalse := fieldValues["0"] || fieldValues["false"] isTrue := fieldValues["1"] || fieldValues["true"] if isFalse == isTrue { // Either no or conflicting truthy/falsy value were provided return defaultValue, &invalidFilter{key, args.Get(key)} } return isTrue, nil } // ExactMatch returns true if the source matches exactly one of the values. func (args Args) ExactMatch(key, source string) bool { fieldValues, ok := args.fields[key] // do not filter if there is no filter set or cannot determine filter if !ok || len(fieldValues) == 0 { return true } // try to match full name value to avoid O(N) regular expression matching return fieldValues[source] } // UniqueExactMatch returns true if there is only one value and the source // matches exactly the value. func (args Args) UniqueExactMatch(key, source string) bool { fieldValues := args.fields[key] // do not filter if there is no filter set or cannot determine filter if len(fieldValues) == 0 { return true } if len(args.fields[key]) != 1 { return false } // try to match full name value to avoid O(N) regular expression matching return fieldValues[source] } // FuzzyMatch returns true if the source matches exactly one value, or the // source has one of the values as a prefix. func (args Args) FuzzyMatch(key, source string) bool { if args.ExactMatch(key, source) { return true } fieldValues := args.fields[key] for prefix := range fieldValues { if strings.HasPrefix(source, prefix) { return true } } return false } // Contains returns true if the key exists in the mapping func (args Args) Contains(field string) bool { _, ok := args.fields[field] return ok } // Validate compared the set of accepted keys against the keys in the mapping. // An error is returned if any mapping keys are not in the accepted set. func (args Args) Validate(accepted map[string]bool) error { for name := range args.fields { if !accepted[name] { return &invalidFilter{name, nil} } } return nil } // WalkValues iterates over the list of values for a key in the mapping and calls // op() for each value. If op returns an error the iteration stops and the // error is returned. func (args Args) WalkValues(field string, op func(value string) error) error { if _, ok := args.fields[field]; !ok { return nil } for v := range args.fields[field] { if err := op(v); err != nil { return err } } return nil } // Clone returns a copy of args. func (args Args) Clone() (newArgs Args) { newArgs.fields = make(map[string]map[string]bool, len(args.fields)) for k, m := range args.fields { var mm map[string]bool if m != nil { mm = make(map[string]bool, len(m)) for kk, v := range m { mm[kk] = v } } newArgs.fields[k] = mm } return newArgs } func deprecatedArgs(d map[string][]string) map[string]map[string]bool { m := map[string]map[string]bool{} for k, v := range d { values := map[string]bool{} for _, vv := range v { values[vv] = true } m[k] = values } return m } func convertArgsToSlice(f map[string]map[string]bool) map[string][]string { m := map[string][]string{} for k, v := range f { values := []string{} for kk := range v { if v[kk] { values = append(values, kk) } } m[k] = values } return m } ================================================ FILE: vendor/github.com/docker/docker/api/types/image/delete_response.go ================================================ package image // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // DeleteResponse delete response // swagger:model DeleteResponse type DeleteResponse struct { // The image ID of an image that was deleted Deleted string `json:"Deleted,omitempty"` // The image ID of an image that was untagged Untagged string `json:"Untagged,omitempty"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/image/image.go ================================================ package image import ( "io" "time" ) // Metadata contains engine-local data about the image. type Metadata struct { // LastTagTime is the date and time at which the image was last tagged. LastTagTime time.Time `json:",omitempty"` } // PruneReport contains the response for Engine API: // POST "/images/prune" type PruneReport struct { ImagesDeleted []DeleteResponse SpaceReclaimed uint64 } // LoadResponse returns information to the client about a load process. // // TODO(thaJeztah): remove this type, and just use an io.ReadCloser // // This type was added in https://github.com/moby/moby/pull/18878, related // to https://github.com/moby/moby/issues/19177; // // Make docker load to output json when the response content type is json // Swarm hijacks the response from docker load and returns JSON rather // than plain text like the Engine does. This makes the API library to return // information to figure that out. // // However the "load" endpoint unconditionally returns JSON; // https://github.com/moby/moby/blob/7b9d2ef6e5518a3d3f3cc418459f8df786cfbbd1/api/server/router/image/image_routes.go#L248-L255 // // PR https://github.com/moby/moby/pull/21959 made the response-type depend // on whether "quiet" was set, but this logic got changed in a follow-up // https://github.com/moby/moby/pull/25557, which made the JSON response-type // unconditionally, but the output produced depend on whether"quiet" was set. // // We should deprecated the "quiet" option, as it's really a client // responsibility. type LoadResponse struct { // Body must be closed to avoid a resource leak Body io.ReadCloser JSON bool } ================================================ FILE: vendor/github.com/docker/docker/api/types/image/image_history.go ================================================ package image // import "github.com/docker/docker/api/types/image" // ---------------------------------------------------------------------------- // Code generated by `swagger generate operation`. DO NOT EDIT. // // See hack/generate-swagger-api.sh // ---------------------------------------------------------------------------- // HistoryResponseItem individual image layer information in response to ImageHistory operation // swagger:model HistoryResponseItem type HistoryResponseItem struct { // comment // Required: true Comment string `json:"Comment"` // created // Required: true Created int64 `json:"Created"` // created by // Required: true CreatedBy string `json:"CreatedBy"` // Id // Required: true ID string `json:"Id"` // size // Required: true Size int64 `json:"Size"` // tags // Required: true Tags []string `json:"Tags"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/image/image_inspect.go ================================================ package image import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/storage" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // RootFS returns Image's RootFS description including the layer IDs. type RootFS struct { Type string `json:",omitempty"` Layers []string `json:",omitempty"` } // InspectResponse contains response of Engine API: // GET "/images/{name:.*}/json" type InspectResponse struct { // ID is the content-addressable ID of an image. // // This identifier is a content-addressable digest calculated from the // image's configuration (which includes the digests of layers used by // the image). // // Note that this digest differs from the `RepoDigests` below, which // holds digests of image manifests that reference the image. ID string `json:"Id"` // RepoTags is a list of image names/tags in the local image cache that // reference this image. // // Multiple image tags can refer to the same image, and this list may be // empty if no tags reference the image, in which case the image is // "untagged", in which case it can still be referenced by its ID. RepoTags []string // RepoDigests is a list of content-addressable digests of locally available // image manifests that the image is referenced from. Multiple manifests can // refer to the same image. // // These digests are usually only available if the image was either pulled // from a registry, or if the image was pushed to a registry, which is when // the manifest is generated and its digest calculated. RepoDigests []string // Parent is the ID of the parent image. // // Depending on how the image was created, this field may be empty and // is only set for images that were built/created locally. This field // is empty if the image was pulled from an image registry. Parent string // Comment is an optional message that can be set when committing or // importing the image. Comment string // Created is the date and time at which the image was created, formatted in // RFC 3339 nano-seconds (time.RFC3339Nano). // // This information is only available if present in the image, // and omitted otherwise. Created string `json:",omitempty"` // Container is the ID of the container that was used to create the image. // // Depending on how the image was created, this field may be empty. // // Deprecated: this field is omitted in API v1.45, but kept for backward compatibility. Container string `json:",omitempty"` // ContainerConfig is an optional field containing the configuration of the // container that was last committed when creating the image. // // Previous versions of Docker builder used this field to store build cache, // and it is not in active use anymore. // // Deprecated: this field is omitted in API v1.45, but kept for backward compatibility. ContainerConfig *container.Config `json:",omitempty"` // DockerVersion is the version of Docker that was used to build the image. // // Depending on how the image was created, this field may be empty. DockerVersion string // Author is the name of the author that was specified when committing the // image, or as specified through MAINTAINER (deprecated) in the Dockerfile. Author string Config *container.Config // Architecture is the hardware CPU architecture that the image runs on. Architecture string // Variant is the CPU architecture variant (presently ARM-only). Variant string `json:",omitempty"` // OS is the Operating System the image is built to run on. Os string // OsVersion is the version of the Operating System the image is built to // run on (especially for Windows). OsVersion string `json:",omitempty"` // Size is the total size of the image including all layers it is composed of. Size int64 // VirtualSize is the total size of the image including all layers it is // composed of. // // Deprecated: this field is omitted in API v1.44, but kept for backward compatibility. Use Size instead. VirtualSize int64 `json:"VirtualSize,omitempty"` // GraphDriver holds information about the storage driver used to store the // container's and image's filesystem. GraphDriver storage.DriverData // RootFS contains information about the image's RootFS, including the // layer IDs. RootFS RootFS // Metadata of the image in the local cache. // // This information is local to the daemon, and not part of the image itself. Metadata Metadata // Descriptor is the OCI descriptor of the image target. // It's only set if the daemon provides a multi-platform image store. // // WARNING: This is experimental and may change at any time without any backward // compatibility. Descriptor *ocispec.Descriptor `json:"Descriptor,omitempty"` // Manifests is a list of image manifests available in this image. It // provides a more detailed view of the platform-specific image manifests or // other image-attached data like build attestations. // // Only available if the daemon provides a multi-platform image store. // // WARNING: This is experimental and may change at any time without any backward // compatibility. Manifests []ManifestSummary `json:"Manifests,omitempty"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/image/manifest.go ================================================ package image import ( "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) type ManifestKind string const ( ManifestKindImage ManifestKind = "image" ManifestKindAttestation ManifestKind = "attestation" ManifestKindUnknown ManifestKind = "unknown" ) type ManifestSummary struct { // ID is the content-addressable ID of an image and is the same as the // digest of the image manifest. // // Required: true ID string `json:"ID"` // Descriptor is the OCI descriptor of the image. // // Required: true Descriptor ocispec.Descriptor `json:"Descriptor"` // Indicates whether all the child content (image config, layers) is // fully available locally // // Required: true Available bool `json:"Available"` // Size is the size information of the content related to this manifest. // Note: These sizes only take the locally available content into account. // // Required: true Size struct { // Content is the size (in bytes) of all the locally present // content in the content store (e.g. image config, layers) // referenced by this manifest and its children. // This only includes blobs in the content store. Content int64 `json:"Content"` // Total is the total size (in bytes) of all the locally present // data (both distributable and non-distributable) that's related to // this manifest and its children. // This equal to the sum of [Content] size AND all the sizes in the // [Size] struct present in the Kind-specific data struct. // For example, for an image kind (Kind == ManifestKindImage), // this would include the size of the image content and unpacked // image snapshots ([Size.Content] + [ImageData.Size.Unpacked]). Total int64 `json:"Total"` } `json:"Size"` // Kind is the kind of the image manifest. // // Required: true Kind ManifestKind `json:"Kind"` // Fields below are specific to the kind of the image manifest. // Present only if Kind == ManifestKindImage. ImageData *ImageProperties `json:"ImageData,omitempty"` // Present only if Kind == ManifestKindAttestation. AttestationData *AttestationProperties `json:"AttestationData,omitempty"` } type ImageProperties struct { // Platform is the OCI platform object describing the platform of the image. // // Required: true Platform ocispec.Platform `json:"Platform"` Size struct { // Unpacked is the size (in bytes) of the locally unpacked // (uncompressed) image content that's directly usable by the containers // running this image. // It's independent of the distributable content - e.g. // the image might still have an unpacked data that's still used by // some container even when the distributable/compressed content is // already gone. // // Required: true Unpacked int64 `json:"Unpacked"` } // Containers is an array containing the IDs of the containers that are // using this image. // // Required: true Containers []string `json:"Containers"` } type AttestationProperties struct { // For is the digest of the image manifest that this attestation is for. For digest.Digest `json:"For"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/image/opts.go ================================================ package image import ( "context" "io" "github.com/docker/docker/api/types/filters" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // ImportSource holds source information for ImageImport type ImportSource struct { Source io.Reader // Source is the data to send to the server to create this image from. You must set SourceName to "-" to leverage this. SourceName string // SourceName is the name of the image to pull. Set to "-" to leverage the Source attribute. } // ImportOptions holds information to import images from the client host. type ImportOptions struct { Tag string // Tag is the name to tag this image with. This attribute is deprecated. Message string // Message is the message to tag the image with Changes []string // Changes are the raw changes to apply to this image Platform string // Platform is the target platform of the image } // CreateOptions holds information to create images. type CreateOptions struct { RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry. Platform string // Platform is the target platform of the image if it needs to be pulled from the registry. } // PullOptions holds information to pull images. type PullOptions struct { All bool RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry // PrivilegeFunc is a function that clients can supply to retry operations // after getting an authorization error. This function returns the registry // authentication header value in base64 encoded format, or an error if the // privilege request fails. // // For details, refer to [github.com/docker/docker/api/types/registry.RequestAuthConfig]. PrivilegeFunc func(context.Context) (string, error) Platform string } // PushOptions holds information to push images. type PushOptions struct { All bool RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry // PrivilegeFunc is a function that clients can supply to retry operations // after getting an authorization error. This function returns the registry // authentication header value in base64 encoded format, or an error if the // privilege request fails. // // For details, refer to [github.com/docker/docker/api/types/registry.RequestAuthConfig]. PrivilegeFunc func(context.Context) (string, error) // Platform is an optional field that selects a specific platform to push // when the image is a multi-platform image. // Using this will only push a single platform-specific manifest. Platform *ocispec.Platform `json:",omitempty"` } // ListOptions holds parameters to list images with. type ListOptions struct { // All controls whether all images in the graph are filtered, or just // the heads. All bool // Filters is a JSON-encoded set of filter arguments. Filters filters.Args // SharedSize indicates whether the shared size of images should be computed. SharedSize bool // ContainerCount indicates whether container count should be computed. ContainerCount bool // Manifests indicates whether the image manifests should be returned. Manifests bool } // RemoveOptions holds parameters to remove images. type RemoveOptions struct { Force bool PruneChildren bool } // HistoryOptions holds parameters to get image history. type HistoryOptions struct { // Platform from the manifest list to use for history. Platform *ocispec.Platform } // LoadOptions holds parameters to load images. type LoadOptions struct { // Quiet suppresses progress output Quiet bool // Platforms selects the platforms to load if the image is a // multi-platform image and has multiple variants. Platforms []ocispec.Platform } type InspectOptions struct { // Manifests returns the image manifests. Manifests bool } // SaveOptions holds parameters to save images. type SaveOptions struct { // Platforms selects the platforms to save if the image is a // multi-platform image and has multiple variants. Platforms []ocispec.Platform } ================================================ FILE: vendor/github.com/docker/docker/api/types/image/summary.go ================================================ package image import ocispec "github.com/opencontainers/image-spec/specs-go/v1" type Summary struct { // Number of containers using this image. Includes both stopped and running // containers. // // This size is not calculated by default, and depends on which API endpoint // is used. `-1` indicates that the value has not been set / calculated. // // Required: true Containers int64 `json:"Containers"` // Date and time at which the image was created as a Unix timestamp // (number of seconds since EPOCH). // // Required: true Created int64 `json:"Created"` // ID is the content-addressable ID of an image. // // This identifier is a content-addressable digest calculated from the // image's configuration (which includes the digests of layers used by // the image). // // Note that this digest differs from the `RepoDigests` below, which // holds digests of image manifests that reference the image. // // Required: true ID string `json:"Id"` // User-defined key/value metadata. // Required: true Labels map[string]string `json:"Labels"` // ID of the parent image. // // Depending on how the image was created, this field may be empty and // is only set for images that were built/created locally. This field // is empty if the image was pulled from an image registry. // // Required: true ParentID string `json:"ParentId"` // Descriptor is the OCI descriptor of the image target. // It's only set if the daemon provides a multi-platform image store. // // WARNING: This is experimental and may change at any time without any backward // compatibility. Descriptor *ocispec.Descriptor `json:"Descriptor,omitempty"` // Manifests is a list of image manifests available in this image. It // provides a more detailed view of the platform-specific image manifests or // other image-attached data like build attestations. // // WARNING: This is experimental and may change at any time without any backward // compatibility. Manifests []ManifestSummary `json:"Manifests,omitempty"` // List of content-addressable digests of locally available image manifests // that the image is referenced from. Multiple manifests can refer to the // same image. // // These digests are usually only available if the image was either pulled // from a registry, or if the image was pushed to a registry, which is when // the manifest is generated and its digest calculated. // // Required: true RepoDigests []string `json:"RepoDigests"` // List of image names/tags in the local image cache that reference this // image. // // Multiple image tags can refer to the same image, and this list may be // empty if no tags reference the image, in which case the image is // "untagged", in which case it can still be referenced by its ID. // // Required: true RepoTags []string `json:"RepoTags"` // Total size of image layers that are shared between this image and other // images. // // This size is not calculated by default. `-1` indicates that the value // has not been set / calculated. // // Required: true SharedSize int64 `json:"SharedSize"` // Total size of the image including all layers it is composed of. // // Required: true Size int64 `json:"Size"` // Total size of the image including all layers it is composed of. // // Deprecated: this field is omitted in API v1.44, but kept for backward compatibility. Use Size instead. VirtualSize int64 `json:"VirtualSize,omitempty"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/mount/mount.go ================================================ package mount // import "github.com/docker/docker/api/types/mount" import ( "os" ) // Type represents the type of a mount. type Type string // Type constants const ( // TypeBind is the type for mounting host dir TypeBind Type = "bind" // TypeVolume is the type for remote storage volumes TypeVolume Type = "volume" // TypeTmpfs is the type for mounting tmpfs TypeTmpfs Type = "tmpfs" // TypeNamedPipe is the type for mounting Windows named pipes TypeNamedPipe Type = "npipe" // TypeCluster is the type for Swarm Cluster Volumes. TypeCluster Type = "cluster" // TypeImage is the type for mounting another image's filesystem TypeImage Type = "image" ) // Mount represents a mount (volume). type Mount struct { Type Type `json:",omitempty"` // Source specifies the name of the mount. Depending on mount type, this // may be a volume name or a host path, or even ignored. // Source is not supported for tmpfs (must be an empty value) Source string `json:",omitempty"` Target string `json:",omitempty"` ReadOnly bool `json:",omitempty"` // attempts recursive read-only if possible Consistency Consistency `json:",omitempty"` BindOptions *BindOptions `json:",omitempty"` VolumeOptions *VolumeOptions `json:",omitempty"` ImageOptions *ImageOptions `json:",omitempty"` TmpfsOptions *TmpfsOptions `json:",omitempty"` ClusterOptions *ClusterOptions `json:",omitempty"` } // Propagation represents the propagation of a mount. type Propagation string const ( // PropagationRPrivate RPRIVATE PropagationRPrivate Propagation = "rprivate" // PropagationPrivate PRIVATE PropagationPrivate Propagation = "private" // PropagationRShared RSHARED PropagationRShared Propagation = "rshared" // PropagationShared SHARED PropagationShared Propagation = "shared" // PropagationRSlave RSLAVE PropagationRSlave Propagation = "rslave" // PropagationSlave SLAVE PropagationSlave Propagation = "slave" ) // Propagations is the list of all valid mount propagations var Propagations = []Propagation{ PropagationRPrivate, PropagationPrivate, PropagationRShared, PropagationShared, PropagationRSlave, PropagationSlave, } // Consistency represents the consistency requirements of a mount. type Consistency string const ( // ConsistencyFull guarantees bind mount-like consistency ConsistencyFull Consistency = "consistent" // ConsistencyCached mounts can cache read data and FS structure ConsistencyCached Consistency = "cached" // ConsistencyDelegated mounts can cache read and written data and structure ConsistencyDelegated Consistency = "delegated" // ConsistencyDefault provides "consistent" behavior unless overridden ConsistencyDefault Consistency = "default" ) // BindOptions defines options specific to mounts of type "bind". type BindOptions struct { Propagation Propagation `json:",omitempty"` NonRecursive bool `json:",omitempty"` CreateMountpoint bool `json:",omitempty"` // ReadOnlyNonRecursive makes the mount non-recursively read-only, but still leaves the mount recursive // (unless NonRecursive is set to true in conjunction). ReadOnlyNonRecursive bool `json:",omitempty"` // ReadOnlyForceRecursive raises an error if the mount cannot be made recursively read-only. ReadOnlyForceRecursive bool `json:",omitempty"` } // VolumeOptions represents the options for a mount of type volume. type VolumeOptions struct { NoCopy bool `json:",omitempty"` Labels map[string]string `json:",omitempty"` Subpath string `json:",omitempty"` DriverConfig *Driver `json:",omitempty"` } type ImageOptions struct { Subpath string `json:",omitempty"` } // Driver represents a volume driver. type Driver struct { Name string `json:",omitempty"` Options map[string]string `json:",omitempty"` } // TmpfsOptions defines options specific to mounts of type "tmpfs". type TmpfsOptions struct { // Size sets the size of the tmpfs, in bytes. // // This will be converted to an operating system specific value // depending on the host. For example, on linux, it will be converted to // use a 'k', 'm' or 'g' syntax. BSD, though not widely supported with // docker, uses a straight byte value. // // Percentages are not supported. SizeBytes int64 `json:",omitempty"` // Mode of the tmpfs upon creation Mode os.FileMode `json:",omitempty"` // Options to be passed to the tmpfs mount. An array of arrays. Flag // options should be provided as 1-length arrays. Other types should be // provided as 2-length arrays, where the first item is the key and the // second the value. Options [][]string `json:",omitempty"` // TODO(stevvooe): There are several more tmpfs flags, specified in the // daemon, that are accepted. Only the most basic are added for now. // // From https://github.com/moby/sys/blob/mount/v0.1.1/mount/flags.go#L47-L56 // // var validFlags = map[string]bool{ // "": true, // "size": true, X // "mode": true, X // "uid": true, // "gid": true, // "nr_inodes": true, // "nr_blocks": true, // "mpol": true, // } // // Some of these may be straightforward to add, but others, such as // uid/gid have implications in a clustered system. } // ClusterOptions specifies options for a Cluster volume. type ClusterOptions struct { // intentionally empty } ================================================ FILE: vendor/github.com/docker/docker/api/types/network/create_response.go ================================================ package network // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // CreateResponse NetworkCreateResponse // // OK response to NetworkCreate operation // swagger:model CreateResponse type CreateResponse struct { // The ID of the created network. // Required: true ID string `json:"Id"` // Warnings encountered when creating the container // Required: true Warning string `json:"Warning"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/network/endpoint.go ================================================ package network import ( "errors" "fmt" "net" "github.com/docker/docker/internal/multierror" ) // EndpointSettings stores the network endpoint details type EndpointSettings struct { // Configurations IPAMConfig *EndpointIPAMConfig Links []string Aliases []string // Aliases holds the list of extra, user-specified DNS names for this endpoint. // MacAddress may be used to specify a MAC address when the container is created. // Once the container is running, it becomes operational data (it may contain a // generated address). MacAddress string DriverOpts map[string]string // GwPriority determines which endpoint will provide the default gateway // for the container. The endpoint with the highest priority will be used. // If multiple endpoints have the same priority, they are lexicographically // sorted based on their network name, and the one that sorts first is picked. GwPriority int // Operational data NetworkID string EndpointID string Gateway string IPAddress string IPPrefixLen int IPv6Gateway string GlobalIPv6Address string GlobalIPv6PrefixLen int // DNSNames holds all the (non fully qualified) DNS names associated to this endpoint. First entry is used to // generate PTR records. DNSNames []string } // Copy makes a deep copy of `EndpointSettings` func (es *EndpointSettings) Copy() *EndpointSettings { epCopy := *es if es.IPAMConfig != nil { epCopy.IPAMConfig = es.IPAMConfig.Copy() } if es.Links != nil { links := make([]string, 0, len(es.Links)) epCopy.Links = append(links, es.Links...) } if es.Aliases != nil { aliases := make([]string, 0, len(es.Aliases)) epCopy.Aliases = append(aliases, es.Aliases...) } if len(es.DNSNames) > 0 { epCopy.DNSNames = make([]string, len(es.DNSNames)) copy(epCopy.DNSNames, es.DNSNames) } return &epCopy } // EndpointIPAMConfig represents IPAM configurations for the endpoint type EndpointIPAMConfig struct { IPv4Address string `json:",omitempty"` IPv6Address string `json:",omitempty"` LinkLocalIPs []string `json:",omitempty"` } // Copy makes a copy of the endpoint ipam config func (cfg *EndpointIPAMConfig) Copy() *EndpointIPAMConfig { cfgCopy := *cfg cfgCopy.LinkLocalIPs = make([]string, 0, len(cfg.LinkLocalIPs)) cfgCopy.LinkLocalIPs = append(cfgCopy.LinkLocalIPs, cfg.LinkLocalIPs...) return &cfgCopy } // NetworkSubnet describes a user-defined subnet for a specific network. It's only used to validate if an // EndpointIPAMConfig is valid for a specific network. type NetworkSubnet interface { // Contains checks whether the NetworkSubnet contains [addr]. Contains(addr net.IP) bool // IsStatic checks whether the subnet was statically allocated (ie. user-defined). IsStatic() bool } // IsInRange checks whether static IP addresses are valid in a specific network. func (cfg *EndpointIPAMConfig) IsInRange(v4Subnets []NetworkSubnet, v6Subnets []NetworkSubnet) error { var errs []error if err := validateEndpointIPAddress(cfg.IPv4Address, v4Subnets); err != nil { errs = append(errs, err) } if err := validateEndpointIPAddress(cfg.IPv6Address, v6Subnets); err != nil { errs = append(errs, err) } return multierror.Join(errs...) } func validateEndpointIPAddress(epAddr string, ipamSubnets []NetworkSubnet) error { if epAddr == "" { return nil } var staticSubnet bool parsedAddr := net.ParseIP(epAddr) for _, subnet := range ipamSubnets { if subnet.IsStatic() { staticSubnet = true if subnet.Contains(parsedAddr) { return nil } } } if staticSubnet { return fmt.Errorf("no configured subnet or ip-range contain the IP address %s", epAddr) } return errors.New("user specified IP address is supported only when connecting to networks with user configured subnets") } // Validate checks whether cfg is valid. func (cfg *EndpointIPAMConfig) Validate() error { if cfg == nil { return nil } var errs []error if cfg.IPv4Address != "" { if addr := net.ParseIP(cfg.IPv4Address); addr == nil || addr.To4() == nil || addr.IsUnspecified() { errs = append(errs, fmt.Errorf("invalid IPv4 address: %s", cfg.IPv4Address)) } } if cfg.IPv6Address != "" { if addr := net.ParseIP(cfg.IPv6Address); addr == nil || addr.To4() != nil || addr.IsUnspecified() { errs = append(errs, fmt.Errorf("invalid IPv6 address: %s", cfg.IPv6Address)) } } for _, addr := range cfg.LinkLocalIPs { if parsed := net.ParseIP(addr); parsed == nil || parsed.IsUnspecified() { errs = append(errs, fmt.Errorf("invalid link-local IP address: %s", addr)) } } return multierror.Join(errs...) } ================================================ FILE: vendor/github.com/docker/docker/api/types/network/ipam.go ================================================ package network import ( "errors" "fmt" "net/netip" "github.com/docker/docker/internal/multierror" ) // IPAM represents IP Address Management type IPAM struct { Driver string Options map[string]string // Per network IPAM driver options Config []IPAMConfig } // IPAMConfig represents IPAM configurations type IPAMConfig struct { Subnet string `json:",omitempty"` IPRange string `json:",omitempty"` Gateway string `json:",omitempty"` AuxAddress map[string]string `json:"AuxiliaryAddresses,omitempty"` } type ipFamily string const ( ip4 ipFamily = "IPv4" ip6 ipFamily = "IPv6" ) // ValidateIPAM checks whether the network's IPAM passed as argument is valid. It returns a joinError of the list of // errors found. func ValidateIPAM(ipam *IPAM, enableIPv6 bool) error { if ipam == nil { return nil } var errs []error for _, cfg := range ipam.Config { subnet, err := netip.ParsePrefix(cfg.Subnet) if err != nil { errs = append(errs, fmt.Errorf("invalid subnet %s: invalid CIDR block notation", cfg.Subnet)) continue } subnetFamily := ip4 if subnet.Addr().Is6() { subnetFamily = ip6 } if !enableIPv6 && subnetFamily == ip6 { continue } if subnet != subnet.Masked() { errs = append(errs, fmt.Errorf("invalid subnet %s: it should be %s", subnet, subnet.Masked())) } if ipRangeErrs := validateIPRange(cfg.IPRange, subnet, subnetFamily); len(ipRangeErrs) > 0 { errs = append(errs, ipRangeErrs...) } if err := validateAddress(cfg.Gateway, subnet, subnetFamily); err != nil { errs = append(errs, fmt.Errorf("invalid gateway %s: %w", cfg.Gateway, err)) } for auxName, aux := range cfg.AuxAddress { if err := validateAddress(aux, subnet, subnetFamily); err != nil { errs = append(errs, fmt.Errorf("invalid auxiliary address %s: %w", auxName, err)) } } } if err := multierror.Join(errs...); err != nil { return fmt.Errorf("invalid network config:\n%w", err) } return nil } func validateIPRange(ipRange string, subnet netip.Prefix, subnetFamily ipFamily) []error { if ipRange == "" { return nil } prefix, err := netip.ParsePrefix(ipRange) if err != nil { return []error{fmt.Errorf("invalid ip-range %s: invalid CIDR block notation", ipRange)} } family := ip4 if prefix.Addr().Is6() { family = ip6 } if family != subnetFamily { return []error{fmt.Errorf("invalid ip-range %s: parent subnet is an %s block", ipRange, subnetFamily)} } var errs []error if prefix.Bits() < subnet.Bits() { errs = append(errs, fmt.Errorf("invalid ip-range %s: CIDR block is bigger than its parent subnet %s", ipRange, subnet)) } if prefix != prefix.Masked() { errs = append(errs, fmt.Errorf("invalid ip-range %s: it should be %s", prefix, prefix.Masked())) } if !subnet.Overlaps(prefix) { errs = append(errs, fmt.Errorf("invalid ip-range %s: parent subnet %s doesn't contain ip-range", ipRange, subnet)) } return errs } func validateAddress(address string, subnet netip.Prefix, subnetFamily ipFamily) error { if address == "" { return nil } addr, err := netip.ParseAddr(address) if err != nil { return errors.New("invalid address") } family := ip4 if addr.Is6() { family = ip6 } if family != subnetFamily { return fmt.Errorf("parent subnet is an %s block", subnetFamily) } if !subnet.Contains(addr) { return fmt.Errorf("parent subnet %s doesn't contain this address", subnet) } return nil } ================================================ FILE: vendor/github.com/docker/docker/api/types/network/network.go ================================================ package network // import "github.com/docker/docker/api/types/network" import ( "time" "github.com/docker/docker/api/types/filters" ) const ( // NetworkDefault is a platform-independent alias to choose the platform-specific default network stack. NetworkDefault = "default" // NetworkHost is the name of the predefined network used when the NetworkMode host is selected (only available on Linux) NetworkHost = "host" // NetworkNone is the name of the predefined network used when the NetworkMode none is selected (available on both Linux and Windows) NetworkNone = "none" // NetworkBridge is the name of the default network on Linux NetworkBridge = "bridge" // NetworkNat is the name of the default network on Windows NetworkNat = "nat" ) // CreateRequest is the request message sent to the server for network create call. type CreateRequest struct { CreateOptions Name string // Name is the requested name of the network. // Deprecated: CheckDuplicate is deprecated since API v1.44, but it defaults to true when sent by the client // package to older daemons. CheckDuplicate *bool `json:",omitempty"` } // CreateOptions holds options to create a network. type CreateOptions struct { Driver string // Driver is the driver-name used to create the network (e.g. `bridge`, `overlay`) Scope string // Scope describes the level at which the network exists (e.g. `swarm` for cluster-wide or `local` for machine level). EnableIPv4 *bool `json:",omitempty"` // EnableIPv4 represents whether to enable IPv4. EnableIPv6 *bool `json:",omitempty"` // EnableIPv6 represents whether to enable IPv6. IPAM *IPAM // IPAM is the network's IP Address Management. Internal bool // Internal represents if the network is used internal only. Attachable bool // Attachable represents if the global scope is manually attachable by regular containers from workers in swarm mode. Ingress bool // Ingress indicates the network is providing the routing-mesh for the swarm cluster. ConfigOnly bool // ConfigOnly creates a config-only network. Config-only networks are place-holder networks for network configurations to be used by other networks. ConfigOnly networks cannot be used directly to run containers or services. ConfigFrom *ConfigReference // ConfigFrom specifies the source which will provide the configuration for this network. The specified network must be a config-only network; see [CreateOptions.ConfigOnly]. Options map[string]string // Options specifies the network-specific options to use for when creating the network. Labels map[string]string // Labels holds metadata specific to the network being created. } // ListOptions holds parameters to filter the list of networks with. type ListOptions struct { Filters filters.Args } // InspectOptions holds parameters to inspect network. type InspectOptions struct { Scope string Verbose bool } // ConnectOptions represents the data to be used to connect a container to the // network. type ConnectOptions struct { Container string EndpointConfig *EndpointSettings `json:",omitempty"` } // DisconnectOptions represents the data to be used to disconnect a container // from the network. type DisconnectOptions struct { Container string Force bool } // Inspect is the body of the "get network" http response message. type Inspect struct { Name string // Name is the name of the network ID string `json:"Id"` // ID uniquely identifies a network on a single machine Created time.Time // Created is the time the network created Scope string // Scope describes the level at which the network exists (e.g. `swarm` for cluster-wide or `local` for machine level) Driver string // Driver is the Driver name used to create the network (e.g. `bridge`, `overlay`) EnableIPv4 bool // EnableIPv4 represents whether IPv4 is enabled EnableIPv6 bool // EnableIPv6 represents whether IPv6 is enabled IPAM IPAM // IPAM is the network's IP Address Management Internal bool // Internal represents if the network is used internal only Attachable bool // Attachable represents if the global scope is manually attachable by regular containers from workers in swarm mode. Ingress bool // Ingress indicates the network is providing the routing-mesh for the swarm cluster. ConfigFrom ConfigReference // ConfigFrom specifies the source which will provide the configuration for this network. ConfigOnly bool // ConfigOnly networks are place-holder networks for network configurations to be used by other networks. ConfigOnly networks cannot be used directly to run containers or services. Containers map[string]EndpointResource // Containers contains endpoints belonging to the network Options map[string]string // Options holds the network specific options to use for when creating the network Labels map[string]string // Labels holds metadata specific to the network being created Peers []PeerInfo `json:",omitempty"` // List of peer nodes for an overlay network Services map[string]ServiceInfo `json:",omitempty"` } // Summary is used as response when listing networks. It currently is an alias // for [Inspect], but may diverge in the future, as not all information may // be included when listing networks. type Summary = Inspect // Address represents an IP address type Address struct { Addr string PrefixLen int } // PeerInfo represents one peer of an overlay network type PeerInfo struct { Name string IP string } // Task carries the information about one backend task type Task struct { Name string EndpointID string EndpointIP string Info map[string]string } // ServiceInfo represents service parameters with the list of service's tasks type ServiceInfo struct { VIP string Ports []string LocalLBIndex int Tasks []Task } // EndpointResource contains network resources allocated and used for a // container in a network. type EndpointResource struct { Name string EndpointID string MacAddress string IPv4Address string IPv6Address string } // NetworkingConfig represents the container's networking configuration for each of its interfaces // Carries the networking configs specified in the `docker run` and `docker network connect` commands type NetworkingConfig struct { EndpointsConfig map[string]*EndpointSettings // Endpoint configs for each connecting network } // ConfigReference specifies the source which provides a network's configuration type ConfigReference struct { Network string } var acceptedFilters = map[string]bool{ "dangling": true, "driver": true, "id": true, "label": true, "name": true, "scope": true, "type": true, } // ValidateFilters validates the list of filter args with the available filters. func ValidateFilters(filter filters.Args) error { return filter.Validate(acceptedFilters) } // PruneReport contains the response for Engine API: // POST "/networks/prune" type PruneReport struct { NetworksDeleted []string } ================================================ FILE: vendor/github.com/docker/docker/api/types/plugin.go ================================================ package types // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // Plugin A plugin for the Engine API // swagger:model Plugin type Plugin struct { // config // Required: true Config PluginConfig `json:"Config"` // True if the plugin is running. False if the plugin is not running, only installed. // Required: true Enabled bool `json:"Enabled"` // Id ID string `json:"Id,omitempty"` // name // Required: true Name string `json:"Name"` // plugin remote reference used to push/pull the plugin PluginReference string `json:"PluginReference,omitempty"` // settings // Required: true Settings PluginSettings `json:"Settings"` } // PluginConfig The config of a plugin. // swagger:model PluginConfig type PluginConfig struct { // args // Required: true Args PluginConfigArgs `json:"Args"` // description // Required: true Description string `json:"Description"` // Docker Version used to create the plugin DockerVersion string `json:"DockerVersion,omitempty"` // documentation // Required: true Documentation string `json:"Documentation"` // entrypoint // Required: true Entrypoint []string `json:"Entrypoint"` // env // Required: true Env []PluginEnv `json:"Env"` // interface // Required: true Interface PluginConfigInterface `json:"Interface"` // ipc host // Required: true IpcHost bool `json:"IpcHost"` // linux // Required: true Linux PluginConfigLinux `json:"Linux"` // mounts // Required: true Mounts []PluginMount `json:"Mounts"` // network // Required: true Network PluginConfigNetwork `json:"Network"` // pid host // Required: true PidHost bool `json:"PidHost"` // propagated mount // Required: true PropagatedMount string `json:"PropagatedMount"` // user User PluginConfigUser `json:"User,omitempty"` // work dir // Required: true WorkDir string `json:"WorkDir"` // rootfs Rootfs *PluginConfigRootfs `json:"rootfs,omitempty"` } // PluginConfigArgs plugin config args // swagger:model PluginConfigArgs type PluginConfigArgs struct { // description // Required: true Description string `json:"Description"` // name // Required: true Name string `json:"Name"` // settable // Required: true Settable []string `json:"Settable"` // value // Required: true Value []string `json:"Value"` } // PluginConfigInterface The interface between Docker and the plugin // swagger:model PluginConfigInterface type PluginConfigInterface struct { // Protocol to use for clients connecting to the plugin. ProtocolScheme string `json:"ProtocolScheme,omitempty"` // socket // Required: true Socket string `json:"Socket"` // types // Required: true Types []PluginInterfaceType `json:"Types"` } // PluginConfigLinux plugin config linux // swagger:model PluginConfigLinux type PluginConfigLinux struct { // allow all devices // Required: true AllowAllDevices bool `json:"AllowAllDevices"` // capabilities // Required: true Capabilities []string `json:"Capabilities"` // devices // Required: true Devices []PluginDevice `json:"Devices"` } // PluginConfigNetwork plugin config network // swagger:model PluginConfigNetwork type PluginConfigNetwork struct { // type // Required: true Type string `json:"Type"` } // PluginConfigRootfs plugin config rootfs // swagger:model PluginConfigRootfs type PluginConfigRootfs struct { // diff ids DiffIds []string `json:"diff_ids"` // type Type string `json:"type,omitempty"` } // PluginConfigUser plugin config user // swagger:model PluginConfigUser type PluginConfigUser struct { // g ID GID uint32 `json:"GID,omitempty"` // UID UID uint32 `json:"UID,omitempty"` } // PluginSettings Settings that can be modified by users. // swagger:model PluginSettings type PluginSettings struct { // args // Required: true Args []string `json:"Args"` // devices // Required: true Devices []PluginDevice `json:"Devices"` // env // Required: true Env []string `json:"Env"` // mounts // Required: true Mounts []PluginMount `json:"Mounts"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/plugin_device.go ================================================ package types // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // PluginDevice plugin device // swagger:model PluginDevice type PluginDevice struct { // description // Required: true Description string `json:"Description"` // name // Required: true Name string `json:"Name"` // path // Required: true Path *string `json:"Path"` // settable // Required: true Settable []string `json:"Settable"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/plugin_env.go ================================================ package types // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // PluginEnv plugin env // swagger:model PluginEnv type PluginEnv struct { // description // Required: true Description string `json:"Description"` // name // Required: true Name string `json:"Name"` // settable // Required: true Settable []string `json:"Settable"` // value // Required: true Value *string `json:"Value"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/plugin_interface_type.go ================================================ package types // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // PluginInterfaceType plugin interface type // swagger:model PluginInterfaceType type PluginInterfaceType struct { // capability // Required: true Capability string `json:"Capability"` // prefix // Required: true Prefix string `json:"Prefix"` // version // Required: true Version string `json:"Version"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/plugin_mount.go ================================================ package types // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // PluginMount plugin mount // swagger:model PluginMount type PluginMount struct { // description // Required: true Description string `json:"Description"` // destination // Required: true Destination string `json:"Destination"` // name // Required: true Name string `json:"Name"` // options // Required: true Options []string `json:"Options"` // settable // Required: true Settable []string `json:"Settable"` // source // Required: true Source *string `json:"Source"` // type // Required: true Type string `json:"Type"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/plugin_responses.go ================================================ package types // import "github.com/docker/docker/api/types" import ( "encoding/json" "fmt" "sort" ) // PluginsListResponse contains the response for the Engine API type PluginsListResponse []*Plugin // UnmarshalJSON implements json.Unmarshaler for PluginInterfaceType func (t *PluginInterfaceType) UnmarshalJSON(p []byte) error { versionIndex := len(p) prefixIndex := 0 if len(p) < 2 || p[0] != '"' || p[len(p)-1] != '"' { return fmt.Errorf("%q is not a plugin interface type", p) } p = p[1 : len(p)-1] loop: for i, b := range p { switch b { case '.': prefixIndex = i case '/': versionIndex = i break loop } } t.Prefix = string(p[:prefixIndex]) t.Capability = string(p[prefixIndex+1 : versionIndex]) if versionIndex < len(p) { t.Version = string(p[versionIndex+1:]) } return nil } // MarshalJSON implements json.Marshaler for PluginInterfaceType func (t *PluginInterfaceType) MarshalJSON() ([]byte, error) { return json.Marshal(t.String()) } // String implements fmt.Stringer for PluginInterfaceType func (t PluginInterfaceType) String() string { return fmt.Sprintf("%s.%s/%s", t.Prefix, t.Capability, t.Version) } // PluginPrivilege describes a permission the user has to accept // upon installing a plugin. type PluginPrivilege struct { Name string Description string Value []string } // PluginPrivileges is a list of PluginPrivilege type PluginPrivileges []PluginPrivilege func (s PluginPrivileges) Len() int { return len(s) } func (s PluginPrivileges) Less(i, j int) bool { return s[i].Name < s[j].Name } func (s PluginPrivileges) Swap(i, j int) { sort.Strings(s[i].Value) sort.Strings(s[j].Value) s[i], s[j] = s[j], s[i] } ================================================ FILE: vendor/github.com/docker/docker/api/types/registry/authconfig.go ================================================ package registry // import "github.com/docker/docker/api/types/registry" import ( "context" "encoding/base64" "encoding/json" "fmt" "io" "strings" ) // AuthHeader is the name of the header used to send encoded registry // authorization credentials for registry operations (push/pull). const AuthHeader = "X-Registry-Auth" // RequestAuthConfig is a function interface that clients can supply // to retry operations after getting an authorization error. // // The function must return the [AuthHeader] value ([AuthConfig]), encoded // in base64url format ([RFC4648, section 5]), which can be decoded by // [DecodeAuthConfig]. // // It must return an error if the privilege request fails. // // [RFC4648, section 5]: https://tools.ietf.org/html/rfc4648#section-5 type RequestAuthConfig func(context.Context) (string, error) // AuthConfig contains authorization information for connecting to a Registry. type AuthConfig struct { Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` Auth string `json:"auth,omitempty"` // Email is an optional value associated with the username. // This field is deprecated and will be removed in a later // version of docker. Email string `json:"email,omitempty"` ServerAddress string `json:"serveraddress,omitempty"` // IdentityToken is used to authenticate the user and get // an access token for the registry. IdentityToken string `json:"identitytoken,omitempty"` // RegistryToken is a bearer token to be sent to a registry RegistryToken string `json:"registrytoken,omitempty"` } // EncodeAuthConfig serializes the auth configuration as a base64url encoded // ([RFC4648, section 5]) JSON string for sending through the X-Registry-Auth header. // // [RFC4648, section 5]: https://tools.ietf.org/html/rfc4648#section-5 func EncodeAuthConfig(authConfig AuthConfig) (string, error) { buf, err := json.Marshal(authConfig) if err != nil { return "", errInvalidParameter{err} } return base64.URLEncoding.EncodeToString(buf), nil } // DecodeAuthConfig decodes base64url encoded ([RFC4648, section 5]) JSON // authentication information as sent through the X-Registry-Auth header. // // This function always returns an [AuthConfig], even if an error occurs. It is up // to the caller to decide if authentication is required, and if the error can // be ignored. // // [RFC4648, section 5]: https://tools.ietf.org/html/rfc4648#section-5 func DecodeAuthConfig(authEncoded string) (*AuthConfig, error) { if authEncoded == "" { return &AuthConfig{}, nil } authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) return decodeAuthConfigFromReader(authJSON) } // DecodeAuthConfigBody decodes authentication information as sent as JSON in the // body of a request. This function is to provide backward compatibility with old // clients and API versions. Current clients and API versions expect authentication // to be provided through the X-Registry-Auth header. // // Like [DecodeAuthConfig], this function always returns an [AuthConfig], even if an // error occurs. It is up to the caller to decide if authentication is required, // and if the error can be ignored. func DecodeAuthConfigBody(rdr io.ReadCloser) (*AuthConfig, error) { return decodeAuthConfigFromReader(rdr) } func decodeAuthConfigFromReader(rdr io.Reader) (*AuthConfig, error) { authConfig := &AuthConfig{} if err := json.NewDecoder(rdr).Decode(authConfig); err != nil { // always return an (empty) AuthConfig to increase compatibility with // the existing API. return &AuthConfig{}, invalid(err) } return authConfig, nil } func invalid(err error) error { return errInvalidParameter{fmt.Errorf("invalid X-Registry-Auth header: %w", err)} } type errInvalidParameter struct{ error } func (errInvalidParameter) InvalidParameter() {} func (e errInvalidParameter) Cause() error { return e.error } func (e errInvalidParameter) Unwrap() error { return e.error } ================================================ FILE: vendor/github.com/docker/docker/api/types/registry/authenticate.go ================================================ package registry // import "github.com/docker/docker/api/types/registry" // ---------------------------------------------------------------------------- // DO NOT EDIT THIS FILE // This file was generated by `swagger generate operation` // // See hack/generate-swagger-api.sh // ---------------------------------------------------------------------------- // AuthenticateOKBody authenticate o k body // swagger:model AuthenticateOKBody type AuthenticateOKBody struct { // An opaque token used to authenticate a user after a successful login // Required: true IdentityToken string `json:"IdentityToken"` // The status of the authentication // Required: true Status string `json:"Status"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/registry/registry.go ================================================ package registry // import "github.com/docker/docker/api/types/registry" import ( "encoding/json" "net" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // ServiceConfig stores daemon registry services configuration. type ServiceConfig struct { AllowNondistributableArtifactsCIDRs []*NetIPNet `json:"AllowNondistributableArtifactsCIDRs,omitempty"` // Deprecated: non-distributable artifacts are deprecated and enabled by default. This field will be removed in the next release. AllowNondistributableArtifactsHostnames []string `json:"AllowNondistributableArtifactsHostnames,omitempty"` // Deprecated: non-distributable artifacts are deprecated and enabled by default. This field will be removed in the next release. InsecureRegistryCIDRs []*NetIPNet `json:"InsecureRegistryCIDRs"` IndexConfigs map[string]*IndexInfo `json:"IndexConfigs"` Mirrors []string } // MarshalJSON implements a custom marshaler to include legacy fields // in API responses. func (sc ServiceConfig) MarshalJSON() ([]byte, error) { tmp := map[string]interface{}{ "InsecureRegistryCIDRs": sc.InsecureRegistryCIDRs, "IndexConfigs": sc.IndexConfigs, "Mirrors": sc.Mirrors, } if sc.AllowNondistributableArtifactsCIDRs != nil { tmp["AllowNondistributableArtifactsCIDRs"] = nil } if sc.AllowNondistributableArtifactsHostnames != nil { tmp["AllowNondistributableArtifactsHostnames"] = nil } return json.Marshal(tmp) } // NetIPNet is the net.IPNet type, which can be marshalled and // unmarshalled to JSON type NetIPNet net.IPNet // String returns the CIDR notation of ipnet func (ipnet *NetIPNet) String() string { return (*net.IPNet)(ipnet).String() } // MarshalJSON returns the JSON representation of the IPNet func (ipnet *NetIPNet) MarshalJSON() ([]byte, error) { return json.Marshal((*net.IPNet)(ipnet).String()) } // UnmarshalJSON sets the IPNet from a byte array of JSON func (ipnet *NetIPNet) UnmarshalJSON(b []byte) (err error) { var ipnetStr string if err = json.Unmarshal(b, &ipnetStr); err == nil { var cidr *net.IPNet if _, cidr, err = net.ParseCIDR(ipnetStr); err == nil { *ipnet = NetIPNet(*cidr) } } return } // IndexInfo contains information about a registry // // RepositoryInfo Examples: // // { // "Index" : { // "Name" : "docker.io", // "Mirrors" : ["https://registry-2.docker.io/v1/", "https://registry-3.docker.io/v1/"], // "Secure" : true, // "Official" : true, // }, // "RemoteName" : "library/debian", // "LocalName" : "debian", // "CanonicalName" : "docker.io/debian" // "Official" : true, // } // // { // "Index" : { // "Name" : "127.0.0.1:5000", // "Mirrors" : [], // "Secure" : false, // "Official" : false, // }, // "RemoteName" : "user/repo", // "LocalName" : "127.0.0.1:5000/user/repo", // "CanonicalName" : "127.0.0.1:5000/user/repo", // "Official" : false, // } type IndexInfo struct { // Name is the name of the registry, such as "docker.io" Name string // Mirrors is a list of mirrors, expressed as URIs Mirrors []string // Secure is set to false if the registry is part of the list of // insecure registries. Insecure registries accept HTTP and/or accept // HTTPS with certificates from unknown CAs. Secure bool // Official indicates whether this is an official registry Official bool } // DistributionInspect describes the result obtained from contacting the // registry to retrieve image metadata type DistributionInspect struct { // Descriptor contains information about the manifest, including // the content addressable digest Descriptor ocispec.Descriptor // Platforms contains the list of platforms supported by the image, // obtained by parsing the manifest Platforms []ocispec.Platform } ================================================ FILE: vendor/github.com/docker/docker/api/types/registry/search.go ================================================ package registry import ( "context" "github.com/docker/docker/api/types/filters" ) // SearchOptions holds parameters to search images with. type SearchOptions struct { RegistryAuth string // PrivilegeFunc is a function that clients can supply to retry operations // after getting an authorization error. This function returns the registry // authentication header value in base64 encoded format, or an error if the // privilege request fails. // // For details, refer to [github.com/docker/docker/api/types/registry.RequestAuthConfig]. PrivilegeFunc func(context.Context) (string, error) Filters filters.Args Limit int } // SearchResult describes a search result returned from a registry type SearchResult struct { // StarCount indicates the number of stars this repository has StarCount int `json:"star_count"` // IsOfficial is true if the result is from an official repository. IsOfficial bool `json:"is_official"` // Name is the name of the repository Name string `json:"name"` // IsAutomated indicates whether the result is automated. // // Deprecated: the "is_automated" field is deprecated and will always be "false". IsAutomated bool `json:"is_automated"` // Description is a textual description of the repository Description string `json:"description"` } // SearchResults lists a collection search results returned from a registry type SearchResults struct { // Query contains the query string that generated the search results Query string `json:"query"` // NumResults indicates the number of results the query returned NumResults int `json:"num_results"` // Results is a slice containing the actual results for the search Results []SearchResult `json:"results"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/storage/driver_data.go ================================================ package storage // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // DriverData Information about the storage driver used to store the container's and // image's filesystem. // // swagger:model DriverData type DriverData struct { // Low-level storage metadata, provided as key/value pairs. // // This information is driver-specific, and depends on the storage-driver // in use, and should be used for informational purposes only. // // Required: true Data map[string]string `json:"Data"` // Name of the storage driver. // Required: true Name string `json:"Name"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/strslice/strslice.go ================================================ package strslice // import "github.com/docker/docker/api/types/strslice" import "encoding/json" // StrSlice represents a string or an array of strings. // We need to override the json decoder to accept both options. type StrSlice []string // UnmarshalJSON decodes the byte slice whether it's a string or an array of // strings. This method is needed to implement json.Unmarshaler. func (e *StrSlice) UnmarshalJSON(b []byte) error { if len(b) == 0 { // With no input, we preserve the existing value by returning nil and // leaving the target alone. This allows defining default values for // the type. return nil } p := make([]string, 0, 1) if err := json.Unmarshal(b, &p); err != nil { var s string if err := json.Unmarshal(b, &s); err != nil { return err } p = append(p, s) } *e = p return nil } ================================================ FILE: vendor/github.com/docker/docker/api/types/swarm/common.go ================================================ package swarm // import "github.com/docker/docker/api/types/swarm" import ( "strconv" "time" ) // Version represents the internal object version. type Version struct { Index uint64 `json:",omitempty"` } // String implements fmt.Stringer interface. func (v Version) String() string { return strconv.FormatUint(v.Index, 10) } // Meta is a base object inherited by most of the other once. type Meta struct { Version Version `json:",omitempty"` CreatedAt time.Time `json:",omitempty"` UpdatedAt time.Time `json:",omitempty"` } // Annotations represents how to describe an object. type Annotations struct { Name string `json:",omitempty"` Labels map[string]string `json:"Labels"` } // Driver represents a driver (network, logging, secrets backend). type Driver struct { Name string `json:",omitempty"` Options map[string]string `json:",omitempty"` } // TLSInfo represents the TLS information about what CA certificate is trusted, // and who the issuer for a TLS certificate is type TLSInfo struct { // TrustRoot is the trusted CA root certificate in PEM format TrustRoot string `json:",omitempty"` // CertIssuer is the raw subject bytes of the issuer CertIssuerSubject []byte `json:",omitempty"` // CertIssuerPublicKey is the raw public key bytes of the issuer CertIssuerPublicKey []byte `json:",omitempty"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/swarm/config.go ================================================ package swarm // import "github.com/docker/docker/api/types/swarm" import "os" // Config represents a config. type Config struct { ID string Meta Spec ConfigSpec } // ConfigSpec represents a config specification from a config in swarm type ConfigSpec struct { Annotations Data []byte `json:",omitempty"` // Templating controls whether and how to evaluate the config payload as // a template. If it is not set, no templating is used. Templating *Driver `json:",omitempty"` } // ConfigReferenceFileTarget is a file target in a config reference type ConfigReferenceFileTarget struct { Name string UID string GID string Mode os.FileMode } // ConfigReferenceRuntimeTarget is a target for a config specifying that it // isn't mounted into the container but instead has some other purpose. type ConfigReferenceRuntimeTarget struct{} // ConfigReference is a reference to a config in swarm type ConfigReference struct { File *ConfigReferenceFileTarget `json:",omitempty"` Runtime *ConfigReferenceRuntimeTarget `json:",omitempty"` ConfigID string ConfigName string } ================================================ FILE: vendor/github.com/docker/docker/api/types/swarm/container.go ================================================ package swarm // import "github.com/docker/docker/api/types/swarm" import ( "time" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/mount" ) // DNSConfig specifies DNS related configurations in resolver configuration file (resolv.conf) // Detailed documentation is available in: // http://man7.org/linux/man-pages/man5/resolv.conf.5.html // `nameserver`, `search`, `options` have been supported. // TODO: `domain` is not supported yet. type DNSConfig struct { // Nameservers specifies the IP addresses of the name servers Nameservers []string `json:",omitempty"` // Search specifies the search list for host-name lookup Search []string `json:",omitempty"` // Options allows certain internal resolver variables to be modified Options []string `json:",omitempty"` } // SELinuxContext contains the SELinux labels of the container. type SELinuxContext struct { Disable bool User string Role string Type string Level string } // SeccompMode is the type used for the enumeration of possible seccomp modes // in SeccompOpts type SeccompMode string const ( SeccompModeDefault SeccompMode = "default" SeccompModeUnconfined SeccompMode = "unconfined" SeccompModeCustom SeccompMode = "custom" ) // SeccompOpts defines the options for configuring seccomp on a swarm-managed // container. type SeccompOpts struct { // Mode is the SeccompMode used for the container. Mode SeccompMode `json:",omitempty"` // Profile is the custom seccomp profile as a json object to be used with // the container. Mode should be set to SeccompModeCustom when using a // custom profile in this manner. Profile []byte `json:",omitempty"` } // AppArmorMode is type used for the enumeration of possible AppArmor modes in // AppArmorOpts type AppArmorMode string const ( AppArmorModeDefault AppArmorMode = "default" AppArmorModeDisabled AppArmorMode = "disabled" ) // AppArmorOpts defines the options for configuring AppArmor on a swarm-managed // container. Currently, custom AppArmor profiles are not supported. type AppArmorOpts struct { Mode AppArmorMode `json:",omitempty"` } // CredentialSpec for managed service account (Windows only) type CredentialSpec struct { Config string File string Registry string } // Privileges defines the security options for the container. type Privileges struct { CredentialSpec *CredentialSpec SELinuxContext *SELinuxContext Seccomp *SeccompOpts `json:",omitempty"` AppArmor *AppArmorOpts `json:",omitempty"` NoNewPrivileges bool } // ContainerSpec represents the spec of a container. type ContainerSpec struct { Image string `json:",omitempty"` Labels map[string]string `json:",omitempty"` Command []string `json:",omitempty"` Args []string `json:",omitempty"` Hostname string `json:",omitempty"` Env []string `json:",omitempty"` Dir string `json:",omitempty"` User string `json:",omitempty"` Groups []string `json:",omitempty"` Privileges *Privileges `json:",omitempty"` Init *bool `json:",omitempty"` StopSignal string `json:",omitempty"` TTY bool `json:",omitempty"` OpenStdin bool `json:",omitempty"` ReadOnly bool `json:",omitempty"` Mounts []mount.Mount `json:",omitempty"` StopGracePeriod *time.Duration `json:",omitempty"` Healthcheck *container.HealthConfig `json:",omitempty"` // The format of extra hosts on swarmkit is specified in: // http://man7.org/linux/man-pages/man5/hosts.5.html // IP_address canonical_hostname [aliases...] Hosts []string `json:",omitempty"` DNSConfig *DNSConfig `json:",omitempty"` Secrets []*SecretReference `json:",omitempty"` Configs []*ConfigReference `json:",omitempty"` Isolation container.Isolation `json:",omitempty"` Sysctls map[string]string `json:",omitempty"` CapabilityAdd []string `json:",omitempty"` CapabilityDrop []string `json:",omitempty"` Ulimits []*container.Ulimit `json:",omitempty"` OomScoreAdj int64 `json:",omitempty"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/swarm/network.go ================================================ package swarm // import "github.com/docker/docker/api/types/swarm" import ( "github.com/docker/docker/api/types/network" ) // Endpoint represents an endpoint. type Endpoint struct { Spec EndpointSpec `json:",omitempty"` Ports []PortConfig `json:",omitempty"` VirtualIPs []EndpointVirtualIP `json:",omitempty"` } // EndpointSpec represents the spec of an endpoint. type EndpointSpec struct { Mode ResolutionMode `json:",omitempty"` Ports []PortConfig `json:",omitempty"` } // ResolutionMode represents a resolution mode. type ResolutionMode string const ( // ResolutionModeVIP VIP ResolutionModeVIP ResolutionMode = "vip" // ResolutionModeDNSRR DNSRR ResolutionModeDNSRR ResolutionMode = "dnsrr" ) // PortConfig represents the config of a port. type PortConfig struct { Name string `json:",omitempty"` Protocol PortConfigProtocol `json:",omitempty"` // TargetPort is the port inside the container TargetPort uint32 `json:",omitempty"` // PublishedPort is the port on the swarm hosts PublishedPort uint32 `json:",omitempty"` // PublishMode is the mode in which port is published PublishMode PortConfigPublishMode `json:",omitempty"` } // PortConfigPublishMode represents the mode in which the port is to // be published. type PortConfigPublishMode string const ( // PortConfigPublishModeIngress is used for ports published // for ingress load balancing using routing mesh. PortConfigPublishModeIngress PortConfigPublishMode = "ingress" // PortConfigPublishModeHost is used for ports published // for direct host level access on the host where the task is running. PortConfigPublishModeHost PortConfigPublishMode = "host" ) // PortConfigProtocol represents the protocol of a port. type PortConfigProtocol string const ( // TODO(stevvooe): These should be used generally, not just for PortConfig. // PortConfigProtocolTCP TCP PortConfigProtocolTCP PortConfigProtocol = "tcp" // PortConfigProtocolUDP UDP PortConfigProtocolUDP PortConfigProtocol = "udp" // PortConfigProtocolSCTP SCTP PortConfigProtocolSCTP PortConfigProtocol = "sctp" ) // EndpointVirtualIP represents the virtual ip of a port. type EndpointVirtualIP struct { NetworkID string `json:",omitempty"` Addr string `json:",omitempty"` } // Network represents a network. type Network struct { ID string Meta Spec NetworkSpec `json:",omitempty"` DriverState Driver `json:",omitempty"` IPAMOptions *IPAMOptions `json:",omitempty"` } // NetworkSpec represents the spec of a network. type NetworkSpec struct { Annotations DriverConfiguration *Driver `json:",omitempty"` IPv6Enabled bool `json:",omitempty"` Internal bool `json:",omitempty"` Attachable bool `json:",omitempty"` Ingress bool `json:",omitempty"` IPAMOptions *IPAMOptions `json:",omitempty"` ConfigFrom *network.ConfigReference `json:",omitempty"` Scope string `json:",omitempty"` } // NetworkAttachmentConfig represents the configuration of a network attachment. type NetworkAttachmentConfig struct { Target string `json:",omitempty"` Aliases []string `json:",omitempty"` DriverOpts map[string]string `json:",omitempty"` } // NetworkAttachment represents a network attachment. type NetworkAttachment struct { Network Network `json:",omitempty"` Addresses []string `json:",omitempty"` } // IPAMOptions represents ipam options. type IPAMOptions struct { Driver Driver `json:",omitempty"` Configs []IPAMConfig `json:",omitempty"` } // IPAMConfig represents ipam configuration. type IPAMConfig struct { Subnet string `json:",omitempty"` Range string `json:",omitempty"` Gateway string `json:",omitempty"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/swarm/node.go ================================================ package swarm // import "github.com/docker/docker/api/types/swarm" // Node represents a node. type Node struct { ID string Meta // Spec defines the desired state of the node as specified by the user. // The system will honor this and will *never* modify it. Spec NodeSpec `json:",omitempty"` // Description encapsulates the properties of the Node as reported by the // agent. Description NodeDescription `json:",omitempty"` // Status provides the current status of the node, as seen by the manager. Status NodeStatus `json:",omitempty"` // ManagerStatus provides the current status of the node's manager // component, if the node is a manager. ManagerStatus *ManagerStatus `json:",omitempty"` } // NodeSpec represents the spec of a node. type NodeSpec struct { Annotations Role NodeRole `json:",omitempty"` Availability NodeAvailability `json:",omitempty"` } // NodeRole represents the role of a node. type NodeRole string const ( // NodeRoleWorker WORKER NodeRoleWorker NodeRole = "worker" // NodeRoleManager MANAGER NodeRoleManager NodeRole = "manager" ) // NodeAvailability represents the availability of a node. type NodeAvailability string const ( // NodeAvailabilityActive ACTIVE NodeAvailabilityActive NodeAvailability = "active" // NodeAvailabilityPause PAUSE NodeAvailabilityPause NodeAvailability = "pause" // NodeAvailabilityDrain DRAIN NodeAvailabilityDrain NodeAvailability = "drain" ) // NodeDescription represents the description of a node. type NodeDescription struct { Hostname string `json:",omitempty"` Platform Platform `json:",omitempty"` Resources Resources `json:",omitempty"` Engine EngineDescription `json:",omitempty"` TLSInfo TLSInfo `json:",omitempty"` CSIInfo []NodeCSIInfo `json:",omitempty"` } // Platform represents the platform (Arch/OS). type Platform struct { Architecture string `json:",omitempty"` OS string `json:",omitempty"` } // EngineDescription represents the description of an engine. type EngineDescription struct { EngineVersion string `json:",omitempty"` Labels map[string]string `json:",omitempty"` Plugins []PluginDescription `json:",omitempty"` } // NodeCSIInfo represents information about a CSI plugin available on the node type NodeCSIInfo struct { // PluginName is the name of the CSI plugin. PluginName string `json:",omitempty"` // NodeID is the ID of the node as reported by the CSI plugin. This is // different from the swarm node ID. NodeID string `json:",omitempty"` // MaxVolumesPerNode is the maximum number of volumes that may be published // to this node MaxVolumesPerNode int64 `json:",omitempty"` // AccessibleTopology indicates the location of this node in the CSI // plugin's topology AccessibleTopology *Topology `json:",omitempty"` } // PluginDescription represents the description of an engine plugin. type PluginDescription struct { Type string `json:",omitempty"` Name string `json:",omitempty"` } // NodeStatus represents the status of a node. type NodeStatus struct { State NodeState `json:",omitempty"` Message string `json:",omitempty"` Addr string `json:",omitempty"` } // Reachability represents the reachability of a node. type Reachability string const ( // ReachabilityUnknown UNKNOWN ReachabilityUnknown Reachability = "unknown" // ReachabilityUnreachable UNREACHABLE ReachabilityUnreachable Reachability = "unreachable" // ReachabilityReachable REACHABLE ReachabilityReachable Reachability = "reachable" ) // ManagerStatus represents the status of a manager. type ManagerStatus struct { Leader bool `json:",omitempty"` Reachability Reachability `json:",omitempty"` Addr string `json:",omitempty"` } // NodeState represents the state of a node. type NodeState string const ( // NodeStateUnknown UNKNOWN NodeStateUnknown NodeState = "unknown" // NodeStateDown DOWN NodeStateDown NodeState = "down" // NodeStateReady READY NodeStateReady NodeState = "ready" // NodeStateDisconnected DISCONNECTED NodeStateDisconnected NodeState = "disconnected" ) // Topology defines the CSI topology of this node. This type is a duplicate of // github.com/docker/docker/api/types.Topology. Because the type definition // is so simple and to avoid complicated structure or circular imports, we just // duplicate it here. See that type for full documentation type Topology struct { Segments map[string]string `json:",omitempty"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/swarm/runtime/gen.go ================================================ //go:generate protoc --gogofaster_out=import_path=github.com/docker/docker/api/types/swarm/runtime:. plugin.proto package runtime // import "github.com/docker/docker/api/types/swarm/runtime" ================================================ FILE: vendor/github.com/docker/docker/api/types/swarm/runtime/plugin.pb.go ================================================ // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: plugin.proto package runtime import ( fmt "fmt" proto "github.com/gogo/protobuf/proto" io "io" math "math" math_bits "math/bits" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // PluginSpec defines the base payload which clients can specify for creating // a service with the plugin runtime. type PluginSpec struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Remote string `protobuf:"bytes,2,opt,name=remote,proto3" json:"remote,omitempty"` Privileges []*PluginPrivilege `protobuf:"bytes,3,rep,name=privileges,proto3" json:"privileges,omitempty"` Disabled bool `protobuf:"varint,4,opt,name=disabled,proto3" json:"disabled,omitempty"` Env []string `protobuf:"bytes,5,rep,name=env,proto3" json:"env,omitempty"` } func (m *PluginSpec) Reset() { *m = PluginSpec{} } func (m *PluginSpec) String() string { return proto.CompactTextString(m) } func (*PluginSpec) ProtoMessage() {} func (*PluginSpec) Descriptor() ([]byte, []int) { return fileDescriptor_22a625af4bc1cc87, []int{0} } func (m *PluginSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *PluginSpec) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_PluginSpec.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *PluginSpec) XXX_Merge(src proto.Message) { xxx_messageInfo_PluginSpec.Merge(m, src) } func (m *PluginSpec) XXX_Size() int { return m.Size() } func (m *PluginSpec) XXX_DiscardUnknown() { xxx_messageInfo_PluginSpec.DiscardUnknown(m) } var xxx_messageInfo_PluginSpec proto.InternalMessageInfo func (m *PluginSpec) GetName() string { if m != nil { return m.Name } return "" } func (m *PluginSpec) GetRemote() string { if m != nil { return m.Remote } return "" } func (m *PluginSpec) GetPrivileges() []*PluginPrivilege { if m != nil { return m.Privileges } return nil } func (m *PluginSpec) GetDisabled() bool { if m != nil { return m.Disabled } return false } func (m *PluginSpec) GetEnv() []string { if m != nil { return m.Env } return nil } // PluginPrivilege describes a permission the user has to accept // upon installing a plugin. type PluginPrivilege struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` Value []string `protobuf:"bytes,3,rep,name=value,proto3" json:"value,omitempty"` } func (m *PluginPrivilege) Reset() { *m = PluginPrivilege{} } func (m *PluginPrivilege) String() string { return proto.CompactTextString(m) } func (*PluginPrivilege) ProtoMessage() {} func (*PluginPrivilege) Descriptor() ([]byte, []int) { return fileDescriptor_22a625af4bc1cc87, []int{1} } func (m *PluginPrivilege) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *PluginPrivilege) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_PluginPrivilege.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *PluginPrivilege) XXX_Merge(src proto.Message) { xxx_messageInfo_PluginPrivilege.Merge(m, src) } func (m *PluginPrivilege) XXX_Size() int { return m.Size() } func (m *PluginPrivilege) XXX_DiscardUnknown() { xxx_messageInfo_PluginPrivilege.DiscardUnknown(m) } var xxx_messageInfo_PluginPrivilege proto.InternalMessageInfo func (m *PluginPrivilege) GetName() string { if m != nil { return m.Name } return "" } func (m *PluginPrivilege) GetDescription() string { if m != nil { return m.Description } return "" } func (m *PluginPrivilege) GetValue() []string { if m != nil { return m.Value } return nil } func init() { proto.RegisterType((*PluginSpec)(nil), "PluginSpec") proto.RegisterType((*PluginPrivilege)(nil), "PluginPrivilege") } func init() { proto.RegisterFile("plugin.proto", fileDescriptor_22a625af4bc1cc87) } var fileDescriptor_22a625af4bc1cc87 = []byte{ // 225 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x29, 0xc8, 0x29, 0x4d, 0xcf, 0xcc, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0x9a, 0xc1, 0xc8, 0xc5, 0x15, 0x00, 0x16, 0x08, 0x2e, 0x48, 0x4d, 0x16, 0x12, 0xe2, 0x62, 0xc9, 0x4b, 0xcc, 0x4d, 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x85, 0xc4, 0xb8, 0xd8, 0x8a, 0x52, 0x73, 0xf3, 0x4b, 0x52, 0x25, 0x98, 0xc0, 0xa2, 0x50, 0x9e, 0x90, 0x01, 0x17, 0x57, 0x41, 0x51, 0x66, 0x59, 0x66, 0x4e, 0x6a, 0x7a, 0x6a, 0xb1, 0x04, 0xb3, 0x02, 0xb3, 0x06, 0xb7, 0x91, 0x80, 0x1e, 0xc4, 0xb0, 0x00, 0x98, 0x44, 0x10, 0x92, 0x1a, 0x21, 0x29, 0x2e, 0x8e, 0x94, 0xcc, 0xe2, 0xc4, 0xa4, 0x9c, 0xd4, 0x14, 0x09, 0x16, 0x05, 0x46, 0x0d, 0x8e, 0x20, 0x38, 0x5f, 0x48, 0x80, 0x8b, 0x39, 0x35, 0xaf, 0x4c, 0x82, 0x55, 0x81, 0x59, 0x83, 0x33, 0x08, 0xc4, 0x54, 0x8a, 0xe5, 0xe2, 0x47, 0x33, 0x0c, 0xab, 0xf3, 0x14, 0xb8, 0xb8, 0x53, 0x52, 0x8b, 0x93, 0x8b, 0x32, 0x0b, 0x4a, 0x32, 0xf3, 0xf3, 0xa0, 0x6e, 0x44, 0x16, 0x12, 0x12, 0xe1, 0x62, 0x2d, 0x4b, 0xcc, 0x29, 0x4d, 0x05, 0xbb, 0x91, 0x33, 0x08, 0xc2, 0x71, 0x92, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x24, 0x36, 0x70, 0xd0, 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x37, 0xea, 0xe2, 0xca, 0x2a, 0x01, 0x00, 0x00, } func (m *PluginSpec) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *PluginSpec) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *PluginSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m.Env) > 0 { for iNdEx := len(m.Env) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.Env[iNdEx]) copy(dAtA[i:], m.Env[iNdEx]) i = encodeVarintPlugin(dAtA, i, uint64(len(m.Env[iNdEx]))) i-- dAtA[i] = 0x2a } } if m.Disabled { i-- if m.Disabled { dAtA[i] = 1 } else { dAtA[i] = 0 } i-- dAtA[i] = 0x20 } if len(m.Privileges) > 0 { for iNdEx := len(m.Privileges) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.Privileges[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintPlugin(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x1a } } if len(m.Remote) > 0 { i -= len(m.Remote) copy(dAtA[i:], m.Remote) i = encodeVarintPlugin(dAtA, i, uint64(len(m.Remote))) i-- dAtA[i] = 0x12 } if len(m.Name) > 0 { i -= len(m.Name) copy(dAtA[i:], m.Name) i = encodeVarintPlugin(dAtA, i, uint64(len(m.Name))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } func (m *PluginPrivilege) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *PluginPrivilege) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *PluginPrivilege) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m.Value) > 0 { for iNdEx := len(m.Value) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.Value[iNdEx]) copy(dAtA[i:], m.Value[iNdEx]) i = encodeVarintPlugin(dAtA, i, uint64(len(m.Value[iNdEx]))) i-- dAtA[i] = 0x1a } } if len(m.Description) > 0 { i -= len(m.Description) copy(dAtA[i:], m.Description) i = encodeVarintPlugin(dAtA, i, uint64(len(m.Description))) i-- dAtA[i] = 0x12 } if len(m.Name) > 0 { i -= len(m.Name) copy(dAtA[i:], m.Name) i = encodeVarintPlugin(dAtA, i, uint64(len(m.Name))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } func encodeVarintPlugin(dAtA []byte, offset int, v uint64) int { offset -= sovPlugin(v) base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) return base } func (m *PluginSpec) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Name) if l > 0 { n += 1 + l + sovPlugin(uint64(l)) } l = len(m.Remote) if l > 0 { n += 1 + l + sovPlugin(uint64(l)) } if len(m.Privileges) > 0 { for _, e := range m.Privileges { l = e.Size() n += 1 + l + sovPlugin(uint64(l)) } } if m.Disabled { n += 2 } if len(m.Env) > 0 { for _, s := range m.Env { l = len(s) n += 1 + l + sovPlugin(uint64(l)) } } return n } func (m *PluginPrivilege) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Name) if l > 0 { n += 1 + l + sovPlugin(uint64(l)) } l = len(m.Description) if l > 0 { n += 1 + l + sovPlugin(uint64(l)) } if len(m.Value) > 0 { for _, s := range m.Value { l = len(s) n += 1 + l + sovPlugin(uint64(l)) } } return n } func sovPlugin(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } func sozPlugin(x uint64) (n int) { return sovPlugin(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } func (m *PluginSpec) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlugin } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: PluginSpec: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: PluginSpec: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlugin } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthPlugin } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthPlugin } if postIndex > l { return io.ErrUnexpectedEOF } m.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Remote", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlugin } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthPlugin } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthPlugin } if postIndex > l { return io.ErrUnexpectedEOF } m.Remote = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Privileges", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlugin } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthPlugin } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthPlugin } if postIndex > l { return io.ErrUnexpectedEOF } m.Privileges = append(m.Privileges, &PluginPrivilege{}) if err := m.Privileges[len(m.Privileges)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 4: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Disabled", wireType) } var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlugin } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int(b&0x7F) << shift if b < 0x80 { break } } m.Disabled = bool(v != 0) case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Env", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlugin } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthPlugin } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthPlugin } if postIndex > l { return io.ErrUnexpectedEOF } m.Env = append(m.Env, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipPlugin(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthPlugin } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *PluginPrivilege) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlugin } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: PluginPrivilege: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: PluginPrivilege: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlugin } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthPlugin } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthPlugin } if postIndex > l { return io.ErrUnexpectedEOF } m.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlugin } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthPlugin } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthPlugin } if postIndex > l { return io.ErrUnexpectedEOF } m.Description = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlugin } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthPlugin } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthPlugin } if postIndex > l { return io.ErrUnexpectedEOF } m.Value = append(m.Value, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipPlugin(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthPlugin } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func skipPlugin(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 depth := 0 for iNdEx < l { var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowPlugin } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } wireType := int(wire & 0x7) switch wireType { case 0: for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowPlugin } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } iNdEx++ if dAtA[iNdEx-1] < 0x80 { break } } case 1: iNdEx += 8 case 2: var length int for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowPlugin } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { break } } if length < 0 { return 0, ErrInvalidLengthPlugin } iNdEx += length case 3: depth++ case 4: if depth == 0 { return 0, ErrUnexpectedEndOfGroupPlugin } depth-- case 5: iNdEx += 4 default: return 0, fmt.Errorf("proto: illegal wireType %d", wireType) } if iNdEx < 0 { return 0, ErrInvalidLengthPlugin } if depth == 0 { return iNdEx, nil } } return 0, io.ErrUnexpectedEOF } var ( ErrInvalidLengthPlugin = fmt.Errorf("proto: negative length found during unmarshaling") ErrIntOverflowPlugin = fmt.Errorf("proto: integer overflow") ErrUnexpectedEndOfGroupPlugin = fmt.Errorf("proto: unexpected end of group") ) ================================================ FILE: vendor/github.com/docker/docker/api/types/swarm/runtime/plugin.proto ================================================ syntax = "proto3"; // PluginSpec defines the base payload which clients can specify for creating // a service with the plugin runtime. message PluginSpec { string name = 1; string remote = 2; repeated PluginPrivilege privileges = 3; bool disabled = 4; repeated string env = 5; } // PluginPrivilege describes a permission the user has to accept // upon installing a plugin. message PluginPrivilege { string name = 1; string description = 2; repeated string value = 3; } ================================================ FILE: vendor/github.com/docker/docker/api/types/swarm/runtime.go ================================================ package swarm // import "github.com/docker/docker/api/types/swarm" // RuntimeType is the type of runtime used for the TaskSpec type RuntimeType string // RuntimeURL is the proto type url type RuntimeURL string const ( // RuntimeContainer is the container based runtime RuntimeContainer RuntimeType = "container" // RuntimePlugin is the plugin based runtime RuntimePlugin RuntimeType = "plugin" // RuntimeNetworkAttachment is the network attachment runtime RuntimeNetworkAttachment RuntimeType = "attachment" // RuntimeURLContainer is the proto url for the container type RuntimeURLContainer RuntimeURL = "types.docker.com/RuntimeContainer" // RuntimeURLPlugin is the proto url for the plugin type RuntimeURLPlugin RuntimeURL = "types.docker.com/RuntimePlugin" ) // NetworkAttachmentSpec represents the runtime spec type for network // attachment tasks type NetworkAttachmentSpec struct { ContainerID string } ================================================ FILE: vendor/github.com/docker/docker/api/types/swarm/secret.go ================================================ package swarm // import "github.com/docker/docker/api/types/swarm" import "os" // Secret represents a secret. type Secret struct { ID string Meta Spec SecretSpec } // SecretSpec represents a secret specification from a secret in swarm type SecretSpec struct { Annotations Data []byte `json:",omitempty"` Driver *Driver `json:",omitempty"` // name of the secrets driver used to fetch the secret's value from an external secret store // Templating controls whether and how to evaluate the secret payload as // a template. If it is not set, no templating is used. Templating *Driver `json:",omitempty"` } // SecretReferenceFileTarget is a file target in a secret reference type SecretReferenceFileTarget struct { Name string UID string GID string Mode os.FileMode } // SecretReference is a reference to a secret in swarm type SecretReference struct { File *SecretReferenceFileTarget SecretID string SecretName string } ================================================ FILE: vendor/github.com/docker/docker/api/types/swarm/service.go ================================================ package swarm // import "github.com/docker/docker/api/types/swarm" import "time" // Service represents a service. type Service struct { ID string Meta Spec ServiceSpec `json:",omitempty"` PreviousSpec *ServiceSpec `json:",omitempty"` Endpoint Endpoint `json:",omitempty"` UpdateStatus *UpdateStatus `json:",omitempty"` // ServiceStatus is an optional, extra field indicating the number of // desired and running tasks. It is provided primarily as a shortcut to // calculating these values client-side, which otherwise would require // listing all tasks for a service, an operation that could be // computation and network expensive. ServiceStatus *ServiceStatus `json:",omitempty"` // JobStatus is the status of a Service which is in one of ReplicatedJob or // GlobalJob modes. It is absent on Replicated and Global services. JobStatus *JobStatus `json:",omitempty"` } // ServiceSpec represents the spec of a service. type ServiceSpec struct { Annotations // TaskTemplate defines how the service should construct new tasks when // orchestrating this service. TaskTemplate TaskSpec `json:",omitempty"` Mode ServiceMode `json:",omitempty"` UpdateConfig *UpdateConfig `json:",omitempty"` RollbackConfig *UpdateConfig `json:",omitempty"` // Networks specifies which networks the service should attach to. // // Deprecated: This field is deprecated since v1.44. The Networks field in TaskSpec should be used instead. Networks []NetworkAttachmentConfig `json:",omitempty"` EndpointSpec *EndpointSpec `json:",omitempty"` } // ServiceMode represents the mode of a service. type ServiceMode struct { Replicated *ReplicatedService `json:",omitempty"` Global *GlobalService `json:",omitempty"` ReplicatedJob *ReplicatedJob `json:",omitempty"` GlobalJob *GlobalJob `json:",omitempty"` } // UpdateState is the state of a service update. type UpdateState string const ( // UpdateStateUpdating is the updating state. UpdateStateUpdating UpdateState = "updating" // UpdateStatePaused is the paused state. UpdateStatePaused UpdateState = "paused" // UpdateStateCompleted is the completed state. UpdateStateCompleted UpdateState = "completed" // UpdateStateRollbackStarted is the state with a rollback in progress. UpdateStateRollbackStarted UpdateState = "rollback_started" // UpdateStateRollbackPaused is the state with a rollback in progress. UpdateStateRollbackPaused UpdateState = "rollback_paused" // UpdateStateRollbackCompleted is the state with a rollback in progress. UpdateStateRollbackCompleted UpdateState = "rollback_completed" ) // UpdateStatus reports the status of a service update. type UpdateStatus struct { State UpdateState `json:",omitempty"` StartedAt *time.Time `json:",omitempty"` CompletedAt *time.Time `json:",omitempty"` Message string `json:",omitempty"` } // ReplicatedService is a kind of ServiceMode. type ReplicatedService struct { Replicas *uint64 `json:",omitempty"` } // GlobalService is a kind of ServiceMode. type GlobalService struct{} // ReplicatedJob is the a type of Service which executes a defined Tasks // in parallel until the specified number of Tasks have succeeded. type ReplicatedJob struct { // MaxConcurrent indicates the maximum number of Tasks that should be // executing simultaneously for this job at any given time. There may be // fewer Tasks that MaxConcurrent executing simultaneously; for example, if // there are fewer than MaxConcurrent tasks needed to reach // TotalCompletions. // // If this field is empty, it will default to a max concurrency of 1. MaxConcurrent *uint64 `json:",omitempty"` // TotalCompletions is the total number of Tasks desired to run to // completion. // // If this field is empty, the value of MaxConcurrent will be used. TotalCompletions *uint64 `json:",omitempty"` } // GlobalJob is the type of a Service which executes a Task on every Node // matching the Service's placement constraints. These tasks run to completion // and then exit. // // This type is deliberately empty. type GlobalJob struct{} const ( // UpdateFailureActionPause PAUSE UpdateFailureActionPause = "pause" // UpdateFailureActionContinue CONTINUE UpdateFailureActionContinue = "continue" // UpdateFailureActionRollback ROLLBACK UpdateFailureActionRollback = "rollback" // UpdateOrderStopFirst STOP_FIRST UpdateOrderStopFirst = "stop-first" // UpdateOrderStartFirst START_FIRST UpdateOrderStartFirst = "start-first" ) // UpdateConfig represents the update configuration. type UpdateConfig struct { // Maximum number of tasks to be updated in one iteration. // 0 means unlimited parallelism. Parallelism uint64 // Amount of time between updates. Delay time.Duration `json:",omitempty"` // FailureAction is the action to take when an update failures. FailureAction string `json:",omitempty"` // Monitor indicates how long to monitor a task for failure after it is // created. If the task fails by ending up in one of the states // REJECTED, COMPLETED, or FAILED, within Monitor from its creation, // this counts as a failure. If it fails after Monitor, it does not // count as a failure. If Monitor is unspecified, a default value will // be used. Monitor time.Duration `json:",omitempty"` // MaxFailureRatio is the fraction of tasks that may fail during // an update before the failure action is invoked. Any task created by // the current update which ends up in one of the states REJECTED, // COMPLETED or FAILED within Monitor from its creation counts as a // failure. The number of failures is divided by the number of tasks // being updated, and if this fraction is greater than // MaxFailureRatio, the failure action is invoked. // // If the failure action is CONTINUE, there is no effect. // If the failure action is PAUSE, no more tasks will be updated until // another update is started. MaxFailureRatio float32 // Order indicates the order of operations when rolling out an updated // task. Either the old task is shut down before the new task is // started, or the new task is started before the old task is shut down. Order string } // ServiceStatus represents the number of running tasks in a service and the // number of tasks desired to be running. type ServiceStatus struct { // RunningTasks is the number of tasks for the service actually in the // Running state RunningTasks uint64 // DesiredTasks is the number of tasks desired to be running by the // service. For replicated services, this is the replica count. For global // services, this is computed by taking the number of tasks with desired // state of not-Shutdown. DesiredTasks uint64 // CompletedTasks is the number of tasks in the state Completed, if this // service is in ReplicatedJob or GlobalJob mode. This field must be // cross-referenced with the service type, because the default value of 0 // may mean that a service is not in a job mode, or it may mean that the // job has yet to complete any tasks. CompletedTasks uint64 } // JobStatus is the status of a job-type service. type JobStatus struct { // JobIteration is a value increased each time a Job is executed, // successfully or otherwise. "Executed", in this case, means the job as a // whole has been started, not that an individual Task has been launched. A // job is "Executed" when its ServiceSpec is updated. JobIteration can be // used to disambiguate Tasks belonging to different executions of a job. // // Though JobIteration will increase with each subsequent execution, it may // not necessarily increase by 1, and so JobIteration should not be used to // keep track of the number of times a job has been executed. JobIteration Version // LastExecution is the time that the job was last executed, as observed by // Swarm manager. LastExecution time.Time `json:",omitempty"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/swarm/service_create_response.go ================================================ package swarm // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // ServiceCreateResponse contains the information returned to a client on the // creation of a new service. // // swagger:model ServiceCreateResponse type ServiceCreateResponse struct { // The ID of the created service. ID string `json:"ID,omitempty"` // Optional warning message. // // FIXME(thaJeztah): this should have "omitempty" in the generated type. // Warnings []string `json:"Warnings"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/swarm/service_update_response.go ================================================ package swarm // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // ServiceUpdateResponse service update response // swagger:model ServiceUpdateResponse type ServiceUpdateResponse struct { // Optional warning messages Warnings []string `json:"Warnings"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/swarm/swarm.go ================================================ package swarm // import "github.com/docker/docker/api/types/swarm" import ( "time" ) // ClusterInfo represents info about the cluster for outputting in "info" // it contains the same information as "Swarm", but without the JoinTokens type ClusterInfo struct { ID string Meta Spec Spec TLSInfo TLSInfo RootRotationInProgress bool DefaultAddrPool []string SubnetSize uint32 DataPathPort uint32 } // Swarm represents a swarm. type Swarm struct { ClusterInfo JoinTokens JoinTokens } // JoinTokens contains the tokens workers and managers need to join the swarm. type JoinTokens struct { // Worker is the join token workers may use to join the swarm. Worker string // Manager is the join token managers may use to join the swarm. Manager string } // Spec represents the spec of a swarm. type Spec struct { Annotations Orchestration OrchestrationConfig `json:",omitempty"` Raft RaftConfig `json:",omitempty"` Dispatcher DispatcherConfig `json:",omitempty"` CAConfig CAConfig `json:",omitempty"` TaskDefaults TaskDefaults `json:",omitempty"` EncryptionConfig EncryptionConfig `json:",omitempty"` } // OrchestrationConfig represents orchestration configuration. type OrchestrationConfig struct { // TaskHistoryRetentionLimit is the number of historic tasks to keep per instance or // node. If negative, never remove completed or failed tasks. TaskHistoryRetentionLimit *int64 `json:",omitempty"` } // TaskDefaults parameterizes cluster-level task creation with default values. type TaskDefaults struct { // LogDriver selects the log driver to use for tasks created in the // orchestrator if unspecified by a service. // // Updating this value will only have an affect on new tasks. Old tasks // will continue use their previously configured log driver until // recreated. LogDriver *Driver `json:",omitempty"` } // EncryptionConfig controls at-rest encryption of data and keys. type EncryptionConfig struct { // AutoLockManagers specifies whether or not managers TLS keys and raft data // should be encrypted at rest in such a way that they must be unlocked // before the manager node starts up again. AutoLockManagers bool } // RaftConfig represents raft configuration. type RaftConfig struct { // SnapshotInterval is the number of log entries between snapshots. SnapshotInterval uint64 `json:",omitempty"` // KeepOldSnapshots is the number of snapshots to keep beyond the // current snapshot. KeepOldSnapshots *uint64 `json:",omitempty"` // LogEntriesForSlowFollowers is the number of log entries to keep // around to sync up slow followers after a snapshot is created. LogEntriesForSlowFollowers uint64 `json:",omitempty"` // ElectionTick is the number of ticks that a follower will wait for a message // from the leader before becoming a candidate and starting an election. // ElectionTick must be greater than HeartbeatTick. // // A tick currently defaults to one second, so these translate directly to // seconds currently, but this is NOT guaranteed. ElectionTick int // HeartbeatTick is the number of ticks between heartbeats. Every // HeartbeatTick ticks, the leader will send a heartbeat to the // followers. // // A tick currently defaults to one second, so these translate directly to // seconds currently, but this is NOT guaranteed. HeartbeatTick int } // DispatcherConfig represents dispatcher configuration. type DispatcherConfig struct { // HeartbeatPeriod defines how often agent should send heartbeats to // dispatcher. HeartbeatPeriod time.Duration `json:",omitempty"` } // CAConfig represents CA configuration. type CAConfig struct { // NodeCertExpiry is the duration certificates should be issued for NodeCertExpiry time.Duration `json:",omitempty"` // ExternalCAs is a list of CAs to which a manager node will make // certificate signing requests for node certificates. ExternalCAs []*ExternalCA `json:",omitempty"` // SigningCACert and SigningCAKey specify the desired signing root CA and // root CA key for the swarm. When inspecting the cluster, the key will // be redacted. SigningCACert string `json:",omitempty"` SigningCAKey string `json:",omitempty"` // If this value changes, and there is no specified signing cert and key, // then the swarm is forced to generate a new root certificate and key. ForceRotate uint64 `json:",omitempty"` } // ExternalCAProtocol represents type of external CA. type ExternalCAProtocol string // ExternalCAProtocolCFSSL CFSSL const ExternalCAProtocolCFSSL ExternalCAProtocol = "cfssl" // ExternalCA defines external CA to be used by the cluster. type ExternalCA struct { // Protocol is the protocol used by this external CA. Protocol ExternalCAProtocol // URL is the URL where the external CA can be reached. URL string // Options is a set of additional key/value pairs whose interpretation // depends on the specified CA type. Options map[string]string `json:",omitempty"` // CACert specifies which root CA is used by this external CA. This certificate must // be in PEM format. CACert string } // InitRequest is the request used to init a swarm. type InitRequest struct { ListenAddr string AdvertiseAddr string DataPathAddr string DataPathPort uint32 ForceNewCluster bool Spec Spec AutoLockManagers bool Availability NodeAvailability DefaultAddrPool []string SubnetSize uint32 } // JoinRequest is the request used to join a swarm. type JoinRequest struct { ListenAddr string AdvertiseAddr string DataPathAddr string RemoteAddrs []string JoinToken string // accept by secret Availability NodeAvailability } // UnlockRequest is the request used to unlock a swarm. type UnlockRequest struct { // UnlockKey is the unlock key in ASCII-armored format. UnlockKey string } // LocalNodeState represents the state of the local node. type LocalNodeState string const ( // LocalNodeStateInactive INACTIVE LocalNodeStateInactive LocalNodeState = "inactive" // LocalNodeStatePending PENDING LocalNodeStatePending LocalNodeState = "pending" // LocalNodeStateActive ACTIVE LocalNodeStateActive LocalNodeState = "active" // LocalNodeStateError ERROR LocalNodeStateError LocalNodeState = "error" // LocalNodeStateLocked LOCKED LocalNodeStateLocked LocalNodeState = "locked" ) // Info represents generic information about swarm. type Info struct { NodeID string NodeAddr string LocalNodeState LocalNodeState ControlAvailable bool Error string RemoteManagers []Peer Nodes int `json:",omitempty"` Managers int `json:",omitempty"` Cluster *ClusterInfo `json:",omitempty"` Warnings []string `json:",omitempty"` } // Status provides information about the current swarm status and role, // obtained from the "Swarm" header in the API response. type Status struct { // NodeState represents the state of the node. NodeState LocalNodeState // ControlAvailable indicates if the node is a swarm manager. ControlAvailable bool } // Peer represents a peer. type Peer struct { NodeID string Addr string } // UpdateFlags contains flags for SwarmUpdate. type UpdateFlags struct { RotateWorkerToken bool RotateManagerToken bool RotateManagerUnlockKey bool } ================================================ FILE: vendor/github.com/docker/docker/api/types/swarm/task.go ================================================ package swarm // import "github.com/docker/docker/api/types/swarm" import ( "time" "github.com/docker/docker/api/types/swarm/runtime" ) // TaskState represents the state of a task. type TaskState string const ( // TaskStateNew NEW TaskStateNew TaskState = "new" // TaskStateAllocated ALLOCATED TaskStateAllocated TaskState = "allocated" // TaskStatePending PENDING TaskStatePending TaskState = "pending" // TaskStateAssigned ASSIGNED TaskStateAssigned TaskState = "assigned" // TaskStateAccepted ACCEPTED TaskStateAccepted TaskState = "accepted" // TaskStatePreparing PREPARING TaskStatePreparing TaskState = "preparing" // TaskStateReady READY TaskStateReady TaskState = "ready" // TaskStateStarting STARTING TaskStateStarting TaskState = "starting" // TaskStateRunning RUNNING TaskStateRunning TaskState = "running" // TaskStateComplete COMPLETE TaskStateComplete TaskState = "complete" // TaskStateShutdown SHUTDOWN TaskStateShutdown TaskState = "shutdown" // TaskStateFailed FAILED TaskStateFailed TaskState = "failed" // TaskStateRejected REJECTED TaskStateRejected TaskState = "rejected" // TaskStateRemove REMOVE TaskStateRemove TaskState = "remove" // TaskStateOrphaned ORPHANED TaskStateOrphaned TaskState = "orphaned" ) // Task represents a task. type Task struct { ID string Meta Annotations Spec TaskSpec `json:",omitempty"` ServiceID string `json:",omitempty"` Slot int `json:",omitempty"` NodeID string `json:",omitempty"` Status TaskStatus `json:",omitempty"` DesiredState TaskState `json:",omitempty"` NetworksAttachments []NetworkAttachment `json:",omitempty"` GenericResources []GenericResource `json:",omitempty"` // JobIteration is the JobIteration of the Service that this Task was // spawned from, if the Service is a ReplicatedJob or GlobalJob. This is // used to determine which Tasks belong to which run of the job. This field // is absent if the Service mode is Replicated or Global. JobIteration *Version `json:",omitempty"` // Volumes is the list of VolumeAttachments for this task. It specifies // which particular volumes are to be used by this particular task, and // fulfilling what mounts in the spec. Volumes []VolumeAttachment } // TaskSpec represents the spec of a task. type TaskSpec struct { // ContainerSpec, NetworkAttachmentSpec, and PluginSpec are mutually exclusive. // PluginSpec is only used when the `Runtime` field is set to `plugin` // NetworkAttachmentSpec is used if the `Runtime` field is set to // `attachment`. ContainerSpec *ContainerSpec `json:",omitempty"` PluginSpec *runtime.PluginSpec `json:",omitempty"` NetworkAttachmentSpec *NetworkAttachmentSpec `json:",omitempty"` Resources *ResourceRequirements `json:",omitempty"` RestartPolicy *RestartPolicy `json:",omitempty"` Placement *Placement `json:",omitempty"` Networks []NetworkAttachmentConfig `json:",omitempty"` // LogDriver specifies the LogDriver to use for tasks created from this // spec. If not present, the one on cluster default on swarm.Spec will be // used, finally falling back to the engine default if not specified. LogDriver *Driver `json:",omitempty"` // ForceUpdate is a counter that triggers an update even if no relevant // parameters have been changed. ForceUpdate uint64 Runtime RuntimeType `json:",omitempty"` } // Resources represents resources (CPU/Memory) which can be advertised by a // node and requested to be reserved for a task. type Resources struct { NanoCPUs int64 `json:",omitempty"` MemoryBytes int64 `json:",omitempty"` GenericResources []GenericResource `json:",omitempty"` } // Limit describes limits on resources which can be requested by a task. type Limit struct { NanoCPUs int64 `json:",omitempty"` MemoryBytes int64 `json:",omitempty"` Pids int64 `json:",omitempty"` } // GenericResource represents a "user defined" resource which can // be either an integer (e.g: SSD=3) or a string (e.g: SSD=sda1) type GenericResource struct { NamedResourceSpec *NamedGenericResource `json:",omitempty"` DiscreteResourceSpec *DiscreteGenericResource `json:",omitempty"` } // NamedGenericResource represents a "user defined" resource which is defined // as a string. // "Kind" is used to describe the Kind of a resource (e.g: "GPU", "FPGA", "SSD", ...) // Value is used to identify the resource (GPU="UUID-1", FPGA="/dev/sdb5", ...) type NamedGenericResource struct { Kind string `json:",omitempty"` Value string `json:",omitempty"` } // DiscreteGenericResource represents a "user defined" resource which is defined // as an integer // "Kind" is used to describe the Kind of a resource (e.g: "GPU", "FPGA", "SSD", ...) // Value is used to count the resource (SSD=5, HDD=3, ...) type DiscreteGenericResource struct { Kind string `json:",omitempty"` Value int64 `json:",omitempty"` } // ResourceRequirements represents resources requirements. type ResourceRequirements struct { Limits *Limit `json:",omitempty"` Reservations *Resources `json:",omitempty"` } // Placement represents orchestration parameters. type Placement struct { Constraints []string `json:",omitempty"` Preferences []PlacementPreference `json:",omitempty"` MaxReplicas uint64 `json:",omitempty"` // Platforms stores all the platforms that the image can run on. // This field is used in the platform filter for scheduling. If empty, // then the platform filter is off, meaning there are no scheduling restrictions. Platforms []Platform `json:",omitempty"` } // PlacementPreference provides a way to make the scheduler aware of factors // such as topology. type PlacementPreference struct { Spread *SpreadOver } // SpreadOver is a scheduling preference that instructs the scheduler to spread // tasks evenly over groups of nodes identified by labels. type SpreadOver struct { // label descriptor, such as engine.labels.az SpreadDescriptor string } // RestartPolicy represents the restart policy. type RestartPolicy struct { Condition RestartPolicyCondition `json:",omitempty"` Delay *time.Duration `json:",omitempty"` MaxAttempts *uint64 `json:",omitempty"` Window *time.Duration `json:",omitempty"` } // RestartPolicyCondition represents when to restart. type RestartPolicyCondition string const ( // RestartPolicyConditionNone NONE RestartPolicyConditionNone RestartPolicyCondition = "none" // RestartPolicyConditionOnFailure ON_FAILURE RestartPolicyConditionOnFailure RestartPolicyCondition = "on-failure" // RestartPolicyConditionAny ANY RestartPolicyConditionAny RestartPolicyCondition = "any" ) // TaskStatus represents the status of a task. type TaskStatus struct { Timestamp time.Time `json:",omitempty"` State TaskState `json:",omitempty"` Message string `json:",omitempty"` Err string `json:",omitempty"` ContainerStatus *ContainerStatus `json:",omitempty"` PortStatus PortStatus `json:",omitempty"` } // ContainerStatus represents the status of a container. type ContainerStatus struct { ContainerID string PID int ExitCode int } // PortStatus represents the port status of a task's host ports whose // service has published host ports type PortStatus struct { Ports []PortConfig `json:",omitempty"` } // VolumeAttachment contains the associating a Volume to a Task. type VolumeAttachment struct { // ID is the Swarmkit ID of the Volume. This is not the CSI VolumeId. ID string `json:",omitempty"` // Source, together with Target, indicates the Mount, as specified in the // ContainerSpec, that this volume fulfills. Source string `json:",omitempty"` // Target, together with Source, indicates the Mount, as specified // in the ContainerSpec, that this volume fulfills. Target string `json:",omitempty"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/system/info.go ================================================ package system import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/swarm" ) // Info contains response of Engine API: // GET "/info" type Info struct { ID string Containers int ContainersRunning int ContainersPaused int ContainersStopped int Images int Driver string DriverStatus [][2]string SystemStatus [][2]string `json:",omitempty"` // SystemStatus is only propagated by the Swarm standalone API Plugins PluginsInfo MemoryLimit bool SwapLimit bool KernelMemory bool `json:",omitempty"` // Deprecated: kernel 5.4 deprecated kmem.limit_in_bytes KernelMemoryTCP bool `json:",omitempty"` // KernelMemoryTCP is not supported on cgroups v2. CPUCfsPeriod bool `json:"CpuCfsPeriod"` CPUCfsQuota bool `json:"CpuCfsQuota"` CPUShares bool CPUSet bool PidsLimit bool IPv4Forwarding bool BridgeNfIptables bool `json:"BridgeNfIptables"` // Deprecated: netfilter module is now loaded on-demand and no longer during daemon startup, making this field obsolete. This field is always false and will be removed in the next release. BridgeNfIP6tables bool `json:"BridgeNfIp6tables"` // Deprecated: netfilter module is now loaded on-demand and no longer during daemon startup, making this field obsolete. This field is always false and will be removed in the next release. Debug bool NFd int OomKillDisable bool NGoroutines int SystemTime string LoggingDriver string CgroupDriver string CgroupVersion string `json:",omitempty"` NEventsListener int KernelVersion string OperatingSystem string OSVersion string OSType string Architecture string IndexServerAddress string RegistryConfig *registry.ServiceConfig NCPU int MemTotal int64 GenericResources []swarm.GenericResource DockerRootDir string HTTPProxy string `json:"HttpProxy"` HTTPSProxy string `json:"HttpsProxy"` NoProxy string Name string Labels []string ExperimentalBuild bool ServerVersion string Runtimes map[string]RuntimeWithStatus DefaultRuntime string Swarm swarm.Info // LiveRestoreEnabled determines whether containers should be kept // running when the daemon is shutdown or upon daemon start if // running containers are detected LiveRestoreEnabled bool Isolation container.Isolation InitBinary string ContainerdCommit Commit RuncCommit Commit InitCommit Commit SecurityOptions []string ProductLicense string `json:",omitempty"` DefaultAddressPools []NetworkAddressPool `json:",omitempty"` CDISpecDirs []string Containerd *ContainerdInfo `json:",omitempty"` // Warnings contains a slice of warnings that occurred while collecting // system information. These warnings are intended to be informational // messages for the user, and are not intended to be parsed / used for // other purposes, as they do not have a fixed format. Warnings []string } // ContainerdInfo holds information about the containerd instance used by the daemon. type ContainerdInfo struct { // Address is the path to the containerd socket. Address string `json:",omitempty"` // Namespaces is the containerd namespaces used by the daemon. Namespaces ContainerdNamespaces } // ContainerdNamespaces reflects the containerd namespaces used by the daemon. // // These namespaces can be configured in the daemon configuration, and are // considered to be used exclusively by the daemon, // // As these namespaces are considered to be exclusively accessed // by the daemon, it is not recommended to change these values, // or to change them to a value that is used by other systems, // such as cri-containerd. type ContainerdNamespaces struct { // Containers holds the default containerd namespace used for // containers managed by the daemon. // // The default namespace for containers is "moby", but will be // suffixed with the `.` of the remapped `root` if // user-namespaces are enabled and the containerd image-store // is used. Containers string // Plugins holds the default containerd namespace used for // plugins managed by the daemon. // // The default namespace for plugins is "moby", but will be // suffixed with the `.` of the remapped `root` if // user-namespaces are enabled and the containerd image-store // is used. Plugins string } // PluginsInfo is a temp struct holding Plugins name // registered with docker daemon. It is used by [Info] struct type PluginsInfo struct { // List of Volume plugins registered Volume []string // List of Network plugins registered Network []string // List of Authorization plugins registered Authorization []string // List of Log plugins registered Log []string } // Commit holds the Git-commit (SHA1) that a binary was built from, as reported // in the version-string of external tools, such as containerd, or runC. type Commit struct { // ID is the actual commit ID or version of external tool. ID string // Expected is the commit ID of external tool expected by dockerd as set at build time. // // Deprecated: this field is no longer used in API v1.49, but kept for backward-compatibility with older API versions. Expected string } // NetworkAddressPool is a temp struct used by [Info] struct. type NetworkAddressPool struct { Base string Size int } ================================================ FILE: vendor/github.com/docker/docker/api/types/system/runtime.go ================================================ package system // Runtime describes an OCI runtime type Runtime struct { // "Legacy" runtime configuration for runc-compatible runtimes. Path string `json:"path,omitempty"` Args []string `json:"runtimeArgs,omitempty"` // Shimv2 runtime configuration. Mutually exclusive with the legacy config above. Type string `json:"runtimeType,omitempty"` Options map[string]interface{} `json:"options,omitempty"` } // RuntimeWithStatus extends [Runtime] to hold [RuntimeStatus]. type RuntimeWithStatus struct { Runtime Status map[string]string `json:"status,omitempty"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/system/security_opts.go ================================================ package system import ( "errors" "fmt" "strings" ) // SecurityOpt contains the name and options of a security option type SecurityOpt struct { Name string Options []KeyValue } // DecodeSecurityOptions decodes a security options string slice to a // type-safe [SecurityOpt]. func DecodeSecurityOptions(opts []string) ([]SecurityOpt, error) { so := []SecurityOpt{} for _, opt := range opts { // support output from a < 1.13 docker daemon if !strings.Contains(opt, "=") { so = append(so, SecurityOpt{Name: opt}) continue } secopt := SecurityOpt{} for _, s := range strings.Split(opt, ",") { k, v, ok := strings.Cut(s, "=") if !ok { return nil, fmt.Errorf("invalid security option %q", s) } if k == "" || v == "" { return nil, errors.New("invalid empty security option") } if k == "name" { secopt.Name = v continue } secopt.Options = append(secopt.Options, KeyValue{Key: k, Value: v}) } so = append(so, secopt) } return so, nil } // KeyValue holds a key/value pair. type KeyValue struct { Key, Value string } ================================================ FILE: vendor/github.com/docker/docker/api/types/time/timestamp.go ================================================ package time // import "github.com/docker/docker/api/types/time" import ( "fmt" "math" "strconv" "strings" "time" ) // These are additional predefined layouts for use in Time.Format and Time.Parse // with --since and --until parameters for `docker logs` and `docker events` const ( rFC3339Local = "2006-01-02T15:04:05" // RFC3339 with local timezone rFC3339NanoLocal = "2006-01-02T15:04:05.999999999" // RFC3339Nano with local timezone dateWithZone = "2006-01-02Z07:00" // RFC3339 with time at 00:00:00 dateLocal = "2006-01-02" // RFC3339 with local timezone and time at 00:00:00 ) // GetTimestamp tries to parse given string as golang duration, // then RFC3339 time and finally as a Unix timestamp. If // any of these were successful, it returns a Unix timestamp // as string otherwise returns the given value back. // In case of duration input, the returned timestamp is computed // as the given reference time minus the amount of the duration. func GetTimestamp(value string, reference time.Time) (string, error) { if d, err := time.ParseDuration(value); value != "0" && err == nil { return strconv.FormatInt(reference.Add(-d).Unix(), 10), nil } var format string // if the string has a Z or a + or three dashes use parse otherwise use parseinlocation parseInLocation := !(strings.ContainsAny(value, "zZ+") || strings.Count(value, "-") == 3) if strings.Contains(value, ".") { if parseInLocation { format = rFC3339NanoLocal } else { format = time.RFC3339Nano } } else if strings.Contains(value, "T") { // we want the number of colons in the T portion of the timestamp tcolons := strings.Count(value, ":") // if parseInLocation is off and we have a +/- zone offset (not Z) then // there will be an extra colon in the input for the tz offset subtract that // colon from the tcolons count if !parseInLocation && !strings.ContainsAny(value, "zZ") && tcolons > 0 { tcolons-- } if parseInLocation { switch tcolons { case 0: format = "2006-01-02T15" case 1: format = "2006-01-02T15:04" default: format = rFC3339Local } } else { switch tcolons { case 0: format = "2006-01-02T15Z07:00" case 1: format = "2006-01-02T15:04Z07:00" default: format = time.RFC3339 } } } else if parseInLocation { format = dateLocal } else { format = dateWithZone } var t time.Time var err error if parseInLocation { t, err = time.ParseInLocation(format, value, time.FixedZone(reference.Zone())) } else { t, err = time.Parse(format, value) } if err != nil { // if there is a `-` then it's an RFC3339 like timestamp if strings.Contains(value, "-") { return "", err // was probably an RFC3339 like timestamp but the parser failed with an error } if _, _, err := parseTimestamp(value); err != nil { return "", fmt.Errorf("failed to parse value as time or duration: %q", value) } return value, nil // unix timestamp in and out case (meaning: the value passed at the command line is already in the right format for passing to the server) } return fmt.Sprintf("%d.%09d", t.Unix(), int64(t.Nanosecond())), nil } // ParseTimestamps returns seconds and nanoseconds from a timestamp that has // the format ("%d.%09d", time.Unix(), int64(time.Nanosecond())). // If the incoming nanosecond portion is longer than 9 digits it is truncated. // The expectation is that the seconds and nanoseconds will be used to create a // time variable. For example: // // seconds, nanoseconds, _ := ParseTimestamp("1136073600.000000001",0) // since := time.Unix(seconds, nanoseconds) // // returns seconds as defaultSeconds if value == "" func ParseTimestamps(value string, defaultSeconds int64) (seconds int64, nanoseconds int64, err error) { if value == "" { return defaultSeconds, 0, nil } return parseTimestamp(value) } func parseTimestamp(value string) (sec int64, nsec int64, err error) { s, n, ok := strings.Cut(value, ".") sec, err = strconv.ParseInt(s, 10, 64) if err != nil { return sec, 0, err } if !ok { return sec, 0, nil } nsec, err = strconv.ParseInt(n, 10, 64) if err != nil { return sec, nsec, err } // should already be in nanoseconds but just in case convert n to nanoseconds nsec = int64(float64(nsec) * math.Pow(float64(10), float64(9-len(n)))) return sec, nsec, nil } ================================================ FILE: vendor/github.com/docker/docker/api/types/types.go ================================================ package types // import "github.com/docker/docker/api/types" import ( "time" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/volume" ) const ( // MediaTypeRawStream is vendor specific MIME-Type set for raw TTY streams MediaTypeRawStream = "application/vnd.docker.raw-stream" // MediaTypeMultiplexedStream is vendor specific MIME-Type set for stdin/stdout/stderr multiplexed streams MediaTypeMultiplexedStream = "application/vnd.docker.multiplexed-stream" ) // Ping contains response of Engine API: // GET "/_ping" type Ping struct { APIVersion string OSType string Experimental bool BuilderVersion BuilderVersion // SwarmStatus provides information about the current swarm status of the // engine, obtained from the "Swarm" header in the API response. // // It can be a nil struct if the API version does not provide this header // in the ping response, or if an error occurred, in which case the client // should use other ways to get the current swarm status, such as the /swarm // endpoint. SwarmStatus *swarm.Status } // ComponentVersion describes the version information for a specific component. type ComponentVersion struct { Name string Version string Details map[string]string `json:",omitempty"` } // Version contains response of Engine API: // GET "/version" type Version struct { Platform struct{ Name string } `json:",omitempty"` Components []ComponentVersion `json:",omitempty"` // The following fields are deprecated, they relate to the Engine component and are kept for backwards compatibility Version string APIVersion string `json:"ApiVersion"` MinAPIVersion string `json:"MinAPIVersion,omitempty"` GitCommit string GoVersion string Os string Arch string KernelVersion string `json:",omitempty"` Experimental bool `json:",omitempty"` BuildTime string `json:",omitempty"` } // DiskUsageObject represents an object type used for disk usage query filtering. type DiskUsageObject string const ( // ContainerObject represents a container DiskUsageObject. ContainerObject DiskUsageObject = "container" // ImageObject represents an image DiskUsageObject. ImageObject DiskUsageObject = "image" // VolumeObject represents a volume DiskUsageObject. VolumeObject DiskUsageObject = "volume" // BuildCacheObject represents a build-cache DiskUsageObject. BuildCacheObject DiskUsageObject = "build-cache" ) // DiskUsageOptions holds parameters for system disk usage query. type DiskUsageOptions struct { // Types specifies what object types to include in the response. If empty, // all object types are returned. Types []DiskUsageObject } // DiskUsage contains response of Engine API: // GET "/system/df" type DiskUsage struct { LayersSize int64 Images []*image.Summary Containers []*container.Summary Volumes []*volume.Volume BuildCache []*BuildCache BuilderSize int64 `json:",omitempty"` // Deprecated: deprecated in API 1.38, and no longer used since API 1.40. } // BuildCachePruneReport contains the response for Engine API: // POST "/build/prune" type BuildCachePruneReport struct { CachesDeleted []string SpaceReclaimed uint64 } // SecretCreateResponse contains the information returned to a client // on the creation of a new secret. type SecretCreateResponse struct { // ID is the id of the created secret. ID string } // SecretListOptions holds parameters to list secrets type SecretListOptions struct { Filters filters.Args } // ConfigCreateResponse contains the information returned to a client // on the creation of a new config. type ConfigCreateResponse struct { // ID is the id of the created config. ID string } // ConfigListOptions holds parameters to list configs type ConfigListOptions struct { Filters filters.Args } // PushResult contains the tag, manifest digest, and manifest size from the // push. It's used to signal this information to the trust code in the client // so it can sign the manifest if necessary. type PushResult struct { Tag string Digest string Size int } // BuildResult contains the image id of a successful build type BuildResult struct { ID string } // BuildCache contains information about a build cache record. type BuildCache struct { // ID is the unique ID of the build cache record. ID string // Parent is the ID of the parent build cache record. // // Deprecated: deprecated in API v1.42 and up, as it was deprecated in BuildKit; use Parents instead. Parent string `json:"Parent,omitempty"` // Parents is the list of parent build cache record IDs. Parents []string `json:" Parents,omitempty"` // Type is the cache record type. Type string // Description is a description of the build-step that produced the build cache. Description string // InUse indicates if the build cache is in use. InUse bool // Shared indicates if the build cache is shared. Shared bool // Size is the amount of disk space used by the build cache (in bytes). Size int64 // CreatedAt is the date and time at which the build cache was created. CreatedAt time.Time // LastUsedAt is the date and time at which the build cache was last used. LastUsedAt *time.Time UsageCount int } // BuildCachePruneOptions hold parameters to prune the build cache type BuildCachePruneOptions struct { All bool ReservedSpace int64 MaxUsedSpace int64 MinFreeSpace int64 Filters filters.Args KeepStorage int64 // Deprecated: deprecated in API 1.48. } ================================================ FILE: vendor/github.com/docker/docker/api/types/types_deprecated.go ================================================ package types import ( "context" "github.com/docker/docker/api/types/common" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/storage" ) // IDResponse Response to an API call that returns just an Id. // // Deprecated: use either [container.CommitResponse] or [container.ExecCreateResponse]. It will be removed in the next release. type IDResponse = common.IDResponse // ContainerJSONBase contains response of Engine API GET "/containers/{name:.*}/json" // for API version 1.18 and older. // // Deprecated: use [container.InspectResponse] or [container.ContainerJSONBase]. It will be removed in the next release. type ContainerJSONBase = container.ContainerJSONBase // ContainerJSON is the response for the GET "/containers/{name:.*}/json" // endpoint. // // Deprecated: use [container.InspectResponse]. It will be removed in the next release. type ContainerJSON = container.InspectResponse // Container contains response of Engine API: // GET "/containers/json" // // Deprecated: use [container.Summary]. type Container = container.Summary // ContainerState stores container's running state // // Deprecated: use [container.State]. type ContainerState = container.State // NetworkSettings exposes the network settings in the api. // // Deprecated: use [container.NetworkSettings]. type NetworkSettings = container.NetworkSettings // NetworkSettingsBase holds networking state for a container when inspecting it. // // Deprecated: use [container.NetworkSettingsBase]. type NetworkSettingsBase = container.NetworkSettingsBase // DefaultNetworkSettings holds network information // during the 2 release deprecation period. // It will be removed in Docker 1.11. // // Deprecated: use [container.DefaultNetworkSettings]. type DefaultNetworkSettings = container.DefaultNetworkSettings // SummaryNetworkSettings provides a summary of container's networks // in /containers/json. // // Deprecated: use [container.NetworkSettingsSummary]. type SummaryNetworkSettings = container.NetworkSettingsSummary // Health states const ( NoHealthcheck = container.NoHealthcheck // Deprecated: use [container.NoHealthcheck]. Starting = container.Starting // Deprecated: use [container.Starting]. Healthy = container.Healthy // Deprecated: use [container.Healthy]. Unhealthy = container.Unhealthy // Deprecated: use [container.Unhealthy]. ) // Health stores information about the container's healthcheck results. // // Deprecated: use [container.Health]. type Health = container.Health // HealthcheckResult stores information about a single run of a healthcheck probe. // // Deprecated: use [container.HealthcheckResult]. type HealthcheckResult = container.HealthcheckResult // MountPoint represents a mount point configuration inside the container. // This is used for reporting the mountpoints in use by a container. // // Deprecated: use [container.MountPoint]. type MountPoint = container.MountPoint // Port An open port on a container // // Deprecated: use [container.Port]. type Port = container.Port // GraphDriverData Information about the storage driver used to store the container's and // image's filesystem. // // Deprecated: use [storage.DriverData]. type GraphDriverData = storage.DriverData // RootFS returns Image's RootFS description including the layer IDs. // // Deprecated: use [image.RootFS]. type RootFS = image.RootFS // ImageInspect contains response of Engine API: // GET "/images/{name:.*}/json" // // Deprecated: use [image.InspectResponse]. type ImageInspect = image.InspectResponse // RequestPrivilegeFunc is a function interface that clients can supply to // retry operations after getting an authorization error. // This function returns the registry authentication header value in base64 // format, or an error if the privilege request fails. // // Deprecated: moved to [github.com/docker/docker/api/types/registry.RequestAuthConfig]. type RequestPrivilegeFunc func(context.Context) (string, error) ================================================ FILE: vendor/github.com/docker/docker/api/types/versions/compare.go ================================================ package versions // import "github.com/docker/docker/api/types/versions" import ( "strconv" "strings" ) // compare compares two version strings // returns -1 if v1 < v2, 1 if v1 > v2, 0 otherwise. func compare(v1, v2 string) int { if v1 == v2 { return 0 } var ( currTab = strings.Split(v1, ".") otherTab = strings.Split(v2, ".") ) maxVer := len(currTab) if len(otherTab) > maxVer { maxVer = len(otherTab) } for i := 0; i < maxVer; i++ { var currInt, otherInt int if len(currTab) > i { currInt, _ = strconv.Atoi(currTab[i]) } if len(otherTab) > i { otherInt, _ = strconv.Atoi(otherTab[i]) } if currInt > otherInt { return 1 } if otherInt > currInt { return -1 } } return 0 } // LessThan checks if a version is less than another func LessThan(v, other string) bool { return compare(v, other) == -1 } // LessThanOrEqualTo checks if a version is less than or equal to another func LessThanOrEqualTo(v, other string) bool { return compare(v, other) <= 0 } // GreaterThan checks if a version is greater than another func GreaterThan(v, other string) bool { return compare(v, other) == 1 } // GreaterThanOrEqualTo checks if a version is greater than or equal to another func GreaterThanOrEqualTo(v, other string) bool { return compare(v, other) >= 0 } // Equal checks if a version is equal to another func Equal(v, other string) bool { return compare(v, other) == 0 } ================================================ FILE: vendor/github.com/docker/docker/api/types/volume/cluster_volume.go ================================================ package volume import ( "github.com/docker/docker/api/types/swarm" ) // ClusterVolume contains options and information specific to, and only present // on, Swarm CSI cluster volumes. type ClusterVolume struct { // ID is the Swarm ID of the volume. Because cluster volumes are Swarm // objects, they have an ID, unlike non-cluster volumes, which only have a // Name. This ID can be used to refer to the cluster volume. ID string // Meta is the swarm metadata about this volume. swarm.Meta // Spec is the cluster-specific options from which this volume is derived. Spec ClusterVolumeSpec // PublishStatus contains the status of the volume as it pertains to its // publishing on Nodes. PublishStatus []*PublishStatus `json:",omitempty"` // Info is information about the global status of the volume. Info *Info `json:",omitempty"` } // ClusterVolumeSpec contains the spec used to create this volume. type ClusterVolumeSpec struct { // Group defines the volume group of this volume. Volumes belonging to the // same group can be referred to by group name when creating Services. // Referring to a volume by group instructs swarm to treat volumes in that // group interchangeably for the purpose of scheduling. Volumes with an // empty string for a group technically all belong to the same, emptystring // group. Group string `json:",omitempty"` // AccessMode defines how the volume is used by tasks. AccessMode *AccessMode `json:",omitempty"` // AccessibilityRequirements specifies where in the cluster a volume must // be accessible from. // // This field must be empty if the plugin does not support // VOLUME_ACCESSIBILITY_CONSTRAINTS capabilities. If it is present but the // plugin does not support it, volume will not be created. // // If AccessibilityRequirements is empty, but the plugin does support // VOLUME_ACCESSIBILITY_CONSTRAINTS, then Swarmkit will assume the entire // cluster is a valid target for the volume. AccessibilityRequirements *TopologyRequirement `json:",omitempty"` // CapacityRange defines the desired capacity that the volume should be // created with. If nil, the plugin will decide the capacity. CapacityRange *CapacityRange `json:",omitempty"` // Secrets defines Swarm Secrets that are passed to the CSI storage plugin // when operating on this volume. Secrets []Secret `json:",omitempty"` // Availability is the Volume's desired availability. Analogous to Node // Availability, this allows the user to take volumes offline in order to // update or delete them. Availability Availability `json:",omitempty"` } // Availability specifies the availability of the volume. type Availability string const ( // AvailabilityActive indicates that the volume is active and fully // schedulable on the cluster. AvailabilityActive Availability = "active" // AvailabilityPause indicates that no new workloads should use the // volume, but existing workloads can continue to use it. AvailabilityPause Availability = "pause" // AvailabilityDrain indicates that all workloads using this volume // should be rescheduled, and the volume unpublished from all nodes. AvailabilityDrain Availability = "drain" ) // AccessMode defines the access mode of a volume. type AccessMode struct { // Scope defines the set of nodes this volume can be used on at one time. Scope Scope `json:",omitempty"` // Sharing defines the number and way that different tasks can use this // volume at one time. Sharing SharingMode `json:",omitempty"` // MountVolume defines options for using this volume as a Mount-type // volume. // // Either BlockVolume or MountVolume, but not both, must be present. MountVolume *TypeMount `json:",omitempty"` // BlockVolume defines options for using this volume as a Block-type // volume. // // Either BlockVolume or MountVolume, but not both, must be present. BlockVolume *TypeBlock `json:",omitempty"` } // Scope defines the Scope of a Cluster Volume. This is how many nodes a // Volume can be accessed simultaneously on. type Scope string const ( // ScopeSingleNode indicates the volume can be used on one node at a // time. ScopeSingleNode Scope = "single" // ScopeMultiNode indicates the volume can be used on many nodes at // the same time. ScopeMultiNode Scope = "multi" ) // SharingMode defines the Sharing of a Cluster Volume. This is how Tasks using a // Volume at the same time can use it. type SharingMode string const ( // SharingNone indicates that only one Task may use the Volume at a // time. SharingNone SharingMode = "none" // SharingReadOnly indicates that the Volume may be shared by any // number of Tasks, but they must be read-only. SharingReadOnly SharingMode = "readonly" // SharingOneWriter indicates that the Volume may be shared by any // number of Tasks, but all after the first must be read-only. SharingOneWriter SharingMode = "onewriter" // SharingAll means that the Volume may be shared by any number of // Tasks, as readers or writers. SharingAll SharingMode = "all" ) // TypeBlock defines options for using a volume as a block-type volume. // // Intentionally empty. type TypeBlock struct{} // TypeMount contains options for using a volume as a Mount-type // volume. type TypeMount struct { // FsType specifies the filesystem type for the mount volume. Optional. FsType string `json:",omitempty"` // MountFlags defines flags to pass when mounting the volume. Optional. MountFlags []string `json:",omitempty"` } // TopologyRequirement expresses the user's requirements for a volume's // accessible topology. type TopologyRequirement struct { // Requisite specifies a list of Topologies, at least one of which the // volume must be accessible from. // // Taken verbatim from the CSI Spec: // // Specifies the list of topologies the provisioned volume MUST be // accessible from. // This field is OPTIONAL. If TopologyRequirement is specified either // requisite or preferred or both MUST be specified. // // If requisite is specified, the provisioned volume MUST be // accessible from at least one of the requisite topologies. // // Given // x = number of topologies provisioned volume is accessible from // n = number of requisite topologies // The CO MUST ensure n >= 1. The SP MUST ensure x >= 1 // If x==n, then the SP MUST make the provisioned volume available to // all topologies from the list of requisite topologies. If it is // unable to do so, the SP MUST fail the CreateVolume call. // For example, if a volume should be accessible from a single zone, // and requisite = // {"region": "R1", "zone": "Z2"} // then the provisioned volume MUST be accessible from the "region" // "R1" and the "zone" "Z2". // Similarly, if a volume should be accessible from two zones, and // requisite = // {"region": "R1", "zone": "Z2"}, // {"region": "R1", "zone": "Z3"} // then the provisioned volume MUST be accessible from the "region" // "R1" and both "zone" "Z2" and "zone" "Z3". // // If xn, then the SP MUST make the provisioned volume available from // all topologies from the list of requisite topologies and MAY choose // the remaining x-n unique topologies from the list of all possible // topologies. If it is unable to do so, the SP MUST fail the // CreateVolume call. // For example, if a volume should be accessible from two zones, and // requisite = // {"region": "R1", "zone": "Z2"} // then the provisioned volume MUST be accessible from the "region" // "R1" and the "zone" "Z2" and the SP may select the second zone // independently, e.g. "R1/Z4". Requisite []Topology `json:",omitempty"` // Preferred is a list of Topologies that the volume should attempt to be // provisioned in. // // Taken from the CSI spec: // // Specifies the list of topologies the CO would prefer the volume to // be provisioned in. // // This field is OPTIONAL. If TopologyRequirement is specified either // requisite or preferred or both MUST be specified. // // An SP MUST attempt to make the provisioned volume available using // the preferred topologies in order from first to last. // // If requisite is specified, all topologies in preferred list MUST // also be present in the list of requisite topologies. // // If the SP is unable to make the provisioned volume available // from any of the preferred topologies, the SP MAY choose a topology // from the list of requisite topologies. // If the list of requisite topologies is not specified, then the SP // MAY choose from the list of all possible topologies. // If the list of requisite topologies is specified and the SP is // unable to make the provisioned volume available from any of the // requisite topologies it MUST fail the CreateVolume call. // // Example 1: // Given a volume should be accessible from a single zone, and // requisite = // {"region": "R1", "zone": "Z2"}, // {"region": "R1", "zone": "Z3"} // preferred = // {"region": "R1", "zone": "Z3"} // then the SP SHOULD first attempt to make the provisioned volume // available from "zone" "Z3" in the "region" "R1" and fall back to // "zone" "Z2" in the "region" "R1" if that is not possible. // // Example 2: // Given a volume should be accessible from a single zone, and // requisite = // {"region": "R1", "zone": "Z2"}, // {"region": "R1", "zone": "Z3"}, // {"region": "R1", "zone": "Z4"}, // {"region": "R1", "zone": "Z5"} // preferred = // {"region": "R1", "zone": "Z4"}, // {"region": "R1", "zone": "Z2"} // then the SP SHOULD first attempt to make the provisioned volume // accessible from "zone" "Z4" in the "region" "R1" and fall back to // "zone" "Z2" in the "region" "R1" if that is not possible. If that // is not possible, the SP may choose between either the "zone" // "Z3" or "Z5" in the "region" "R1". // // Example 3: // Given a volume should be accessible from TWO zones (because an // opaque parameter in CreateVolumeRequest, for example, specifies // the volume is accessible from two zones, aka synchronously // replicated), and // requisite = // {"region": "R1", "zone": "Z2"}, // {"region": "R1", "zone": "Z3"}, // {"region": "R1", "zone": "Z4"}, // {"region": "R1", "zone": "Z5"} // preferred = // {"region": "R1", "zone": "Z5"}, // {"region": "R1", "zone": "Z3"} // then the SP SHOULD first attempt to make the provisioned volume // accessible from the combination of the two "zones" "Z5" and "Z3" in // the "region" "R1". If that's not possible, it should fall back to // a combination of "Z5" and other possibilities from the list of // requisite. If that's not possible, it should fall back to a // combination of "Z3" and other possibilities from the list of // requisite. If that's not possible, it should fall back to a // combination of other possibilities from the list of requisite. Preferred []Topology `json:",omitempty"` } // Topology is a map of topological domains to topological segments. // // This description is taken verbatim from the CSI Spec: // // A topological domain is a sub-division of a cluster, like "region", // "zone", "rack", etc. // A topological segment is a specific instance of a topological domain, // like "zone3", "rack3", etc. // For example {"com.company/zone": "Z1", "com.company/rack": "R3"} // Valid keys have two segments: an OPTIONAL prefix and name, separated // by a slash (/), for example: "com.company.example/zone". // The key name segment is REQUIRED. The prefix is OPTIONAL. // The key name MUST be 63 characters or less, begin and end with an // alphanumeric character ([a-z0-9A-Z]), and contain only dashes (-), // underscores (_), dots (.), or alphanumerics in between, for example // "zone". // The key prefix MUST be 63 characters or less, begin and end with a // lower-case alphanumeric character ([a-z0-9]), contain only // dashes (-), dots (.), or lower-case alphanumerics in between, and // follow domain name notation format // (https://tools.ietf.org/html/rfc1035#section-2.3.1). // The key prefix SHOULD include the plugin's host company name and/or // the plugin name, to minimize the possibility of collisions with keys // from other plugins. // If a key prefix is specified, it MUST be identical across all // topology keys returned by the SP (across all RPCs). // Keys MUST be case-insensitive. Meaning the keys "Zone" and "zone" // MUST not both exist. // Each value (topological segment) MUST contain 1 or more strings. // Each string MUST be 63 characters or less and begin and end with an // alphanumeric character with '-', '_', '.', or alphanumerics in // between. type Topology struct { Segments map[string]string `json:",omitempty"` } // CapacityRange describes the minimum and maximum capacity a volume should be // created with type CapacityRange struct { // RequiredBytes specifies that a volume must be at least this big. The // value of 0 indicates an unspecified minimum. RequiredBytes int64 // LimitBytes specifies that a volume must not be bigger than this. The // value of 0 indicates an unspecified maximum LimitBytes int64 } // Secret represents a Swarm Secret value that must be passed to the CSI // storage plugin when operating on this Volume. It represents one key-value // pair of possibly many. type Secret struct { // Key is the name of the key of the key-value pair passed to the plugin. Key string // Secret is the swarm Secret object from which to read data. This can be a // Secret name or ID. The Secret data is retrieved by Swarm and used as the // value of the key-value pair passed to the plugin. Secret string } // PublishState represents the state of a Volume as it pertains to its // use on a particular Node. type PublishState string const ( // StatePending indicates that the volume should be published on // this node, but the call to ControllerPublishVolume has not been // successfully completed yet and the result recorded by swarmkit. StatePending PublishState = "pending-publish" // StatePublished means the volume is published successfully to the node. StatePublished PublishState = "published" // StatePendingNodeUnpublish indicates that the Volume should be // unpublished on the Node, and we're waiting for confirmation that it has // done so. After the Node has confirmed that the Volume has been // unpublished, the state will move to StatePendingUnpublish. StatePendingNodeUnpublish PublishState = "pending-node-unpublish" // StatePendingUnpublish means the volume is still published to the node // by the controller, awaiting the operation to unpublish it. StatePendingUnpublish PublishState = "pending-controller-unpublish" ) // PublishStatus represents the status of the volume as published to an // individual node type PublishStatus struct { // NodeID is the ID of the swarm node this Volume is published to. NodeID string `json:",omitempty"` // State is the publish state of the volume. State PublishState `json:",omitempty"` // PublishContext is the PublishContext returned by the CSI plugin when // a volume is published. PublishContext map[string]string `json:",omitempty"` } // Info contains information about the Volume as a whole as provided by // the CSI storage plugin. type Info struct { // CapacityBytes is the capacity of the volume in bytes. A value of 0 // indicates that the capacity is unknown. CapacityBytes int64 `json:",omitempty"` // VolumeContext is the context originating from the CSI storage plugin // when the Volume is created. VolumeContext map[string]string `json:",omitempty"` // VolumeID is the ID of the Volume as seen by the CSI storage plugin. This // is distinct from the Volume's Swarm ID, which is the ID used by all of // the Docker Engine to refer to the Volume. If this field is blank, then // the Volume has not been successfully created yet. VolumeID string `json:",omitempty"` // AccessibleTopology is the topology this volume is actually accessible // from. AccessibleTopology []Topology `json:",omitempty"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/volume/create_options.go ================================================ package volume // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // CreateOptions VolumeConfig // // Volume configuration // swagger:model CreateOptions type CreateOptions struct { // cluster volume spec ClusterVolumeSpec *ClusterVolumeSpec `json:"ClusterVolumeSpec,omitempty"` // Name of the volume driver to use. Driver string `json:"Driver,omitempty"` // A mapping of driver options and values. These options are // passed directly to the driver and are driver specific. // DriverOpts map[string]string `json:"DriverOpts,omitempty"` // User-defined key/value metadata. Labels map[string]string `json:"Labels,omitempty"` // The new volume's name. If not specified, Docker generates a name. // Name string `json:"Name,omitempty"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/volume/list_response.go ================================================ package volume // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // ListResponse VolumeListResponse // // Volume list response // swagger:model ListResponse type ListResponse struct { // List of volumes Volumes []*Volume `json:"Volumes"` // Warnings that occurred when fetching the list of volumes. // Warnings []string `json:"Warnings"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/volume/options.go ================================================ package volume // import "github.com/docker/docker/api/types/volume" import "github.com/docker/docker/api/types/filters" // ListOptions holds parameters to list volumes. type ListOptions struct { Filters filters.Args } // PruneReport contains the response for Engine API: // POST "/volumes/prune" type PruneReport struct { VolumesDeleted []string SpaceReclaimed uint64 } ================================================ FILE: vendor/github.com/docker/docker/api/types/volume/volume.go ================================================ package volume // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command // Volume volume // swagger:model Volume type Volume struct { // cluster volume ClusterVolume *ClusterVolume `json:"ClusterVolume,omitempty"` // Date/Time the volume was created. CreatedAt string `json:"CreatedAt,omitempty"` // Name of the volume driver used by the volume. // Required: true Driver string `json:"Driver"` // User-defined key/value metadata. // Required: true Labels map[string]string `json:"Labels"` // Mount path of the volume on the host. // Required: true Mountpoint string `json:"Mountpoint"` // Name of the volume. // Required: true Name string `json:"Name"` // The driver specific options used when creating the volume. // // Required: true Options map[string]string `json:"Options"` // The level at which the volume exists. Either `global` for cluster-wide, // or `local` for machine level. // // Required: true Scope string `json:"Scope"` // Low-level details about the volume, provided by the volume driver. // Details are returned as a map with key/value pairs: // `{"key":"value","key2":"value2"}`. // // The `Status` field is optional, and is omitted if the volume driver // does not support this feature. // Status map[string]interface{} `json:"Status,omitempty"` // usage data UsageData *UsageData `json:"UsageData,omitempty"` } // UsageData Usage details about the volume. This information is used by the // `GET /system/df` endpoint, and omitted in other endpoints. // // swagger:model UsageData type UsageData struct { // The number of containers referencing this volume. This field // is set to `-1` if the reference-count is not available. // // Required: true RefCount int64 `json:"RefCount"` // Amount of disk space used by the volume (in bytes). This information // is only available for volumes created with the `"local"` volume // driver. For volumes created with other volume drivers, this field // is set to `-1` ("not available") // // Required: true Size int64 `json:"Size"` } ================================================ FILE: vendor/github.com/docker/docker/api/types/volume/volume_update.go ================================================ package volume // import "github.com/docker/docker/api/types/volume" // UpdateOptions is configuration to update a Volume with. type UpdateOptions struct { // Spec is the ClusterVolumeSpec to update the volume to. Spec *ClusterVolumeSpec `json:"Spec,omitempty"` } ================================================ FILE: vendor/github.com/docker/docker/client/README.md ================================================ # Go client for the Docker Engine API The `docker` command uses this package to communicate with the daemon. It can also be used by your own Go applications to do anything the command-line interface does – running containers, pulling images, managing swarms, etc. For example, to list all containers (the equivalent of `docker ps --all`): ```go package main import ( "context" "fmt" "github.com/docker/docker/api/types/container" "github.com/docker/docker/client" ) func main() { apiClient, err := client.NewClientWithOpts(client.FromEnv) if err != nil { panic(err) } defer apiClient.Close() containers, err := apiClient.ContainerList(context.Background(), container.ListOptions{All: true}) if err != nil { panic(err) } for _, ctr := range containers { fmt.Printf("%s %s (status: %s)\n", ctr.ID, ctr.Image, ctr.Status) } } ``` [Full documentation is available on pkg.go.dev.](https://pkg.go.dev/github.com/docker/docker/client) ================================================ FILE: vendor/github.com/docker/docker/client/build_cancel.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" ) // BuildCancel requests the daemon to cancel the ongoing build request. func (cli *Client) BuildCancel(ctx context.Context, id string) error { query := url.Values{} query.Set("id", id) resp, err := cli.post(ctx, "/build/cancel", query, nil, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/build_prune.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/url" "strconv" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/pkg/errors" ) // BuildCachePrune requests the daemon to delete unused cache data func (cli *Client) BuildCachePrune(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error) { if err := cli.NewVersionError(ctx, "1.31", "build prune"); err != nil { return nil, err } query := url.Values{} if opts.All { query.Set("all", "1") } if opts.KeepStorage != 0 { query.Set("keep-storage", strconv.Itoa(int(opts.KeepStorage))) } if opts.ReservedSpace != 0 { query.Set("reserved-space", strconv.Itoa(int(opts.ReservedSpace))) } if opts.MaxUsedSpace != 0 { query.Set("max-used-space", strconv.Itoa(int(opts.MaxUsedSpace))) } if opts.MinFreeSpace != 0 { query.Set("min-free-space", strconv.Itoa(int(opts.MinFreeSpace))) } f, err := filters.ToJSON(opts.Filters) if err != nil { return nil, errors.Wrap(err, "prune could not marshal filters option") } query.Set("filters", f) resp, err := cli.post(ctx, "/build/prune", query, nil, nil) defer ensureReaderClosed(resp) if err != nil { return nil, err } report := types.BuildCachePruneReport{} if err := json.NewDecoder(resp.Body).Decode(&report); err != nil { return nil, errors.Wrap(err, "error retrieving disk usage") } return &report, nil } ================================================ FILE: vendor/github.com/docker/docker/client/checkpoint.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "github.com/docker/docker/api/types/checkpoint" ) // CheckpointAPIClient defines API client methods for the checkpoints. // // Experimental: checkpoint and restore is still an experimental feature, // and only available if the daemon is running with experimental features // enabled. type CheckpointAPIClient interface { CheckpointCreate(ctx context.Context, container string, options checkpoint.CreateOptions) error CheckpointDelete(ctx context.Context, container string, options checkpoint.DeleteOptions) error CheckpointList(ctx context.Context, container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error) } ================================================ FILE: vendor/github.com/docker/docker/client/checkpoint_create.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "github.com/docker/docker/api/types/checkpoint" ) // CheckpointCreate creates a checkpoint from the given container with the given name func (cli *Client) CheckpointCreate(ctx context.Context, containerID string, options checkpoint.CreateOptions) error { containerID, err := trimID("container", containerID) if err != nil { return err } resp, err := cli.post(ctx, "/containers/"+containerID+"/checkpoints", nil, options, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/checkpoint_delete.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" "github.com/docker/docker/api/types/checkpoint" ) // CheckpointDelete deletes the checkpoint with the given name from the given container func (cli *Client) CheckpointDelete(ctx context.Context, containerID string, options checkpoint.DeleteOptions) error { containerID, err := trimID("container", containerID) if err != nil { return err } query := url.Values{} if options.CheckpointDir != "" { query.Set("dir", options.CheckpointDir) } resp, err := cli.delete(ctx, "/containers/"+containerID+"/checkpoints/"+options.CheckpointID, query, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/checkpoint_list.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/url" "github.com/docker/docker/api/types/checkpoint" ) // CheckpointList returns the checkpoints of the given container in the docker host func (cli *Client) CheckpointList(ctx context.Context, container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error) { var checkpoints []checkpoint.Summary query := url.Values{} if options.CheckpointDir != "" { query.Set("dir", options.CheckpointDir) } resp, err := cli.get(ctx, "/containers/"+container+"/checkpoints", query, nil) defer ensureReaderClosed(resp) if err != nil { return checkpoints, err } err = json.NewDecoder(resp.Body).Decode(&checkpoints) return checkpoints, err } ================================================ FILE: vendor/github.com/docker/docker/client/client.go ================================================ /* Package client is a Go client for the Docker Engine API. For more information about the Engine API, see the documentation: https://docs.docker.com/reference/api/engine/ # Usage You use the library by constructing a client object using [NewClientWithOpts] and calling methods on it. The client can be configured from environment variables by passing the [FromEnv] option, or configured manually by passing any of the other available [Opts]. For example, to list running containers (the equivalent of "docker ps"): package main import ( "context" "fmt" "github.com/docker/docker/api/types/container" "github.com/docker/docker/client" ) func main() { cli, err := client.NewClientWithOpts(client.FromEnv) if err != nil { panic(err) } containers, err := cli.ContainerList(context.Background(), container.ListOptions{}) if err != nil { panic(err) } for _, ctr := range containers { fmt.Printf("%s %s\n", ctr.ID, ctr.Image) } } */ package client // import "github.com/docker/docker/client" import ( "context" "crypto/tls" "net" "net/http" "net/url" "path" "strings" "sync" "sync/atomic" "time" "github.com/docker/docker/api" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/versions" "github.com/docker/go-connections/sockets" "github.com/pkg/errors" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) // DummyHost is a hostname used for local communication. // // It acts as a valid formatted hostname for local connections (such as "unix://" // or "npipe://") which do not require a hostname. It should never be resolved, // but uses the special-purpose ".localhost" TLD (as defined in [RFC 2606, Section 2] // and [RFC 6761, Section 6.3]). // // [RFC 7230, Section 5.4] defines that an empty header must be used for such // cases: // // If the authority component is missing or undefined for the target URI, // then a client MUST send a Host header field with an empty field-value. // // However, [Go stdlib] enforces the semantics of HTTP(S) over TCP, does not // allow an empty header to be used, and requires req.URL.Scheme to be either // "http" or "https". // // For further details, refer to: // // - https://github.com/docker/engine-api/issues/189 // - https://github.com/golang/go/issues/13624 // - https://github.com/golang/go/issues/61076 // - https://github.com/moby/moby/issues/45935 // // [RFC 2606, Section 2]: https://www.rfc-editor.org/rfc/rfc2606.html#section-2 // [RFC 6761, Section 6.3]: https://www.rfc-editor.org/rfc/rfc6761#section-6.3 // [RFC 7230, Section 5.4]: https://datatracker.ietf.org/doc/html/rfc7230#section-5.4 // [Go stdlib]: https://github.com/golang/go/blob/6244b1946bc2101b01955468f1be502dbadd6807/src/net/http/transport.go#L558-L569 const DummyHost = "api.moby.localhost" // fallbackAPIVersion is the version to fallback to if API-version negotiation // fails. This version is the highest version of the API before API-version // negotiation was introduced. If negotiation fails (or no API version was // included in the API response), we assume the API server uses the most // recent version before negotiation was introduced. const fallbackAPIVersion = "1.24" // Ensure that Client always implements APIClient. var _ APIClient = &Client{} // Client is the API client that performs all operations // against a docker server. type Client struct { // scheme sets the scheme for the client scheme string // host holds the server address to connect to host string // proto holds the client protocol i.e. unix. proto string // addr holds the client address. addr string // basePath holds the path to prepend to the requests. basePath string // client used to send and receive http requests. client *http.Client // version of the server to talk to. version string // userAgent is the User-Agent header to use for HTTP requests. It takes // precedence over User-Agent headers set in customHTTPHeaders, and other // header variables. When set to an empty string, the User-Agent header // is removed, and no header is sent. userAgent *string // custom HTTP headers configured by users. customHTTPHeaders map[string]string // manualOverride is set to true when the version was set by users. manualOverride bool // negotiateVersion indicates if the client should automatically negotiate // the API version to use when making requests. API version negotiation is // performed on the first request, after which negotiated is set to "true" // so that subsequent requests do not re-negotiate. negotiateVersion bool // negotiated indicates that API version negotiation took place negotiated atomic.Bool // negotiateLock is used to single-flight the version negotiation process negotiateLock sync.Mutex traceOpts []otelhttp.Option // When the client transport is an *http.Transport (default) we need to do some extra things (like closing idle connections). // Store the original transport as the http.Client transport will be wrapped with tracing libs. baseTransport *http.Transport } // ErrRedirect is the error returned by checkRedirect when the request is non-GET. var ErrRedirect = errors.New("unexpected redirect in response") // CheckRedirect specifies the policy for dealing with redirect responses. It // can be set on [http.Client.CheckRedirect] to prevent HTTP redirects for // non-GET requests. It returns an [ErrRedirect] for non-GET request, otherwise // returns a [http.ErrUseLastResponse], which is special-cased by http.Client // to use the last response. // // Go 1.8 changed behavior for HTTP redirects (specifically 301, 307, and 308) // in the client. The client (and by extension API client) can be made to send // a request like "POST /containers//start" where what would normally be in the // name section of the URL is empty. This triggers an HTTP 301 from the daemon. // // In go 1.8 this 301 is converted to a GET request, and ends up getting // a 404 from the daemon. This behavior change manifests in the client in that // before, the 301 was not followed and the client did not generate an error, // but now results in a message like "Error response from daemon: page not found". func CheckRedirect(_ *http.Request, via []*http.Request) error { if via[0].Method == http.MethodGet { return http.ErrUseLastResponse } return ErrRedirect } // NewClientWithOpts initializes a new API client with a default HTTPClient, and // default API host and version. It also initializes the custom HTTP headers to // add to each request. // // It takes an optional list of [Opt] functional arguments, which are applied in // the order they're provided, which allows modifying the defaults when creating // the client. For example, the following initializes a client that configures // itself with values from environment variables ([FromEnv]), and has automatic // API version negotiation enabled ([WithAPIVersionNegotiation]). // // cli, err := client.NewClientWithOpts( // client.FromEnv, // client.WithAPIVersionNegotiation(), // ) func NewClientWithOpts(ops ...Opt) (*Client, error) { hostURL, err := ParseHostURL(DefaultDockerHost) if err != nil { return nil, err } client, err := defaultHTTPClient(hostURL) if err != nil { return nil, err } c := &Client{ host: DefaultDockerHost, version: api.DefaultVersion, client: client, proto: hostURL.Scheme, addr: hostURL.Host, traceOpts: []otelhttp.Option{ otelhttp.WithSpanNameFormatter(func(_ string, req *http.Request) string { return req.Method + " " + req.URL.Path }), }, } for _, op := range ops { if err := op(c); err != nil { return nil, err } } if tr, ok := c.client.Transport.(*http.Transport); ok { // Store the base transport before we wrap it in tracing libs below // This is used, as an example, to close idle connections when the client is closed c.baseTransport = tr } if c.scheme == "" { // TODO(stevvooe): This isn't really the right way to write clients in Go. // `NewClient` should probably only take an `*http.Client` and work from there. // Unfortunately, the model of having a host-ish/url-thingy as the connection // string has us confusing protocol and transport layers. We continue doing // this to avoid breaking existing clients but this should be addressed. if c.tlsConfig() != nil { c.scheme = "https" } else { c.scheme = "http" } } c.client.Transport = otelhttp.NewTransport(c.client.Transport, c.traceOpts...) return c, nil } func (cli *Client) tlsConfig() *tls.Config { if cli.baseTransport == nil { return nil } return cli.baseTransport.TLSClientConfig } func defaultHTTPClient(hostURL *url.URL) (*http.Client, error) { transport := &http.Transport{} // Necessary to prevent long-lived processes using the // client from leaking connections due to idle connections // not being released. // TODO: see if we can also address this from the server side, // or in go-connections. // see: https://github.com/moby/moby/issues/45539 transport.MaxIdleConns = 6 transport.IdleConnTimeout = 30 * time.Second err := sockets.ConfigureTransport(transport, hostURL.Scheme, hostURL.Host) if err != nil { return nil, err } return &http.Client{ Transport: transport, CheckRedirect: CheckRedirect, }, nil } // Close the transport used by the client func (cli *Client) Close() error { if cli.baseTransport != nil { cli.baseTransport.CloseIdleConnections() return nil } return nil } // checkVersion manually triggers API version negotiation (if configured). // This allows for version-dependent code to use the same version as will // be negotiated when making the actual requests, and for which cases // we cannot do the negotiation lazily. func (cli *Client) checkVersion(ctx context.Context) error { if !cli.manualOverride && cli.negotiateVersion && !cli.negotiated.Load() { // Ensure exclusive write access to version and negotiated fields cli.negotiateLock.Lock() defer cli.negotiateLock.Unlock() // May have been set during last execution of critical zone if cli.negotiated.Load() { return nil } ping, err := cli.Ping(ctx) if err != nil { return err } cli.negotiateAPIVersionPing(ping) } return nil } // getAPIPath returns the versioned request path to call the API. // It appends the query parameters to the path if they are not empty. func (cli *Client) getAPIPath(ctx context.Context, p string, query url.Values) string { var apiPath string _ = cli.checkVersion(ctx) if cli.version != "" { apiPath = path.Join(cli.basePath, "/v"+strings.TrimPrefix(cli.version, "v"), p) } else { apiPath = path.Join(cli.basePath, p) } return (&url.URL{Path: apiPath, RawQuery: query.Encode()}).String() } // ClientVersion returns the API version used by this client. func (cli *Client) ClientVersion() string { return cli.version } // NegotiateAPIVersion queries the API and updates the version to match the API // version. NegotiateAPIVersion downgrades the client's API version to match the // APIVersion if the ping version is lower than the default version. If the API // version reported by the server is higher than the maximum version supported // by the client, it uses the client's maximum version. // // If a manual override is in place, either through the "DOCKER_API_VERSION" // ([EnvOverrideAPIVersion]) environment variable, or if the client is initialized // with a fixed version ([WithVersion]), no negotiation is performed. // // If the API server's ping response does not contain an API version, or if the // client did not get a successful ping response, it assumes it is connected with // an old daemon that does not support API version negotiation, in which case it // downgrades to the latest version of the API before version negotiation was // added (1.24). func (cli *Client) NegotiateAPIVersion(ctx context.Context) { if !cli.manualOverride { // Avoid concurrent modification of version-related fields cli.negotiateLock.Lock() defer cli.negotiateLock.Unlock() ping, err := cli.Ping(ctx) if err != nil { // FIXME(thaJeztah): Ping returns an error when failing to connect to the API; we should not swallow the error here, and instead returning it. return } cli.negotiateAPIVersionPing(ping) } } // NegotiateAPIVersionPing downgrades the client's API version to match the // APIVersion in the ping response. If the API version in pingResponse is higher // than the maximum version supported by the client, it uses the client's maximum // version. // // If a manual override is in place, either through the "DOCKER_API_VERSION" // ([EnvOverrideAPIVersion]) environment variable, or if the client is initialized // with a fixed version ([WithVersion]), no negotiation is performed. // // If the API server's ping response does not contain an API version, we assume // we are connected with an old daemon without API version negotiation support, // and downgrade to the latest version of the API before version negotiation was // added (1.24). func (cli *Client) NegotiateAPIVersionPing(pingResponse types.Ping) { if !cli.manualOverride { // Avoid concurrent modification of version-related fields cli.negotiateLock.Lock() defer cli.negotiateLock.Unlock() cli.negotiateAPIVersionPing(pingResponse) } } // negotiateAPIVersionPing queries the API and updates the version to match the // API version from the ping response. func (cli *Client) negotiateAPIVersionPing(pingResponse types.Ping) { // default to the latest version before versioning headers existed if pingResponse.APIVersion == "" { pingResponse.APIVersion = fallbackAPIVersion } // if the client is not initialized with a version, start with the latest supported version if cli.version == "" { cli.version = api.DefaultVersion } // if server version is lower than the client version, downgrade if versions.LessThan(pingResponse.APIVersion, cli.version) { cli.version = pingResponse.APIVersion } // Store the results, so that automatic API version negotiation (if enabled) // won't be performed on the next request. if cli.negotiateVersion { cli.negotiated.Store(true) } } // DaemonHost returns the host address used by the client func (cli *Client) DaemonHost() string { return cli.host } // HTTPClient returns a copy of the HTTP client bound to the server func (cli *Client) HTTPClient() *http.Client { c := *cli.client return &c } // ParseHostURL parses a url string, validates the string is a host url, and // returns the parsed URL func ParseHostURL(host string) (*url.URL, error) { proto, addr, ok := strings.Cut(host, "://") if !ok || addr == "" { return nil, errors.Errorf("unable to parse docker host `%s`", host) } var basePath string if proto == "tcp" { parsed, err := url.Parse("tcp://" + addr) if err != nil { return nil, err } addr = parsed.Host basePath = parsed.Path } return &url.URL{ Scheme: proto, Host: addr, Path: basePath, }, nil } func (cli *Client) dialerFromTransport() func(context.Context, string, string) (net.Conn, error) { if cli.baseTransport == nil || cli.baseTransport.DialContext == nil { return nil } if cli.baseTransport.TLSClientConfig != nil { // When using a tls config we don't use the configured dialer but instead a fallback dialer... // Note: It seems like this should use the normal dialer and wrap the returned net.Conn in a tls.Conn // I honestly don't know why it doesn't do that, but it doesn't and such a change is entirely unrelated to the change in this commit. return nil } return cli.baseTransport.DialContext } // Dialer returns a dialer for a raw stream connection, with an HTTP/1.1 header, // that can be used for proxying the daemon connection. It is used by // ["docker dial-stdio"]. // // ["docker dial-stdio"]: https://github.com/docker/cli/pull/1014 func (cli *Client) Dialer() func(context.Context) (net.Conn, error) { return cli.dialer() } func (cli *Client) dialer() func(context.Context) (net.Conn, error) { return func(ctx context.Context) (net.Conn, error) { if dialFn := cli.dialerFromTransport(); dialFn != nil { return dialFn(ctx, cli.proto, cli.addr) } switch cli.proto { case "unix": return net.Dial(cli.proto, cli.addr) case "npipe": return sockets.DialPipe(cli.addr, 32*time.Second) default: if tlsConfig := cli.tlsConfig(); tlsConfig != nil { return tls.Dial(cli.proto, cli.addr, tlsConfig) } return net.Dial(cli.proto, cli.addr) } } } ================================================ FILE: vendor/github.com/docker/docker/client/client_deprecated.go ================================================ package client import "net/http" // NewClient initializes a new API client for the given host and API version. // It uses the given http client as transport. // It also initializes the custom http headers to add to each request. // // It won't send any version information if the version number is empty. It is // highly recommended that you set a version or your client may break if the // server is upgraded. // // Deprecated: use [NewClientWithOpts] passing the [WithHost], [WithVersion], // [WithHTTPClient] and [WithHTTPHeaders] options. We recommend enabling API // version negotiation by passing the [WithAPIVersionNegotiation] option instead // of WithVersion. func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) { return NewClientWithOpts(WithHost(host), WithVersion(version), WithHTTPClient(client), WithHTTPHeaders(httpHeaders)) } // NewEnvClient initializes a new API client based on environment variables. // See FromEnv for a list of support environment variables. // // Deprecated: use [NewClientWithOpts] passing the [FromEnv] option. func NewEnvClient() (*Client, error) { return NewClientWithOpts(FromEnv) } ================================================ FILE: vendor/github.com/docker/docker/client/client_interfaces.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "io" "net" "net/http" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/events" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/system" "github.com/docker/docker/api/types/volume" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // CommonAPIClient is the common methods between stable and experimental versions of APIClient. // // Deprecated: use [APIClient] instead. This type will be an alias for [APIClient] in the next release, and removed after. type CommonAPIClient = stableAPIClient // APIClient is an interface that clients that talk with a docker server must implement. type APIClient interface { stableAPIClient CheckpointAPIClient // CheckpointAPIClient is still experimental. } type stableAPIClient interface { ConfigAPIClient ContainerAPIClient DistributionAPIClient ImageAPIClient NetworkAPIClient PluginAPIClient SystemAPIClient VolumeAPIClient ClientVersion() string DaemonHost() string HTTPClient() *http.Client ServerVersion(ctx context.Context) (types.Version, error) NegotiateAPIVersion(ctx context.Context) NegotiateAPIVersionPing(types.Ping) HijackDialer Dialer() func(context.Context) (net.Conn, error) Close() error SwarmManagementAPIClient } // SwarmManagementAPIClient defines all methods for managing Swarm-specific // objects. type SwarmManagementAPIClient interface { SwarmAPIClient NodeAPIClient ServiceAPIClient SecretAPIClient ConfigAPIClient } // HijackDialer defines methods for a hijack dialer. type HijackDialer interface { DialHijack(ctx context.Context, url, proto string, meta map[string][]string) (net.Conn, error) } // ContainerAPIClient defines API client methods for the containers type ContainerAPIClient interface { ContainerAttach(ctx context.Context, container string, options container.AttachOptions) (types.HijackedResponse, error) ContainerCommit(ctx context.Context, container string, options container.CommitOptions) (container.CommitResponse, error) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) ContainerDiff(ctx context.Context, container string) ([]container.FilesystemChange, error) ContainerExecAttach(ctx context.Context, execID string, options container.ExecAttachOptions) (types.HijackedResponse, error) ContainerExecCreate(ctx context.Context, container string, options container.ExecOptions) (container.ExecCreateResponse, error) ContainerExecInspect(ctx context.Context, execID string) (container.ExecInspect, error) ContainerExecResize(ctx context.Context, execID string, options container.ResizeOptions) error ContainerExecStart(ctx context.Context, execID string, options container.ExecStartOptions) error ContainerExport(ctx context.Context, container string) (io.ReadCloser, error) ContainerInspect(ctx context.Context, container string) (container.InspectResponse, error) ContainerInspectWithRaw(ctx context.Context, container string, getSize bool) (container.InspectResponse, []byte, error) ContainerKill(ctx context.Context, container, signal string) error ContainerList(ctx context.Context, options container.ListOptions) ([]container.Summary, error) ContainerLogs(ctx context.Context, container string, options container.LogsOptions) (io.ReadCloser, error) ContainerPause(ctx context.Context, container string) error ContainerRemove(ctx context.Context, container string, options container.RemoveOptions) error ContainerRename(ctx context.Context, container, newContainerName string) error ContainerResize(ctx context.Context, container string, options container.ResizeOptions) error ContainerRestart(ctx context.Context, container string, options container.StopOptions) error ContainerStatPath(ctx context.Context, container, path string) (container.PathStat, error) ContainerStats(ctx context.Context, container string, stream bool) (container.StatsResponseReader, error) ContainerStatsOneShot(ctx context.Context, container string) (container.StatsResponseReader, error) ContainerStart(ctx context.Context, container string, options container.StartOptions) error ContainerStop(ctx context.Context, container string, options container.StopOptions) error ContainerTop(ctx context.Context, container string, arguments []string) (container.TopResponse, error) ContainerUnpause(ctx context.Context, container string) error ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.UpdateResponse, error) ContainerWait(ctx context.Context, container string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, container.PathStat, error) CopyToContainer(ctx context.Context, container, path string, content io.Reader, options container.CopyToContainerOptions) error ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) } // DistributionAPIClient defines API client methods for the registry type DistributionAPIClient interface { DistributionInspect(ctx context.Context, image, encodedRegistryAuth string) (registry.DistributionInspect, error) } // ImageAPIClient defines API client methods for the images type ImageAPIClient interface { ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) BuildCachePrune(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error) BuildCancel(ctx context.Context, id string) error ImageCreate(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error) ImageImport(ctx context.Context, source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error) ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error) ImagePull(ctx context.Context, ref string, options image.PullOptions) (io.ReadCloser, error) ImagePush(ctx context.Context, ref string, options image.PushOptions) (io.ReadCloser, error) ImageRemove(ctx context.Context, image string, options image.RemoveOptions) ([]image.DeleteResponse, error) ImageSearch(ctx context.Context, term string, options registry.SearchOptions) ([]registry.SearchResult, error) ImageTag(ctx context.Context, image, ref string) error ImagesPrune(ctx context.Context, pruneFilter filters.Args) (image.PruneReport, error) ImageInspect(ctx context.Context, image string, _ ...ImageInspectOption) (image.InspectResponse, error) ImageHistory(ctx context.Context, image string, _ ...ImageHistoryOption) ([]image.HistoryResponseItem, error) ImageLoad(ctx context.Context, input io.Reader, _ ...ImageLoadOption) (image.LoadResponse, error) ImageSave(ctx context.Context, images []string, _ ...ImageSaveOption) (io.ReadCloser, error) ImageAPIClientDeprecated } // ImageAPIClientDeprecated defines deprecated methods of the ImageAPIClient. type ImageAPIClientDeprecated interface { // ImageInspectWithRaw returns the image information and its raw representation. // // Deprecated: Use [Client.ImageInspect] instead. Raw response can be obtained using the [ImageInspectWithRawResponse] option. ImageInspectWithRaw(ctx context.Context, image string) (image.InspectResponse, []byte, error) } // NetworkAPIClient defines API client methods for the networks type NetworkAPIClient interface { NetworkConnect(ctx context.Context, network, container string, config *network.EndpointSettings) error NetworkCreate(ctx context.Context, name string, options network.CreateOptions) (network.CreateResponse, error) NetworkDisconnect(ctx context.Context, network, container string, force bool) error NetworkInspect(ctx context.Context, network string, options network.InspectOptions) (network.Inspect, error) NetworkInspectWithRaw(ctx context.Context, network string, options network.InspectOptions) (network.Inspect, []byte, error) NetworkList(ctx context.Context, options network.ListOptions) ([]network.Summary, error) NetworkRemove(ctx context.Context, network string) error NetworksPrune(ctx context.Context, pruneFilter filters.Args) (network.PruneReport, error) } // NodeAPIClient defines API client methods for the nodes type NodeAPIClient interface { NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) NodeRemove(ctx context.Context, nodeID string, options types.NodeRemoveOptions) error NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error } // PluginAPIClient defines API client methods for the plugins type PluginAPIClient interface { PluginList(ctx context.Context, filter filters.Args) (types.PluginsListResponse, error) PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error PluginEnable(ctx context.Context, name string, options types.PluginEnableOptions) error PluginDisable(ctx context.Context, name string, options types.PluginDisableOptions) error PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) (io.ReadCloser, error) PluginUpgrade(ctx context.Context, name string, options types.PluginInstallOptions) (io.ReadCloser, error) PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error) PluginSet(ctx context.Context, name string, args []string) error PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) PluginCreate(ctx context.Context, createContext io.Reader, options types.PluginCreateOptions) error } // ServiceAPIClient defines API client methods for the services type ServiceAPIClient interface { ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (swarm.ServiceCreateResponse, error) ServiceInspectWithRaw(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) ServiceRemove(ctx context.Context, serviceID string) error ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) ServiceLogs(ctx context.Context, serviceID string, options container.LogsOptions) (io.ReadCloser, error) TaskLogs(ctx context.Context, taskID string, options container.LogsOptions) (io.ReadCloser, error) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) } // SwarmAPIClient defines API client methods for the swarm type SwarmAPIClient interface { SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error) SwarmJoin(ctx context.Context, req swarm.JoinRequest) error SwarmGetUnlockKey(ctx context.Context) (types.SwarmUnlockKeyResponse, error) SwarmUnlock(ctx context.Context, req swarm.UnlockRequest) error SwarmLeave(ctx context.Context, force bool) error SwarmInspect(ctx context.Context) (swarm.Swarm, error) SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec, flags swarm.UpdateFlags) error } // SystemAPIClient defines API client methods for the system type SystemAPIClient interface { Events(ctx context.Context, options events.ListOptions) (<-chan events.Message, <-chan error) Info(ctx context.Context) (system.Info, error) RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error) DiskUsage(ctx context.Context, options types.DiskUsageOptions) (types.DiskUsage, error) Ping(ctx context.Context) (types.Ping, error) } // VolumeAPIClient defines API client methods for the volumes type VolumeAPIClient interface { VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error) VolumeInspect(ctx context.Context, volumeID string) (volume.Volume, error) VolumeInspectWithRaw(ctx context.Context, volumeID string) (volume.Volume, []byte, error) VolumeList(ctx context.Context, options volume.ListOptions) (volume.ListResponse, error) VolumeRemove(ctx context.Context, volumeID string, force bool) error VolumesPrune(ctx context.Context, pruneFilter filters.Args) (volume.PruneReport, error) VolumeUpdate(ctx context.Context, volumeID string, version swarm.Version, options volume.UpdateOptions) error } // SecretAPIClient defines API client methods for secrets type SecretAPIClient interface { SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (types.SecretCreateResponse, error) SecretRemove(ctx context.Context, id string) error SecretInspectWithRaw(ctx context.Context, name string) (swarm.Secret, []byte, error) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error } // ConfigAPIClient defines API client methods for configs type ConfigAPIClient interface { ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (types.ConfigCreateResponse, error) ConfigRemove(ctx context.Context, id string) error ConfigInspectWithRaw(ctx context.Context, name string) (swarm.Config, []byte, error) ConfigUpdate(ctx context.Context, id string, version swarm.Version, config swarm.ConfigSpec) error } ================================================ FILE: vendor/github.com/docker/docker/client/client_unix.go ================================================ //go:build !windows package client // import "github.com/docker/docker/client" // DefaultDockerHost defines OS-specific default host if the DOCKER_HOST // (EnvOverrideHost) environment variable is unset or empty. const DefaultDockerHost = "unix:///var/run/docker.sock" ================================================ FILE: vendor/github.com/docker/docker/client/client_windows.go ================================================ package client // import "github.com/docker/docker/client" // DefaultDockerHost defines OS-specific default host if the DOCKER_HOST // (EnvOverrideHost) environment variable is unset or empty. const DefaultDockerHost = "npipe:////./pipe/docker_engine" ================================================ FILE: vendor/github.com/docker/docker/client/config_create.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" ) // ConfigCreate creates a new config. func (cli *Client) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (types.ConfigCreateResponse, error) { var response types.ConfigCreateResponse if err := cli.NewVersionError(ctx, "1.30", "config create"); err != nil { return response, err } resp, err := cli.post(ctx, "/configs/create", nil, config, nil) defer ensureReaderClosed(resp) if err != nil { return response, err } err = json.NewDecoder(resp.Body).Decode(&response) return response, err } ================================================ FILE: vendor/github.com/docker/docker/client/config_inspect.go ================================================ package client // import "github.com/docker/docker/client" import ( "bytes" "context" "encoding/json" "io" "github.com/docker/docker/api/types/swarm" ) // ConfigInspectWithRaw returns the config information with raw data func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.Config, []byte, error) { id, err := trimID("contig", id) if err != nil { return swarm.Config{}, nil, err } if err := cli.NewVersionError(ctx, "1.30", "config inspect"); err != nil { return swarm.Config{}, nil, err } resp, err := cli.get(ctx, "/configs/"+id, nil, nil) defer ensureReaderClosed(resp) if err != nil { return swarm.Config{}, nil, err } body, err := io.ReadAll(resp.Body) if err != nil { return swarm.Config{}, nil, err } var config swarm.Config rdr := bytes.NewReader(body) err = json.NewDecoder(rdr).Decode(&config) return config, body, err } ================================================ FILE: vendor/github.com/docker/docker/client/config_list.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/url" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" ) // ConfigList returns the list of configs. func (cli *Client) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) { if err := cli.NewVersionError(ctx, "1.30", "config list"); err != nil { return nil, err } query := url.Values{} if options.Filters.Len() > 0 { filterJSON, err := filters.ToJSON(options.Filters) if err != nil { return nil, err } query.Set("filters", filterJSON) } resp, err := cli.get(ctx, "/configs", query, nil) defer ensureReaderClosed(resp) if err != nil { return nil, err } var configs []swarm.Config err = json.NewDecoder(resp.Body).Decode(&configs) return configs, err } ================================================ FILE: vendor/github.com/docker/docker/client/config_remove.go ================================================ package client // import "github.com/docker/docker/client" import "context" // ConfigRemove removes a config. func (cli *Client) ConfigRemove(ctx context.Context, id string) error { id, err := trimID("config", id) if err != nil { return err } if err := cli.NewVersionError(ctx, "1.30", "config remove"); err != nil { return err } resp, err := cli.delete(ctx, "/configs/"+id, nil, nil) defer ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/config_update.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" "github.com/docker/docker/api/types/swarm" ) // ConfigUpdate attempts to update a config func (cli *Client) ConfigUpdate(ctx context.Context, id string, version swarm.Version, config swarm.ConfigSpec) error { id, err := trimID("config", id) if err != nil { return err } if err := cli.NewVersionError(ctx, "1.30", "config update"); err != nil { return err } query := url.Values{} query.Set("version", version.String()) resp, err := cli.post(ctx, "/configs/"+id+"/update", query, config, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/container_attach.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/http" "net/url" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" ) // ContainerAttach attaches a connection to a container in the server. // It returns a types.HijackedConnection with the hijacked connection // and the a reader to get output. It's up to the called to close // the hijacked connection by calling types.HijackedResponse.Close. // // The stream format on the response will be in one of two formats: // // If the container is using a TTY, there is only a single stream (stdout), and // data is copied directly from the container output stream, no extra // multiplexing or headers. // // If the container is *not* using a TTY, streams for stdout and stderr are // multiplexed. // The format of the multiplexed stream is as follows: // // [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}[]byte{OUTPUT} // // STREAM_TYPE can be 1 for stdout and 2 for stderr // // SIZE1, SIZE2, SIZE3, and SIZE4 are four bytes of uint32 encoded as big endian. // This is the size of OUTPUT. // // You can use github.com/docker/docker/pkg/stdcopy.StdCopy to demultiplex this // stream. func (cli *Client) ContainerAttach(ctx context.Context, containerID string, options container.AttachOptions) (types.HijackedResponse, error) { containerID, err := trimID("container", containerID) if err != nil { return types.HijackedResponse{}, err } query := url.Values{} if options.Stream { query.Set("stream", "1") } if options.Stdin { query.Set("stdin", "1") } if options.Stdout { query.Set("stdout", "1") } if options.Stderr { query.Set("stderr", "1") } if options.DetachKeys != "" { query.Set("detachKeys", options.DetachKeys) } if options.Logs { query.Set("logs", "1") } return cli.postHijacked(ctx, "/containers/"+containerID+"/attach", query, nil, http.Header{ "Content-Type": {"text/plain"}, }) } ================================================ FILE: vendor/github.com/docker/docker/client/container_commit.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "errors" "net/url" "github.com/distribution/reference" "github.com/docker/docker/api/types/container" ) // ContainerCommit applies changes to a container and creates a new tagged image. func (cli *Client) ContainerCommit(ctx context.Context, containerID string, options container.CommitOptions) (container.CommitResponse, error) { containerID, err := trimID("container", containerID) if err != nil { return container.CommitResponse{}, err } var repository, tag string if options.Reference != "" { ref, err := reference.ParseNormalizedNamed(options.Reference) if err != nil { return container.CommitResponse{}, err } if _, isCanonical := ref.(reference.Canonical); isCanonical { return container.CommitResponse{}, errors.New("refusing to create a tag with a digest reference") } ref = reference.TagNameOnly(ref) if tagged, ok := ref.(reference.Tagged); ok { tag = tagged.Tag() } repository = reference.FamiliarName(ref) } query := url.Values{} query.Set("container", containerID) query.Set("repo", repository) query.Set("tag", tag) query.Set("comment", options.Comment) query.Set("author", options.Author) for _, change := range options.Changes { query.Add("changes", change) } if !options.Pause { query.Set("pause", "0") } var response container.CommitResponse resp, err := cli.post(ctx, "/commit", query, options.Config, nil) defer ensureReaderClosed(resp) if err != nil { return response, err } err = json.NewDecoder(resp.Body).Decode(&response) return response, err } ================================================ FILE: vendor/github.com/docker/docker/client/container_copy.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/base64" "encoding/json" "fmt" "io" "net/http" "net/url" "path/filepath" "strings" "github.com/docker/docker/api/types/container" ) // ContainerStatPath returns stat information about a path inside the container filesystem. func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path string) (container.PathStat, error) { containerID, err := trimID("container", containerID) if err != nil { return container.PathStat{}, err } query := url.Values{} query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API. resp, err := cli.head(ctx, "/containers/"+containerID+"/archive", query, nil) defer ensureReaderClosed(resp) if err != nil { return container.PathStat{}, err } return getContainerPathStatFromHeader(resp.Header) } // CopyToContainer copies content into the container filesystem. // Note that `content` must be a Reader for a TAR archive func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath string, content io.Reader, options container.CopyToContainerOptions) error { containerID, err := trimID("container", containerID) if err != nil { return err } query := url.Values{} query.Set("path", filepath.ToSlash(dstPath)) // Normalize the paths used in the API. // Do not allow for an existing directory to be overwritten by a non-directory and vice versa. if !options.AllowOverwriteDirWithFile { query.Set("noOverwriteDirNonDir", "true") } if options.CopyUIDGID { query.Set("copyUIDGID", "true") } response, err := cli.putRaw(ctx, "/containers/"+containerID+"/archive", query, content, nil) defer ensureReaderClosed(response) if err != nil { return err } return nil } // CopyFromContainer gets the content from the container and returns it as a Reader // for a TAR archive to manipulate it in the host. It's up to the caller to close the reader. func (cli *Client) CopyFromContainer(ctx context.Context, containerID, srcPath string) (io.ReadCloser, container.PathStat, error) { containerID, err := trimID("container", containerID) if err != nil { return nil, container.PathStat{}, err } query := make(url.Values, 1) query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API. resp, err := cli.get(ctx, "/containers/"+containerID+"/archive", query, nil) if err != nil { return nil, container.PathStat{}, err } // In order to get the copy behavior right, we need to know information // about both the source and the destination. The response headers include // stat info about the source that we can use in deciding exactly how to // copy it locally. Along with the stat info about the local destination, // we have everything we need to handle the multiple possibilities there // can be when copying a file/dir from one location to another file/dir. stat, err := getContainerPathStatFromHeader(resp.Header) if err != nil { return nil, stat, fmt.Errorf("unable to get resource stat from response: %s", err) } return resp.Body, stat, err } func getContainerPathStatFromHeader(header http.Header) (container.PathStat, error) { var stat container.PathStat encodedStat := header.Get("X-Docker-Container-Path-Stat") statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodedStat)) err := json.NewDecoder(statDecoder).Decode(&stat) if err != nil { err = fmt.Errorf("unable to decode container path stat header: %s", err) } return stat, err } ================================================ FILE: vendor/github.com/docker/docker/client/container_create.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/url" "path" "sort" "strings" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/versions" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // ContainerCreate creates a new container based on the given configuration. // It can be associated with a name, but it's not mandatory. func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) { var response container.CreateResponse // Make sure we negotiated (if the client is configured to do so), // as code below contains API-version specific handling of options. // // Normally, version-negotiation (if enabled) would not happen until // the API request is made. if err := cli.checkVersion(ctx); err != nil { return response, err } if err := cli.NewVersionError(ctx, "1.25", "stop timeout"); config != nil && config.StopTimeout != nil && err != nil { return response, err } if err := cli.NewVersionError(ctx, "1.41", "specify container image platform"); platform != nil && err != nil { return response, err } if err := cli.NewVersionError(ctx, "1.44", "specify health-check start interval"); config != nil && config.Healthcheck != nil && config.Healthcheck.StartInterval != 0 && err != nil { return response, err } if err := cli.NewVersionError(ctx, "1.44", "specify mac-address per network"); hasEndpointSpecificMacAddress(networkingConfig) && err != nil { return response, err } if hostConfig != nil { if versions.LessThan(cli.ClientVersion(), "1.25") { // When using API 1.24 and under, the client is responsible for removing the container hostConfig.AutoRemove = false } if versions.GreaterThanOrEqualTo(cli.ClientVersion(), "1.42") || versions.LessThan(cli.ClientVersion(), "1.40") { // KernelMemory was added in API 1.40, and deprecated in API 1.42 hostConfig.KernelMemory = 0 } if platform != nil && platform.OS == "linux" && versions.LessThan(cli.ClientVersion(), "1.42") { // When using API under 1.42, the Linux daemon doesn't respect the ConsoleSize hostConfig.ConsoleSize = [2]uint{0, 0} } hostConfig.CapAdd = normalizeCapabilities(hostConfig.CapAdd) hostConfig.CapDrop = normalizeCapabilities(hostConfig.CapDrop) } // Since API 1.44, the container-wide MacAddress is deprecated and will trigger a WARNING if it's specified. if versions.GreaterThanOrEqualTo(cli.ClientVersion(), "1.44") { config.MacAddress = "" //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44. } query := url.Values{} if p := formatPlatform(platform); p != "" { query.Set("platform", p) } if containerName != "" { query.Set("name", containerName) } body := container.CreateRequest{ Config: config, HostConfig: hostConfig, NetworkingConfig: networkingConfig, } resp, err := cli.post(ctx, "/containers/create", query, body, nil) defer ensureReaderClosed(resp) if err != nil { return response, err } err = json.NewDecoder(resp.Body).Decode(&response) return response, err } // formatPlatform returns a formatted string representing platform (e.g. linux/arm/v7). // // Similar to containerd's platforms.Format(), but does allow components to be // omitted (e.g. pass "architecture" only, without "os": // https://github.com/containerd/containerd/blob/v1.5.2/platforms/platforms.go#L243-L263 func formatPlatform(platform *ocispec.Platform) string { if platform == nil { return "" } return path.Join(platform.OS, platform.Architecture, platform.Variant) } // hasEndpointSpecificMacAddress checks whether one of the endpoint in networkingConfig has a MacAddress defined. func hasEndpointSpecificMacAddress(networkingConfig *network.NetworkingConfig) bool { if networkingConfig == nil { return false } for _, endpoint := range networkingConfig.EndpointsConfig { if endpoint.MacAddress != "" { return true } } return false } // allCapabilities is a magic value for "all capabilities" const allCapabilities = "ALL" // normalizeCapabilities normalizes capabilities to their canonical form, // removes duplicates, and sorts the results. // // It is similar to [github.com/docker/docker/oci/caps.NormalizeLegacyCapabilities], // but performs no validation based on supported capabilities. func normalizeCapabilities(caps []string) []string { var normalized []string unique := make(map[string]struct{}) for _, c := range caps { c = normalizeCap(c) if _, ok := unique[c]; ok { continue } unique[c] = struct{}{} normalized = append(normalized, c) } sort.Strings(normalized) return normalized } // normalizeCap normalizes a capability to its canonical format by upper-casing // and adding a "CAP_" prefix (if not yet present). It also accepts the "ALL" // magic-value. func normalizeCap(cap string) string { cap = strings.ToUpper(cap) if cap == allCapabilities { return cap } if !strings.HasPrefix(cap, "CAP_") { cap = "CAP_" + cap } return cap } ================================================ FILE: vendor/github.com/docker/docker/client/container_diff.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/url" "github.com/docker/docker/api/types/container" ) // ContainerDiff shows differences in a container filesystem since it was started. func (cli *Client) ContainerDiff(ctx context.Context, containerID string) ([]container.FilesystemChange, error) { containerID, err := trimID("container", containerID) if err != nil { return nil, err } resp, err := cli.get(ctx, "/containers/"+containerID+"/changes", url.Values{}, nil) defer ensureReaderClosed(resp) if err != nil { return nil, err } var changes []container.FilesystemChange err = json.NewDecoder(resp.Body).Decode(&changes) if err != nil { return nil, err } return changes, err } ================================================ FILE: vendor/github.com/docker/docker/client/container_exec.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/http" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/versions" ) // ContainerExecCreate creates a new exec configuration to run an exec process. func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string, options container.ExecOptions) (container.ExecCreateResponse, error) { containerID, err := trimID("container", containerID) if err != nil { return container.ExecCreateResponse{}, err } // Make sure we negotiated (if the client is configured to do so), // as code below contains API-version specific handling of options. // // Normally, version-negotiation (if enabled) would not happen until // the API request is made. if err := cli.checkVersion(ctx); err != nil { return container.ExecCreateResponse{}, err } if err := cli.NewVersionError(ctx, "1.25", "env"); len(options.Env) != 0 && err != nil { return container.ExecCreateResponse{}, err } if versions.LessThan(cli.ClientVersion(), "1.42") { options.ConsoleSize = nil } resp, err := cli.post(ctx, "/containers/"+containerID+"/exec", nil, options, nil) defer ensureReaderClosed(resp) if err != nil { return container.ExecCreateResponse{}, err } var response container.ExecCreateResponse err = json.NewDecoder(resp.Body).Decode(&response) return response, err } // ContainerExecStart starts an exec process already created in the docker host. func (cli *Client) ContainerExecStart(ctx context.Context, execID string, config container.ExecStartOptions) error { if versions.LessThan(cli.ClientVersion(), "1.42") { config.ConsoleSize = nil } resp, err := cli.post(ctx, "/exec/"+execID+"/start", nil, config, nil) ensureReaderClosed(resp) return err } // ContainerExecAttach attaches a connection to an exec process in the server. // It returns a types.HijackedConnection with the hijacked connection // and the a reader to get output. It's up to the called to close // the hijacked connection by calling types.HijackedResponse.Close. func (cli *Client) ContainerExecAttach(ctx context.Context, execID string, config container.ExecAttachOptions) (types.HijackedResponse, error) { if versions.LessThan(cli.ClientVersion(), "1.42") { config.ConsoleSize = nil } return cli.postHijacked(ctx, "/exec/"+execID+"/start", nil, config, http.Header{ "Content-Type": {"application/json"}, }) } // ContainerExecInspect returns information about a specific exec process on the docker host. func (cli *Client) ContainerExecInspect(ctx context.Context, execID string) (container.ExecInspect, error) { var response container.ExecInspect resp, err := cli.get(ctx, "/exec/"+execID+"/json", nil, nil) if err != nil { return response, err } err = json.NewDecoder(resp.Body).Decode(&response) ensureReaderClosed(resp) return response, err } ================================================ FILE: vendor/github.com/docker/docker/client/container_export.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "io" "net/url" ) // ContainerExport retrieves the raw contents of a container // and returns them as an io.ReadCloser. It's up to the caller // to close the stream. func (cli *Client) ContainerExport(ctx context.Context, containerID string) (io.ReadCloser, error) { containerID, err := trimID("container", containerID) if err != nil { return nil, err } resp, err := cli.get(ctx, "/containers/"+containerID+"/export", url.Values{}, nil) if err != nil { return nil, err } return resp.Body, nil } ================================================ FILE: vendor/github.com/docker/docker/client/container_inspect.go ================================================ package client // import "github.com/docker/docker/client" import ( "bytes" "context" "encoding/json" "io" "net/url" "github.com/docker/docker/api/types/container" ) // ContainerInspect returns the container information. func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (container.InspectResponse, error) { containerID, err := trimID("container", containerID) if err != nil { return container.InspectResponse{}, err } resp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil) defer ensureReaderClosed(resp) if err != nil { return container.InspectResponse{}, err } var response container.InspectResponse err = json.NewDecoder(resp.Body).Decode(&response) return response, err } // ContainerInspectWithRaw returns the container information and its raw representation. func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID string, getSize bool) (container.InspectResponse, []byte, error) { containerID, err := trimID("container", containerID) if err != nil { return container.InspectResponse{}, nil, err } query := url.Values{} if getSize { query.Set("size", "1") } resp, err := cli.get(ctx, "/containers/"+containerID+"/json", query, nil) defer ensureReaderClosed(resp) if err != nil { return container.InspectResponse{}, nil, err } body, err := io.ReadAll(resp.Body) if err != nil { return container.InspectResponse{}, nil, err } var response container.InspectResponse rdr := bytes.NewReader(body) err = json.NewDecoder(rdr).Decode(&response) return response, body, err } ================================================ FILE: vendor/github.com/docker/docker/client/container_kill.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" ) // ContainerKill terminates the container process but does not remove the container from the docker host. func (cli *Client) ContainerKill(ctx context.Context, containerID, signal string) error { containerID, err := trimID("container", containerID) if err != nil { return err } query := url.Values{} if signal != "" { query.Set("signal", signal) } resp, err := cli.post(ctx, "/containers/"+containerID+"/kill", query, nil, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/container_list.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/url" "strconv" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" ) // ContainerList returns the list of containers in the docker host. func (cli *Client) ContainerList(ctx context.Context, options container.ListOptions) ([]container.Summary, error) { query := url.Values{} if options.All { query.Set("all", "1") } if options.Limit > 0 { query.Set("limit", strconv.Itoa(options.Limit)) } if options.Since != "" { query.Set("since", options.Since) } if options.Before != "" { query.Set("before", options.Before) } if options.Size { query.Set("size", "1") } if options.Filters.Len() > 0 { //nolint:staticcheck // ignore SA1019 for old code filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters) if err != nil { return nil, err } query.Set("filters", filterJSON) } resp, err := cli.get(ctx, "/containers/json", query, nil) defer ensureReaderClosed(resp) if err != nil { return nil, err } var containers []container.Summary err = json.NewDecoder(resp.Body).Decode(&containers) return containers, err } ================================================ FILE: vendor/github.com/docker/docker/client/container_logs.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "io" "net/url" "time" "github.com/docker/docker/api/types/container" timetypes "github.com/docker/docker/api/types/time" "github.com/pkg/errors" ) // ContainerLogs returns the logs generated by a container in an io.ReadCloser. // It's up to the caller to close the stream. // // The stream format on the response will be in one of two formats: // // If the container is using a TTY, there is only a single stream (stdout), and // data is copied directly from the container output stream, no extra // multiplexing or headers. // // If the container is *not* using a TTY, streams for stdout and stderr are // multiplexed. // The format of the multiplexed stream is as follows: // // [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}[]byte{OUTPUT} // // STREAM_TYPE can be 1 for stdout and 2 for stderr // // SIZE1, SIZE2, SIZE3, and SIZE4 are four bytes of uint32 encoded as big endian. // This is the size of OUTPUT. // // You can use github.com/docker/docker/pkg/stdcopy.StdCopy to demultiplex this // stream. func (cli *Client) ContainerLogs(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) { containerID, err := trimID("container", containerID) if err != nil { return nil, err } query := url.Values{} if options.ShowStdout { query.Set("stdout", "1") } if options.ShowStderr { query.Set("stderr", "1") } if options.Since != "" { ts, err := timetypes.GetTimestamp(options.Since, time.Now()) if err != nil { return nil, errors.Wrap(err, `invalid value for "since"`) } query.Set("since", ts) } if options.Until != "" { ts, err := timetypes.GetTimestamp(options.Until, time.Now()) if err != nil { return nil, errors.Wrap(err, `invalid value for "until"`) } query.Set("until", ts) } if options.Timestamps { query.Set("timestamps", "1") } if options.Details { query.Set("details", "1") } if options.Follow { query.Set("follow", "1") } query.Set("tail", options.Tail) resp, err := cli.get(ctx, "/containers/"+containerID+"/logs", query, nil) if err != nil { return nil, err } return resp.Body, nil } ================================================ FILE: vendor/github.com/docker/docker/client/container_pause.go ================================================ package client // import "github.com/docker/docker/client" import "context" // ContainerPause pauses the main process of a given container without terminating it. func (cli *Client) ContainerPause(ctx context.Context, containerID string) error { containerID, err := trimID("container", containerID) if err != nil { return err } resp, err := cli.post(ctx, "/containers/"+containerID+"/pause", nil, nil, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/container_prune.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "fmt" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" ) // ContainersPrune requests the daemon to delete unused data func (cli *Client) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) { if err := cli.NewVersionError(ctx, "1.25", "container prune"); err != nil { return container.PruneReport{}, err } query, err := getFiltersQuery(pruneFilters) if err != nil { return container.PruneReport{}, err } resp, err := cli.post(ctx, "/containers/prune", query, nil, nil) defer ensureReaderClosed(resp) if err != nil { return container.PruneReport{}, err } var report container.PruneReport if err := json.NewDecoder(resp.Body).Decode(&report); err != nil { return container.PruneReport{}, fmt.Errorf("Error retrieving disk usage: %v", err) } return report, nil } ================================================ FILE: vendor/github.com/docker/docker/client/container_remove.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" "github.com/docker/docker/api/types/container" ) // ContainerRemove kills and removes a container from the docker host. func (cli *Client) ContainerRemove(ctx context.Context, containerID string, options container.RemoveOptions) error { containerID, err := trimID("container", containerID) if err != nil { return err } query := url.Values{} if options.RemoveVolumes { query.Set("v", "1") } if options.RemoveLinks { query.Set("link", "1") } if options.Force { query.Set("force", "1") } resp, err := cli.delete(ctx, "/containers/"+containerID, query, nil) defer ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/container_rename.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" ) // ContainerRename changes the name of a given container. func (cli *Client) ContainerRename(ctx context.Context, containerID, newContainerName string) error { containerID, err := trimID("container", containerID) if err != nil { return err } query := url.Values{} query.Set("name", newContainerName) resp, err := cli.post(ctx, "/containers/"+containerID+"/rename", query, nil, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/container_resize.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" "strconv" "github.com/docker/docker/api/types/container" ) // ContainerResize changes the size of the tty for a container. func (cli *Client) ContainerResize(ctx context.Context, containerID string, options container.ResizeOptions) error { containerID, err := trimID("container", containerID) if err != nil { return err } return cli.resize(ctx, "/containers/"+containerID, options.Height, options.Width) } // ContainerExecResize changes the size of the tty for an exec process running inside a container. func (cli *Client) ContainerExecResize(ctx context.Context, execID string, options container.ResizeOptions) error { execID, err := trimID("exec", execID) if err != nil { return err } return cli.resize(ctx, "/exec/"+execID, options.Height, options.Width) } func (cli *Client) resize(ctx context.Context, basePath string, height, width uint) error { // FIXME(thaJeztah): the API / backend accepts uint32, but container.ResizeOptions uses uint. query := url.Values{} query.Set("h", strconv.FormatUint(uint64(height), 10)) query.Set("w", strconv.FormatUint(uint64(width), 10)) resp, err := cli.post(ctx, basePath+"/resize", query, nil, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/container_restart.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" "strconv" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/versions" ) // ContainerRestart stops and starts a container again. // It makes the daemon wait for the container to be up again for // a specific amount of time, given the timeout. func (cli *Client) ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error { containerID, err := trimID("container", containerID) if err != nil { return err } query := url.Values{} if options.Timeout != nil { query.Set("t", strconv.Itoa(*options.Timeout)) } if options.Signal != "" { // Make sure we negotiated (if the client is configured to do so), // as code below contains API-version specific handling of options. // // Normally, version-negotiation (if enabled) would not happen until // the API request is made. if err := cli.checkVersion(ctx); err != nil { return err } if versions.GreaterThanOrEqualTo(cli.version, "1.42") { query.Set("signal", options.Signal) } } resp, err := cli.post(ctx, "/containers/"+containerID+"/restart", query, nil, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/container_start.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" "github.com/docker/docker/api/types/container" ) // ContainerStart sends a request to the docker daemon to start a container. func (cli *Client) ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error { containerID, err := trimID("container", containerID) if err != nil { return err } query := url.Values{} if len(options.CheckpointID) != 0 { query.Set("checkpoint", options.CheckpointID) } if len(options.CheckpointDir) != 0 { query.Set("checkpoint-dir", options.CheckpointDir) } resp, err := cli.post(ctx, "/containers/"+containerID+"/start", query, nil, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/container_stats.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" "github.com/docker/docker/api/types/container" ) // ContainerStats returns near realtime stats for a given container. // It's up to the caller to close the io.ReadCloser returned. func (cli *Client) ContainerStats(ctx context.Context, containerID string, stream bool) (container.StatsResponseReader, error) { containerID, err := trimID("container", containerID) if err != nil { return container.StatsResponseReader{}, err } query := url.Values{} query.Set("stream", "0") if stream { query.Set("stream", "1") } resp, err := cli.get(ctx, "/containers/"+containerID+"/stats", query, nil) if err != nil { return container.StatsResponseReader{}, err } return container.StatsResponseReader{ Body: resp.Body, OSType: getDockerOS(resp.Header.Get("Server")), }, nil } // ContainerStatsOneShot gets a single stat entry from a container. // It differs from `ContainerStats` in that the API should not wait to prime the stats func (cli *Client) ContainerStatsOneShot(ctx context.Context, containerID string) (container.StatsResponseReader, error) { containerID, err := trimID("container", containerID) if err != nil { return container.StatsResponseReader{}, err } query := url.Values{} query.Set("stream", "0") query.Set("one-shot", "1") resp, err := cli.get(ctx, "/containers/"+containerID+"/stats", query, nil) if err != nil { return container.StatsResponseReader{}, err } return container.StatsResponseReader{ Body: resp.Body, OSType: getDockerOS(resp.Header.Get("Server")), }, nil } ================================================ FILE: vendor/github.com/docker/docker/client/container_stop.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" "strconv" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/versions" ) // ContainerStop stops a container. In case the container fails to stop // gracefully within a time frame specified by the timeout argument, // it is forcefully terminated (killed). // // If the timeout is nil, the container's StopTimeout value is used, if set, // otherwise the engine default. A negative timeout value can be specified, // meaning no timeout, i.e. no forceful termination is performed. func (cli *Client) ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error { containerID, err := trimID("container", containerID) if err != nil { return err } query := url.Values{} if options.Timeout != nil { query.Set("t", strconv.Itoa(*options.Timeout)) } if options.Signal != "" { // Make sure we negotiated (if the client is configured to do so), // as code below contains API-version specific handling of options. // // Normally, version-negotiation (if enabled) would not happen until // the API request is made. if err := cli.checkVersion(ctx); err != nil { return err } if versions.GreaterThanOrEqualTo(cli.version, "1.42") { query.Set("signal", options.Signal) } } resp, err := cli.post(ctx, "/containers/"+containerID+"/stop", query, nil, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/container_top.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/url" "strings" "github.com/docker/docker/api/types/container" ) // ContainerTop shows process information from within a container. func (cli *Client) ContainerTop(ctx context.Context, containerID string, arguments []string) (container.TopResponse, error) { containerID, err := trimID("container", containerID) if err != nil { return container.TopResponse{}, err } query := url.Values{} if len(arguments) > 0 { query.Set("ps_args", strings.Join(arguments, " ")) } resp, err := cli.get(ctx, "/containers/"+containerID+"/top", query, nil) defer ensureReaderClosed(resp) if err != nil { return container.TopResponse{}, err } var response container.TopResponse err = json.NewDecoder(resp.Body).Decode(&response) return response, err } ================================================ FILE: vendor/github.com/docker/docker/client/container_unpause.go ================================================ package client // import "github.com/docker/docker/client" import "context" // ContainerUnpause resumes the process execution within a container func (cli *Client) ContainerUnpause(ctx context.Context, containerID string) error { containerID, err := trimID("container", containerID) if err != nil { return err } resp, err := cli.post(ctx, "/containers/"+containerID+"/unpause", nil, nil, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/container_update.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "github.com/docker/docker/api/types/container" ) // ContainerUpdate updates the resources of a container. func (cli *Client) ContainerUpdate(ctx context.Context, containerID string, updateConfig container.UpdateConfig) (container.UpdateResponse, error) { containerID, err := trimID("container", containerID) if err != nil { return container.UpdateResponse{}, err } resp, err := cli.post(ctx, "/containers/"+containerID+"/update", nil, updateConfig, nil) defer ensureReaderClosed(resp) if err != nil { return container.UpdateResponse{}, err } var response container.UpdateResponse err = json.NewDecoder(resp.Body).Decode(&response) return response, err } ================================================ FILE: vendor/github.com/docker/docker/client/container_wait.go ================================================ package client // import "github.com/docker/docker/client" import ( "bytes" "context" "encoding/json" "errors" "io" "net/url" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/versions" ) const containerWaitErrorMsgLimit = 2 * 1024 /* Max: 2KiB */ // ContainerWait waits until the specified container is in a certain state // indicated by the given condition, either "not-running" (default), // "next-exit", or "removed". // // If this client's API version is before 1.30, condition is ignored and // ContainerWait will return immediately with the two channels, as the server // will wait as if the condition were "not-running". // // If this client's API version is at least 1.30, ContainerWait blocks until // the request has been acknowledged by the server (with a response header), // then returns two channels on which the caller can wait for the exit status // of the container or an error if there was a problem either beginning the // wait request or in getting the response. This allows the caller to // synchronize ContainerWait with other calls, such as specifying a // "next-exit" condition before issuing a ContainerStart request. func (cli *Client) ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) { resultC := make(chan container.WaitResponse) errC := make(chan error, 1) containerID, err := trimID("container", containerID) if err != nil { errC <- err return resultC, errC } // Make sure we negotiated (if the client is configured to do so), // as code below contains API-version specific handling of options. // // Normally, version-negotiation (if enabled) would not happen until // the API request is made. if err := cli.checkVersion(ctx); err != nil { errC <- err return resultC, errC } if versions.LessThan(cli.ClientVersion(), "1.30") { return cli.legacyContainerWait(ctx, containerID) } query := url.Values{} if condition != "" { query.Set("condition", string(condition)) } resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", query, nil, nil) if err != nil { defer ensureReaderClosed(resp) errC <- err return resultC, errC } go func() { defer ensureReaderClosed(resp) responseText := bytes.NewBuffer(nil) stream := io.TeeReader(resp.Body, responseText) var res container.WaitResponse if err := json.NewDecoder(stream).Decode(&res); err != nil { // NOTE(nicks): The /wait API does not work well with HTTP proxies. // At any time, the proxy could cut off the response stream. // // But because the HTTP status has already been written, the proxy's // only option is to write a plaintext error message. // // If there's a JSON parsing error, read the real error message // off the body and send it to the client. if errors.As(err, new(*json.SyntaxError)) { _, _ = io.ReadAll(io.LimitReader(stream, containerWaitErrorMsgLimit)) errC <- errors.New(responseText.String()) } else { errC <- err } return } resultC <- res }() return resultC, errC } // legacyContainerWait returns immediately and doesn't have an option to wait // until the container is removed. func (cli *Client) legacyContainerWait(ctx context.Context, containerID string) (<-chan container.WaitResponse, <-chan error) { resultC := make(chan container.WaitResponse) errC := make(chan error) go func() { resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", nil, nil, nil) if err != nil { errC <- err return } defer ensureReaderClosed(resp) var res container.WaitResponse if err := json.NewDecoder(resp.Body).Decode(&res); err != nil { errC <- err return } resultC <- res }() return resultC, errC } ================================================ FILE: vendor/github.com/docker/docker/client/disk_usage.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "fmt" "net/url" "github.com/docker/docker/api/types" ) // DiskUsage requests the current data usage from the daemon func (cli *Client) DiskUsage(ctx context.Context, options types.DiskUsageOptions) (types.DiskUsage, error) { var query url.Values if len(options.Types) > 0 { query = url.Values{} for _, t := range options.Types { query.Add("type", string(t)) } } resp, err := cli.get(ctx, "/system/df", query, nil) defer ensureReaderClosed(resp) if err != nil { return types.DiskUsage{}, err } var du types.DiskUsage if err := json.NewDecoder(resp.Body).Decode(&du); err != nil { return types.DiskUsage{}, fmt.Errorf("Error retrieving disk usage: %v", err) } return du, nil } ================================================ FILE: vendor/github.com/docker/docker/client/distribution_inspect.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/http" "net/url" "github.com/docker/docker/api/types/registry" ) // DistributionInspect returns the image digest with the full manifest. func (cli *Client) DistributionInspect(ctx context.Context, imageRef, encodedRegistryAuth string) (registry.DistributionInspect, error) { if imageRef == "" { return registry.DistributionInspect{}, objectNotFoundError{object: "distribution", id: imageRef} } if err := cli.NewVersionError(ctx, "1.30", "distribution inspect"); err != nil { return registry.DistributionInspect{}, err } var headers http.Header if encodedRegistryAuth != "" { headers = http.Header{ registry.AuthHeader: {encodedRegistryAuth}, } } // Contact the registry to retrieve digest and platform information resp, err := cli.get(ctx, "/distribution/"+imageRef+"/json", url.Values{}, headers) defer ensureReaderClosed(resp) if err != nil { return registry.DistributionInspect{}, err } var distributionInspect registry.DistributionInspect err = json.NewDecoder(resp.Body).Decode(&distributionInspect) return distributionInspect, err } ================================================ FILE: vendor/github.com/docker/docker/client/envvars.go ================================================ package client // import "github.com/docker/docker/client" const ( // EnvOverrideHost is the name of the environment variable that can be used // to override the default host to connect to (DefaultDockerHost). // // This env-var is read by FromEnv and WithHostFromEnv and when set to a // non-empty value, takes precedence over the default host (which is platform // specific), or any host already set. EnvOverrideHost = "DOCKER_HOST" // EnvOverrideAPIVersion is the name of the environment variable that can // be used to override the API version to use. Value should be // formatted as MAJOR.MINOR, for example, "1.19". // // This env-var is read by FromEnv and WithVersionFromEnv and when set to a // non-empty value, takes precedence over API version negotiation. // // This environment variable should be used for debugging purposes only, as // it can set the client to use an incompatible (or invalid) API version. EnvOverrideAPIVersion = "DOCKER_API_VERSION" // EnvOverrideCertPath is the name of the environment variable that can be // used to specify the directory from which to load the TLS certificates // (ca.pem, cert.pem, key.pem) from. These certificates are used to configure // the Client for a TCP connection protected by TLS client authentication. // // TLS certificate verification is enabled by default if the Client is configured // to use a TLS connection. Refer to EnvTLSVerify below to learn how to // disable verification for testing purposes. // // WARNING: Access to the remote API is equivalent to root access to the // host where the daemon runs. Do not expose the API without protection, // and only if needed. Make sure you are familiar with the "daemon attack // surface" (https://docs.docker.com/go/attack-surface/). // // For local access to the API, it is recommended to connect with the daemon // using the default local socket connection (on Linux), or the named pipe // (on Windows). // // If you need to access the API of a remote daemon, consider using an SSH // (ssh://) connection, which is easier to set up, and requires no additional // configuration if the host is accessible using ssh. // // If you cannot use the alternatives above, and you must expose the API over // a TCP connection, refer to https://docs.docker.com/engine/security/protect-access/ // to learn how to configure the daemon and client to use a TCP connection // with TLS client authentication. Make sure you know the differences between // a regular TLS connection and a TLS connection protected by TLS client // authentication, and verify that the API cannot be accessed by other clients. EnvOverrideCertPath = "DOCKER_CERT_PATH" // EnvTLSVerify is the name of the environment variable that can be used to // enable or disable TLS certificate verification. When set to a non-empty // value, TLS certificate verification is enabled, and the client is configured // to use a TLS connection, using certificates from the default directories // (within `~/.docker`); refer to EnvOverrideCertPath above for additional // details. // // WARNING: Access to the remote API is equivalent to root access to the // host where the daemon runs. Do not expose the API without protection, // and only if needed. Make sure you are familiar with the "daemon attack // surface" (https://docs.docker.com/go/attack-surface/). // // Before setting up your client and daemon to use a TCP connection with TLS // client authentication, consider using one of the alternatives mentioned // in EnvOverrideCertPath above. // // Disabling TLS certificate verification (for testing purposes) // // TLS certificate verification is enabled by default if the Client is configured // to use a TLS connection, and it is highly recommended to keep verification // enabled to prevent machine-in-the-middle attacks. Refer to the documentation // at https://docs.docker.com/engine/security/protect-access/ and pages linked // from that page to learn how to configure the daemon and client to use a // TCP connection with TLS client authentication enabled. // // Set the "DOCKER_TLS_VERIFY" environment to an empty string ("") to // disable TLS certificate verification. Disabling verification is insecure, // so should only be done for testing purposes. From the Go documentation // (https://pkg.go.dev/crypto/tls#Config): // // InsecureSkipVerify controls whether a client verifies the server's // certificate chain and host name. If InsecureSkipVerify is true, crypto/tls // accepts any certificate presented by the server and any host name in that // certificate. In this mode, TLS is susceptible to machine-in-the-middle // attacks unless custom verification is used. This should be used only for // testing or in combination with VerifyConnection or VerifyPeerCertificate. EnvTLSVerify = "DOCKER_TLS_VERIFY" ) ================================================ FILE: vendor/github.com/docker/docker/client/errors.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "errors" "fmt" "github.com/docker/docker/api/types/versions" "github.com/docker/docker/errdefs" ) // errConnectionFailed implements an error returned when connection failed. type errConnectionFailed struct { error } // Error returns a string representation of an errConnectionFailed func (e errConnectionFailed) Error() string { return e.error.Error() } func (e errConnectionFailed) Unwrap() error { return e.error } // IsErrConnectionFailed returns true if the error is caused by connection failed. func IsErrConnectionFailed(err error) bool { return errors.As(err, &errConnectionFailed{}) } // ErrorConnectionFailed returns an error with host in the error message when connection to docker daemon failed. // // Deprecated: this function was only used internally, and will be removed in the next release. func ErrorConnectionFailed(host string) error { return connectionFailed(host) } // connectionFailed returns an error with host in the error message when connection // to docker daemon failed. func connectionFailed(host string) error { var err error if host == "" { err = errors.New("Cannot connect to the Docker daemon. Is the docker daemon running on this host?") } else { err = fmt.Errorf("Cannot connect to the Docker daemon at %s. Is the docker daemon running?", host) } return errConnectionFailed{error: err} } // IsErrNotFound returns true if the error is a NotFound error, which is returned // by the API when some object is not found. It is an alias for [errdefs.IsNotFound]. func IsErrNotFound(err error) bool { return errdefs.IsNotFound(err) } type objectNotFoundError struct { object string id string } func (e objectNotFoundError) NotFound() {} func (e objectNotFoundError) Error() string { return fmt.Sprintf("Error: No such %s: %s", e.object, e.id) } // NewVersionError returns an error if the APIVersion required is less than the // current supported version. // // It performs API-version negotiation if the Client is configured with this // option, otherwise it assumes the latest API version is used. func (cli *Client) NewVersionError(ctx context.Context, APIrequired, feature string) error { // Make sure we negotiated (if the client is configured to do so), // as code below contains API-version specific handling of options. // // Normally, version-negotiation (if enabled) would not happen until // the API request is made. if err := cli.checkVersion(ctx); err != nil { return err } if cli.version != "" && versions.LessThan(cli.version, APIrequired) { return fmt.Errorf("%q requires API version %s, but the Docker daemon API version is %s", feature, APIrequired, cli.version) } return nil } ================================================ FILE: vendor/github.com/docker/docker/client/events.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/url" "time" "github.com/docker/docker/api/types/events" "github.com/docker/docker/api/types/filters" timetypes "github.com/docker/docker/api/types/time" ) // Events returns a stream of events in the daemon. It's up to the caller to close the stream // by cancelling the context. Once the stream has been completely read an io.EOF error will // be sent over the error channel. If an error is sent all processing will be stopped. It's up // to the caller to reopen the stream in the event of an error by reinvoking this method. func (cli *Client) Events(ctx context.Context, options events.ListOptions) (<-chan events.Message, <-chan error) { messages := make(chan events.Message) errs := make(chan error, 1) started := make(chan struct{}) go func() { defer close(errs) query, err := buildEventsQueryParams(cli.version, options) if err != nil { close(started) errs <- err return } resp, err := cli.get(ctx, "/events", query, nil) if err != nil { close(started) errs <- err return } defer resp.Body.Close() decoder := json.NewDecoder(resp.Body) close(started) for { select { case <-ctx.Done(): errs <- ctx.Err() return default: var event events.Message if err := decoder.Decode(&event); err != nil { errs <- err return } select { case messages <- event: case <-ctx.Done(): errs <- ctx.Err() return } } } }() <-started return messages, errs } func buildEventsQueryParams(cliVersion string, options events.ListOptions) (url.Values, error) { query := url.Values{} ref := time.Now() if options.Since != "" { ts, err := timetypes.GetTimestamp(options.Since, ref) if err != nil { return nil, err } query.Set("since", ts) } if options.Until != "" { ts, err := timetypes.GetTimestamp(options.Until, ref) if err != nil { return nil, err } query.Set("until", ts) } if options.Filters.Len() > 0 { //nolint:staticcheck // ignore SA1019 for old code filterJSON, err := filters.ToParamWithVersion(cliVersion, options.Filters) if err != nil { return nil, err } query.Set("filters", filterJSON) } return query, nil } ================================================ FILE: vendor/github.com/docker/docker/client/hijack.go ================================================ package client // import "github.com/docker/docker/client" import ( "bufio" "context" "fmt" "net" "net/http" "net/url" "time" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/versions" "github.com/pkg/errors" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) // postHijacked sends a POST request and hijacks the connection. func (cli *Client) postHijacked(ctx context.Context, path string, query url.Values, body interface{}, headers map[string][]string) (types.HijackedResponse, error) { bodyEncoded, err := encodeData(body) if err != nil { return types.HijackedResponse{}, err } req, err := cli.buildRequest(ctx, http.MethodPost, cli.getAPIPath(ctx, path, query), bodyEncoded, headers) if err != nil { return types.HijackedResponse{}, err } conn, mediaType, err := setupHijackConn(cli.dialer(), req, "tcp") if err != nil { return types.HijackedResponse{}, err } if versions.LessThan(cli.ClientVersion(), "1.42") { // Prior to 1.42, Content-Type is always set to raw-stream and not relevant mediaType = "" } return types.NewHijackedResponse(conn, mediaType), nil } // DialHijack returns a hijacked connection with negotiated protocol proto. func (cli *Client) DialHijack(ctx context.Context, url, proto string, meta map[string][]string) (net.Conn, error) { req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil) if err != nil { return nil, err } req = cli.addHeaders(req, meta) conn, _, err := setupHijackConn(cli.Dialer(), req, proto) return conn, err } func setupHijackConn(dialer func(context.Context) (net.Conn, error), req *http.Request, proto string) (_ net.Conn, _ string, retErr error) { ctx := req.Context() req.Header.Set("Connection", "Upgrade") req.Header.Set("Upgrade", proto) conn, err := dialer(ctx) if err != nil { return nil, "", errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?") } defer func() { if retErr != nil { conn.Close() } }() // When we set up a TCP connection for hijack, there could be long periods // of inactivity (a long running command with no output) that in certain // network setups may cause ECONNTIMEOUT, leaving the client in an unknown // state. Setting TCP KeepAlive on the socket connection will prohibit // ECONNTIMEOUT unless the socket connection truly is broken if tcpConn, ok := conn.(*net.TCPConn); ok { _ = tcpConn.SetKeepAlive(true) _ = tcpConn.SetKeepAlivePeriod(30 * time.Second) } hc := &hijackedConn{conn, bufio.NewReader(conn)} // Server hijacks the connection, error 'connection closed' expected resp, err := otelhttp.NewTransport(hc).RoundTrip(req) if err != nil { return nil, "", err } if resp.StatusCode != http.StatusSwitchingProtocols { _ = resp.Body.Close() return nil, "", fmt.Errorf("unable to upgrade to %s, received %d", proto, resp.StatusCode) } if hc.r.Buffered() > 0 { // If there is buffered content, wrap the connection. We return an // object that implements CloseWrite if the underlying connection // implements it. if _, ok := hc.Conn.(types.CloseWriter); ok { conn = &hijackedConnCloseWriter{hc} } else { conn = hc } } else { hc.r.Reset(nil) } return conn, resp.Header.Get("Content-Type"), nil } // hijackedConn wraps a net.Conn and is returned by setupHijackConn in the case // that a) there was already buffered data in the http layer when Hijack() was // called, and b) the underlying net.Conn does *not* implement CloseWrite(). // hijackedConn does not implement CloseWrite() either. type hijackedConn struct { net.Conn r *bufio.Reader } func (c *hijackedConn) RoundTrip(req *http.Request) (*http.Response, error) { if err := req.Write(c.Conn); err != nil { return nil, err } return http.ReadResponse(c.r, req) } func (c *hijackedConn) Read(b []byte) (int, error) { return c.r.Read(b) } // hijackedConnCloseWriter is a hijackedConn which additionally implements // CloseWrite(). It is returned by setupHijackConn in the case that a) there // was already buffered data in the http layer when Hijack() was called, and b) // the underlying net.Conn *does* implement CloseWrite(). type hijackedConnCloseWriter struct { *hijackedConn } var _ types.CloseWriter = &hijackedConnCloseWriter{} func (c *hijackedConnCloseWriter) CloseWrite() error { conn := c.Conn.(types.CloseWriter) return conn.CloseWrite() } ================================================ FILE: vendor/github.com/docker/docker/client/image_build.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/base64" "encoding/json" "io" "net/http" "net/url" "strconv" "strings" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" ) // ImageBuild sends a request to the daemon to build images. // The Body in the response implements an io.ReadCloser and it's up to the caller to // close it. func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) { query, err := cli.imageBuildOptionsToQuery(ctx, options) if err != nil { return types.ImageBuildResponse{}, err } buf, err := json.Marshal(options.AuthConfigs) if err != nil { return types.ImageBuildResponse{}, err } headers := http.Header{} headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf)) headers.Set("Content-Type", "application/x-tar") resp, err := cli.postRaw(ctx, "/build", query, buildContext, headers) if err != nil { return types.ImageBuildResponse{}, err } return types.ImageBuildResponse{ Body: resp.Body, OSType: getDockerOS(resp.Header.Get("Server")), }, nil } func (cli *Client) imageBuildOptionsToQuery(ctx context.Context, options types.ImageBuildOptions) (url.Values, error) { query := url.Values{} if len(options.Tags) > 0 { query["t"] = options.Tags } if len(options.SecurityOpt) > 0 { query["securityopt"] = options.SecurityOpt } if len(options.ExtraHosts) > 0 { query["extrahosts"] = options.ExtraHosts } if options.SuppressOutput { query.Set("q", "1") } if options.RemoteContext != "" { query.Set("remote", options.RemoteContext) } if options.NoCache { query.Set("nocache", "1") } if !options.Remove { // only send value when opting out because the daemon's default is // to remove intermediate containers after a successful build, // // TODO(thaJeztah): deprecate "Remove" option, and provide a "NoRemove" or "Keep" option instead. query.Set("rm", "0") } if options.ForceRemove { query.Set("forcerm", "1") } if options.PullParent { query.Set("pull", "1") } if options.Squash { if err := cli.NewVersionError(ctx, "1.25", "squash"); err != nil { return query, err } query.Set("squash", "1") } if !container.Isolation.IsDefault(options.Isolation) { query.Set("isolation", string(options.Isolation)) } if options.CPUSetCPUs != "" { query.Set("cpusetcpus", options.CPUSetCPUs) } if options.NetworkMode != "" && options.NetworkMode != network.NetworkDefault { query.Set("networkmode", options.NetworkMode) } if options.CPUSetMems != "" { query.Set("cpusetmems", options.CPUSetMems) } if options.CPUShares != 0 { query.Set("cpushares", strconv.FormatInt(options.CPUShares, 10)) } if options.CPUQuota != 0 { query.Set("cpuquota", strconv.FormatInt(options.CPUQuota, 10)) } if options.CPUPeriod != 0 { query.Set("cpuperiod", strconv.FormatInt(options.CPUPeriod, 10)) } if options.Memory != 0 { query.Set("memory", strconv.FormatInt(options.Memory, 10)) } if options.MemorySwap != 0 { query.Set("memswap", strconv.FormatInt(options.MemorySwap, 10)) } if options.CgroupParent != "" { query.Set("cgroupparent", options.CgroupParent) } if options.ShmSize != 0 { query.Set("shmsize", strconv.FormatInt(options.ShmSize, 10)) } if options.Dockerfile != "" { query.Set("dockerfile", options.Dockerfile) } if options.Target != "" { query.Set("target", options.Target) } if len(options.Ulimits) != 0 { ulimitsJSON, err := json.Marshal(options.Ulimits) if err != nil { return query, err } query.Set("ulimits", string(ulimitsJSON)) } if len(options.BuildArgs) != 0 { buildArgsJSON, err := json.Marshal(options.BuildArgs) if err != nil { return query, err } query.Set("buildargs", string(buildArgsJSON)) } if len(options.Labels) != 0 { labelsJSON, err := json.Marshal(options.Labels) if err != nil { return query, err } query.Set("labels", string(labelsJSON)) } if len(options.CacheFrom) != 0 { cacheFromJSON, err := json.Marshal(options.CacheFrom) if err != nil { return query, err } query.Set("cachefrom", string(cacheFromJSON)) } if options.SessionID != "" { query.Set("session", options.SessionID) } if options.Platform != "" { if err := cli.NewVersionError(ctx, "1.32", "platform"); err != nil { return query, err } query.Set("platform", strings.ToLower(options.Platform)) } if options.BuildID != "" { query.Set("buildid", options.BuildID) } if options.Version != "" { query.Set("version", string(options.Version)) } if options.Outputs != nil { outputsJSON, err := json.Marshal(options.Outputs) if err != nil { return query, err } query.Set("outputs", string(outputsJSON)) } return query, nil } ================================================ FILE: vendor/github.com/docker/docker/client/image_create.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "io" "net/http" "net/url" "strings" "github.com/distribution/reference" "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/registry" ) // ImageCreate creates a new image based on the parent options. // It returns the JSON content in the response body. func (cli *Client) ImageCreate(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error) { ref, err := reference.ParseNormalizedNamed(parentReference) if err != nil { return nil, err } query := url.Values{} query.Set("fromImage", reference.FamiliarName(ref)) query.Set("tag", getAPITagFromNamedRef(ref)) if options.Platform != "" { query.Set("platform", strings.ToLower(options.Platform)) } resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth) if err != nil { return nil, err } return resp.Body, nil } func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string) (*http.Response, error) { return cli.post(ctx, "/images/create", query, nil, http.Header{ registry.AuthHeader: {registryAuth}, }) } ================================================ FILE: vendor/github.com/docker/docker/client/image_history.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "fmt" "net/url" "github.com/docker/docker/api/types/image" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // ImageHistoryWithPlatform sets the platform for the image history operation. func ImageHistoryWithPlatform(platform ocispec.Platform) ImageHistoryOption { return imageHistoryOptionFunc(func(opt *imageHistoryOpts) error { if opt.apiOptions.Platform != nil { return fmt.Errorf("platform already set to %s", *opt.apiOptions.Platform) } opt.apiOptions.Platform = &platform return nil }) } // ImageHistory returns the changes in an image in history format. func (cli *Client) ImageHistory(ctx context.Context, imageID string, historyOpts ...ImageHistoryOption) ([]image.HistoryResponseItem, error) { query := url.Values{} var opts imageHistoryOpts for _, o := range historyOpts { if err := o.Apply(&opts); err != nil { return nil, err } } if opts.apiOptions.Platform != nil { if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil { return nil, err } p, err := encodePlatform(opts.apiOptions.Platform) if err != nil { return nil, err } query.Set("platform", p) } resp, err := cli.get(ctx, "/images/"+imageID+"/history", query, nil) defer ensureReaderClosed(resp) if err != nil { return nil, err } var history []image.HistoryResponseItem err = json.NewDecoder(resp.Body).Decode(&history) return history, err } ================================================ FILE: vendor/github.com/docker/docker/client/image_history_opts.go ================================================ package client import ( "github.com/docker/docker/api/types/image" ) // ImageHistoryOption is a type representing functional options for the image history operation. type ImageHistoryOption interface { Apply(*imageHistoryOpts) error } type imageHistoryOptionFunc func(opt *imageHistoryOpts) error func (f imageHistoryOptionFunc) Apply(o *imageHistoryOpts) error { return f(o) } type imageHistoryOpts struct { apiOptions image.HistoryOptions } ================================================ FILE: vendor/github.com/docker/docker/client/image_import.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "io" "net/url" "strings" "github.com/distribution/reference" "github.com/docker/docker/api/types/image" ) // ImageImport creates a new image based on the source options. // It returns the JSON content in the response body. func (cli *Client) ImageImport(ctx context.Context, source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error) { if ref != "" { // Check if the given image name can be resolved if _, err := reference.ParseNormalizedNamed(ref); err != nil { return nil, err } } query := url.Values{} if source.SourceName != "" { query.Set("fromSrc", source.SourceName) } if ref != "" { query.Set("repo", ref) } if options.Tag != "" { query.Set("tag", options.Tag) } if options.Message != "" { query.Set("message", options.Message) } if options.Platform != "" { query.Set("platform", strings.ToLower(options.Platform)) } for _, change := range options.Changes { query.Add("changes", change) } resp, err := cli.postRaw(ctx, "/images/create", query, source.Source, nil) if err != nil { return nil, err } return resp.Body, nil } ================================================ FILE: vendor/github.com/docker/docker/client/image_inspect.go ================================================ package client // import "github.com/docker/docker/client" import ( "bytes" "context" "encoding/json" "fmt" "io" "net/url" "github.com/docker/docker/api/types/image" ) // ImageInspect returns the image information. func (cli *Client) ImageInspect(ctx context.Context, imageID string, inspectOpts ...ImageInspectOption) (image.InspectResponse, error) { if imageID == "" { return image.InspectResponse{}, objectNotFoundError{object: "image", id: imageID} } var opts imageInspectOpts for _, opt := range inspectOpts { if err := opt.Apply(&opts); err != nil { return image.InspectResponse{}, fmt.Errorf("error applying image inspect option: %w", err) } } query := url.Values{} if opts.apiOptions.Manifests { if err := cli.NewVersionError(ctx, "1.48", "manifests"); err != nil { return image.InspectResponse{}, err } query.Set("manifests", "1") } resp, err := cli.get(ctx, "/images/"+imageID+"/json", query, nil) defer ensureReaderClosed(resp) if err != nil { return image.InspectResponse{}, err } buf := opts.raw if buf == nil { buf = &bytes.Buffer{} } if _, err := io.Copy(buf, resp.Body); err != nil { return image.InspectResponse{}, err } var response image.InspectResponse err = json.Unmarshal(buf.Bytes(), &response) return response, err } // ImageInspectWithRaw returns the image information and its raw representation. // // Deprecated: Use [Client.ImageInspect] instead. Raw response can be obtained using the [ImageInspectWithRawResponse] option. func (cli *Client) ImageInspectWithRaw(ctx context.Context, imageID string) (image.InspectResponse, []byte, error) { var buf bytes.Buffer resp, err := cli.ImageInspect(ctx, imageID, ImageInspectWithRawResponse(&buf)) if err != nil { return image.InspectResponse{}, nil, err } return resp, buf.Bytes(), err } ================================================ FILE: vendor/github.com/docker/docker/client/image_inspect_opts.go ================================================ package client import ( "bytes" "github.com/docker/docker/api/types/image" ) // ImageInspectOption is a type representing functional options for the image inspect operation. type ImageInspectOption interface { Apply(*imageInspectOpts) error } type imageInspectOptionFunc func(opt *imageInspectOpts) error func (f imageInspectOptionFunc) Apply(o *imageInspectOpts) error { return f(o) } // ImageInspectWithRawResponse instructs the client to additionally store the // raw inspect response in the provided buffer. func ImageInspectWithRawResponse(raw *bytes.Buffer) ImageInspectOption { return imageInspectOptionFunc(func(opts *imageInspectOpts) error { opts.raw = raw return nil }) } // ImageInspectWithManifests sets manifests API option for the image inspect operation. // This option is only available for API version 1.48 and up. // With this option set, the image inspect operation response will have the // [image.InspectResponse.Manifests] field populated if the server is multi-platform capable. func ImageInspectWithManifests(manifests bool) ImageInspectOption { return imageInspectOptionFunc(func(clientOpts *imageInspectOpts) error { clientOpts.apiOptions.Manifests = manifests return nil }) } // ImageInspectWithAPIOpts sets the API options for the image inspect operation. func ImageInspectWithAPIOpts(opts image.InspectOptions) ImageInspectOption { return imageInspectOptionFunc(func(clientOpts *imageInspectOpts) error { clientOpts.apiOptions = opts return nil }) } type imageInspectOpts struct { raw *bytes.Buffer apiOptions image.InspectOptions } ================================================ FILE: vendor/github.com/docker/docker/client/image_list.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/url" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/versions" ) // ImageList returns a list of images in the docker host. // // Experimental: Setting the [options.Manifest] will populate // [image.Summary.Manifests] with information about image manifests. // This is experimental and might change in the future without any backward // compatibility. func (cli *Client) ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error) { var images []image.Summary // Make sure we negotiated (if the client is configured to do so), // as code below contains API-version specific handling of options. // // Normally, version-negotiation (if enabled) would not happen until // the API request is made. if err := cli.checkVersion(ctx); err != nil { return images, err } query := url.Values{} optionFilters := options.Filters referenceFilters := optionFilters.Get("reference") if versions.LessThan(cli.version, "1.25") && len(referenceFilters) > 0 { query.Set("filter", referenceFilters[0]) for _, filterValue := range referenceFilters { optionFilters.Del("reference", filterValue) } } if optionFilters.Len() > 0 { //nolint:staticcheck // ignore SA1019 for old code filterJSON, err := filters.ToParamWithVersion(cli.version, optionFilters) if err != nil { return images, err } query.Set("filters", filterJSON) } if options.All { query.Set("all", "1") } if options.SharedSize && versions.GreaterThanOrEqualTo(cli.version, "1.42") { query.Set("shared-size", "1") } if options.Manifests && versions.GreaterThanOrEqualTo(cli.version, "1.47") { query.Set("manifests", "1") } resp, err := cli.get(ctx, "/images/json", query, nil) defer ensureReaderClosed(resp) if err != nil { return images, err } err = json.NewDecoder(resp.Body).Decode(&images) return images, err } ================================================ FILE: vendor/github.com/docker/docker/client/image_load.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "io" "net/http" "net/url" "github.com/docker/docker/api/types/image" ) // ImageLoad loads an image in the docker host from the client host. // It's up to the caller to close the io.ReadCloser in the // ImageLoadResponse returned by this function. // // Platform is an optional parameter that specifies the platform to load from // the provided multi-platform image. This is only has effect if the input image // is a multi-platform image. func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, loadOpts ...ImageLoadOption) (image.LoadResponse, error) { var opts imageLoadOpts for _, opt := range loadOpts { if err := opt.Apply(&opts); err != nil { return image.LoadResponse{}, err } } query := url.Values{} query.Set("quiet", "0") if opts.apiOptions.Quiet { query.Set("quiet", "1") } if len(opts.apiOptions.Platforms) > 0 { if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil { return image.LoadResponse{}, err } p, err := encodePlatforms(opts.apiOptions.Platforms...) if err != nil { return image.LoadResponse{}, err } query["platform"] = p } resp, err := cli.postRaw(ctx, "/images/load", query, input, http.Header{ "Content-Type": {"application/x-tar"}, }) if err != nil { return image.LoadResponse{}, err } return image.LoadResponse{ Body: resp.Body, JSON: resp.Header.Get("Content-Type") == "application/json", }, nil } ================================================ FILE: vendor/github.com/docker/docker/client/image_load_opts.go ================================================ package client import ( "fmt" "github.com/docker/docker/api/types/image" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // ImageLoadOption is a type representing functional options for the image load operation. type ImageLoadOption interface { Apply(*imageLoadOpts) error } type imageLoadOptionFunc func(opt *imageLoadOpts) error func (f imageLoadOptionFunc) Apply(o *imageLoadOpts) error { return f(o) } type imageLoadOpts struct { apiOptions image.LoadOptions } // ImageLoadWithQuiet sets the quiet option for the image load operation. func ImageLoadWithQuiet(quiet bool) ImageLoadOption { return imageLoadOptionFunc(func(opt *imageLoadOpts) error { opt.apiOptions.Quiet = quiet return nil }) } // ImageLoadWithPlatforms sets the platforms to be loaded from the image. func ImageLoadWithPlatforms(platforms ...ocispec.Platform) ImageLoadOption { return imageLoadOptionFunc(func(opt *imageLoadOpts) error { if opt.apiOptions.Platforms != nil { return fmt.Errorf("platforms already set to %v", opt.apiOptions.Platforms) } opt.apiOptions.Platforms = platforms return nil }) } ================================================ FILE: vendor/github.com/docker/docker/client/image_prune.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "fmt" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/image" ) // ImagesPrune requests the daemon to delete unused data func (cli *Client) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (image.PruneReport, error) { if err := cli.NewVersionError(ctx, "1.25", "image prune"); err != nil { return image.PruneReport{}, err } query, err := getFiltersQuery(pruneFilters) if err != nil { return image.PruneReport{}, err } resp, err := cli.post(ctx, "/images/prune", query, nil, nil) defer ensureReaderClosed(resp) if err != nil { return image.PruneReport{}, err } var report image.PruneReport if err := json.NewDecoder(resp.Body).Decode(&report); err != nil { return image.PruneReport{}, fmt.Errorf("Error retrieving disk usage: %v", err) } return report, nil } ================================================ FILE: vendor/github.com/docker/docker/client/image_pull.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "io" "net/url" "strings" "github.com/distribution/reference" "github.com/docker/docker/api/types/image" "github.com/docker/docker/errdefs" ) // ImagePull requests the docker host to pull an image from a remote registry. // It executes the privileged function if the operation is unauthorized // and it tries one more time. // It's up to the caller to handle the io.ReadCloser and close it properly. // // FIXME(vdemeester): there is currently used in a few way in docker/docker // - if not in trusted content, ref is used to pass the whole reference, and tag is empty // - if in trusted content, ref is used to pass the reference name, and tag for the digest func (cli *Client) ImagePull(ctx context.Context, refStr string, options image.PullOptions) (io.ReadCloser, error) { ref, err := reference.ParseNormalizedNamed(refStr) if err != nil { return nil, err } query := url.Values{} query.Set("fromImage", reference.FamiliarName(ref)) if !options.All { query.Set("tag", getAPITagFromNamedRef(ref)) } if options.Platform != "" { query.Set("platform", strings.ToLower(options.Platform)) } resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth) if errdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil { newAuthHeader, privilegeErr := options.PrivilegeFunc(ctx) if privilegeErr != nil { return nil, privilegeErr } resp, err = cli.tryImageCreate(ctx, query, newAuthHeader) } if err != nil { return nil, err } return resp.Body, nil } // getAPITagFromNamedRef returns a tag from the specified reference. // This function is necessary as long as the docker "server" api expects // digests to be sent as tags and makes a distinction between the name // and tag/digest part of a reference. func getAPITagFromNamedRef(ref reference.Named) string { if digested, ok := ref.(reference.Digested); ok { return digested.Digest().String() } ref = reference.TagNameOnly(ref) if tagged, ok := ref.(reference.Tagged); ok { return tagged.Tag() } return "" } ================================================ FILE: vendor/github.com/docker/docker/client/image_push.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "github.com/distribution/reference" "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/registry" "github.com/docker/docker/errdefs" ) // ImagePush requests the docker host to push an image to a remote registry. // It executes the privileged function if the operation is unauthorized // and it tries one more time. // It's up to the caller to handle the io.ReadCloser and close it properly. func (cli *Client) ImagePush(ctx context.Context, image string, options image.PushOptions) (io.ReadCloser, error) { ref, err := reference.ParseNormalizedNamed(image) if err != nil { return nil, err } if _, isCanonical := ref.(reference.Canonical); isCanonical { return nil, errors.New("cannot push a digest reference") } name := reference.FamiliarName(ref) query := url.Values{} if !options.All { ref = reference.TagNameOnly(ref) if tagged, ok := ref.(reference.Tagged); ok { query.Set("tag", tagged.Tag()) } } if options.Platform != nil { if err := cli.NewVersionError(ctx, "1.46", "platform"); err != nil { return nil, err } p := *options.Platform pJson, err := json.Marshal(p) if err != nil { return nil, fmt.Errorf("invalid platform: %v", err) } query.Set("platform", string(pJson)) } resp, err := cli.tryImagePush(ctx, name, query, options.RegistryAuth) if errdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil { newAuthHeader, privilegeErr := options.PrivilegeFunc(ctx) if privilegeErr != nil { return nil, privilegeErr } resp, err = cli.tryImagePush(ctx, name, query, newAuthHeader) } if err != nil { return nil, err } return resp.Body, nil } func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, registryAuth string) (*http.Response, error) { return cli.post(ctx, "/images/"+imageID+"/push", query, nil, http.Header{ registry.AuthHeader: {registryAuth}, }) } ================================================ FILE: vendor/github.com/docker/docker/client/image_remove.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/url" "github.com/docker/docker/api/types/image" ) // ImageRemove removes an image from the docker host. func (cli *Client) ImageRemove(ctx context.Context, imageID string, options image.RemoveOptions) ([]image.DeleteResponse, error) { query := url.Values{} if options.Force { query.Set("force", "1") } if !options.PruneChildren { query.Set("noprune", "1") } resp, err := cli.delete(ctx, "/images/"+imageID, query, nil) defer ensureReaderClosed(resp) if err != nil { return nil, err } var dels []image.DeleteResponse err = json.NewDecoder(resp.Body).Decode(&dels) return dels, err } ================================================ FILE: vendor/github.com/docker/docker/client/image_save.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "io" "net/url" ) // ImageSave retrieves one or more images from the docker host as an io.ReadCloser. // // Platforms is an optional parameter that specifies the platforms to save from the image. // This is only has effect if the input image is a multi-platform image. func (cli *Client) ImageSave(ctx context.Context, imageIDs []string, saveOpts ...ImageSaveOption) (io.ReadCloser, error) { var opts imageSaveOpts for _, opt := range saveOpts { if err := opt.Apply(&opts); err != nil { return nil, err } } query := url.Values{ "names": imageIDs, } if len(opts.apiOptions.Platforms) > 0 { if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil { return nil, err } p, err := encodePlatforms(opts.apiOptions.Platforms...) if err != nil { return nil, err } query["platform"] = p } resp, err := cli.get(ctx, "/images/get", query, nil) if err != nil { return nil, err } return resp.Body, nil } ================================================ FILE: vendor/github.com/docker/docker/client/image_save_opts.go ================================================ package client import ( "fmt" "github.com/docker/docker/api/types/image" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) type ImageSaveOption interface { Apply(*imageSaveOpts) error } type imageSaveOptionFunc func(opt *imageSaveOpts) error func (f imageSaveOptionFunc) Apply(o *imageSaveOpts) error { return f(o) } // ImageSaveWithPlatforms sets the platforms to be saved from the image. func ImageSaveWithPlatforms(platforms ...ocispec.Platform) ImageSaveOption { return imageSaveOptionFunc(func(opt *imageSaveOpts) error { if opt.apiOptions.Platforms != nil { return fmt.Errorf("platforms already set to %v", opt.apiOptions.Platforms) } opt.apiOptions.Platforms = platforms return nil }) } type imageSaveOpts struct { apiOptions image.SaveOptions } ================================================ FILE: vendor/github.com/docker/docker/client/image_search.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/http" "net/url" "strconv" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/registry" "github.com/docker/docker/errdefs" ) // ImageSearch makes the docker host search by a term in a remote registry. // The list of results is not sorted in any fashion. func (cli *Client) ImageSearch(ctx context.Context, term string, options registry.SearchOptions) ([]registry.SearchResult, error) { var results []registry.SearchResult query := url.Values{} query.Set("term", term) if options.Limit > 0 { query.Set("limit", strconv.Itoa(options.Limit)) } if options.Filters.Len() > 0 { filterJSON, err := filters.ToJSON(options.Filters) if err != nil { return results, err } query.Set("filters", filterJSON) } resp, err := cli.tryImageSearch(ctx, query, options.RegistryAuth) defer ensureReaderClosed(resp) if errdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil { newAuthHeader, privilegeErr := options.PrivilegeFunc(ctx) if privilegeErr != nil { return results, privilegeErr } resp, err = cli.tryImageSearch(ctx, query, newAuthHeader) } if err != nil { return results, err } err = json.NewDecoder(resp.Body).Decode(&results) return results, err } func (cli *Client) tryImageSearch(ctx context.Context, query url.Values, registryAuth string) (*http.Response, error) { return cli.get(ctx, "/images/search", query, http.Header{ registry.AuthHeader: {registryAuth}, }) } ================================================ FILE: vendor/github.com/docker/docker/client/image_tag.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" "github.com/distribution/reference" "github.com/pkg/errors" ) // ImageTag tags an image in the docker host func (cli *Client) ImageTag(ctx context.Context, source, target string) error { if _, err := reference.ParseAnyReference(source); err != nil { return errors.Wrapf(err, "Error parsing reference: %q is not a valid repository/tag", source) } ref, err := reference.ParseNormalizedNamed(target) if err != nil { return errors.Wrapf(err, "Error parsing reference: %q is not a valid repository/tag", target) } if _, isCanonical := ref.(reference.Canonical); isCanonical { return errors.New("refusing to create a tag with a digest reference") } ref = reference.TagNameOnly(ref) query := url.Values{} query.Set("repo", reference.FamiliarName(ref)) if tagged, ok := ref.(reference.Tagged); ok { query.Set("tag", tagged.Tag()) } resp, err := cli.post(ctx, "/images/"+source+"/tag", query, nil, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/info.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "fmt" "net/url" "github.com/docker/docker/api/types/system" ) // Info returns information about the docker server. func (cli *Client) Info(ctx context.Context) (system.Info, error) { var info system.Info resp, err := cli.get(ctx, "/info", url.Values{}, nil) defer ensureReaderClosed(resp) if err != nil { return info, err } if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { return info, fmt.Errorf("Error reading remote info: %v", err) } return info, nil } ================================================ FILE: vendor/github.com/docker/docker/client/login.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/url" "github.com/docker/docker/api/types/registry" ) // RegistryLogin authenticates the docker server with a given docker registry. // It returns unauthorizedError when the authentication fails. func (cli *Client) RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error) { resp, err := cli.post(ctx, "/auth", url.Values{}, auth, nil) defer ensureReaderClosed(resp) if err != nil { return registry.AuthenticateOKBody{}, err } var response registry.AuthenticateOKBody err = json.NewDecoder(resp.Body).Decode(&response) return response, err } ================================================ FILE: vendor/github.com/docker/docker/client/network_connect.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "github.com/docker/docker/api/types/network" ) // NetworkConnect connects a container to an existent network in the docker host. func (cli *Client) NetworkConnect(ctx context.Context, networkID, containerID string, config *network.EndpointSettings) error { networkID, err := trimID("network", networkID) if err != nil { return err } containerID, err = trimID("container", containerID) if err != nil { return err } nc := network.ConnectOptions{ Container: containerID, EndpointConfig: config, } resp, err := cli.post(ctx, "/networks/"+networkID+"/connect", nil, nc, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/network_create.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/versions" ) // NetworkCreate creates a new network in the docker host. func (cli *Client) NetworkCreate(ctx context.Context, name string, options network.CreateOptions) (network.CreateResponse, error) { // Make sure we negotiated (if the client is configured to do so), // as code below contains API-version specific handling of options. // // Normally, version-negotiation (if enabled) would not happen until // the API request is made. if err := cli.checkVersion(ctx); err != nil { return network.CreateResponse{}, err } networkCreateRequest := network.CreateRequest{ CreateOptions: options, Name: name, } if versions.LessThan(cli.version, "1.44") { enabled := true networkCreateRequest.CheckDuplicate = &enabled //nolint:staticcheck // ignore SA1019: CheckDuplicate is deprecated since API v1.44. } resp, err := cli.post(ctx, "/networks/create", nil, networkCreateRequest, nil) defer ensureReaderClosed(resp) if err != nil { return network.CreateResponse{}, err } var response network.CreateResponse err = json.NewDecoder(resp.Body).Decode(&response) return response, err } ================================================ FILE: vendor/github.com/docker/docker/client/network_disconnect.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "github.com/docker/docker/api/types/network" ) // NetworkDisconnect disconnects a container from an existent network in the docker host. func (cli *Client) NetworkDisconnect(ctx context.Context, networkID, containerID string, force bool) error { networkID, err := trimID("network", networkID) if err != nil { return err } containerID, err = trimID("container", containerID) if err != nil { return err } nd := network.DisconnectOptions{ Container: containerID, Force: force, } resp, err := cli.post(ctx, "/networks/"+networkID+"/disconnect", nil, nd, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/network_inspect.go ================================================ package client // import "github.com/docker/docker/client" import ( "bytes" "context" "encoding/json" "io" "net/url" "github.com/docker/docker/api/types/network" ) // NetworkInspect returns the information for a specific network configured in the docker host. func (cli *Client) NetworkInspect(ctx context.Context, networkID string, options network.InspectOptions) (network.Inspect, error) { networkResource, _, err := cli.NetworkInspectWithRaw(ctx, networkID, options) return networkResource, err } // NetworkInspectWithRaw returns the information for a specific network configured in the docker host and its raw representation. func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string, options network.InspectOptions) (network.Inspect, []byte, error) { networkID, err := trimID("network", networkID) if err != nil { return network.Inspect{}, nil, err } query := url.Values{} if options.Verbose { query.Set("verbose", "true") } if options.Scope != "" { query.Set("scope", options.Scope) } resp, err := cli.get(ctx, "/networks/"+networkID, query, nil) defer ensureReaderClosed(resp) if err != nil { return network.Inspect{}, nil, err } raw, err := io.ReadAll(resp.Body) if err != nil { return network.Inspect{}, nil, err } var nw network.Inspect err = json.NewDecoder(bytes.NewReader(raw)).Decode(&nw) return nw, raw, err } ================================================ FILE: vendor/github.com/docker/docker/client/network_list.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/url" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/network" ) // NetworkList returns the list of networks configured in the docker host. func (cli *Client) NetworkList(ctx context.Context, options network.ListOptions) ([]network.Summary, error) { query := url.Values{} if options.Filters.Len() > 0 { //nolint:staticcheck // ignore SA1019 for old code filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters) if err != nil { return nil, err } query.Set("filters", filterJSON) } var networkResources []network.Summary resp, err := cli.get(ctx, "/networks", query, nil) defer ensureReaderClosed(resp) if err != nil { return networkResources, err } err = json.NewDecoder(resp.Body).Decode(&networkResources) return networkResources, err } ================================================ FILE: vendor/github.com/docker/docker/client/network_prune.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "fmt" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/network" ) // NetworksPrune requests the daemon to delete unused networks func (cli *Client) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (network.PruneReport, error) { if err := cli.NewVersionError(ctx, "1.25", "network prune"); err != nil { return network.PruneReport{}, err } query, err := getFiltersQuery(pruneFilters) if err != nil { return network.PruneReport{}, err } resp, err := cli.post(ctx, "/networks/prune", query, nil, nil) defer ensureReaderClosed(resp) if err != nil { return network.PruneReport{}, err } var report network.PruneReport if err := json.NewDecoder(resp.Body).Decode(&report); err != nil { return network.PruneReport{}, fmt.Errorf("Error retrieving network prune report: %v", err) } return report, nil } ================================================ FILE: vendor/github.com/docker/docker/client/network_remove.go ================================================ package client // import "github.com/docker/docker/client" import "context" // NetworkRemove removes an existent network from the docker host. func (cli *Client) NetworkRemove(ctx context.Context, networkID string) error { networkID, err := trimID("network", networkID) if err != nil { return err } resp, err := cli.delete(ctx, "/networks/"+networkID, nil, nil) defer ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/node_inspect.go ================================================ package client // import "github.com/docker/docker/client" import ( "bytes" "context" "encoding/json" "io" "github.com/docker/docker/api/types/swarm" ) // NodeInspectWithRaw returns the node information. func (cli *Client) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) { nodeID, err := trimID("node", nodeID) if err != nil { return swarm.Node{}, nil, err } resp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil) defer ensureReaderClosed(resp) if err != nil { return swarm.Node{}, nil, err } body, err := io.ReadAll(resp.Body) if err != nil { return swarm.Node{}, nil, err } var response swarm.Node rdr := bytes.NewReader(body) err = json.NewDecoder(rdr).Decode(&response) return response, body, err } ================================================ FILE: vendor/github.com/docker/docker/client/node_list.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/url" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" ) // NodeList returns the list of nodes. func (cli *Client) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) { query := url.Values{} if options.Filters.Len() > 0 { filterJSON, err := filters.ToJSON(options.Filters) if err != nil { return nil, err } query.Set("filters", filterJSON) } resp, err := cli.get(ctx, "/nodes", query, nil) defer ensureReaderClosed(resp) if err != nil { return nil, err } var nodes []swarm.Node err = json.NewDecoder(resp.Body).Decode(&nodes) return nodes, err } ================================================ FILE: vendor/github.com/docker/docker/client/node_remove.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" "github.com/docker/docker/api/types" ) // NodeRemove removes a Node. func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options types.NodeRemoveOptions) error { nodeID, err := trimID("node", nodeID) if err != nil { return err } query := url.Values{} if options.Force { query.Set("force", "1") } resp, err := cli.delete(ctx, "/nodes/"+nodeID, query, nil) defer ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/node_update.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" "github.com/docker/docker/api/types/swarm" ) // NodeUpdate updates a Node. func (cli *Client) NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error { nodeID, err := trimID("node", nodeID) if err != nil { return err } query := url.Values{} query.Set("version", version.String()) resp, err := cli.post(ctx, "/nodes/"+nodeID+"/update", query, node, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/options.go ================================================ package client import ( "context" "net" "net/http" "os" "path/filepath" "strings" "time" "github.com/docker/go-connections/sockets" "github.com/docker/go-connections/tlsconfig" "github.com/pkg/errors" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel/trace" ) // Opt is a configuration option to initialize a [Client]. type Opt func(*Client) error // FromEnv configures the client with values from environment variables. It // is the equivalent of using the [WithTLSClientConfigFromEnv], [WithHostFromEnv], // and [WithVersionFromEnv] options. // // FromEnv uses the following environment variables: // // - DOCKER_HOST ([EnvOverrideHost]) to set the URL to the docker server. // - DOCKER_API_VERSION ([EnvOverrideAPIVersion]) to set the version of the // API to use, leave empty for latest. // - DOCKER_CERT_PATH ([EnvOverrideCertPath]) to specify the directory from // which to load the TLS certificates ("ca.pem", "cert.pem", "key.pem'). // - DOCKER_TLS_VERIFY ([EnvTLSVerify]) to enable or disable TLS verification // (off by default). func FromEnv(c *Client) error { ops := []Opt{ WithTLSClientConfigFromEnv(), WithHostFromEnv(), WithVersionFromEnv(), } for _, op := range ops { if err := op(c); err != nil { return err } } return nil } // WithDialContext applies the dialer to the client transport. This can be // used to set the Timeout and KeepAlive settings of the client. It returns // an error if the client does not have a [http.Transport] configured. func WithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) Opt { return func(c *Client) error { if transport, ok := c.client.Transport.(*http.Transport); ok { transport.DialContext = dialContext return nil } return errors.Errorf("cannot apply dialer to transport: %T", c.client.Transport) } } // WithHost overrides the client host with the specified one. func WithHost(host string) Opt { return func(c *Client) error { hostURL, err := ParseHostURL(host) if err != nil { return err } c.host = host c.proto = hostURL.Scheme c.addr = hostURL.Host c.basePath = hostURL.Path if transport, ok := c.client.Transport.(*http.Transport); ok { return sockets.ConfigureTransport(transport, c.proto, c.addr) } return errors.Errorf("cannot apply host to transport: %T", c.client.Transport) } } // WithHostFromEnv overrides the client host with the host specified in the // DOCKER_HOST ([EnvOverrideHost]) environment variable. If DOCKER_HOST is not set, // or set to an empty value, the host is not modified. func WithHostFromEnv() Opt { return func(c *Client) error { if host := os.Getenv(EnvOverrideHost); host != "" { return WithHost(host)(c) } return nil } } // WithHTTPClient overrides the client's HTTP client with the specified one. func WithHTTPClient(client *http.Client) Opt { return func(c *Client) error { if client != nil { c.client = client } return nil } } // WithTimeout configures the time limit for requests made by the HTTP client. func WithTimeout(timeout time.Duration) Opt { return func(c *Client) error { c.client.Timeout = timeout return nil } } // WithUserAgent configures the User-Agent header to use for HTTP requests. // It overrides any User-Agent set in headers. When set to an empty string, // the User-Agent header is removed, and no header is sent. func WithUserAgent(ua string) Opt { return func(c *Client) error { c.userAgent = &ua return nil } } // WithHTTPHeaders appends custom HTTP headers to the client's default headers. // It does not allow for built-in headers (such as "User-Agent", if set) to // be overridden. Also see [WithUserAgent]. func WithHTTPHeaders(headers map[string]string) Opt { return func(c *Client) error { c.customHTTPHeaders = headers return nil } } // WithScheme overrides the client scheme with the specified one. func WithScheme(scheme string) Opt { return func(c *Client) error { c.scheme = scheme return nil } } // WithTLSClientConfig applies a TLS config to the client transport. func WithTLSClientConfig(cacertPath, certPath, keyPath string) Opt { return func(c *Client) error { transport, ok := c.client.Transport.(*http.Transport) if !ok { return errors.Errorf("cannot apply tls config to transport: %T", c.client.Transport) } config, err := tlsconfig.Client(tlsconfig.Options{ CAFile: cacertPath, CertFile: certPath, KeyFile: keyPath, ExclusiveRootPools: true, }) if err != nil { return errors.Wrap(err, "failed to create tls config") } transport.TLSClientConfig = config return nil } } // WithTLSClientConfigFromEnv configures the client's TLS settings with the // settings in the DOCKER_CERT_PATH ([EnvOverrideCertPath]) and DOCKER_TLS_VERIFY // ([EnvTLSVerify]) environment variables. If DOCKER_CERT_PATH is not set or empty, // TLS configuration is not modified. // // WithTLSClientConfigFromEnv uses the following environment variables: // // - DOCKER_CERT_PATH ([EnvOverrideCertPath]) to specify the directory from // which to load the TLS certificates ("ca.pem", "cert.pem", "key.pem"). // - DOCKER_TLS_VERIFY ([EnvTLSVerify]) to enable or disable TLS verification // (off by default). func WithTLSClientConfigFromEnv() Opt { return func(c *Client) error { dockerCertPath := os.Getenv(EnvOverrideCertPath) if dockerCertPath == "" { return nil } tlsc, err := tlsconfig.Client(tlsconfig.Options{ CAFile: filepath.Join(dockerCertPath, "ca.pem"), CertFile: filepath.Join(dockerCertPath, "cert.pem"), KeyFile: filepath.Join(dockerCertPath, "key.pem"), InsecureSkipVerify: os.Getenv(EnvTLSVerify) == "", }) if err != nil { return err } c.client = &http.Client{ Transport: &http.Transport{TLSClientConfig: tlsc}, CheckRedirect: CheckRedirect, } return nil } } // WithVersion overrides the client version with the specified one. If an empty // version is provided, the value is ignored to allow version negotiation // (see [WithAPIVersionNegotiation]). func WithVersion(version string) Opt { return func(c *Client) error { if v := strings.TrimPrefix(version, "v"); v != "" { c.version = v c.manualOverride = true } return nil } } // WithVersionFromEnv overrides the client version with the version specified in // the DOCKER_API_VERSION ([EnvOverrideAPIVersion]) environment variable. // If DOCKER_API_VERSION is not set, or set to an empty value, the version // is not modified. func WithVersionFromEnv() Opt { return func(c *Client) error { return WithVersion(os.Getenv(EnvOverrideAPIVersion))(c) } } // WithAPIVersionNegotiation enables automatic API version negotiation for the client. // With this option enabled, the client automatically negotiates the API version // to use when making requests. API version negotiation is performed on the first // request; subsequent requests do not re-negotiate. func WithAPIVersionNegotiation() Opt { return func(c *Client) error { c.negotiateVersion = true return nil } } // WithTraceProvider sets the trace provider for the client. // If this is not set then the global trace provider will be used. func WithTraceProvider(provider trace.TracerProvider) Opt { return WithTraceOptions(otelhttp.WithTracerProvider(provider)) } // WithTraceOptions sets tracing span options for the client. func WithTraceOptions(opts ...otelhttp.Option) Opt { return func(c *Client) error { c.traceOpts = append(c.traceOpts, opts...) return nil } } ================================================ FILE: vendor/github.com/docker/docker/client/ping.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/http" "path" "strings" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" ) // Ping pings the server and returns the value of the "Docker-Experimental", // "Builder-Version", "OS-Type" & "API-Version" headers. It attempts to use // a HEAD request on the endpoint, but falls back to GET if HEAD is not supported // by the daemon. It ignores internal server errors returned by the API, which // may be returned if the daemon is in an unhealthy state, but returns errors // for other non-success status codes, failing to connect to the API, or failing // to parse the API response. func (cli *Client) Ping(ctx context.Context) (types.Ping, error) { var ping types.Ping // Using cli.buildRequest() + cli.doRequest() instead of cli.sendRequest() // because ping requests are used during API version negotiation, so we want // to hit the non-versioned /_ping endpoint, not /v1.xx/_ping req, err := cli.buildRequest(ctx, http.MethodHead, path.Join(cli.basePath, "/_ping"), nil, nil) if err != nil { return ping, err } resp, err := cli.doRequest(req) if err != nil { if IsErrConnectionFailed(err) { return ping, err } // We managed to connect, but got some error; continue and try GET request. } else { defer ensureReaderClosed(resp) switch resp.StatusCode { case http.StatusOK, http.StatusInternalServerError: // Server handled the request, so parse the response return parsePingResponse(cli, resp) } } // HEAD failed; fallback to GET. req.Method = http.MethodGet resp, err = cli.doRequest(req) defer ensureReaderClosed(resp) if err != nil { return ping, err } return parsePingResponse(cli, resp) } func parsePingResponse(cli *Client, resp *http.Response) (types.Ping, error) { if resp == nil { return types.Ping{}, nil } var ping types.Ping if resp.Header == nil { return ping, cli.checkResponseErr(resp) } ping.APIVersion = resp.Header.Get("Api-Version") ping.OSType = resp.Header.Get("Ostype") if resp.Header.Get("Docker-Experimental") == "true" { ping.Experimental = true } if bv := resp.Header.Get("Builder-Version"); bv != "" { ping.BuilderVersion = types.BuilderVersion(bv) } if si := resp.Header.Get("Swarm"); si != "" { state, role, _ := strings.Cut(si, "/") ping.SwarmStatus = &swarm.Status{ NodeState: swarm.LocalNodeState(state), ControlAvailable: role == "manager", } } return ping, cli.checkResponseErr(resp) } ================================================ FILE: vendor/github.com/docker/docker/client/plugin_create.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "io" "net/http" "net/url" "github.com/docker/docker/api/types" ) // PluginCreate creates a plugin func (cli *Client) PluginCreate(ctx context.Context, createContext io.Reader, createOptions types.PluginCreateOptions) error { headers := http.Header(make(map[string][]string)) headers.Set("Content-Type", "application/x-tar") query := url.Values{} query.Set("name", createOptions.RepoName) resp, err := cli.postRaw(ctx, "/plugins/create", query, createContext, headers) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/plugin_disable.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" "github.com/docker/docker/api/types" ) // PluginDisable disables a plugin func (cli *Client) PluginDisable(ctx context.Context, name string, options types.PluginDisableOptions) error { name, err := trimID("plugin", name) if err != nil { return err } query := url.Values{} if options.Force { query.Set("force", "1") } resp, err := cli.post(ctx, "/plugins/"+name+"/disable", query, nil, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/plugin_enable.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" "strconv" "github.com/docker/docker/api/types" ) // PluginEnable enables a plugin func (cli *Client) PluginEnable(ctx context.Context, name string, options types.PluginEnableOptions) error { name, err := trimID("plugin", name) if err != nil { return err } query := url.Values{} query.Set("timeout", strconv.Itoa(options.Timeout)) resp, err := cli.post(ctx, "/plugins/"+name+"/enable", query, nil, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/plugin_inspect.go ================================================ package client // import "github.com/docker/docker/client" import ( "bytes" "context" "encoding/json" "io" "github.com/docker/docker/api/types" ) // PluginInspectWithRaw inspects an existing plugin func (cli *Client) PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) { name, err := trimID("plugin", name) if err != nil { return nil, nil, err } resp, err := cli.get(ctx, "/plugins/"+name+"/json", nil, nil) defer ensureReaderClosed(resp) if err != nil { return nil, nil, err } body, err := io.ReadAll(resp.Body) if err != nil { return nil, nil, err } var p types.Plugin rdr := bytes.NewReader(body) err = json.NewDecoder(rdr).Decode(&p) return &p, body, err } ================================================ FILE: vendor/github.com/docker/docker/client/plugin_install.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "io" "net/http" "net/url" "github.com/distribution/reference" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/registry" "github.com/docker/docker/errdefs" "github.com/pkg/errors" ) // PluginInstall installs a plugin func (cli *Client) PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) (rc io.ReadCloser, err error) { query := url.Values{} if _, err := reference.ParseNormalizedNamed(options.RemoteRef); err != nil { return nil, errors.Wrap(err, "invalid remote reference") } query.Set("remote", options.RemoteRef) privileges, err := cli.checkPluginPermissions(ctx, query, options) if err != nil { return nil, err } // set name for plugin pull, if empty should default to remote reference query.Set("name", name) resp, err := cli.tryPluginPull(ctx, query, privileges, options.RegistryAuth) if err != nil { return nil, err } name = resp.Header.Get("Docker-Plugin-Name") pr, pw := io.Pipe() go func() { // todo: the client should probably be designed more around the actual api _, err := io.Copy(pw, resp.Body) if err != nil { _ = pw.CloseWithError(err) return } defer func() { if err != nil { delResp, _ := cli.delete(ctx, "/plugins/"+name, nil, nil) ensureReaderClosed(delResp) } }() if len(options.Args) > 0 { if err := cli.PluginSet(ctx, name, options.Args); err != nil { _ = pw.CloseWithError(err) return } } if options.Disabled { _ = pw.Close() return } enableErr := cli.PluginEnable(ctx, name, types.PluginEnableOptions{Timeout: 0}) _ = pw.CloseWithError(enableErr) }() return pr, nil } func (cli *Client) tryPluginPrivileges(ctx context.Context, query url.Values, registryAuth string) (*http.Response, error) { return cli.get(ctx, "/plugins/privileges", query, http.Header{ registry.AuthHeader: {registryAuth}, }) } func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, privileges types.PluginPrivileges, registryAuth string) (*http.Response, error) { return cli.post(ctx, "/plugins/pull", query, privileges, http.Header{ registry.AuthHeader: {registryAuth}, }) } func (cli *Client) checkPluginPermissions(ctx context.Context, query url.Values, options types.PluginInstallOptions) (types.PluginPrivileges, error) { resp, err := cli.tryPluginPrivileges(ctx, query, options.RegistryAuth) if errdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil { // todo: do inspect before to check existing name before checking privileges newAuthHeader, privilegeErr := options.PrivilegeFunc(ctx) if privilegeErr != nil { ensureReaderClosed(resp) return nil, privilegeErr } options.RegistryAuth = newAuthHeader resp, err = cli.tryPluginPrivileges(ctx, query, options.RegistryAuth) } if err != nil { ensureReaderClosed(resp) return nil, err } var privileges types.PluginPrivileges if err := json.NewDecoder(resp.Body).Decode(&privileges); err != nil { ensureReaderClosed(resp) return nil, err } ensureReaderClosed(resp) if !options.AcceptAllPermissions && options.AcceptPermissionsFunc != nil && len(privileges) > 0 { accept, err := options.AcceptPermissionsFunc(ctx, privileges) if err != nil { return nil, err } if !accept { return nil, errors.Errorf("permission denied while installing plugin %s", options.RemoteRef) } } return privileges, nil } ================================================ FILE: vendor/github.com/docker/docker/client/plugin_list.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/url" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" ) // PluginList returns the installed plugins func (cli *Client) PluginList(ctx context.Context, filter filters.Args) (types.PluginsListResponse, error) { var plugins types.PluginsListResponse query := url.Values{} if filter.Len() > 0 { //nolint:staticcheck // ignore SA1019 for old code filterJSON, err := filters.ToParamWithVersion(cli.version, filter) if err != nil { return plugins, err } query.Set("filters", filterJSON) } resp, err := cli.get(ctx, "/plugins", query, nil) defer ensureReaderClosed(resp) if err != nil { return plugins, err } err = json.NewDecoder(resp.Body).Decode(&plugins) return plugins, err } ================================================ FILE: vendor/github.com/docker/docker/client/plugin_push.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "io" "net/http" "github.com/docker/docker/api/types/registry" ) // PluginPush pushes a plugin to a registry func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error) { name, err := trimID("plugin", name) if err != nil { return nil, err } resp, err := cli.post(ctx, "/plugins/"+name+"/push", nil, nil, http.Header{ registry.AuthHeader: {registryAuth}, }) if err != nil { return nil, err } return resp.Body, nil } ================================================ FILE: vendor/github.com/docker/docker/client/plugin_remove.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" "github.com/docker/docker/api/types" ) // PluginRemove removes a plugin func (cli *Client) PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error { name, err := trimID("plugin", name) if err != nil { return err } query := url.Values{} if options.Force { query.Set("force", "1") } resp, err := cli.delete(ctx, "/plugins/"+name, query, nil) defer ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/plugin_set.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" ) // PluginSet modifies settings for an existing plugin func (cli *Client) PluginSet(ctx context.Context, name string, args []string) error { name, err := trimID("plugin", name) if err != nil { return err } resp, err := cli.post(ctx, "/plugins/"+name+"/set", nil, args, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/plugin_upgrade.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "io" "net/http" "net/url" "github.com/distribution/reference" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/registry" "github.com/pkg/errors" ) // PluginUpgrade upgrades a plugin func (cli *Client) PluginUpgrade(ctx context.Context, name string, options types.PluginInstallOptions) (io.ReadCloser, error) { name, err := trimID("plugin", name) if err != nil { return nil, err } if err := cli.NewVersionError(ctx, "1.26", "plugin upgrade"); err != nil { return nil, err } query := url.Values{} if _, err := reference.ParseNormalizedNamed(options.RemoteRef); err != nil { return nil, errors.Wrap(err, "invalid remote reference") } query.Set("remote", options.RemoteRef) privileges, err := cli.checkPluginPermissions(ctx, query, options) if err != nil { return nil, err } resp, err := cli.tryPluginUpgrade(ctx, query, privileges, name, options.RegistryAuth) if err != nil { return nil, err } return resp.Body, nil } func (cli *Client) tryPluginUpgrade(ctx context.Context, query url.Values, privileges types.PluginPrivileges, name, registryAuth string) (*http.Response, error) { return cli.post(ctx, "/plugins/"+name+"/upgrade", query, privileges, http.Header{ registry.AuthHeader: {registryAuth}, }) } ================================================ FILE: vendor/github.com/docker/docker/client/request.go ================================================ package client // import "github.com/docker/docker/client" import ( "bytes" "context" "encoding/json" "fmt" "io" "net" "net/http" "net/url" "os" "reflect" "strings" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/versions" "github.com/docker/docker/errdefs" "github.com/pkg/errors" ) // head sends an http request to the docker API using the method HEAD. func (cli *Client) head(ctx context.Context, path string, query url.Values, headers http.Header) (*http.Response, error) { return cli.sendRequest(ctx, http.MethodHead, path, query, nil, headers) } // get sends an http request to the docker API using the method GET with a specific Go context. func (cli *Client) get(ctx context.Context, path string, query url.Values, headers http.Header) (*http.Response, error) { return cli.sendRequest(ctx, http.MethodGet, path, query, nil, headers) } // post sends an http request to the docker API using the method POST with a specific Go context. func (cli *Client) post(ctx context.Context, path string, query url.Values, obj interface{}, headers http.Header) (*http.Response, error) { body, headers, err := encodeBody(obj, headers) if err != nil { return nil, err } return cli.sendRequest(ctx, http.MethodPost, path, query, body, headers) } func (cli *Client) postRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers http.Header) (*http.Response, error) { return cli.sendRequest(ctx, http.MethodPost, path, query, body, headers) } func (cli *Client) put(ctx context.Context, path string, query url.Values, obj interface{}, headers http.Header) (*http.Response, error) { body, headers, err := encodeBody(obj, headers) if err != nil { return nil, err } return cli.putRaw(ctx, path, query, body, headers) } // putRaw sends an http request to the docker API using the method PUT. func (cli *Client) putRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers http.Header) (*http.Response, error) { // PUT requests are expected to always have a body (apparently) // so explicitly pass an empty body to sendRequest to signal that // it should set the Content-Type header if not already present. if body == nil { body = http.NoBody } return cli.sendRequest(ctx, http.MethodPut, path, query, body, headers) } // delete sends an http request to the docker API using the method DELETE. func (cli *Client) delete(ctx context.Context, path string, query url.Values, headers http.Header) (*http.Response, error) { return cli.sendRequest(ctx, http.MethodDelete, path, query, nil, headers) } func encodeBody(obj interface{}, headers http.Header) (io.Reader, http.Header, error) { if obj == nil { return nil, headers, nil } // encoding/json encodes a nil pointer as the JSON document `null`, // irrespective of whether the type implements json.Marshaler or encoding.TextMarshaler. // That is almost certainly not what the caller intended as the request body. if reflect.TypeOf(obj).Kind() == reflect.Ptr && reflect.ValueOf(obj).IsNil() { return nil, headers, nil } body, err := encodeData(obj) if err != nil { return nil, headers, err } if headers == nil { headers = make(map[string][]string) } headers["Content-Type"] = []string{"application/json"} return body, headers, nil } func (cli *Client) buildRequest(ctx context.Context, method, path string, body io.Reader, headers http.Header) (*http.Request, error) { req, err := http.NewRequestWithContext(ctx, method, path, body) if err != nil { return nil, err } req = cli.addHeaders(req, headers) req.URL.Scheme = cli.scheme req.URL.Host = cli.addr if cli.proto == "unix" || cli.proto == "npipe" { // Override host header for non-tcp connections. req.Host = DummyHost } if body != nil && req.Header.Get("Content-Type") == "" { req.Header.Set("Content-Type", "text/plain") } return req, nil } func (cli *Client) sendRequest(ctx context.Context, method, path string, query url.Values, body io.Reader, headers http.Header) (*http.Response, error) { req, err := cli.buildRequest(ctx, method, cli.getAPIPath(ctx, path, query), body, headers) if err != nil { return nil, err } resp, err := cli.doRequest(req) switch { case errors.Is(err, context.Canceled): return nil, errdefs.Cancelled(err) case errors.Is(err, context.DeadlineExceeded): return nil, errdefs.Deadline(err) case err == nil: return resp, cli.checkResponseErr(resp) default: return resp, err } } func (cli *Client) doRequest(req *http.Request) (*http.Response, error) { resp, err := cli.client.Do(req) if err != nil { if cli.scheme != "https" && strings.Contains(err.Error(), "malformed HTTP response") { return nil, errConnectionFailed{fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)} } if cli.scheme == "https" && strings.Contains(err.Error(), "bad certificate") { return nil, errConnectionFailed{errors.Wrap(err, "the server probably has client authentication (--tlsverify) enabled; check your TLS client certification settings")} } // Don't decorate context sentinel errors; users may be comparing to // them directly. if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { return nil, err } var uErr *url.Error if errors.As(err, &uErr) { var nErr *net.OpError if errors.As(uErr.Err, &nErr) { if os.IsPermission(nErr.Err) { return nil, errConnectionFailed{errors.Wrapf(err, "permission denied while trying to connect to the Docker daemon socket at %v", cli.host)} } } } var nErr net.Error if errors.As(err, &nErr) { // FIXME(thaJeztah): any net.Error should be considered a connection error (but we should include the original error)? if nErr.Timeout() { return nil, connectionFailed(cli.host) } if strings.Contains(nErr.Error(), "connection refused") || strings.Contains(nErr.Error(), "dial unix") { return nil, connectionFailed(cli.host) } } // Although there's not a strongly typed error for this in go-winio, // lots of people are using the default configuration for the docker // daemon on Windows where the daemon is listening on a named pipe // `//./pipe/docker_engine, and the client must be running elevated. // Give users a clue rather than the not-overly useful message // such as `error during connect: Get http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.26/info: // open //./pipe/docker_engine: The system cannot find the file specified.`. // Note we can't string compare "The system cannot find the file specified" as // this is localised - for example in French the error would be // `open //./pipe/docker_engine: Le fichier spécifié est introuvable.` if strings.Contains(err.Error(), `open //./pipe/docker_engine`) { // Checks if client is running with elevated privileges if f, elevatedErr := os.Open(`\\.\PHYSICALDRIVE0`); elevatedErr != nil { err = errors.Wrap(err, "in the default daemon configuration on Windows, the docker client must be run with elevated privileges to connect") } else { _ = f.Close() err = errors.Wrap(err, "this error may indicate that the docker daemon is not running") } } return nil, errConnectionFailed{errors.Wrap(err, "error during connect")} } return resp, nil } func (cli *Client) checkResponseErr(serverResp *http.Response) (retErr error) { if serverResp == nil { return nil } if serverResp.StatusCode >= 200 && serverResp.StatusCode < 400 { return nil } defer func() { retErr = errdefs.FromStatusCode(retErr, serverResp.StatusCode) }() var body []byte var err error var reqURL string if serverResp.Request != nil { reqURL = serverResp.Request.URL.String() } statusMsg := serverResp.Status if statusMsg == "" { statusMsg = http.StatusText(serverResp.StatusCode) } if serverResp.Body != nil { bodyMax := 1 * 1024 * 1024 // 1 MiB bodyR := &io.LimitedReader{ R: serverResp.Body, N: int64(bodyMax), } body, err = io.ReadAll(bodyR) if err != nil { return err } if bodyR.N == 0 { if reqURL != "" { return fmt.Errorf("request returned %s with a message (> %d bytes) for API route and version %s, check if the server supports the requested API version", statusMsg, bodyMax, reqURL) } return fmt.Errorf("request returned %s with a message (> %d bytes); check if the server supports the requested API version", statusMsg, bodyMax) } } if len(body) == 0 { if reqURL != "" { return fmt.Errorf("request returned %s for API route and version %s, check if the server supports the requested API version", statusMsg, reqURL) } return fmt.Errorf("request returned %s; check if the server supports the requested API version", statusMsg) } var daemonErr error if serverResp.Header.Get("Content-Type") == "application/json" && (cli.version == "" || versions.GreaterThan(cli.version, "1.23")) { var errorResponse types.ErrorResponse if err := json.Unmarshal(body, &errorResponse); err != nil { return errors.Wrap(err, "Error reading JSON") } if errorResponse.Message == "" { // Error-message is empty, which means that we successfully parsed the // JSON-response (no error produced), but it didn't contain an error // message. This could either be because the response was empty, or // the response was valid JSON, but not with the expected schema // ([types.ErrorResponse]). // // We cannot use "strict" JSON handling (json.NewDecoder with DisallowUnknownFields) // due to the API using an open schema (we must anticipate fields // being added to [types.ErrorResponse] in the future, and not // reject those responses. // // For these cases, we construct an error with the status-code // returned, but we could consider returning (a truncated version // of) the actual response as-is. // // TODO(thaJeztah): consider adding a log.Debug to allow clients to debug the actual response when enabling debug logging. daemonErr = fmt.Errorf(`API returned a %d (%s) but provided no error-message`, serverResp.StatusCode, http.StatusText(serverResp.StatusCode), ) } else { daemonErr = errors.New(strings.TrimSpace(errorResponse.Message)) } } else { // Fall back to returning the response as-is for API versions < 1.24 // that didn't support JSON error responses, and for situations // where a plain text error is returned. This branch may also catch // situations where a proxy is involved, returning a HTML response. daemonErr = errors.New(strings.TrimSpace(string(body))) } return errors.Wrap(daemonErr, "Error response from daemon") } func (cli *Client) addHeaders(req *http.Request, headers http.Header) *http.Request { // Add CLI Config's HTTP Headers BEFORE we set the Docker headers // then the user can't change OUR headers for k, v := range cli.customHTTPHeaders { if versions.LessThan(cli.version, "1.25") && http.CanonicalHeaderKey(k) == "User-Agent" { continue } req.Header.Set(k, v) } for k, v := range headers { req.Header[http.CanonicalHeaderKey(k)] = v } if cli.userAgent != nil { if *cli.userAgent == "" { req.Header.Del("User-Agent") } else { req.Header.Set("User-Agent", *cli.userAgent) } } return req } func encodeData(data interface{}) (*bytes.Buffer, error) { params := bytes.NewBuffer(nil) if data != nil { if err := json.NewEncoder(params).Encode(data); err != nil { return nil, err } } return params, nil } func ensureReaderClosed(response *http.Response) { if response != nil && response.Body != nil { // Drain up to 512 bytes and close the body to let the Transport reuse the connection // see https://github.com/google/go-github/pull/317/files#r57536827 // // TODO(thaJeztah): see if this optimization is still needed, or already implemented in stdlib, // and check if context-cancellation should handle this as well. If still needed, consider // wrapping response.Body, or returning a "closer()" from [Client.sendRequest] and related // methods. _, _ = io.CopyN(io.Discard, response.Body, 512) _ = response.Body.Close() } } ================================================ FILE: vendor/github.com/docker/docker/client/secret_create.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" ) // SecretCreate creates a new secret. func (cli *Client) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (types.SecretCreateResponse, error) { if err := cli.NewVersionError(ctx, "1.25", "secret create"); err != nil { return types.SecretCreateResponse{}, err } resp, err := cli.post(ctx, "/secrets/create", nil, secret, nil) defer ensureReaderClosed(resp) if err != nil { return types.SecretCreateResponse{}, err } var response types.SecretCreateResponse err = json.NewDecoder(resp.Body).Decode(&response) return response, err } ================================================ FILE: vendor/github.com/docker/docker/client/secret_inspect.go ================================================ package client // import "github.com/docker/docker/client" import ( "bytes" "context" "encoding/json" "io" "github.com/docker/docker/api/types/swarm" ) // SecretInspectWithRaw returns the secret information with raw data func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.Secret, []byte, error) { id, err := trimID("secret", id) if err != nil { return swarm.Secret{}, nil, err } if err := cli.NewVersionError(ctx, "1.25", "secret inspect"); err != nil { return swarm.Secret{}, nil, err } resp, err := cli.get(ctx, "/secrets/"+id, nil, nil) defer ensureReaderClosed(resp) if err != nil { return swarm.Secret{}, nil, err } body, err := io.ReadAll(resp.Body) if err != nil { return swarm.Secret{}, nil, err } var secret swarm.Secret rdr := bytes.NewReader(body) err = json.NewDecoder(rdr).Decode(&secret) return secret, body, err } ================================================ FILE: vendor/github.com/docker/docker/client/secret_list.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/url" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" ) // SecretList returns the list of secrets. func (cli *Client) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) { if err := cli.NewVersionError(ctx, "1.25", "secret list"); err != nil { return nil, err } query := url.Values{} if options.Filters.Len() > 0 { filterJSON, err := filters.ToJSON(options.Filters) if err != nil { return nil, err } query.Set("filters", filterJSON) } resp, err := cli.get(ctx, "/secrets", query, nil) defer ensureReaderClosed(resp) if err != nil { return nil, err } var secrets []swarm.Secret err = json.NewDecoder(resp.Body).Decode(&secrets) return secrets, err } ================================================ FILE: vendor/github.com/docker/docker/client/secret_remove.go ================================================ package client // import "github.com/docker/docker/client" import "context" // SecretRemove removes a secret. func (cli *Client) SecretRemove(ctx context.Context, id string) error { id, err := trimID("secret", id) if err != nil { return err } if err := cli.NewVersionError(ctx, "1.25", "secret remove"); err != nil { return err } resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil) defer ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/secret_update.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" "github.com/docker/docker/api/types/swarm" ) // SecretUpdate attempts to update a secret. func (cli *Client) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error { id, err := trimID("secret", id) if err != nil { return err } if err := cli.NewVersionError(ctx, "1.25", "secret update"); err != nil { return err } query := url.Values{} query.Set("version", version.String()) resp, err := cli.post(ctx, "/secrets/"+id+"/update", query, secret, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/service_create.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "fmt" "net/http" "strings" "github.com/distribution/reference" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/versions" "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) // ServiceCreate creates a new service. func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (swarm.ServiceCreateResponse, error) { var response swarm.ServiceCreateResponse // Make sure we negotiated (if the client is configured to do so), // as code below contains API-version specific handling of options. // // Normally, version-negotiation (if enabled) would not happen until // the API request is made. if err := cli.checkVersion(ctx); err != nil { return response, err } // Make sure containerSpec is not nil when no runtime is set or the runtime is set to container if service.TaskTemplate.ContainerSpec == nil && (service.TaskTemplate.Runtime == "" || service.TaskTemplate.Runtime == swarm.RuntimeContainer) { service.TaskTemplate.ContainerSpec = &swarm.ContainerSpec{} } if err := validateServiceSpec(service); err != nil { return response, err } // ensure that the image is tagged var resolveWarning string switch { case service.TaskTemplate.ContainerSpec != nil: if taggedImg := imageWithTagString(service.TaskTemplate.ContainerSpec.Image); taggedImg != "" { service.TaskTemplate.ContainerSpec.Image = taggedImg } if options.QueryRegistry { resolveWarning = resolveContainerSpecImage(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth) } case service.TaskTemplate.PluginSpec != nil: if taggedImg := imageWithTagString(service.TaskTemplate.PluginSpec.Remote); taggedImg != "" { service.TaskTemplate.PluginSpec.Remote = taggedImg } if options.QueryRegistry { resolveWarning = resolvePluginSpecRemote(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth) } } headers := http.Header{} if versions.LessThan(cli.version, "1.30") { // the custom "version" header was used by engine API before 20.10 // (API 1.30) to switch between client- and server-side lookup of // image digests. headers["version"] = []string{cli.version} } if options.EncodedRegistryAuth != "" { headers[registry.AuthHeader] = []string{options.EncodedRegistryAuth} } resp, err := cli.post(ctx, "/services/create", nil, service, headers) defer ensureReaderClosed(resp) if err != nil { return response, err } err = json.NewDecoder(resp.Body).Decode(&response) if resolveWarning != "" { response.Warnings = append(response.Warnings, resolveWarning) } return response, err } func resolveContainerSpecImage(ctx context.Context, cli DistributionAPIClient, taskSpec *swarm.TaskSpec, encodedAuth string) string { var warning string if img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.ContainerSpec.Image, encodedAuth); err != nil { warning = digestWarning(taskSpec.ContainerSpec.Image) } else { taskSpec.ContainerSpec.Image = img if len(imgPlatforms) > 0 { if taskSpec.Placement == nil { taskSpec.Placement = &swarm.Placement{} } taskSpec.Placement.Platforms = imgPlatforms } } return warning } func resolvePluginSpecRemote(ctx context.Context, cli DistributionAPIClient, taskSpec *swarm.TaskSpec, encodedAuth string) string { var warning string if img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.PluginSpec.Remote, encodedAuth); err != nil { warning = digestWarning(taskSpec.PluginSpec.Remote) } else { taskSpec.PluginSpec.Remote = img if len(imgPlatforms) > 0 { if taskSpec.Placement == nil { taskSpec.Placement = &swarm.Placement{} } taskSpec.Placement.Platforms = imgPlatforms } } return warning } func imageDigestAndPlatforms(ctx context.Context, cli DistributionAPIClient, image, encodedAuth string) (string, []swarm.Platform, error) { distributionInspect, err := cli.DistributionInspect(ctx, image, encodedAuth) var platforms []swarm.Platform if err != nil { return "", nil, err } imageWithDigest := imageWithDigestString(image, distributionInspect.Descriptor.Digest) if len(distributionInspect.Platforms) > 0 { platforms = make([]swarm.Platform, 0, len(distributionInspect.Platforms)) for _, p := range distributionInspect.Platforms { // clear architecture field for arm. This is a temporary patch to address // https://github.com/docker/swarmkit/issues/2294. The issue is that while // image manifests report "arm" as the architecture, the node reports // something like "armv7l" (includes the variant), which causes arm images // to stop working with swarm mode. This patch removes the architecture // constraint for arm images to ensure tasks get scheduled. arch := p.Architecture if strings.ToLower(arch) == "arm" { arch = "" } platforms = append(platforms, swarm.Platform{ Architecture: arch, OS: p.OS, }) } } return imageWithDigest, platforms, err } // imageWithDigestString takes an image string and a digest, and updates // the image string if it didn't originally contain a digest. It returns // image unmodified in other situations. func imageWithDigestString(image string, dgst digest.Digest) string { namedRef, err := reference.ParseNormalizedNamed(image) if err == nil { if _, isCanonical := namedRef.(reference.Canonical); !isCanonical { // ensure that image gets a default tag if none is provided img, err := reference.WithDigest(namedRef, dgst) if err == nil { return reference.FamiliarString(img) } } } return image } // imageWithTagString takes an image string, and returns a tagged image // string, adding a 'latest' tag if one was not provided. It returns an // empty string if a canonical reference was provided func imageWithTagString(image string) string { namedRef, err := reference.ParseNormalizedNamed(image) if err == nil { return reference.FamiliarString(reference.TagNameOnly(namedRef)) } return "" } // digestWarning constructs a formatted warning string using the // image name that could not be pinned by digest. The formatting // is hardcoded, but could me made smarter in the future func digestWarning(image string) string { return fmt.Sprintf("image %s could not be accessed on a registry to record\nits digest. Each node will access %s independently,\npossibly leading to different nodes running different\nversions of the image.\n", image, image) } func validateServiceSpec(s swarm.ServiceSpec) error { if s.TaskTemplate.ContainerSpec != nil && s.TaskTemplate.PluginSpec != nil { return errors.New("must not specify both a container spec and a plugin spec in the task template") } if s.TaskTemplate.PluginSpec != nil && s.TaskTemplate.Runtime != swarm.RuntimePlugin { return errors.New("mismatched runtime with plugin spec") } if s.TaskTemplate.ContainerSpec != nil && (s.TaskTemplate.Runtime != "" && s.TaskTemplate.Runtime != swarm.RuntimeContainer) { return errors.New("mismatched runtime with container spec") } return nil } ================================================ FILE: vendor/github.com/docker/docker/client/service_inspect.go ================================================ package client // import "github.com/docker/docker/client" import ( "bytes" "context" "encoding/json" "fmt" "io" "net/url" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" ) // ServiceInspectWithRaw returns the service information and the raw data. func (cli *Client) ServiceInspectWithRaw(ctx context.Context, serviceID string, opts types.ServiceInspectOptions) (swarm.Service, []byte, error) { serviceID, err := trimID("service", serviceID) if err != nil { return swarm.Service{}, nil, err } query := url.Values{} query.Set("insertDefaults", fmt.Sprintf("%v", opts.InsertDefaults)) resp, err := cli.get(ctx, "/services/"+serviceID, query, nil) defer ensureReaderClosed(resp) if err != nil { return swarm.Service{}, nil, err } body, err := io.ReadAll(resp.Body) if err != nil { return swarm.Service{}, nil, err } var response swarm.Service rdr := bytes.NewReader(body) err = json.NewDecoder(rdr).Decode(&response) return response, body, err } ================================================ FILE: vendor/github.com/docker/docker/client/service_list.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/url" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" ) // ServiceList returns the list of services. func (cli *Client) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) { query := url.Values{} if options.Filters.Len() > 0 { filterJSON, err := filters.ToJSON(options.Filters) if err != nil { return nil, err } query.Set("filters", filterJSON) } if options.Status { query.Set("status", "true") } resp, err := cli.get(ctx, "/services", query, nil) defer ensureReaderClosed(resp) if err != nil { return nil, err } var services []swarm.Service err = json.NewDecoder(resp.Body).Decode(&services) return services, err } ================================================ FILE: vendor/github.com/docker/docker/client/service_logs.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "io" "net/url" "time" "github.com/docker/docker/api/types/container" timetypes "github.com/docker/docker/api/types/time" "github.com/pkg/errors" ) // ServiceLogs returns the logs generated by a service in an io.ReadCloser. // It's up to the caller to close the stream. func (cli *Client) ServiceLogs(ctx context.Context, serviceID string, options container.LogsOptions) (io.ReadCloser, error) { serviceID, err := trimID("service", serviceID) if err != nil { return nil, err } query := url.Values{} if options.ShowStdout { query.Set("stdout", "1") } if options.ShowStderr { query.Set("stderr", "1") } if options.Since != "" { ts, err := timetypes.GetTimestamp(options.Since, time.Now()) if err != nil { return nil, errors.Wrap(err, `invalid value for "since"`) } query.Set("since", ts) } if options.Timestamps { query.Set("timestamps", "1") } if options.Details { query.Set("details", "1") } if options.Follow { query.Set("follow", "1") } query.Set("tail", options.Tail) resp, err := cli.get(ctx, "/services/"+serviceID+"/logs", query, nil) if err != nil { return nil, err } return resp.Body, nil } ================================================ FILE: vendor/github.com/docker/docker/client/service_remove.go ================================================ package client // import "github.com/docker/docker/client" import "context" // ServiceRemove kills and removes a service. func (cli *Client) ServiceRemove(ctx context.Context, serviceID string) error { serviceID, err := trimID("service", serviceID) if err != nil { return err } resp, err := cli.delete(ctx, "/services/"+serviceID, nil, nil) defer ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/service_update.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/http" "net/url" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/versions" ) // ServiceUpdate updates a Service. The version number is required to avoid conflicting writes. // It should be the value as set *before* the update. You can find this value in the Meta field // of swarm.Service, which can be found using ServiceInspectWithRaw. func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) { serviceID, err := trimID("service", serviceID) if err != nil { return swarm.ServiceUpdateResponse{}, err } // Make sure we negotiated (if the client is configured to do so), // as code below contains API-version specific handling of options. // // Normally, version-negotiation (if enabled) would not happen until // the API request is made. if err := cli.checkVersion(ctx); err != nil { return swarm.ServiceUpdateResponse{}, err } query := url.Values{} if options.RegistryAuthFrom != "" { query.Set("registryAuthFrom", options.RegistryAuthFrom) } if options.Rollback != "" { query.Set("rollback", options.Rollback) } query.Set("version", version.String()) if err := validateServiceSpec(service); err != nil { return swarm.ServiceUpdateResponse{}, err } // ensure that the image is tagged var resolveWarning string switch { case service.TaskTemplate.ContainerSpec != nil: if taggedImg := imageWithTagString(service.TaskTemplate.ContainerSpec.Image); taggedImg != "" { service.TaskTemplate.ContainerSpec.Image = taggedImg } if options.QueryRegistry { resolveWarning = resolveContainerSpecImage(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth) } case service.TaskTemplate.PluginSpec != nil: if taggedImg := imageWithTagString(service.TaskTemplate.PluginSpec.Remote); taggedImg != "" { service.TaskTemplate.PluginSpec.Remote = taggedImg } if options.QueryRegistry { resolveWarning = resolvePluginSpecRemote(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth) } } headers := http.Header{} if versions.LessThan(cli.version, "1.30") { // the custom "version" header was used by engine API before 20.10 // (API 1.30) to switch between client- and server-side lookup of // image digests. headers["version"] = []string{cli.version} } if options.EncodedRegistryAuth != "" { headers[registry.AuthHeader] = []string{options.EncodedRegistryAuth} } resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers) defer ensureReaderClosed(resp) if err != nil { return swarm.ServiceUpdateResponse{}, err } var response swarm.ServiceUpdateResponse err = json.NewDecoder(resp.Body).Decode(&response) if resolveWarning != "" { response.Warnings = append(response.Warnings, resolveWarning) } return response, err } ================================================ FILE: vendor/github.com/docker/docker/client/swarm_get_unlock_key.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "github.com/docker/docker/api/types" ) // SwarmGetUnlockKey retrieves the swarm's unlock key. func (cli *Client) SwarmGetUnlockKey(ctx context.Context) (types.SwarmUnlockKeyResponse, error) { resp, err := cli.get(ctx, "/swarm/unlockkey", nil, nil) defer ensureReaderClosed(resp) if err != nil { return types.SwarmUnlockKeyResponse{}, err } var response types.SwarmUnlockKeyResponse err = json.NewDecoder(resp.Body).Decode(&response) return response, err } ================================================ FILE: vendor/github.com/docker/docker/client/swarm_init.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "github.com/docker/docker/api/types/swarm" ) // SwarmInit initializes the swarm. func (cli *Client) SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error) { resp, err := cli.post(ctx, "/swarm/init", nil, req, nil) defer ensureReaderClosed(resp) if err != nil { return "", err } var response string err = json.NewDecoder(resp.Body).Decode(&response) return response, err } ================================================ FILE: vendor/github.com/docker/docker/client/swarm_inspect.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "github.com/docker/docker/api/types/swarm" ) // SwarmInspect inspects the swarm. func (cli *Client) SwarmInspect(ctx context.Context) (swarm.Swarm, error) { resp, err := cli.get(ctx, "/swarm", nil, nil) defer ensureReaderClosed(resp) if err != nil { return swarm.Swarm{}, err } var response swarm.Swarm err = json.NewDecoder(resp.Body).Decode(&response) return response, err } ================================================ FILE: vendor/github.com/docker/docker/client/swarm_join.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "github.com/docker/docker/api/types/swarm" ) // SwarmJoin joins the swarm. func (cli *Client) SwarmJoin(ctx context.Context, req swarm.JoinRequest) error { resp, err := cli.post(ctx, "/swarm/join", nil, req, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/swarm_leave.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" ) // SwarmLeave leaves the swarm. func (cli *Client) SwarmLeave(ctx context.Context, force bool) error { query := url.Values{} if force { query.Set("force", "1") } resp, err := cli.post(ctx, "/swarm/leave", query, nil, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/swarm_unlock.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "github.com/docker/docker/api/types/swarm" ) // SwarmUnlock unlocks locked swarm. func (cli *Client) SwarmUnlock(ctx context.Context, req swarm.UnlockRequest) error { resp, err := cli.post(ctx, "/swarm/unlock", nil, req, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/swarm_update.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" "strconv" "github.com/docker/docker/api/types/swarm" ) // SwarmUpdate updates the swarm. func (cli *Client) SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec, flags swarm.UpdateFlags) error { query := url.Values{} query.Set("version", version.String()) query.Set("rotateWorkerToken", strconv.FormatBool(flags.RotateWorkerToken)) query.Set("rotateManagerToken", strconv.FormatBool(flags.RotateManagerToken)) query.Set("rotateManagerUnlockKey", strconv.FormatBool(flags.RotateManagerUnlockKey)) resp, err := cli.post(ctx, "/swarm/update", query, swarm, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/task_inspect.go ================================================ package client // import "github.com/docker/docker/client" import ( "bytes" "context" "encoding/json" "io" "github.com/docker/docker/api/types/swarm" ) // TaskInspectWithRaw returns the task information and its raw representation. func (cli *Client) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) { taskID, err := trimID("task", taskID) if err != nil { return swarm.Task{}, nil, err } resp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil) defer ensureReaderClosed(resp) if err != nil { return swarm.Task{}, nil, err } body, err := io.ReadAll(resp.Body) if err != nil { return swarm.Task{}, nil, err } var response swarm.Task rdr := bytes.NewReader(body) err = json.NewDecoder(rdr).Decode(&response) return response, body, err } ================================================ FILE: vendor/github.com/docker/docker/client/task_list.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/url" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" ) // TaskList returns the list of tasks. func (cli *Client) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) { query := url.Values{} if options.Filters.Len() > 0 { filterJSON, err := filters.ToJSON(options.Filters) if err != nil { return nil, err } query.Set("filters", filterJSON) } resp, err := cli.get(ctx, "/tasks", query, nil) defer ensureReaderClosed(resp) if err != nil { return nil, err } var tasks []swarm.Task err = json.NewDecoder(resp.Body).Decode(&tasks) return tasks, err } ================================================ FILE: vendor/github.com/docker/docker/client/task_logs.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "io" "net/url" "time" "github.com/docker/docker/api/types/container" timetypes "github.com/docker/docker/api/types/time" ) // TaskLogs returns the logs generated by a task in an io.ReadCloser. // It's up to the caller to close the stream. func (cli *Client) TaskLogs(ctx context.Context, taskID string, options container.LogsOptions) (io.ReadCloser, error) { query := url.Values{} if options.ShowStdout { query.Set("stdout", "1") } if options.ShowStderr { query.Set("stderr", "1") } if options.Since != "" { ts, err := timetypes.GetTimestamp(options.Since, time.Now()) if err != nil { return nil, err } query.Set("since", ts) } if options.Timestamps { query.Set("timestamps", "1") } if options.Details { query.Set("details", "1") } if options.Follow { query.Set("follow", "1") } query.Set("tail", options.Tail) resp, err := cli.get(ctx, "/tasks/"+taskID+"/logs", query, nil) if err != nil { return nil, err } return resp.Body, nil } ================================================ FILE: vendor/github.com/docker/docker/client/utils.go ================================================ package client // import "github.com/docker/docker/client" import ( "encoding/json" "fmt" "net/url" "strings" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/errdefs" "github.com/docker/docker/internal/lazyregexp" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) var headerRegexp = lazyregexp.New(`\ADocker/.+\s\((.+)\)\z`) type emptyIDError string func (e emptyIDError) InvalidParameter() {} func (e emptyIDError) Error() string { return "invalid " + string(e) + " name or ID: value is empty" } // trimID trims the given object-ID / name, returning an error if it's empty. func trimID(objType, id string) (string, error) { id = strings.TrimSpace(id) if len(id) == 0 { return "", emptyIDError(objType) } return id, nil } // getDockerOS returns the operating system based on the server header from the daemon. func getDockerOS(serverHeader string) string { var osType string matches := headerRegexp.FindStringSubmatch(serverHeader) if len(matches) > 0 { osType = matches[1] } return osType } // getFiltersQuery returns a url query with "filters" query term, based on the // filters provided. func getFiltersQuery(f filters.Args) (url.Values, error) { query := url.Values{} if f.Len() > 0 { filterJSON, err := filters.ToJSON(f) if err != nil { return query, err } query.Set("filters", filterJSON) } return query, nil } // encodePlatforms marshals the given platform(s) to JSON format, to // be used for query-parameters for filtering / selecting platforms. func encodePlatforms(platform ...ocispec.Platform) ([]string, error) { if len(platform) == 0 { return []string{}, nil } if len(platform) == 1 { p, err := encodePlatform(&platform[0]) if err != nil { return nil, err } return []string{p}, nil } seen := make(map[string]struct{}, len(platform)) out := make([]string, 0, len(platform)) for i := range platform { p, err := encodePlatform(&platform[i]) if err != nil { return nil, err } if _, ok := seen[p]; !ok { out = append(out, p) seen[p] = struct{}{} } } return out, nil } // encodePlatform marshals the given platform to JSON format, to // be used for query-parameters for filtering / selecting platforms. It // is used as a helper for encodePlatforms, func encodePlatform(platform *ocispec.Platform) (string, error) { p, err := json.Marshal(platform) if err != nil { return "", errdefs.InvalidParameter(fmt.Errorf("invalid platform: %v", err)) } return string(p), nil } ================================================ FILE: vendor/github.com/docker/docker/client/version.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "github.com/docker/docker/api/types" ) // ServerVersion returns information of the docker client and server host. func (cli *Client) ServerVersion(ctx context.Context) (types.Version, error) { resp, err := cli.get(ctx, "/version", nil, nil) defer ensureReaderClosed(resp) if err != nil { return types.Version{}, err } var server types.Version err = json.NewDecoder(resp.Body).Decode(&server) return server, err } ================================================ FILE: vendor/github.com/docker/docker/client/volume_create.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "github.com/docker/docker/api/types/volume" ) // VolumeCreate creates a volume in the docker host. func (cli *Client) VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error) { resp, err := cli.post(ctx, "/volumes/create", nil, options, nil) defer ensureReaderClosed(resp) if err != nil { return volume.Volume{}, err } var vol volume.Volume err = json.NewDecoder(resp.Body).Decode(&vol) return vol, err } ================================================ FILE: vendor/github.com/docker/docker/client/volume_inspect.go ================================================ package client // import "github.com/docker/docker/client" import ( "bytes" "context" "encoding/json" "io" "github.com/docker/docker/api/types/volume" ) // VolumeInspect returns the information about a specific volume in the docker host. func (cli *Client) VolumeInspect(ctx context.Context, volumeID string) (volume.Volume, error) { vol, _, err := cli.VolumeInspectWithRaw(ctx, volumeID) return vol, err } // VolumeInspectWithRaw returns the information about a specific volume in the docker host and its raw representation func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (volume.Volume, []byte, error) { volumeID, err := trimID("volume", volumeID) if err != nil { return volume.Volume{}, nil, err } resp, err := cli.get(ctx, "/volumes/"+volumeID, nil, nil) defer ensureReaderClosed(resp) if err != nil { return volume.Volume{}, nil, err } body, err := io.ReadAll(resp.Body) if err != nil { return volume.Volume{}, nil, err } var vol volume.Volume rdr := bytes.NewReader(body) err = json.NewDecoder(rdr).Decode(&vol) return vol, body, err } ================================================ FILE: vendor/github.com/docker/docker/client/volume_list.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "net/url" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/volume" ) // VolumeList returns the volumes configured in the docker host. func (cli *Client) VolumeList(ctx context.Context, options volume.ListOptions) (volume.ListResponse, error) { query := url.Values{} if options.Filters.Len() > 0 { //nolint:staticcheck // ignore SA1019 for old code filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters) if err != nil { return volume.ListResponse{}, err } query.Set("filters", filterJSON) } resp, err := cli.get(ctx, "/volumes", query, nil) defer ensureReaderClosed(resp) if err != nil { return volume.ListResponse{}, err } var volumes volume.ListResponse err = json.NewDecoder(resp.Body).Decode(&volumes) return volumes, err } ================================================ FILE: vendor/github.com/docker/docker/client/volume_prune.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" "fmt" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/volume" ) // VolumesPrune requests the daemon to delete unused data func (cli *Client) VolumesPrune(ctx context.Context, pruneFilters filters.Args) (volume.PruneReport, error) { if err := cli.NewVersionError(ctx, "1.25", "volume prune"); err != nil { return volume.PruneReport{}, err } query, err := getFiltersQuery(pruneFilters) if err != nil { return volume.PruneReport{}, err } resp, err := cli.post(ctx, "/volumes/prune", query, nil, nil) defer ensureReaderClosed(resp) if err != nil { return volume.PruneReport{}, err } var report volume.PruneReport if err := json.NewDecoder(resp.Body).Decode(&report); err != nil { return volume.PruneReport{}, fmt.Errorf("Error retrieving volume prune report: %v", err) } return report, nil } ================================================ FILE: vendor/github.com/docker/docker/client/volume_remove.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" "github.com/docker/docker/api/types/versions" ) // VolumeRemove removes a volume from the docker host. func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool) error { volumeID, err := trimID("volume", volumeID) if err != nil { return err } query := url.Values{} if force { // Make sure we negotiated (if the client is configured to do so), // as code below contains API-version specific handling of options. // // Normally, version-negotiation (if enabled) would not happen until // the API request is made. if err := cli.checkVersion(ctx); err != nil { return err } if versions.GreaterThanOrEqualTo(cli.version, "1.25") { query.Set("force", "1") } } resp, err := cli.delete(ctx, "/volumes/"+volumeID, query, nil) defer ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/client/volume_update.go ================================================ package client // import "github.com/docker/docker/client" import ( "context" "net/url" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/volume" ) // VolumeUpdate updates a volume. This only works for Cluster Volumes, and // only some fields can be updated. func (cli *Client) VolumeUpdate(ctx context.Context, volumeID string, version swarm.Version, options volume.UpdateOptions) error { volumeID, err := trimID("volume", volumeID) if err != nil { return err } if err := cli.NewVersionError(ctx, "1.42", "volume update"); err != nil { return err } query := url.Values{} query.Set("version", version.String()) resp, err := cli.put(ctx, "/volumes/"+volumeID, query, options, nil) ensureReaderClosed(resp) return err } ================================================ FILE: vendor/github.com/docker/docker/errdefs/defs.go ================================================ package errdefs // ErrNotFound signals that the requested object doesn't exist type ErrNotFound interface { NotFound() } // ErrInvalidParameter signals that the user input is invalid type ErrInvalidParameter interface { InvalidParameter() } // ErrConflict signals that some internal state conflicts with the requested action and can't be performed. // A change in state should be able to clear this error. type ErrConflict interface { Conflict() } // ErrUnauthorized is used to signify that the user is not authorized to perform a specific action type ErrUnauthorized interface { Unauthorized() } // ErrUnavailable signals that the requested action/subsystem is not available. type ErrUnavailable interface { Unavailable() } // ErrForbidden signals that the requested action cannot be performed under any circumstances. // When a ErrForbidden is returned, the caller should never retry the action. type ErrForbidden interface { Forbidden() } // ErrSystem signals that some internal error occurred. // An example of this would be a failed mount request. type ErrSystem interface { System() } // ErrNotModified signals that an action can't be performed because it's already in the desired state type ErrNotModified interface { NotModified() } // ErrNotImplemented signals that the requested action/feature is not implemented on the system as configured. type ErrNotImplemented interface { NotImplemented() } // ErrUnknown signals that the kind of error that occurred is not known. type ErrUnknown interface { Unknown() } // ErrCancelled signals that the action was cancelled. type ErrCancelled interface { Cancelled() } // ErrDeadline signals that the deadline was reached before the action completed. type ErrDeadline interface { DeadlineExceeded() } // ErrDataLoss indicates that data was lost or there is data corruption. type ErrDataLoss interface { DataLoss() } ================================================ FILE: vendor/github.com/docker/docker/errdefs/doc.go ================================================ // Package errdefs defines a set of error interfaces that packages should use for communicating classes of errors. // Errors that cross the package boundary should implement one (and only one) of these interfaces. // // Packages should not reference these interfaces directly, only implement them. // To check if a particular error implements one of these interfaces, there are helper // functions provided (e.g. `Is`) which can be used rather than asserting the interfaces directly. // If you must assert on these interfaces, be sure to check the causal chain (`err.Cause()`). package errdefs // import "github.com/docker/docker/errdefs" ================================================ FILE: vendor/github.com/docker/docker/errdefs/helpers.go ================================================ package errdefs import "context" type errNotFound struct{ error } func (errNotFound) NotFound() {} func (e errNotFound) Cause() error { return e.error } func (e errNotFound) Unwrap() error { return e.error } // NotFound creates an [ErrNotFound] error from the given error. // It returns the error as-is if it is either nil (no error) or already implements // [ErrNotFound], func NotFound(err error) error { if err == nil || IsNotFound(err) { return err } return errNotFound{err} } type errInvalidParameter struct{ error } func (errInvalidParameter) InvalidParameter() {} func (e errInvalidParameter) Cause() error { return e.error } func (e errInvalidParameter) Unwrap() error { return e.error } // InvalidParameter creates an [ErrInvalidParameter] error from the given error. // It returns the error as-is if it is either nil (no error) or already implements // [ErrInvalidParameter], func InvalidParameter(err error) error { if err == nil || IsInvalidParameter(err) { return err } return errInvalidParameter{err} } type errConflict struct{ error } func (errConflict) Conflict() {} func (e errConflict) Cause() error { return e.error } func (e errConflict) Unwrap() error { return e.error } // Conflict creates an [ErrConflict] error from the given error. // It returns the error as-is if it is either nil (no error) or already implements // [ErrConflict], func Conflict(err error) error { if err == nil || IsConflict(err) { return err } return errConflict{err} } type errUnauthorized struct{ error } func (errUnauthorized) Unauthorized() {} func (e errUnauthorized) Cause() error { return e.error } func (e errUnauthorized) Unwrap() error { return e.error } // Unauthorized creates an [ErrUnauthorized] error from the given error. // It returns the error as-is if it is either nil (no error) or already implements // [ErrUnauthorized], func Unauthorized(err error) error { if err == nil || IsUnauthorized(err) { return err } return errUnauthorized{err} } type errUnavailable struct{ error } func (errUnavailable) Unavailable() {} func (e errUnavailable) Cause() error { return e.error } func (e errUnavailable) Unwrap() error { return e.error } // Unavailable creates an [ErrUnavailable] error from the given error. // It returns the error as-is if it is either nil (no error) or already implements // [ErrUnavailable], func Unavailable(err error) error { if err == nil || IsUnavailable(err) { return err } return errUnavailable{err} } type errForbidden struct{ error } func (errForbidden) Forbidden() {} func (e errForbidden) Cause() error { return e.error } func (e errForbidden) Unwrap() error { return e.error } // Forbidden creates an [ErrForbidden] error from the given error. // It returns the error as-is if it is either nil (no error) or already implements // [ErrForbidden], func Forbidden(err error) error { if err == nil || IsForbidden(err) { return err } return errForbidden{err} } type errSystem struct{ error } func (errSystem) System() {} func (e errSystem) Cause() error { return e.error } func (e errSystem) Unwrap() error { return e.error } // System creates an [ErrSystem] error from the given error. // It returns the error as-is if it is either nil (no error) or already implements // [ErrSystem], func System(err error) error { if err == nil || IsSystem(err) { return err } return errSystem{err} } type errNotModified struct{ error } func (errNotModified) NotModified() {} func (e errNotModified) Cause() error { return e.error } func (e errNotModified) Unwrap() error { return e.error } // NotModified creates an [ErrNotModified] error from the given error. // It returns the error as-is if it is either nil (no error) or already implements // [NotModified], func NotModified(err error) error { if err == nil || IsNotModified(err) { return err } return errNotModified{err} } type errNotImplemented struct{ error } func (errNotImplemented) NotImplemented() {} func (e errNotImplemented) Cause() error { return e.error } func (e errNotImplemented) Unwrap() error { return e.error } // NotImplemented creates an [ErrNotImplemented] error from the given error. // It returns the error as-is if it is either nil (no error) or already implements // [ErrNotImplemented], func NotImplemented(err error) error { if err == nil || IsNotImplemented(err) { return err } return errNotImplemented{err} } type errUnknown struct{ error } func (errUnknown) Unknown() {} func (e errUnknown) Cause() error { return e.error } func (e errUnknown) Unwrap() error { return e.error } // Unknown creates an [ErrUnknown] error from the given error. // It returns the error as-is if it is either nil (no error) or already implements // [ErrUnknown], func Unknown(err error) error { if err == nil || IsUnknown(err) { return err } return errUnknown{err} } type errCancelled struct{ error } func (errCancelled) Cancelled() {} func (e errCancelled) Cause() error { return e.error } func (e errCancelled) Unwrap() error { return e.error } // Cancelled creates an [ErrCancelled] error from the given error. // It returns the error as-is if it is either nil (no error) or already implements // [ErrCancelled], func Cancelled(err error) error { if err == nil || IsCancelled(err) { return err } return errCancelled{err} } type errDeadline struct{ error } func (errDeadline) DeadlineExceeded() {} func (e errDeadline) Cause() error { return e.error } func (e errDeadline) Unwrap() error { return e.error } // Deadline creates an [ErrDeadline] error from the given error. // It returns the error as-is if it is either nil (no error) or already implements // [ErrDeadline], func Deadline(err error) error { if err == nil || IsDeadline(err) { return err } return errDeadline{err} } type errDataLoss struct{ error } func (errDataLoss) DataLoss() {} func (e errDataLoss) Cause() error { return e.error } func (e errDataLoss) Unwrap() error { return e.error } // DataLoss creates an [ErrDataLoss] error from the given error. // It returns the error as-is if it is either nil (no error) or already implements // [ErrDataLoss], func DataLoss(err error) error { if err == nil || IsDataLoss(err) { return err } return errDataLoss{err} } // FromContext returns the error class from the passed in context func FromContext(ctx context.Context) error { e := ctx.Err() if e == nil { return nil } if e == context.Canceled { return Cancelled(e) } if e == context.DeadlineExceeded { return Deadline(e) } return Unknown(e) } ================================================ FILE: vendor/github.com/docker/docker/errdefs/http_helpers.go ================================================ package errdefs import ( "net/http" ) // FromStatusCode creates an errdef error, based on the provided HTTP status-code func FromStatusCode(err error, statusCode int) error { if err == nil { return nil } switch statusCode { case http.StatusNotFound: return NotFound(err) case http.StatusBadRequest: return InvalidParameter(err) case http.StatusConflict: return Conflict(err) case http.StatusUnauthorized: return Unauthorized(err) case http.StatusServiceUnavailable: return Unavailable(err) case http.StatusForbidden: return Forbidden(err) case http.StatusNotModified: return NotModified(err) case http.StatusNotImplemented: return NotImplemented(err) case http.StatusInternalServerError: if IsCancelled(err) || IsSystem(err) || IsUnknown(err) || IsDataLoss(err) || IsDeadline(err) { return err } return System(err) default: switch { case statusCode >= 200 && statusCode < 400: // it's a client error return err case statusCode >= 400 && statusCode < 500: return InvalidParameter(err) case statusCode >= 500 && statusCode < 600: return System(err) default: return Unknown(err) } } } ================================================ FILE: vendor/github.com/docker/docker/errdefs/is.go ================================================ package errdefs import ( "context" "errors" ) type causer interface { Cause() error } type wrapErr interface { Unwrap() error } func getImplementer(err error) error { switch e := err.(type) { case ErrNotFound, ErrInvalidParameter, ErrConflict, ErrUnauthorized, ErrUnavailable, ErrForbidden, ErrSystem, ErrNotModified, ErrNotImplemented, ErrCancelled, ErrDeadline, ErrDataLoss, ErrUnknown: return err case causer: return getImplementer(e.Cause()) case wrapErr: return getImplementer(e.Unwrap()) default: return err } } // IsNotFound returns if the passed in error is an [ErrNotFound], func IsNotFound(err error) bool { _, ok := getImplementer(err).(ErrNotFound) return ok } // IsInvalidParameter returns if the passed in error is an [ErrInvalidParameter]. func IsInvalidParameter(err error) bool { _, ok := getImplementer(err).(ErrInvalidParameter) return ok } // IsConflict returns if the passed in error is an [ErrConflict]. func IsConflict(err error) bool { _, ok := getImplementer(err).(ErrConflict) return ok } // IsUnauthorized returns if the passed in error is an [ErrUnauthorized]. func IsUnauthorized(err error) bool { _, ok := getImplementer(err).(ErrUnauthorized) return ok } // IsUnavailable returns if the passed in error is an [ErrUnavailable]. func IsUnavailable(err error) bool { _, ok := getImplementer(err).(ErrUnavailable) return ok } // IsForbidden returns if the passed in error is an [ErrForbidden]. func IsForbidden(err error) bool { _, ok := getImplementer(err).(ErrForbidden) return ok } // IsSystem returns if the passed in error is an [ErrSystem]. func IsSystem(err error) bool { _, ok := getImplementer(err).(ErrSystem) return ok } // IsNotModified returns if the passed in error is an [ErrNotModified]. func IsNotModified(err error) bool { _, ok := getImplementer(err).(ErrNotModified) return ok } // IsNotImplemented returns if the passed in error is an [ErrNotImplemented]. func IsNotImplemented(err error) bool { _, ok := getImplementer(err).(ErrNotImplemented) return ok } // IsUnknown returns if the passed in error is an [ErrUnknown]. func IsUnknown(err error) bool { _, ok := getImplementer(err).(ErrUnknown) return ok } // IsCancelled returns if the passed in error is an [ErrCancelled]. func IsCancelled(err error) bool { _, ok := getImplementer(err).(ErrCancelled) return ok } // IsDeadline returns if the passed in error is an [ErrDeadline]. func IsDeadline(err error) bool { _, ok := getImplementer(err).(ErrDeadline) return ok } // IsDataLoss returns if the passed in error is an [ErrDataLoss]. func IsDataLoss(err error) bool { _, ok := getImplementer(err).(ErrDataLoss) return ok } // IsContext returns if the passed in error is due to context cancellation or deadline exceeded. func IsContext(err error) bool { return errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) } ================================================ FILE: vendor/github.com/docker/docker/internal/lazyregexp/lazyregexp.go ================================================ // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Code below was largely copied from golang.org/x/mod@v0.22; // https://github.com/golang/mod/blob/v0.22.0/internal/lazyregexp/lazyre.go // with some additional methods added. // Package lazyregexp is a thin wrapper over regexp, allowing the use of global // regexp variables without forcing them to be compiled at init. package lazyregexp import ( "os" "regexp" "strings" "sync" ) // Regexp is a wrapper around [regexp.Regexp], where the underlying regexp will be // compiled the first time it is needed. type Regexp struct { str string once sync.Once rx *regexp.Regexp } func (r *Regexp) re() *regexp.Regexp { r.once.Do(r.build) return r.rx } func (r *Regexp) build() { r.rx = regexp.MustCompile(r.str) r.str = "" } func (r *Regexp) FindSubmatch(s []byte) [][]byte { return r.re().FindSubmatch(s) } func (r *Regexp) FindAllStringSubmatch(s string, n int) [][]string { return r.re().FindAllStringSubmatch(s, n) } func (r *Regexp) FindStringSubmatch(s string) []string { return r.re().FindStringSubmatch(s) } func (r *Regexp) FindStringSubmatchIndex(s string) []int { return r.re().FindStringSubmatchIndex(s) } func (r *Regexp) ReplaceAllString(src, repl string) string { return r.re().ReplaceAllString(src, repl) } func (r *Regexp) FindString(s string) string { return r.re().FindString(s) } func (r *Regexp) FindAllString(s string, n int) []string { return r.re().FindAllString(s, n) } func (r *Regexp) MatchString(s string) bool { return r.re().MatchString(s) } func (r *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string { return r.re().ReplaceAllStringFunc(src, repl) } func (r *Regexp) SubexpNames() []string { return r.re().SubexpNames() } var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test") // New creates a new lazy regexp, delaying the compiling work until it is first // needed. If the code is being run as part of tests, the regexp compiling will // happen immediately. func New(str string) *Regexp { lr := &Regexp{str: str} if inTest { // In tests, always compile the regexps early. lr.re() } return lr } ================================================ FILE: vendor/github.com/docker/docker/internal/multierror/multierror.go ================================================ package multierror import ( "strings" ) // Join is a drop-in replacement for errors.Join with better formatting. func Join(errs ...error) error { n := 0 for _, err := range errs { if err != nil { n++ } } if n == 0 { return nil } e := &joinError{ errs: make([]error, 0, n), } for _, err := range errs { if err != nil { e.errs = append(e.errs, err) } } return e } type joinError struct { errs []error } func (e *joinError) Error() string { if len(e.errs) == 1 { return strings.TrimSpace(e.errs[0].Error()) } stringErrs := make([]string, 0, len(e.errs)) for _, subErr := range e.errs { stringErrs = append(stringErrs, strings.Replace(subErr.Error(), "\n", "\n\t", -1)) } return "* " + strings.Join(stringErrs, "\n* ") } func (e *joinError) Unwrap() []error { return e.errs } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/archive.go ================================================ // Package archive provides helper functions for dealing with archive files. package archive import ( "archive/tar" "bufio" "bytes" "compress/bzip2" "compress/gzip" "context" "encoding/binary" "errors" "fmt" "io" "os" "os/exec" "path/filepath" "runtime" "runtime/debug" "strconv" "strings" "sync" "sync/atomic" "syscall" "time" "github.com/containerd/log" "github.com/docker/docker/pkg/idtools" "github.com/klauspost/compress/zstd" "github.com/moby/patternmatcher" "github.com/moby/sys/sequential" ) // ImpliedDirectoryMode represents the mode (Unix permissions) applied to directories that are implied by files in a // tar, but that do not have their own header entry. // // The permissions mask is stored in a constant instead of locally to ensure that magic numbers do not // proliferate in the codebase. The default value 0755 has been selected based on the default umask of 0022, and // a convention of mkdir(1) calling mkdir(2) with permissions of 0777, resulting in a final value of 0755. // // This value is currently implementation-defined, and not captured in any cross-runtime specification. Thus, it is // subject to change in Moby at any time -- image authors who require consistent or known directory permissions // should explicitly control them by ensuring that header entries exist for any applicable path. const ImpliedDirectoryMode = 0o755 type ( // Compression is the state represents if compressed or not. Compression int // WhiteoutFormat is the format of whiteouts unpacked WhiteoutFormat int // TarOptions wraps the tar options. TarOptions struct { IncludeFiles []string ExcludePatterns []string Compression Compression NoLchown bool IDMap idtools.IdentityMapping ChownOpts *idtools.Identity IncludeSourceDir bool // WhiteoutFormat is the expected on disk format for whiteout files. // This format will be converted to the standard format on pack // and from the standard format on unpack. WhiteoutFormat WhiteoutFormat // When unpacking, specifies whether overwriting a directory with a // non-directory is allowed and vice versa. NoOverwriteDirNonDir bool // For each include when creating an archive, the included name will be // replaced with the matching name from this map. RebaseNames map[string]string InUserNS bool // Allow unpacking to succeed in spite of failures to set extended // attributes on the unpacked files due to the destination filesystem // not supporting them or a lack of permissions. Extended attributes // were probably in the archive for a reason, so set this option at // your own peril. BestEffortXattrs bool } ) // Archiver implements the Archiver interface and allows the reuse of most utility functions of // this package with a pluggable Untar function. Also, to facilitate the passing of specific id // mappings for untar, an Archiver can be created with maps which will then be passed to Untar operations. type Archiver struct { Untar func(io.Reader, string, *TarOptions) error IDMapping idtools.IdentityMapping } // NewDefaultArchiver returns a new Archiver without any IdentityMapping func NewDefaultArchiver() *Archiver { return &Archiver{Untar: Untar} } // breakoutError is used to differentiate errors related to breaking out // When testing archive breakout in the unit tests, this error is expected // in order for the test to pass. type breakoutError error const ( Uncompressed Compression = 0 // Uncompressed represents the uncompressed. Bzip2 Compression = 1 // Bzip2 is bzip2 compression algorithm. Gzip Compression = 2 // Gzip is gzip compression algorithm. Xz Compression = 3 // Xz is xz compression algorithm. Zstd Compression = 4 // Zstd is zstd compression algorithm. ) const ( AUFSWhiteoutFormat WhiteoutFormat = 0 // AUFSWhiteoutFormat is the default format for whiteouts OverlayWhiteoutFormat WhiteoutFormat = 1 // OverlayWhiteoutFormat formats whiteout according to the overlay standard. ) // IsArchivePath checks if the (possibly compressed) file at the given path // starts with a tar file header. func IsArchivePath(path string) bool { file, err := os.Open(path) if err != nil { return false } defer file.Close() rdr, err := DecompressStream(file) if err != nil { return false } defer rdr.Close() r := tar.NewReader(rdr) _, err = r.Next() return err == nil } const ( zstdMagicSkippableStart = 0x184D2A50 zstdMagicSkippableMask = 0xFFFFFFF0 ) var ( bzip2Magic = []byte{0x42, 0x5A, 0x68} gzipMagic = []byte{0x1F, 0x8B, 0x08} xzMagic = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00} zstdMagic = []byte{0x28, 0xb5, 0x2f, 0xfd} ) type matcher = func([]byte) bool func magicNumberMatcher(m []byte) matcher { return func(source []byte) bool { return bytes.HasPrefix(source, m) } } // zstdMatcher detects zstd compression algorithm. // Zstandard compressed data is made of one or more frames. // There are two frame formats defined by Zstandard: Zstandard frames and Skippable frames. // See https://datatracker.ietf.org/doc/html/rfc8878#section-3 for more details. func zstdMatcher() matcher { return func(source []byte) bool { if bytes.HasPrefix(source, zstdMagic) { // Zstandard frame return true } // skippable frame if len(source) < 8 { return false } // magic number from 0x184D2A50 to 0x184D2A5F. if binary.LittleEndian.Uint32(source[:4])&zstdMagicSkippableMask == zstdMagicSkippableStart { return true } return false } } // DetectCompression detects the compression algorithm of the source. func DetectCompression(source []byte) Compression { compressionMap := map[Compression]matcher{ Bzip2: magicNumberMatcher(bzip2Magic), Gzip: magicNumberMatcher(gzipMagic), Xz: magicNumberMatcher(xzMagic), Zstd: zstdMatcher(), } for _, compression := range []Compression{Bzip2, Gzip, Xz, Zstd} { fn := compressionMap[compression] if fn(source) { return compression } } return Uncompressed } func xzDecompress(ctx context.Context, archive io.Reader) (io.ReadCloser, error) { args := []string{"xz", "-d", "-c", "-q"} return cmdStream(exec.CommandContext(ctx, args[0], args[1:]...), archive) } func gzDecompress(ctx context.Context, buf io.Reader) (io.ReadCloser, error) { if noPigzEnv := os.Getenv("MOBY_DISABLE_PIGZ"); noPigzEnv != "" { noPigz, err := strconv.ParseBool(noPigzEnv) if err != nil { log.G(ctx).WithError(err).Warn("invalid value in MOBY_DISABLE_PIGZ env var") } if noPigz { log.G(ctx).Debugf("Use of pigz is disabled due to MOBY_DISABLE_PIGZ=%s", noPigzEnv) return gzip.NewReader(buf) } } unpigzPath, err := exec.LookPath("unpigz") if err != nil { log.G(ctx).Debugf("unpigz binary not found, falling back to go gzip library") return gzip.NewReader(buf) } log.G(ctx).Debugf("Using %s to decompress", unpigzPath) return cmdStream(exec.CommandContext(ctx, unpigzPath, "-d", "-c"), buf) } type readCloserWrapper struct { io.Reader closer func() error closed atomic.Bool } func (r *readCloserWrapper) Close() error { if !r.closed.CompareAndSwap(false, true) { log.G(context.TODO()).Error("subsequent attempt to close readCloserWrapper") if log.GetLevel() >= log.DebugLevel { log.G(context.TODO()).Errorf("stack trace: %s", string(debug.Stack())) } return nil } if r.closer != nil { return r.closer() } return nil } var ( bufioReader32KPool = &sync.Pool{ New: func() interface{} { return bufio.NewReaderSize(nil, 32*1024) }, } ) type bufferedReader struct { buf *bufio.Reader } func newBufferedReader(r io.Reader) *bufferedReader { buf := bufioReader32KPool.Get().(*bufio.Reader) buf.Reset(r) return &bufferedReader{buf} } func (r *bufferedReader) Read(p []byte) (n int, err error) { if r.buf == nil { return 0, io.EOF } n, err = r.buf.Read(p) if err == io.EOF { r.buf.Reset(nil) bufioReader32KPool.Put(r.buf) r.buf = nil } return } func (r *bufferedReader) Peek(n int) ([]byte, error) { if r.buf == nil { return nil, io.EOF } return r.buf.Peek(n) } // DecompressStream decompresses the archive and returns a ReaderCloser with the decompressed archive. func DecompressStream(archive io.Reader) (io.ReadCloser, error) { buf := newBufferedReader(archive) bs, err := buf.Peek(10) if err != nil && err != io.EOF { // Note: we'll ignore any io.EOF error because there are some odd // cases where the layer.tar file will be empty (zero bytes) and // that results in an io.EOF from the Peek() call. So, in those // cases we'll just treat it as a non-compressed stream and // that means just create an empty layer. // See Issue 18170 return nil, err } compression := DetectCompression(bs) switch compression { case Uncompressed: return &readCloserWrapper{ Reader: buf, }, nil case Gzip: ctx, cancel := context.WithCancel(context.Background()) gzReader, err := gzDecompress(ctx, buf) if err != nil { cancel() return nil, err } return &readCloserWrapper{ Reader: gzReader, closer: func() error { cancel() return gzReader.Close() }, }, nil case Bzip2: bz2Reader := bzip2.NewReader(buf) return &readCloserWrapper{ Reader: bz2Reader, }, nil case Xz: ctx, cancel := context.WithCancel(context.Background()) xzReader, err := xzDecompress(ctx, buf) if err != nil { cancel() return nil, err } return &readCloserWrapper{ Reader: xzReader, closer: func() error { cancel() return xzReader.Close() }, }, nil case Zstd: zstdReader, err := zstd.NewReader(buf) if err != nil { return nil, err } return &readCloserWrapper{ Reader: zstdReader, closer: func() error { zstdReader.Close() return nil }, }, nil default: return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension()) } } type nopWriteCloser struct { io.Writer } func (nopWriteCloser) Close() error { return nil } // CompressStream compresses the dest with specified compression algorithm. func CompressStream(dest io.Writer, compression Compression) (io.WriteCloser, error) { switch compression { case Uncompressed: return nopWriteCloser{dest}, nil case Gzip: return gzip.NewWriter(dest), nil case Bzip2, Xz: // archive/bzip2 does not support writing, and there is no xz support at all // However, this is not a problem as docker only currently generates gzipped tars return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension()) default: return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension()) } } // TarModifierFunc is a function that can be passed to ReplaceFileTarWrapper to // modify the contents or header of an entry in the archive. If the file already // exists in the archive the TarModifierFunc will be called with the Header and // a reader which will return the files content. If the file does not exist both // header and content will be nil. type TarModifierFunc func(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) // ReplaceFileTarWrapper converts inputTarStream to a new tar stream. Files in the // tar stream are modified if they match any of the keys in mods. func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModifierFunc) io.ReadCloser { pipeReader, pipeWriter := io.Pipe() go func() { tarReader := tar.NewReader(inputTarStream) tarWriter := tar.NewWriter(pipeWriter) defer inputTarStream.Close() defer tarWriter.Close() modify := func(name string, original *tar.Header, modifier TarModifierFunc, tarReader io.Reader) error { header, data, err := modifier(name, original, tarReader) switch { case err != nil: return err case header == nil: return nil } if header.Name == "" { header.Name = name } header.Size = int64(len(data)) if err := tarWriter.WriteHeader(header); err != nil { return err } if len(data) != 0 { if _, err := tarWriter.Write(data); err != nil { return err } } return nil } var err error var originalHeader *tar.Header for { originalHeader, err = tarReader.Next() if err == io.EOF { break } if err != nil { pipeWriter.CloseWithError(err) return } modifier, ok := mods[originalHeader.Name] if !ok { // No modifiers for this file, copy the header and data if err := tarWriter.WriteHeader(originalHeader); err != nil { pipeWriter.CloseWithError(err) return } if _, err := copyWithBuffer(tarWriter, tarReader); err != nil { pipeWriter.CloseWithError(err) return } continue } delete(mods, originalHeader.Name) if err := modify(originalHeader.Name, originalHeader, modifier, tarReader); err != nil { pipeWriter.CloseWithError(err) return } } // Apply the modifiers that haven't matched any files in the archive for name, modifier := range mods { if err := modify(name, nil, modifier, nil); err != nil { pipeWriter.CloseWithError(err) return } } pipeWriter.Close() }() return pipeReader } // Extension returns the extension of a file that uses the specified compression algorithm. func (compression *Compression) Extension() string { switch *compression { case Uncompressed: return "tar" case Bzip2: return "tar.bz2" case Gzip: return "tar.gz" case Xz: return "tar.xz" case Zstd: return "tar.zst" } return "" } // assert that we implement [tar.FileInfoNames]. // // TODO(thaJeztah): disabled to allow compiling on < go1.23. un-comment once we drop support for older versions of go. // var _ tar.FileInfoNames = (*nosysFileInfo)(nil) // nosysFileInfo hides the system-dependent info of the wrapped FileInfo to // prevent tar.FileInfoHeader from introspecting it and potentially calling into // glibc. // // It implements [tar.FileInfoNames] to further prevent [tar.FileInfoHeader] // from performing any lookups on go1.23 and up. see https://go.dev/issue/50102 type nosysFileInfo struct { os.FileInfo } // Uname stubs out looking up username. It implements [tar.FileInfoNames] // to prevent [tar.FileInfoHeader] from loading libraries to perform // username lookups. func (fi nosysFileInfo) Uname() (string, error) { return "", nil } // Gname stubs out looking up group-name. It implements [tar.FileInfoNames] // to prevent [tar.FileInfoHeader] from loading libraries to perform // username lookups. func (fi nosysFileInfo) Gname() (string, error) { return "", nil } func (fi nosysFileInfo) Sys() interface{} { // A Sys value of type *tar.Header is safe as it is system-independent. // The tar.FileInfoHeader function copies the fields into the returned // header without performing any OS lookups. if sys, ok := fi.FileInfo.Sys().(*tar.Header); ok { return sys } return nil } // sysStat, if non-nil, populates hdr from system-dependent fields of fi. var sysStat func(fi os.FileInfo, hdr *tar.Header) error // FileInfoHeaderNoLookups creates a partially-populated tar.Header from fi. // // Compared to the archive/tar.FileInfoHeader function, this function is safe to // call from a chrooted process as it does not populate fields which would // require operating system lookups. It behaves identically to // tar.FileInfoHeader when fi is a FileInfo value returned from // tar.Header.FileInfo(). // // When fi is a FileInfo for a native file, such as returned from os.Stat() and // os.Lstat(), the returned Header value differs from one returned from // tar.FileInfoHeader in the following ways. The Uname and Gname fields are not // set as OS lookups would be required to populate them. The AccessTime and // ChangeTime fields are not currently set (not yet implemented) although that // is subject to change. Callers which require the AccessTime or ChangeTime // fields to be zeroed should explicitly zero them out in the returned Header // value to avoid any compatibility issues in the future. func FileInfoHeaderNoLookups(fi os.FileInfo, link string) (*tar.Header, error) { hdr, err := tar.FileInfoHeader(nosysFileInfo{fi}, link) if err != nil { return nil, err } if sysStat != nil { return hdr, sysStat(fi, hdr) } return hdr, nil } // FileInfoHeader creates a populated Header from fi. // // Compared to the archive/tar package, this function fills in less information // but is safe to call from a chrooted process. The AccessTime and ChangeTime // fields are not set in the returned header, ModTime is truncated to one-second // precision, and the Uname and Gname fields are only set when fi is a FileInfo // value returned from tar.Header.FileInfo(). func FileInfoHeader(name string, fi os.FileInfo, link string) (*tar.Header, error) { hdr, err := FileInfoHeaderNoLookups(fi, link) if err != nil { return nil, err } hdr.Format = tar.FormatPAX hdr.ModTime = hdr.ModTime.Truncate(time.Second) hdr.AccessTime = time.Time{} hdr.ChangeTime = time.Time{} hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode))) hdr.Name = canonicalTarName(name, fi.IsDir()) return hdr, nil } const paxSchilyXattr = "SCHILY.xattr." // ReadSecurityXattrToTarHeader reads security.capability xattr from filesystem // to a tar header func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error { const ( // Values based on linux/include/uapi/linux/capability.h xattrCapsSz2 = 20 versionOffset = 3 vfsCapRevision2 = 2 vfsCapRevision3 = 3 ) capability, _ := lgetxattr(path, "security.capability") if capability != nil { if capability[versionOffset] == vfsCapRevision3 { // Convert VFS_CAP_REVISION_3 to VFS_CAP_REVISION_2 as root UID makes no // sense outside the user namespace the archive is built in. capability[versionOffset] = vfsCapRevision2 capability = capability[:xattrCapsSz2] } if hdr.PAXRecords == nil { hdr.PAXRecords = make(map[string]string) } hdr.PAXRecords[paxSchilyXattr+"security.capability"] = string(capability) } return nil } type tarWhiteoutConverter interface { ConvertWrite(*tar.Header, string, os.FileInfo) (*tar.Header, error) ConvertRead(*tar.Header, string) (bool, error) } type tarAppender struct { TarWriter *tar.Writer // for hardlink mapping SeenFiles map[uint64]string IdentityMapping idtools.IdentityMapping ChownOpts *idtools.Identity // For packing and unpacking whiteout files in the // non standard format. The whiteout files defined // by the AUFS standard are used as the tar whiteout // standard. WhiteoutConverter tarWhiteoutConverter } func newTarAppender(idMapping idtools.IdentityMapping, writer io.Writer, chownOpts *idtools.Identity) *tarAppender { return &tarAppender{ SeenFiles: make(map[uint64]string), TarWriter: tar.NewWriter(writer), IdentityMapping: idMapping, ChownOpts: chownOpts, } } // canonicalTarName provides a platform-independent and consistent POSIX-style // path for files and directories to be archived regardless of the platform. func canonicalTarName(name string, isDir bool) string { name = filepath.ToSlash(name) // suffix with '/' for directories if isDir && !strings.HasSuffix(name, "/") { name += "/" } return name } // addTarFile adds to the tar archive a file from `path` as `name` func (ta *tarAppender) addTarFile(path, name string) error { fi, err := os.Lstat(path) if err != nil { return err } var link string if fi.Mode()&os.ModeSymlink != 0 { var err error link, err = os.Readlink(path) if err != nil { return err } } hdr, err := FileInfoHeader(name, fi, link) if err != nil { return err } if err := ReadSecurityXattrToTarHeader(path, hdr); err != nil { return err } // if it's not a directory and has more than 1 link, // it's hard linked, so set the type flag accordingly if !fi.IsDir() && hasHardlinks(fi) { inode, err := getInodeFromStat(fi.Sys()) if err != nil { return err } // a link should have a name that it links too // and that linked name should be first in the tar archive if oldpath, ok := ta.SeenFiles[inode]; ok { hdr.Typeflag = tar.TypeLink hdr.Linkname = oldpath hdr.Size = 0 // This Must be here for the writer math to add up! } else { ta.SeenFiles[inode] = name } } // check whether the file is overlayfs whiteout // if yes, skip re-mapping container ID mappings. isOverlayWhiteout := fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 // handle re-mapping container ID mappings back to host ID mappings before // writing tar headers/files. We skip whiteout files because they were written // by the kernel and already have proper ownership relative to the host if !isOverlayWhiteout && !strings.HasPrefix(filepath.Base(hdr.Name), WhiteoutPrefix) && !ta.IdentityMapping.Empty() { fileIDPair, err := getFileUIDGID(fi.Sys()) if err != nil { return err } hdr.Uid, hdr.Gid, err = ta.IdentityMapping.ToContainer(fileIDPair) if err != nil { return err } } // explicitly override with ChownOpts if ta.ChownOpts != nil { hdr.Uid = ta.ChownOpts.UID hdr.Gid = ta.ChownOpts.GID } if ta.WhiteoutConverter != nil { wo, err := ta.WhiteoutConverter.ConvertWrite(hdr, path, fi) if err != nil { return err } // If a new whiteout file exists, write original hdr, then // replace hdr with wo to be written after. Whiteouts should // always be written after the original. Note the original // hdr may have been updated to be a whiteout with returning // a whiteout header if wo != nil { if err := ta.TarWriter.WriteHeader(hdr); err != nil { return err } if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 { return fmt.Errorf("tar: cannot use whiteout for non-empty file") } hdr = wo } } if err := ta.TarWriter.WriteHeader(hdr); err != nil { return err } if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 { // We use sequential file access to avoid depleting the standby list on // Windows. On Linux, this equates to a regular os.Open. file, err := sequential.Open(path) if err != nil { return err } _, err = copyWithBuffer(ta.TarWriter, file) file.Close() if err != nil { return err } } return nil } func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, opts *TarOptions) error { var ( Lchown = true inUserns, bestEffortXattrs bool chownOpts *idtools.Identity ) // TODO(thaJeztah): make opts a required argument. if opts != nil { Lchown = !opts.NoLchown inUserns = opts.InUserNS // TODO(thaJeztah): consider deprecating opts.InUserNS and detect locally. chownOpts = opts.ChownOpts bestEffortXattrs = opts.BestEffortXattrs } // hdr.Mode is in linux format, which we can use for sycalls, // but for os.Foo() calls we need the mode converted to os.FileMode, // so use hdrInfo.Mode() (they differ for e.g. setuid bits) hdrInfo := hdr.FileInfo() switch hdr.Typeflag { case tar.TypeDir: // Create directory unless it exists as a directory already. // In that case we just want to merge the two if fi, err := os.Lstat(path); !(err == nil && fi.IsDir()) { if err := os.Mkdir(path, hdrInfo.Mode()); err != nil { return err } } case tar.TypeReg: // Source is regular file. We use sequential file access to avoid depleting // the standby list on Windows. On Linux, this equates to a regular os.OpenFile. file, err := sequential.OpenFile(path, os.O_CREATE|os.O_WRONLY, hdrInfo.Mode()) if err != nil { return err } if _, err := copyWithBuffer(file, reader); err != nil { file.Close() return err } file.Close() case tar.TypeBlock, tar.TypeChar: if inUserns { // cannot create devices in a userns log.G(context.TODO()).WithFields(log.Fields{"path": path, "type": hdr.Typeflag}).Debug("skipping device nodes in a userns") return nil } // Handle this is an OS-specific way if err := handleTarTypeBlockCharFifo(hdr, path); err != nil { return err } case tar.TypeFifo: // Handle this is an OS-specific way if err := handleTarTypeBlockCharFifo(hdr, path); err != nil { if inUserns && errors.Is(err, syscall.EPERM) { // In most cases, cannot create a fifo if running in user namespace log.G(context.TODO()).WithFields(log.Fields{"error": err, "path": path, "type": hdr.Typeflag}).Debug("creating fifo node in a userns") return nil } return err } case tar.TypeLink: // #nosec G305 -- The target path is checked for path traversal. targetPath := filepath.Join(extractDir, hdr.Linkname) // check for hardlink breakout if !strings.HasPrefix(targetPath, extractDir) { return breakoutError(fmt.Errorf("invalid hardlink %q -> %q", targetPath, hdr.Linkname)) } if err := os.Link(targetPath, path); err != nil { return err } case tar.TypeSymlink: // path -> hdr.Linkname = targetPath // e.g. /extractDir/path/to/symlink -> ../2/file = /extractDir/path/2/file targetPath := filepath.Join(filepath.Dir(path), hdr.Linkname) // #nosec G305 -- The target path is checked for path traversal. // the reason we don't need to check symlinks in the path (with FollowSymlinkInScope) is because // that symlink would first have to be created, which would be caught earlier, at this very check: if !strings.HasPrefix(targetPath, extractDir) { return breakoutError(fmt.Errorf("invalid symlink %q -> %q", path, hdr.Linkname)) } if err := os.Symlink(hdr.Linkname, path); err != nil { return err } case tar.TypeXGlobalHeader: log.G(context.TODO()).Debug("PAX Global Extended Headers found and ignored") return nil default: return fmt.Errorf("unhandled tar header type %d", hdr.Typeflag) } // Lchown is not supported on Windows. if Lchown && runtime.GOOS != "windows" { if chownOpts == nil { chownOpts = &idtools.Identity{UID: hdr.Uid, GID: hdr.Gid} } if err := os.Lchown(path, chownOpts.UID, chownOpts.GID); err != nil { var msg string if inUserns && errors.Is(err, syscall.EINVAL) { msg = " (try increasing the number of subordinate IDs in /etc/subuid and /etc/subgid)" } return fmt.Errorf("failed to Lchown %q for UID %d, GID %d%s: %w", path, hdr.Uid, hdr.Gid, msg, err) } } var xattrErrs []string for key, value := range hdr.PAXRecords { xattr, ok := strings.CutPrefix(key, paxSchilyXattr) if !ok { continue } if err := lsetxattr(path, xattr, []byte(value), 0); err != nil { if bestEffortXattrs && errors.Is(err, syscall.ENOTSUP) || errors.Is(err, syscall.EPERM) { // EPERM occurs if modifying xattrs is not allowed. This can // happen when running in userns with restrictions (ChromeOS). xattrErrs = append(xattrErrs, err.Error()) continue } return err } } if len(xattrErrs) > 0 { log.G(context.TODO()).WithFields(log.Fields{ "errors": xattrErrs, }).Warn("ignored xattrs in archive: underlying filesystem doesn't support them") } // There is no LChmod, so ignore mode for symlink. Also, this // must happen after chown, as that can modify the file mode if err := handleLChmod(hdr, path, hdrInfo); err != nil { return err } aTime := boundTime(latestTime(hdr.AccessTime, hdr.ModTime)) mTime := boundTime(hdr.ModTime) // chtimes doesn't support a NOFOLLOW flag atm if hdr.Typeflag == tar.TypeLink { if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { if err := chtimes(path, aTime, mTime); err != nil { return err } } } else if hdr.Typeflag != tar.TypeSymlink { if err := chtimes(path, aTime, mTime); err != nil { return err } } else { if err := lchtimes(path, aTime, mTime); err != nil { return err } } return nil } // Tar creates an archive from the directory at `path`, and returns it as a // stream of bytes. func Tar(path string, compression Compression) (io.ReadCloser, error) { return TarWithOptions(path, &TarOptions{Compression: compression}) } // TarWithOptions creates an archive from the directory at `path`, only including files whose relative // paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`. func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) { tb, err := NewTarballer(srcPath, options) if err != nil { return nil, err } go tb.Do() return tb.Reader(), nil } // Tarballer is a lower-level interface to TarWithOptions which gives the caller // control over which goroutine the archiving operation executes on. type Tarballer struct { srcPath string options *TarOptions pm *patternmatcher.PatternMatcher pipeReader *io.PipeReader pipeWriter *io.PipeWriter compressWriter io.WriteCloser whiteoutConverter tarWhiteoutConverter } // NewTarballer constructs a new tarballer. The arguments are the same as for // TarWithOptions. func NewTarballer(srcPath string, options *TarOptions) (*Tarballer, error) { pm, err := patternmatcher.New(options.ExcludePatterns) if err != nil { return nil, err } pipeReader, pipeWriter := io.Pipe() compressWriter, err := CompressStream(pipeWriter, options.Compression) if err != nil { return nil, err } return &Tarballer{ // Fix the source path to work with long path names. This is a no-op // on platforms other than Windows. srcPath: addLongPathPrefix(srcPath), options: options, pm: pm, pipeReader: pipeReader, pipeWriter: pipeWriter, compressWriter: compressWriter, whiteoutConverter: getWhiteoutConverter(options.WhiteoutFormat), }, nil } // Reader returns the reader for the created archive. func (t *Tarballer) Reader() io.ReadCloser { return t.pipeReader } // Do performs the archiving operation in the background. The resulting archive // can be read from t.Reader(). Do should only be called once on each Tarballer // instance. func (t *Tarballer) Do() { ta := newTarAppender( t.options.IDMap, t.compressWriter, t.options.ChownOpts, ) ta.WhiteoutConverter = t.whiteoutConverter defer func() { // Make sure to check the error on Close. if err := ta.TarWriter.Close(); err != nil { log.G(context.TODO()).Errorf("Can't close tar writer: %s", err) } if err := t.compressWriter.Close(); err != nil { log.G(context.TODO()).Errorf("Can't close compress writer: %s", err) } if err := t.pipeWriter.Close(); err != nil { log.G(context.TODO()).Errorf("Can't close pipe writer: %s", err) } }() // In general we log errors here but ignore them because // during e.g. a diff operation the container can continue // mutating the filesystem and we can see transient errors // from this stat, err := os.Lstat(t.srcPath) if err != nil { return } if !stat.IsDir() { // We can't later join a non-dir with any includes because the // 'walk' will error if "file/." is stat-ed and "file" is not a // directory. So, we must split the source path and use the // basename as the include. if len(t.options.IncludeFiles) > 0 { log.G(context.TODO()).Warn("Tar: Can't archive a file with includes") } dir, base := SplitPathDirEntry(t.srcPath) t.srcPath = dir t.options.IncludeFiles = []string{base} } if len(t.options.IncludeFiles) == 0 { t.options.IncludeFiles = []string{"."} } seen := make(map[string]bool) for _, include := range t.options.IncludeFiles { rebaseName := t.options.RebaseNames[include] var ( parentMatchInfo []patternmatcher.MatchInfo parentDirs []string ) walkRoot := getWalkRoot(t.srcPath, include) filepath.WalkDir(walkRoot, func(filePath string, f os.DirEntry, err error) error { if err != nil { log.G(context.TODO()).Errorf("Tar: Can't stat file %s to tar: %s", t.srcPath, err) return nil } relFilePath, err := filepath.Rel(t.srcPath, filePath) if err != nil || (!t.options.IncludeSourceDir && relFilePath == "." && f.IsDir()) { // Error getting relative path OR we are looking // at the source directory path. Skip in both situations. return nil } if t.options.IncludeSourceDir && include == "." && relFilePath != "." { relFilePath = strings.Join([]string{".", relFilePath}, string(filepath.Separator)) } skip := false // If "include" is an exact match for the current file // then even if there's an "excludePatterns" pattern that // matches it, don't skip it. IOW, assume an explicit 'include' // is asking for that file no matter what - which is true // for some files, like .dockerignore and Dockerfile (sometimes) if include != relFilePath { for len(parentDirs) != 0 { lastParentDir := parentDirs[len(parentDirs)-1] if strings.HasPrefix(relFilePath, lastParentDir+string(os.PathSeparator)) { break } parentDirs = parentDirs[:len(parentDirs)-1] parentMatchInfo = parentMatchInfo[:len(parentMatchInfo)-1] } var matchInfo patternmatcher.MatchInfo if len(parentMatchInfo) != 0 { skip, matchInfo, err = t.pm.MatchesUsingParentResults(relFilePath, parentMatchInfo[len(parentMatchInfo)-1]) } else { skip, matchInfo, err = t.pm.MatchesUsingParentResults(relFilePath, patternmatcher.MatchInfo{}) } if err != nil { log.G(context.TODO()).Errorf("Error matching %s: %v", relFilePath, err) return err } if f.IsDir() { parentDirs = append(parentDirs, relFilePath) parentMatchInfo = append(parentMatchInfo, matchInfo) } } if skip { // If we want to skip this file and its a directory // then we should first check to see if there's an // excludes pattern (e.g. !dir/file) that starts with this // dir. If so then we can't skip this dir. // Its not a dir then so we can just return/skip. if !f.IsDir() { return nil } // No exceptions (!...) in patterns so just skip dir if !t.pm.Exclusions() { return filepath.SkipDir } dirSlash := relFilePath + string(filepath.Separator) for _, pat := range t.pm.Patterns() { if !pat.Exclusion() { continue } if strings.HasPrefix(pat.String()+string(filepath.Separator), dirSlash) { // found a match - so can't skip this dir return nil } } // No matching exclusion dir so just skip dir return filepath.SkipDir } if seen[relFilePath] { return nil } seen[relFilePath] = true // Rename the base resource. if rebaseName != "" { var replacement string if rebaseName != string(filepath.Separator) { // Special case the root directory to replace with an // empty string instead so that we don't end up with // double slashes in the paths. replacement = rebaseName } relFilePath = strings.Replace(relFilePath, include, replacement, 1) } if err := ta.addTarFile(filePath, relFilePath); err != nil { log.G(context.TODO()).Errorf("Can't add file %s to tar: %s", filePath, err) // if pipe is broken, stop writing tar stream to it if err == io.ErrClosedPipe { return err } } return nil }) } } // Unpack unpacks the decompressedArchive to dest with options. func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) error { tr := tar.NewReader(decompressedArchive) var dirs []*tar.Header whiteoutConverter := getWhiteoutConverter(options.WhiteoutFormat) // Iterate through the files in the archive. loop: for { hdr, err := tr.Next() if err == io.EOF { // end of tar archive break } if err != nil { return err } // ignore XGlobalHeader early to avoid creating parent directories for them if hdr.Typeflag == tar.TypeXGlobalHeader { log.G(context.TODO()).Debugf("PAX Global Extended Headers found for %s and ignored", hdr.Name) continue } // Normalize name, for safety and for a simple is-root check // This keeps "../" as-is, but normalizes "/../" to "/". Or Windows: // This keeps "..\" as-is, but normalizes "\..\" to "\". hdr.Name = filepath.Clean(hdr.Name) for _, exclude := range options.ExcludePatterns { if strings.HasPrefix(hdr.Name, exclude) { continue loop } } // Ensure that the parent directory exists. err = createImpliedDirectories(dest, hdr, options) if err != nil { return err } // #nosec G305 -- The joined path is checked for path traversal. path := filepath.Join(dest, hdr.Name) rel, err := filepath.Rel(dest, path) if err != nil { return err } if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { return breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest)) } // If path exits we almost always just want to remove and replace it // The only exception is when it is a directory *and* the file from // the layer is also a directory. Then we want to merge them (i.e. // just apply the metadata from the layer). if fi, err := os.Lstat(path); err == nil { if options.NoOverwriteDirNonDir && fi.IsDir() && hdr.Typeflag != tar.TypeDir { // If NoOverwriteDirNonDir is true then we cannot replace // an existing directory with a non-directory from the archive. return fmt.Errorf("cannot overwrite directory %q with non-directory %q", path, dest) } if options.NoOverwriteDirNonDir && !fi.IsDir() && hdr.Typeflag == tar.TypeDir { // If NoOverwriteDirNonDir is true then we cannot replace // an existing non-directory with a directory from the archive. return fmt.Errorf("cannot overwrite non-directory %q with directory %q", path, dest) } if fi.IsDir() && hdr.Name == "." { continue } if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) { if err := os.RemoveAll(path); err != nil { return err } } } if err := remapIDs(options.IDMap, hdr); err != nil { return err } if whiteoutConverter != nil { writeFile, err := whiteoutConverter.ConvertRead(hdr, path) if err != nil { return err } if !writeFile { continue } } if err := createTarFile(path, dest, hdr, tr, options); err != nil { return err } // Directory mtimes must be handled at the end to avoid further // file creation in them to modify the directory mtime if hdr.Typeflag == tar.TypeDir { dirs = append(dirs, hdr) } } for _, hdr := range dirs { // #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice. path := filepath.Join(dest, hdr.Name) if err := chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime)); err != nil { return err } } return nil } // createImpliedDirectories will create all parent directories of the current path with default permissions, if they do // not already exist. This is possible as the tar format supports 'implicit' directories, where their existence is // defined by the paths of files in the tar, but there are no header entries for the directories themselves, and thus // we most both create them and choose metadata like permissions. // // The caller should have performed filepath.Clean(hdr.Name), so hdr.Name will now be in the filepath format for the OS // on which the daemon is running. This precondition is required because this function assumes a OS-specific path // separator when checking that a path is not the root. func createImpliedDirectories(dest string, hdr *tar.Header, options *TarOptions) error { // Not the root directory, ensure that the parent directory exists if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) { parent := filepath.Dir(hdr.Name) parentPath := filepath.Join(dest, parent) if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { // RootPair() is confined inside this loop as most cases will not require a call, so we can spend some // unneeded function calls in the uncommon case to encapsulate logic -- implied directories are a niche // usage that reduces the portability of an image. rootIDs := options.IDMap.RootPair() err = idtools.MkdirAllAndChownNew(parentPath, ImpliedDirectoryMode, rootIDs) if err != nil { return err } } } return nil } // Untar reads a stream of bytes from `archive`, parses it as a tar archive, // and unpacks it into the directory at `dest`. // The archive may be compressed with one of the following algorithms: // identity (uncompressed), gzip, bzip2, xz. // // FIXME: specify behavior when target path exists vs. doesn't exist. func Untar(tarArchive io.Reader, dest string, options *TarOptions) error { return untarHandler(tarArchive, dest, options, true) } // UntarUncompressed reads a stream of bytes from `archive`, parses it as a tar archive, // and unpacks it into the directory at `dest`. // The archive must be an uncompressed stream. func UntarUncompressed(tarArchive io.Reader, dest string, options *TarOptions) error { return untarHandler(tarArchive, dest, options, false) } // Handler for teasing out the automatic decompression func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decompress bool) error { if tarArchive == nil { return fmt.Errorf("Empty archive") } dest = filepath.Clean(dest) if options == nil { options = &TarOptions{} } if options.ExcludePatterns == nil { options.ExcludePatterns = []string{} } r := tarArchive if decompress { decompressedArchive, err := DecompressStream(tarArchive) if err != nil { return err } defer decompressedArchive.Close() r = decompressedArchive } return Unpack(r, dest, options) } // TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other. // If either Tar or Untar fails, TarUntar aborts and returns the error. func (archiver *Archiver) TarUntar(src, dst string) error { archive, err := TarWithOptions(src, &TarOptions{Compression: Uncompressed}) if err != nil { return err } defer archive.Close() options := &TarOptions{ IDMap: archiver.IDMapping, } return archiver.Untar(archive, dst, options) } // UntarPath untar a file from path to a destination, src is the source tar file path. func (archiver *Archiver) UntarPath(src, dst string) error { archive, err := os.Open(src) if err != nil { return err } defer archive.Close() options := &TarOptions{ IDMap: archiver.IDMapping, } return archiver.Untar(archive, dst, options) } // CopyWithTar creates a tar archive of filesystem path `src`, and // unpacks it at filesystem path `dst`. // The archive is streamed directly with fixed buffering and no // intermediary disk IO. func (archiver *Archiver) CopyWithTar(src, dst string) error { srcSt, err := os.Stat(src) if err != nil { return err } if !srcSt.IsDir() { return archiver.CopyFileWithTar(src, dst) } // if this Archiver is set up with ID mapping we need to create // the new destination directory with the remapped root UID/GID pair // as owner rootIDs := archiver.IDMapping.RootPair() // Create dst, copy src's content into it if err := idtools.MkdirAllAndChownNew(dst, 0o755, rootIDs); err != nil { return err } return archiver.TarUntar(src, dst) } // CopyFileWithTar emulates the behavior of the 'cp' command-line // for a single file. It copies a regular file from path `src` to // path `dst`, and preserves all its metadata. func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { srcSt, err := os.Stat(src) if err != nil { return err } if srcSt.IsDir() { return fmt.Errorf("Can't copy a directory") } // Clean up the trailing slash. This must be done in an operating // system specific manner. if dst[len(dst)-1] == os.PathSeparator { dst = filepath.Join(dst, filepath.Base(src)) } // Create the holding directory if necessary if err := os.MkdirAll(filepath.Dir(dst), 0o700); err != nil { return err } r, w := io.Pipe() errC := make(chan error, 1) go func() { defer close(errC) errC <- func() error { defer w.Close() srcF, err := os.Open(src) if err != nil { return err } defer srcF.Close() hdr, err := FileInfoHeaderNoLookups(srcSt, "") if err != nil { return err } hdr.Format = tar.FormatPAX hdr.ModTime = hdr.ModTime.Truncate(time.Second) hdr.AccessTime = time.Time{} hdr.ChangeTime = time.Time{} hdr.Name = filepath.Base(dst) hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode))) if err := remapIDs(archiver.IDMapping, hdr); err != nil { return err } tw := tar.NewWriter(w) defer tw.Close() if err := tw.WriteHeader(hdr); err != nil { return err } if _, err := copyWithBuffer(tw, srcF); err != nil { return err } return nil }() }() defer func() { if er := <-errC; err == nil && er != nil { err = er } }() err = archiver.Untar(r, filepath.Dir(dst), nil) if err != nil { r.CloseWithError(err) } return err } // IdentityMapping returns the IdentityMapping of the archiver. func (archiver *Archiver) IdentityMapping() idtools.IdentityMapping { return archiver.IDMapping } func remapIDs(idMapping idtools.IdentityMapping, hdr *tar.Header) error { ids, err := idMapping.ToHost(idtools.Identity{UID: hdr.Uid, GID: hdr.Gid}) hdr.Uid, hdr.Gid = ids.UID, ids.GID return err } // cmdStream executes a command, and returns its stdout as a stream. // If the command fails to run or doesn't complete successfully, an error // will be returned, including anything written on stderr. func cmdStream(cmd *exec.Cmd, input io.Reader) (io.ReadCloser, error) { cmd.Stdin = input pipeR, pipeW := io.Pipe() cmd.Stdout = pipeW var errBuf bytes.Buffer cmd.Stderr = &errBuf // Run the command and return the pipe if err := cmd.Start(); err != nil { return nil, err } // Ensure the command has exited before we clean anything up done := make(chan struct{}) // Copy stdout to the returned pipe go func() { if err := cmd.Wait(); err != nil { pipeW.CloseWithError(fmt.Errorf("%s: %s", err, errBuf.String())) } else { pipeW.Close() } close(done) }() return &readCloserWrapper{ Reader: pipeR, closer: func() error { // Close pipeR, and then wait for the command to complete before returning. We have to close pipeR first, as // cmd.Wait waits for any non-file stdout/stderr/stdin to close. err := pipeR.Close() <-done return err }, }, nil } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/archive_linux.go ================================================ package archive import ( "archive/tar" "fmt" "os" "path/filepath" "strings" "github.com/moby/sys/userns" "golang.org/x/sys/unix" ) func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter { if format == OverlayWhiteoutFormat { return overlayWhiteoutConverter{} } return nil } type overlayWhiteoutConverter struct{} func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os.FileInfo) (wo *tar.Header, err error) { // convert whiteouts to AUFS format if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 { // we just rename the file and make it normal dir, filename := filepath.Split(hdr.Name) hdr.Name = filepath.Join(dir, WhiteoutPrefix+filename) hdr.Mode = 0o600 hdr.Typeflag = tar.TypeReg hdr.Size = 0 } if fi.Mode()&os.ModeDir != 0 { opaqueXattrName := "trusted.overlay.opaque" if userns.RunningInUserNS() { opaqueXattrName = "user.overlay.opaque" } // convert opaque dirs to AUFS format by writing an empty file with the prefix opaque, err := lgetxattr(path, opaqueXattrName) if err != nil { return nil, err } if len(opaque) == 1 && opaque[0] == 'y' { delete(hdr.PAXRecords, paxSchilyXattr+opaqueXattrName) // create a header for the whiteout file // it should inherit some properties from the parent, but be a regular file wo = &tar.Header{ Typeflag: tar.TypeReg, Mode: hdr.Mode & int64(os.ModePerm), Name: filepath.Join(hdr.Name, WhiteoutOpaqueDir), // #nosec G305 -- An archive is being created, not extracted. Size: 0, Uid: hdr.Uid, Uname: hdr.Uname, Gid: hdr.Gid, Gname: hdr.Gname, AccessTime: hdr.AccessTime, ChangeTime: hdr.ChangeTime, } } } return } func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (bool, error) { base := filepath.Base(path) dir := filepath.Dir(path) // if a directory is marked as opaque by the AUFS special file, we need to translate that to overlay if base == WhiteoutOpaqueDir { opaqueXattrName := "trusted.overlay.opaque" if userns.RunningInUserNS() { opaqueXattrName = "user.overlay.opaque" } err := unix.Setxattr(dir, opaqueXattrName, []byte{'y'}, 0) if err != nil { return false, fmt.Errorf("setxattr('%s', %s=y): %w", dir, opaqueXattrName, err) } // don't write the file itself return false, err } // if a file was deleted and we are using overlay, we need to create a character device if strings.HasPrefix(base, WhiteoutPrefix) { originalBase := base[len(WhiteoutPrefix):] originalPath := filepath.Join(dir, originalBase) if err := unix.Mknod(originalPath, unix.S_IFCHR, 0); err != nil { return false, fmt.Errorf("failed to mknod('%s', S_IFCHR, 0): %w", originalPath, err) } if err := os.Chown(originalPath, hdr.Uid, hdr.Gid); err != nil { return false, err } // don't write the file itself return false, nil } return true, nil } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/archive_other.go ================================================ //go:build !linux package archive func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter { return nil } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/archive_unix.go ================================================ //go:build !windows package archive import ( "archive/tar" "errors" "os" "path/filepath" "runtime" "strings" "syscall" "github.com/docker/docker/pkg/idtools" "golang.org/x/sys/unix" ) func init() { sysStat = statUnix } // addLongPathPrefix adds the Windows long path prefix to the path provided if // it does not already have it. It is a no-op on platforms other than Windows. func addLongPathPrefix(srcPath string) string { return srcPath } // getWalkRoot calculates the root path when performing a TarWithOptions. // We use a separate function as this is platform specific. On Linux, we // can't use filepath.Join(srcPath,include) because this will clean away // a trailing "." or "/" which may be important. func getWalkRoot(srcPath string, include string) string { return strings.TrimSuffix(srcPath, string(filepath.Separator)) + string(filepath.Separator) + include } // chmodTarEntry is used to adjust the file permissions used in tar header based // on the platform the archival is done. func chmodTarEntry(perm os.FileMode) os.FileMode { return perm // noop for unix as golang APIs provide perm bits correctly } // statUnix populates hdr from system-dependent fields of fi without performing // any OS lookups. func statUnix(fi os.FileInfo, hdr *tar.Header) error { // Devmajor and Devminor are only needed for special devices. // In FreeBSD, RDev for regular files is -1 (unless overridden by FS): // https://cgit.freebsd.org/src/tree/sys/kern/vfs_default.c?h=stable/13#n1531 // (NODEV is -1: https://cgit.freebsd.org/src/tree/sys/sys/param.h?h=stable/13#n241). // ZFS in particular does not override the default: // https://cgit.freebsd.org/src/tree/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops_os.c?h=stable/13#n2027 // Since `Stat_t.Rdev` is uint64, the cast turns -1 into (2^64 - 1). // Such large values cannot be encoded in a tar header. if runtime.GOOS == "freebsd" && hdr.Typeflag != tar.TypeBlock && hdr.Typeflag != tar.TypeChar { return nil } s, ok := fi.Sys().(*syscall.Stat_t) if !ok { return nil } hdr.Uid = int(s.Uid) hdr.Gid = int(s.Gid) if s.Mode&unix.S_IFBLK != 0 || s.Mode&unix.S_IFCHR != 0 { hdr.Devmajor = int64(unix.Major(uint64(s.Rdev))) //nolint: unconvert hdr.Devminor = int64(unix.Minor(uint64(s.Rdev))) //nolint: unconvert } return nil } func getInodeFromStat(stat interface{}) (inode uint64, err error) { s, ok := stat.(*syscall.Stat_t) if ok { inode = s.Ino } return } func getFileUIDGID(stat interface{}) (idtools.Identity, error) { s, ok := stat.(*syscall.Stat_t) if !ok { return idtools.Identity{}, errors.New("cannot convert stat value to syscall.Stat_t") } return idtools.Identity{UID: int(s.Uid), GID: int(s.Gid)}, nil } // handleTarTypeBlockCharFifo is an OS-specific helper function used by // createTarFile to handle the following types of header: Block; Char; Fifo. // // Creating device nodes is not supported when running in a user namespace, // produces a [syscall.EPERM] in most cases. func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error { mode := uint32(hdr.Mode & 0o7777) switch hdr.Typeflag { case tar.TypeBlock: mode |= unix.S_IFBLK case tar.TypeChar: mode |= unix.S_IFCHR case tar.TypeFifo: mode |= unix.S_IFIFO } return mknod(path, mode, unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor))) } func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error { if hdr.Typeflag == tar.TypeLink { if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { if err := os.Chmod(path, hdrInfo.Mode()); err != nil { return err } } } else if hdr.Typeflag != tar.TypeSymlink { if err := os.Chmod(path, hdrInfo.Mode()); err != nil { return err } } return nil } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/archive_windows.go ================================================ package archive import ( "archive/tar" "os" "path/filepath" "strings" "github.com/docker/docker/pkg/idtools" ) // longPathPrefix is the longpath prefix for Windows file paths. const longPathPrefix = `\\?\` // addLongPathPrefix adds the Windows long path prefix to the path provided if // it does not already have it. It is a no-op on platforms other than Windows. // // addLongPathPrefix is a copy of [github.com/docker/docker/pkg/longpath.AddPrefix]. func addLongPathPrefix(srcPath string) string { if strings.HasPrefix(srcPath, longPathPrefix) { return srcPath } if strings.HasPrefix(srcPath, `\\`) { // This is a UNC path, so we need to add 'UNC' to the path as well. return longPathPrefix + `UNC` + srcPath[1:] } return longPathPrefix + srcPath } // getWalkRoot calculates the root path when performing a TarWithOptions. // We use a separate function as this is platform specific. func getWalkRoot(srcPath string, include string) string { return filepath.Join(srcPath, include) } // chmodTarEntry is used to adjust the file permissions used in tar header based // on the platform the archival is done. func chmodTarEntry(perm os.FileMode) os.FileMode { // Remove group- and world-writable bits. perm &= 0o755 // Add the x bit: make everything +x on Windows return perm | 0o111 } func setHeaderForSpecialDevice(hdr *tar.Header, name string, stat interface{}) (err error) { // do nothing. no notion of Rdev, Nlink in stat on Windows return } func getInodeFromStat(stat interface{}) (inode uint64, err error) { // do nothing. no notion of Inode in stat on Windows return } // handleTarTypeBlockCharFifo is an OS-specific helper function used by // createTarFile to handle the following types of header: Block; Char; Fifo func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error { return nil } func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error { return nil } func getFileUIDGID(stat interface{}) (idtools.Identity, error) { // no notion of file ownership mapping yet on Windows return idtools.Identity{UID: 0, GID: 0}, nil } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/changes.go ================================================ package archive import ( "archive/tar" "bytes" "context" "fmt" "io" "io/fs" "os" "path/filepath" "sort" "strings" "time" "github.com/containerd/log" "github.com/docker/docker/pkg/idtools" ) // ChangeType represents the change type. type ChangeType int const ( ChangeModify = 0 // ChangeModify represents the modify operation. ChangeAdd = 1 // ChangeAdd represents the add operation. ChangeDelete = 2 // ChangeDelete represents the delete operation. ) func (c ChangeType) String() string { switch c { case ChangeModify: return "C" case ChangeAdd: return "A" case ChangeDelete: return "D" } return "" } // Change represents a change, it wraps the change type and path. // It describes changes of the files in the path respect to the // parent layers. The change could be modify, add, delete. // This is used for layer diff. type Change struct { Path string Kind ChangeType } func (change *Change) String() string { return fmt.Sprintf("%s %s", change.Kind, change.Path) } // for sort.Sort type changesByPath []Change func (c changesByPath) Less(i, j int) bool { return c[i].Path < c[j].Path } func (c changesByPath) Len() int { return len(c) } func (c changesByPath) Swap(i, j int) { c[j], c[i] = c[i], c[j] } // Gnu tar doesn't have sub-second mtime precision. The go tar // writer (1.10+) does when using PAX format, but we round times to seconds // to ensure archives have the same hashes for backwards compatibility. // See https://github.com/moby/moby/pull/35739/commits/fb170206ba12752214630b269a40ac7be6115ed4. // // Non-sub-second is problematic when we apply changes via tar // files. We handle this by comparing for exact times, *or* same // second count and either a or b having exactly 0 nanoseconds func sameFsTime(a, b time.Time) bool { return a.Equal(b) || (a.Unix() == b.Unix() && (a.Nanosecond() == 0 || b.Nanosecond() == 0)) } // Changes walks the path rw and determines changes for the files in the path, // with respect to the parent layers func Changes(layers []string, rw string) ([]Change, error) { return changes(layers, rw, aufsDeletedFile, aufsMetadataSkip) } func aufsMetadataSkip(path string) (skip bool, err error) { skip, err = filepath.Match(string(os.PathSeparator)+WhiteoutMetaPrefix+"*", path) if err != nil { skip = true } return } func aufsDeletedFile(root, path string, fi os.FileInfo) (string, error) { f := filepath.Base(path) // If there is a whiteout, then the file was removed if strings.HasPrefix(f, WhiteoutPrefix) { originalFile := f[len(WhiteoutPrefix):] return filepath.Join(filepath.Dir(path), originalFile), nil } return "", nil } type ( skipChange func(string) (bool, error) deleteChange func(string, string, os.FileInfo) (string, error) ) func changes(layers []string, rw string, dc deleteChange, sc skipChange) ([]Change, error) { var ( changes []Change changedDirs = make(map[string]struct{}) ) err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error { if err != nil { return err } // Rebase path path, err = filepath.Rel(rw, path) if err != nil { return err } // As this runs on the daemon side, file paths are OS specific. path = filepath.Join(string(os.PathSeparator), path) // Skip root if path == string(os.PathSeparator) { return nil } if sc != nil { if skip, err := sc(path); skip { return err } } change := Change{ Path: path, } deletedFile, err := dc(rw, path, f) if err != nil { return err } // Find out what kind of modification happened if deletedFile != "" { change.Path = deletedFile change.Kind = ChangeDelete } else { // Otherwise, the file was added change.Kind = ChangeAdd // ...Unless it already existed in a top layer, in which case, it's a modification for _, layer := range layers { stat, err := os.Stat(filepath.Join(layer, path)) if err != nil && !os.IsNotExist(err) { return err } if err == nil { // The file existed in the top layer, so that's a modification // However, if it's a directory, maybe it wasn't actually modified. // If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar if stat.IsDir() && f.IsDir() { if f.Size() == stat.Size() && f.Mode() == stat.Mode() && sameFsTime(f.ModTime(), stat.ModTime()) { // Both directories are the same, don't record the change return nil } } change.Kind = ChangeModify break } } } // If /foo/bar/file.txt is modified, then /foo/bar must be part of the changed files. // This block is here to ensure the change is recorded even if the // modify time, mode and size of the parent directory in the rw and ro layers are all equal. // Check https://github.com/docker/docker/pull/13590 for details. if f.IsDir() { changedDirs[path] = struct{}{} } if change.Kind == ChangeAdd || change.Kind == ChangeDelete { parent := filepath.Dir(path) if _, ok := changedDirs[parent]; !ok && parent != "/" { changes = append(changes, Change{Path: parent, Kind: ChangeModify}) changedDirs[parent] = struct{}{} } } // Record change changes = append(changes, change) return nil }) if err != nil && !os.IsNotExist(err) { return nil, err } return changes, nil } // FileInfo describes the information of a file. type FileInfo struct { parent *FileInfo name string stat fs.FileInfo children map[string]*FileInfo capability []byte added bool } // LookUp looks up the file information of a file. func (info *FileInfo) LookUp(path string) *FileInfo { // As this runs on the daemon side, file paths are OS specific. parent := info if path == string(os.PathSeparator) { return info } pathElements := strings.Split(path, string(os.PathSeparator)) for _, elem := range pathElements { if elem != "" { child := parent.children[elem] if child == nil { return nil } parent = child } } return parent } func (info *FileInfo) path() string { if info.parent == nil { // As this runs on the daemon side, file paths are OS specific. return string(os.PathSeparator) } return filepath.Join(info.parent.path(), info.name) } func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) { sizeAtEntry := len(*changes) if oldInfo == nil { // add change := Change{ Path: info.path(), Kind: ChangeAdd, } *changes = append(*changes, change) info.added = true } // We make a copy so we can modify it to detect additions // also, we only recurse on the old dir if the new info is a directory // otherwise any previous delete/change is considered recursive oldChildren := make(map[string]*FileInfo) if oldInfo != nil && info.isDir() { for k, v := range oldInfo.children { oldChildren[k] = v } } for name, newChild := range info.children { oldChild := oldChildren[name] if oldChild != nil { // change? oldStat := oldChild.stat newStat := newChild.stat // Note: We can't compare inode or ctime or blocksize here, because these change // when copying a file into a container. However, that is not generally a problem // because any content change will change mtime, and any status change should // be visible when actually comparing the stat fields. The only time this // breaks down is if some code intentionally hides a change by setting // back mtime if statDifferent(oldStat, newStat) || !bytes.Equal(oldChild.capability, newChild.capability) { change := Change{ Path: newChild.path(), Kind: ChangeModify, } *changes = append(*changes, change) newChild.added = true } // Remove from copy so we can detect deletions delete(oldChildren, name) } newChild.addChanges(oldChild, changes) } for _, oldChild := range oldChildren { // delete change := Change{ Path: oldChild.path(), Kind: ChangeDelete, } *changes = append(*changes, change) } // If there were changes inside this directory, we need to add it, even if the directory // itself wasn't changed. This is needed to properly save and restore filesystem permissions. // As this runs on the daemon side, file paths are OS specific. if len(*changes) > sizeAtEntry && info.isDir() && !info.added && info.path() != string(os.PathSeparator) { change := Change{ Path: info.path(), Kind: ChangeModify, } // Let's insert the directory entry before the recently added entries located inside this dir *changes = append(*changes, change) // just to resize the slice, will be overwritten copy((*changes)[sizeAtEntry+1:], (*changes)[sizeAtEntry:]) (*changes)[sizeAtEntry] = change } } // Changes add changes to file information. func (info *FileInfo) Changes(oldInfo *FileInfo) []Change { var changes []Change info.addChanges(oldInfo, &changes) return changes } func newRootFileInfo() *FileInfo { // As this runs on the daemon side, file paths are OS specific. root := &FileInfo{ name: string(os.PathSeparator), children: make(map[string]*FileInfo), } return root } // ChangesDirs compares two directories and generates an array of Change objects describing the changes. // If oldDir is "", then all files in newDir will be Add-Changes. func ChangesDirs(newDir, oldDir string) ([]Change, error) { var oldRoot, newRoot *FileInfo if oldDir == "" { emptyDir, err := os.MkdirTemp("", "empty") if err != nil { return nil, err } defer os.Remove(emptyDir) oldDir = emptyDir } oldRoot, newRoot, err := collectFileInfoForChanges(oldDir, newDir) if err != nil { return nil, err } return newRoot.Changes(oldRoot), nil } // ChangesSize calculates the size in bytes of the provided changes, based on newDir. func ChangesSize(newDir string, changes []Change) int64 { var ( size int64 sf = make(map[uint64]struct{}) ) for _, change := range changes { if change.Kind == ChangeModify || change.Kind == ChangeAdd { file := filepath.Join(newDir, change.Path) fileInfo, err := os.Lstat(file) if err != nil { log.G(context.TODO()).Errorf("Can not stat %q: %s", file, err) continue } if fileInfo != nil && !fileInfo.IsDir() { if hasHardlinks(fileInfo) { inode := getIno(fileInfo) if _, ok := sf[inode]; !ok { size += fileInfo.Size() sf[inode] = struct{}{} } } else { size += fileInfo.Size() } } } } return size } // ExportChanges produces an Archive from the provided changes, relative to dir. func ExportChanges(dir string, changes []Change, idMap idtools.IdentityMapping) (io.ReadCloser, error) { reader, writer := io.Pipe() go func() { ta := newTarAppender(idMap, writer, nil) sort.Sort(changesByPath(changes)) // In general we log errors here but ignore them because // during e.g. a diff operation the container can continue // mutating the filesystem and we can see transient errors // from this for _, change := range changes { if change.Kind == ChangeDelete { whiteOutDir := filepath.Dir(change.Path) whiteOutBase := filepath.Base(change.Path) whiteOut := filepath.Join(whiteOutDir, WhiteoutPrefix+whiteOutBase) timestamp := time.Now() hdr := &tar.Header{ Name: whiteOut[1:], Size: 0, ModTime: timestamp, AccessTime: timestamp, ChangeTime: timestamp, } if err := ta.TarWriter.WriteHeader(hdr); err != nil { log.G(context.TODO()).Debugf("Can't write whiteout header: %s", err) } } else { path := filepath.Join(dir, change.Path) if err := ta.addTarFile(path, change.Path[1:]); err != nil { log.G(context.TODO()).Debugf("Can't add file %s to tar: %s", path, err) } } } // Make sure to check the error on Close. if err := ta.TarWriter.Close(); err != nil { log.G(context.TODO()).Debugf("Can't close layer: %s", err) } if err := writer.Close(); err != nil { log.G(context.TODO()).Debugf("failed close Changes writer: %s", err) } }() return reader, nil } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/changes_linux.go ================================================ package archive import ( "fmt" "os" "path/filepath" "sort" "strings" "syscall" "unsafe" "golang.org/x/sys/unix" ) // walker is used to implement collectFileInfoForChanges on linux. Where this // method in general returns the entire contents of two directory trees, we // optimize some FS calls out on linux. In particular, we take advantage of the // fact that getdents(2) returns the inode of each file in the directory being // walked, which, when walking two trees in parallel to generate a list of // changes, can be used to prune subtrees without ever having to lstat(2) them // directly. Eliminating stat calls in this way can save up to seconds on large // images. type walker struct { dir1 string dir2 string root1 *FileInfo root2 *FileInfo } // collectFileInfoForChanges returns a complete representation of the trees // rooted at dir1 and dir2, with one important exception: any subtree or // leaf where the inode and device numbers are an exact match between dir1 // and dir2 will be pruned from the results. This method is *only* to be used // to generating a list of changes between the two directories, as it does not // reflect the full contents. func collectFileInfoForChanges(dir1, dir2 string) (*FileInfo, *FileInfo, error) { w := &walker{ dir1: dir1, dir2: dir2, root1: newRootFileInfo(), root2: newRootFileInfo(), } i1, err := os.Lstat(w.dir1) if err != nil { return nil, nil, err } i2, err := os.Lstat(w.dir2) if err != nil { return nil, nil, err } if err := w.walk("/", i1, i2); err != nil { return nil, nil, err } return w.root1, w.root2, nil } // Given a FileInfo, its path info, and a reference to the root of the tree // being constructed, register this file with the tree. func walkchunk(path string, fi os.FileInfo, dir string, root *FileInfo) error { if fi == nil { return nil } parent := root.LookUp(filepath.Dir(path)) if parent == nil { return fmt.Errorf("walkchunk: Unexpectedly no parent for %s", path) } info := &FileInfo{ name: filepath.Base(path), children: make(map[string]*FileInfo), parent: parent, } cpath := filepath.Join(dir, path) info.stat = fi info.capability, _ = lgetxattr(cpath, "security.capability") // lgetxattr(2): fs access parent.children[info.name] = info return nil } // Walk a subtree rooted at the same path in both trees being iterated. For // example, /docker/overlay/1234/a/b/c/d and /docker/overlay/8888/a/b/c/d func (w *walker) walk(path string, i1, i2 os.FileInfo) (err error) { // Register these nodes with the return trees, unless we're still at the // (already-created) roots: if path != "/" { if err := walkchunk(path, i1, w.dir1, w.root1); err != nil { return err } if err := walkchunk(path, i2, w.dir2, w.root2); err != nil { return err } } is1Dir := i1 != nil && i1.IsDir() is2Dir := i2 != nil && i2.IsDir() sameDevice := false if i1 != nil && i2 != nil { si1 := i1.Sys().(*syscall.Stat_t) si2 := i2.Sys().(*syscall.Stat_t) if si1.Dev == si2.Dev { sameDevice = true } } // If these files are both non-existent, or leaves (non-dirs), we are done. if !is1Dir && !is2Dir { return nil } // Fetch the names of all the files contained in both directories being walked: var names1, names2 []nameIno if is1Dir { names1, err = readdirnames(filepath.Join(w.dir1, path)) // getdents(2): fs access if err != nil { return err } } if is2Dir { names2, err = readdirnames(filepath.Join(w.dir2, path)) // getdents(2): fs access if err != nil { return err } } // We have lists of the files contained in both parallel directories, sorted // in the same order. Walk them in parallel, generating a unique merged list // of all items present in either or both directories. var names []string ix1 := 0 ix2 := 0 for { if ix1 >= len(names1) { break } if ix2 >= len(names2) { break } ni1 := names1[ix1] ni2 := names2[ix2] switch strings.Compare(ni1.name, ni2.name) { case -1: // ni1 < ni2 -- advance ni1 // we will not encounter ni1 in names2 names = append(names, ni1.name) ix1++ case 0: // ni1 == ni2 if ni1.ino != ni2.ino || !sameDevice { names = append(names, ni1.name) } ix1++ ix2++ case 1: // ni1 > ni2 -- advance ni2 // we will not encounter ni2 in names1 names = append(names, ni2.name) ix2++ } } for ix1 < len(names1) { names = append(names, names1[ix1].name) ix1++ } for ix2 < len(names2) { names = append(names, names2[ix2].name) ix2++ } // For each of the names present in either or both of the directories being // iterated, stat the name under each root, and recurse the pair of them: for _, name := range names { fname := filepath.Join(path, name) var cInfo1, cInfo2 os.FileInfo if is1Dir { cInfo1, err = os.Lstat(filepath.Join(w.dir1, fname)) // lstat(2): fs access if err != nil && !os.IsNotExist(err) { return err } } if is2Dir { cInfo2, err = os.Lstat(filepath.Join(w.dir2, fname)) // lstat(2): fs access if err != nil && !os.IsNotExist(err) { return err } } if err = w.walk(fname, cInfo1, cInfo2); err != nil { return err } } return nil } // {name,inode} pairs used to support the early-pruning logic of the walker type type nameIno struct { name string ino uint64 } type nameInoSlice []nameIno func (s nameInoSlice) Len() int { return len(s) } func (s nameInoSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s nameInoSlice) Less(i, j int) bool { return s[i].name < s[j].name } // readdirnames is a hacked-apart version of the Go stdlib code, exposing inode // numbers further up the stack when reading directory contents. Unlike // os.Readdirnames, which returns a list of filenames, this function returns a // list of {filename,inode} pairs. func readdirnames(dirname string) (names []nameIno, err error) { var ( size = 100 buf = make([]byte, 4096) nbuf int bufp int nb int ) f, err := os.Open(dirname) if err != nil { return nil, err } defer f.Close() names = make([]nameIno, 0, size) // Empty with room to grow. for { // Refill the buffer if necessary if bufp >= nbuf { bufp = 0 nbuf, err = unix.ReadDirent(int(f.Fd()), buf) // getdents on linux if nbuf < 0 { nbuf = 0 } if err != nil { return nil, os.NewSyscallError("readdirent", err) } if nbuf <= 0 { break // EOF } } // Drain the buffer nb, names = parseDirent(buf[bufp:nbuf], names) bufp += nb } sl := nameInoSlice(names) sort.Sort(sl) return sl, nil } // parseDirent is a minor modification of unix.ParseDirent (linux version) // which returns {name,inode} pairs instead of just names. func parseDirent(buf []byte, names []nameIno) (consumed int, newnames []nameIno) { origlen := len(buf) for len(buf) > 0 { dirent := (*unix.Dirent)(unsafe.Pointer(&buf[0])) // #nosec G103 -- Ignore "G103: Use of unsafe calls should be audited" buf = buf[dirent.Reclen:] if dirent.Ino == 0 { // File absent in directory. continue } b := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0])) // #nosec G103 -- Ignore "G103: Use of unsafe calls should be audited" name := string(b[0:clen(b[:])]) if name == "." || name == ".." { // Useless names continue } names = append(names, nameIno{name, dirent.Ino}) } return origlen - len(buf), names } func clen(n []byte) int { for i := 0; i < len(n); i++ { if n[i] == 0 { return i } } return len(n) } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/changes_other.go ================================================ //go:build !linux package archive import ( "fmt" "os" "path/filepath" "runtime" "strings" ) func collectFileInfoForChanges(oldDir, newDir string) (*FileInfo, *FileInfo, error) { var ( oldRoot, newRoot *FileInfo err1, err2 error errs = make(chan error, 2) ) go func() { oldRoot, err1 = collectFileInfo(oldDir) errs <- err1 }() go func() { newRoot, err2 = collectFileInfo(newDir) errs <- err2 }() // block until both routines have returned for i := 0; i < 2; i++ { if err := <-errs; err != nil { return nil, nil, err } } return oldRoot, newRoot, nil } func collectFileInfo(sourceDir string) (*FileInfo, error) { root := newRootFileInfo() err := filepath.WalkDir(sourceDir, func(path string, _ os.DirEntry, err error) error { if err != nil { return err } // Rebase path relPath, err := filepath.Rel(sourceDir, path) if err != nil { return err } // As this runs on the daemon side, file paths are OS specific. relPath = filepath.Join(string(os.PathSeparator), relPath) // See https://github.com/golang/go/issues/9168 - bug in filepath.Join. // Temporary workaround. If the returned path starts with two backslashes, // trim it down to a single backslash. Only relevant on Windows. if runtime.GOOS == "windows" { if strings.HasPrefix(relPath, `\\`) { relPath = relPath[1:] } } if relPath == string(os.PathSeparator) { return nil } parent := root.LookUp(filepath.Dir(relPath)) if parent == nil { return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) } s, err := os.Lstat(path) if err != nil { return err } info := &FileInfo{ name: filepath.Base(relPath), children: make(map[string]*FileInfo), parent: parent, stat: s, } info.capability, _ = lgetxattr(path, "security.capability") parent.children[info.name] = info return nil }) if err != nil { return nil, err } return root, nil } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/changes_unix.go ================================================ //go:build !windows package archive import ( "io/fs" "os" "syscall" ) func statDifferent(oldStat fs.FileInfo, newStat fs.FileInfo) bool { oldSys := oldStat.Sys().(*syscall.Stat_t) newSys := newStat.Sys().(*syscall.Stat_t) // Don't look at size for dirs, its not a good measure of change if oldStat.Mode() != newStat.Mode() || oldSys.Uid != newSys.Uid || oldSys.Gid != newSys.Gid || oldSys.Rdev != newSys.Rdev || // Don't look at size or modification time for dirs, its not a good // measure of change. See https://github.com/moby/moby/issues/9874 // for a description of the issue with modification time, and // https://github.com/moby/moby/pull/11422 for the change. // (Note that in the Windows implementation of this function, // modification time IS taken as a change). See // https://github.com/moby/moby/pull/37982 for more information. (!oldStat.Mode().IsDir() && (!sameFsTime(oldStat.ModTime(), newStat.ModTime()) || (oldStat.Size() != newStat.Size()))) { return true } return false } func (info *FileInfo) isDir() bool { return info.parent == nil || info.stat.Mode().IsDir() } func getIno(fi os.FileInfo) uint64 { return fi.Sys().(*syscall.Stat_t).Ino } func hasHardlinks(fi os.FileInfo) bool { return fi.Sys().(*syscall.Stat_t).Nlink > 1 } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/changes_windows.go ================================================ package archive import ( "io/fs" "os" ) func statDifferent(oldStat fs.FileInfo, newStat fs.FileInfo) bool { // Note there is slight difference between the Linux and Windows // implementations here. Due to https://github.com/moby/moby/issues/9874, // and the fix at https://github.com/moby/moby/pull/11422, Linux does not // consider a change to the directory time as a change. Windows on NTFS // does. See https://github.com/moby/moby/pull/37982 for more information. if !sameFsTime(oldStat.ModTime(), newStat.ModTime()) || oldStat.Mode() != newStat.Mode() || oldStat.Size() != newStat.Size() && !oldStat.Mode().IsDir() { return true } return false } func (info *FileInfo) isDir() bool { return info.parent == nil || info.stat.Mode().IsDir() } func getIno(fi os.FileInfo) (inode uint64) { return } func hasHardlinks(fi os.FileInfo) bool { return false } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/copy.go ================================================ package archive import ( "archive/tar" "context" "errors" "io" "os" "path/filepath" "strings" "sync" "github.com/containerd/log" ) // Errors used or returned by this file. var ( ErrNotDirectory = errors.New("not a directory") ErrDirNotExists = errors.New("no such directory") ErrCannotCopyDir = errors.New("cannot copy directory") ErrInvalidCopySource = errors.New("invalid copy source content") ) var copyPool = sync.Pool{ New: func() interface{} { s := make([]byte, 32*1024); return &s }, } func copyWithBuffer(dst io.Writer, src io.Reader) (written int64, err error) { buf := copyPool.Get().(*[]byte) written, err = io.CopyBuffer(dst, src, *buf) copyPool.Put(buf) return } // PreserveTrailingDotOrSeparator returns the given cleaned path (after // processing using any utility functions from the path or filepath stdlib // packages) and appends a trailing `/.` or `/` if its corresponding original // path (from before being processed by utility functions from the path or // filepath stdlib packages) ends with a trailing `/.` or `/`. If the cleaned // path already ends in a `.` path segment, then another is not added. If the // clean path already ends in a path separator, then another is not added. func PreserveTrailingDotOrSeparator(cleanedPath string, originalPath string) string { // Ensure paths are in platform semantics cleanedPath = normalizePath(cleanedPath) originalPath = normalizePath(originalPath) if !specifiesCurrentDir(cleanedPath) && specifiesCurrentDir(originalPath) { if !hasTrailingPathSeparator(cleanedPath) { // Add a separator if it doesn't already end with one (a cleaned // path would only end in a separator if it is the root). cleanedPath += string(filepath.Separator) } cleanedPath += "." } if !hasTrailingPathSeparator(cleanedPath) && hasTrailingPathSeparator(originalPath) { cleanedPath += string(filepath.Separator) } return cleanedPath } // assertsDirectory returns whether the given path is // asserted to be a directory, i.e., the path ends with // a trailing '/' or `/.`, assuming a path separator of `/`. func assertsDirectory(path string) bool { return hasTrailingPathSeparator(path) || specifiesCurrentDir(path) } // hasTrailingPathSeparator returns whether the given // path ends with the system's path separator character. func hasTrailingPathSeparator(path string) bool { return len(path) > 0 && path[len(path)-1] == filepath.Separator } // specifiesCurrentDir returns whether the given path specifies // a "current directory", i.e., the last path segment is `.`. func specifiesCurrentDir(path string) bool { return filepath.Base(path) == "." } // SplitPathDirEntry splits the given path between its directory name and its // basename by first cleaning the path but preserves a trailing "." if the // original path specified the current directory. func SplitPathDirEntry(path string) (dir, base string) { cleanedPath := filepath.Clean(filepath.FromSlash(path)) if specifiesCurrentDir(path) { cleanedPath += string(os.PathSeparator) + "." } return filepath.Dir(cleanedPath), filepath.Base(cleanedPath) } // TarResource archives the resource described by the given CopyInfo to a Tar // archive. A non-nil error is returned if sourcePath does not exist or is // asserted to be a directory but exists as another type of file. // // This function acts as a convenient wrapper around TarWithOptions, which // requires a directory as the source path. TarResource accepts either a // directory or a file path and correctly sets the Tar options. func TarResource(sourceInfo CopyInfo) (content io.ReadCloser, err error) { return TarResourceRebase(sourceInfo.Path, sourceInfo.RebaseName) } // TarResourceRebase is like TarResource but renames the first path element of // items in the resulting tar archive to match the given rebaseName if not "". func TarResourceRebase(sourcePath, rebaseName string) (content io.ReadCloser, err error) { sourcePath = normalizePath(sourcePath) if _, err = os.Lstat(sourcePath); err != nil { // Catches the case where the source does not exist or is not a // directory if asserted to be a directory, as this also causes an // error. return } // Separate the source path between its directory and // the entry in that directory which we are archiving. sourceDir, sourceBase := SplitPathDirEntry(sourcePath) opts := TarResourceRebaseOpts(sourceBase, rebaseName) log.G(context.TODO()).Debugf("copying %q from %q", sourceBase, sourceDir) return TarWithOptions(sourceDir, opts) } // TarResourceRebaseOpts does not preform the Tar, but instead just creates the rebase // parameters to be sent to TarWithOptions (the TarOptions struct) func TarResourceRebaseOpts(sourceBase string, rebaseName string) *TarOptions { filter := []string{sourceBase} return &TarOptions{ Compression: Uncompressed, IncludeFiles: filter, IncludeSourceDir: true, RebaseNames: map[string]string{ sourceBase: rebaseName, }, } } // CopyInfo holds basic info about the source // or destination path of a copy operation. type CopyInfo struct { Path string Exists bool IsDir bool RebaseName string } // CopyInfoSourcePath stats the given path to create a CopyInfo // struct representing that resource for the source of an archive copy // operation. The given path should be an absolute local path. A source path // has all symlinks evaluated that appear before the last path separator ("/" // on Unix). As it is to be a copy source, the path must exist. func CopyInfoSourcePath(path string, followLink bool) (CopyInfo, error) { // normalize the file path and then evaluate the symbol link // we will use the target file instead of the symbol link if // followLink is set path = normalizePath(path) resolvedPath, rebaseName, err := ResolveHostSourcePath(path, followLink) if err != nil { return CopyInfo{}, err } stat, err := os.Lstat(resolvedPath) if err != nil { return CopyInfo{}, err } return CopyInfo{ Path: resolvedPath, Exists: true, IsDir: stat.IsDir(), RebaseName: rebaseName, }, nil } // CopyInfoDestinationPath stats the given path to create a CopyInfo // struct representing that resource for the destination of an archive copy // operation. The given path should be an absolute local path. func CopyInfoDestinationPath(path string) (info CopyInfo, err error) { maxSymlinkIter := 10 // filepath.EvalSymlinks uses 255, but 10 already seems like a lot. path = normalizePath(path) originalPath := path stat, err := os.Lstat(path) if err == nil && stat.Mode()&os.ModeSymlink == 0 { // The path exists and is not a symlink. return CopyInfo{ Path: path, Exists: true, IsDir: stat.IsDir(), }, nil } // While the path is a symlink. for n := 0; err == nil && stat.Mode()&os.ModeSymlink != 0; n++ { if n > maxSymlinkIter { // Don't follow symlinks more than this arbitrary number of times. return CopyInfo{}, errors.New("too many symlinks in " + originalPath) } // The path is a symbolic link. We need to evaluate it so that the // destination of the copy operation is the link target and not the // link itself. This is notably different than CopyInfoSourcePath which // only evaluates symlinks before the last appearing path separator. // Also note that it is okay if the last path element is a broken // symlink as the copy operation should create the target. var linkTarget string linkTarget, err = os.Readlink(path) if err != nil { return CopyInfo{}, err } if !filepath.IsAbs(linkTarget) { // Join with the parent directory. dstParent, _ := SplitPathDirEntry(path) linkTarget = filepath.Join(dstParent, linkTarget) } path = linkTarget stat, err = os.Lstat(path) } if err != nil { // It's okay if the destination path doesn't exist. We can still // continue the copy operation if the parent directory exists. if !os.IsNotExist(err) { return CopyInfo{}, err } // Ensure destination parent dir exists. dstParent, _ := SplitPathDirEntry(path) parentDirStat, err := os.Stat(dstParent) if err != nil { return CopyInfo{}, err } if !parentDirStat.IsDir() { return CopyInfo{}, ErrNotDirectory } return CopyInfo{Path: path}, nil } // The path exists after resolving symlinks. return CopyInfo{ Path: path, Exists: true, IsDir: stat.IsDir(), }, nil } // PrepareArchiveCopy prepares the given srcContent archive, which should // contain the archived resource described by srcInfo, to the destination // described by dstInfo. Returns the possibly modified content archive along // with the path to the destination directory which it should be extracted to. func PrepareArchiveCopy(srcContent io.Reader, srcInfo, dstInfo CopyInfo) (dstDir string, content io.ReadCloser, err error) { // Ensure in platform semantics srcInfo.Path = normalizePath(srcInfo.Path) dstInfo.Path = normalizePath(dstInfo.Path) // Separate the destination path between its directory and base // components in case the source archive contents need to be rebased. dstDir, dstBase := SplitPathDirEntry(dstInfo.Path) _, srcBase := SplitPathDirEntry(srcInfo.Path) switch { case dstInfo.Exists && dstInfo.IsDir: // The destination exists as a directory. No alteration // to srcContent is needed as its contents can be // simply extracted to the destination directory. return dstInfo.Path, io.NopCloser(srcContent), nil case dstInfo.Exists && srcInfo.IsDir: // The destination exists as some type of file and the source // content is a directory. This is an error condition since // you cannot copy a directory to an existing file location. return "", nil, ErrCannotCopyDir case dstInfo.Exists: // The destination exists as some type of file and the source content // is also a file. The source content entry will have to be renamed to // have a basename which matches the destination path's basename. if len(srcInfo.RebaseName) != 0 { srcBase = srcInfo.RebaseName } return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil case srcInfo.IsDir: // The destination does not exist and the source content is an archive // of a directory. The archive should be extracted to the parent of // the destination path instead, and when it is, the directory that is // created as a result should take the name of the destination path. // The source content entries will have to be renamed to have a // basename which matches the destination path's basename. if len(srcInfo.RebaseName) != 0 { srcBase = srcInfo.RebaseName } return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil case assertsDirectory(dstInfo.Path): // The destination does not exist and is asserted to be created as a // directory, but the source content is not a directory. This is an // error condition since you cannot create a directory from a file // source. return "", nil, ErrDirNotExists default: // The last remaining case is when the destination does not exist, is // not asserted to be a directory, and the source content is not an // archive of a directory. It this case, the destination file will need // to be created when the archive is extracted and the source content // entry will have to be renamed to have a basename which matches the // destination path's basename. if len(srcInfo.RebaseName) != 0 { srcBase = srcInfo.RebaseName } return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil } } // RebaseArchiveEntries rewrites the given srcContent archive replacing // an occurrence of oldBase with newBase at the beginning of entry names. func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.ReadCloser { if oldBase == string(os.PathSeparator) { // If oldBase specifies the root directory, use an empty string as // oldBase instead so that newBase doesn't replace the path separator // that all paths will start with. oldBase = "" } rebased, w := io.Pipe() go func() { srcTar := tar.NewReader(srcContent) rebasedTar := tar.NewWriter(w) for { hdr, err := srcTar.Next() if err == io.EOF { // Signals end of archive. rebasedTar.Close() w.Close() return } if err != nil { w.CloseWithError(err) return } // srcContent tar stream, as served by TarWithOptions(), is // definitely in PAX format, but tar.Next() mistakenly guesses it // as USTAR, which creates a problem: if the newBase is >100 // characters long, WriteHeader() returns an error like // "archive/tar: cannot encode header: Format specifies USTAR; and USTAR cannot encode Name=...". // // To fix, set the format to PAX here. See docker/for-linux issue #484. hdr.Format = tar.FormatPAX hdr.Name = strings.Replace(hdr.Name, oldBase, newBase, 1) if hdr.Typeflag == tar.TypeLink { hdr.Linkname = strings.Replace(hdr.Linkname, oldBase, newBase, 1) } if err = rebasedTar.WriteHeader(hdr); err != nil { w.CloseWithError(err) return } // Ignoring GoSec G110. See https://github.com/securego/gosec/pull/433 // and https://cure53.de/pentest-report_opa.pdf, which recommends to // replace io.Copy with io.CopyN7. The latter allows to specify the // maximum number of bytes that should be read. By properly defining // the limit, it can be assured that a GZip compression bomb cannot // easily cause a Denial-of-Service. // After reviewing with @tonistiigi and @cpuguy83, this should not // affect us, because here we do not read into memory, hence should // not be vulnerable to this code consuming memory. //nolint:gosec // G110: Potential DoS vulnerability via decompression bomb (gosec) if _, err = io.Copy(rebasedTar, srcTar); err != nil { w.CloseWithError(err) return } } }() return rebased } // CopyResource performs an archive copy from the given source path to the // given destination path. The source path MUST exist and the destination // path's parent directory must exist. func CopyResource(srcPath, dstPath string, followLink bool) error { var ( srcInfo CopyInfo err error ) // Ensure in platform semantics srcPath = normalizePath(srcPath) dstPath = normalizePath(dstPath) // Clean the source and destination paths. srcPath = PreserveTrailingDotOrSeparator(filepath.Clean(srcPath), srcPath) dstPath = PreserveTrailingDotOrSeparator(filepath.Clean(dstPath), dstPath) if srcInfo, err = CopyInfoSourcePath(srcPath, followLink); err != nil { return err } content, err := TarResource(srcInfo) if err != nil { return err } defer content.Close() return CopyTo(content, srcInfo, dstPath) } // CopyTo handles extracting the given content whose // entries should be sourced from srcInfo to dstPath. func CopyTo(content io.Reader, srcInfo CopyInfo, dstPath string) error { // The destination path need not exist, but CopyInfoDestinationPath will // ensure that at least the parent directory exists. dstInfo, err := CopyInfoDestinationPath(normalizePath(dstPath)) if err != nil { return err } dstDir, copyArchive, err := PrepareArchiveCopy(content, srcInfo, dstInfo) if err != nil { return err } defer copyArchive.Close() options := &TarOptions{ NoLchown: true, NoOverwriteDirNonDir: true, } return Untar(copyArchive, dstDir, options) } // ResolveHostSourcePath decides real path need to be copied with parameters such as // whether to follow symbol link or not, if followLink is true, resolvedPath will return // link target of any symbol link file, else it will only resolve symlink of directory // but return symbol link file itself without resolving. func ResolveHostSourcePath(path string, followLink bool) (resolvedPath, rebaseName string, err error) { if followLink { resolvedPath, err = filepath.EvalSymlinks(path) if err != nil { return } resolvedPath, rebaseName = GetRebaseName(path, resolvedPath) } else { dirPath, basePath := filepath.Split(path) // if not follow symbol link, then resolve symbol link of parent dir var resolvedDirPath string resolvedDirPath, err = filepath.EvalSymlinks(dirPath) if err != nil { return } // resolvedDirPath will have been cleaned (no trailing path separators) so // we can manually join it with the base path element. resolvedPath = resolvedDirPath + string(filepath.Separator) + basePath if hasTrailingPathSeparator(path) && filepath.Base(path) != filepath.Base(resolvedPath) { rebaseName = filepath.Base(path) } } return resolvedPath, rebaseName, nil } // GetRebaseName normalizes and compares path and resolvedPath, // return completed resolved path and rebased file name func GetRebaseName(path, resolvedPath string) (string, string) { // linkTarget will have been cleaned (no trailing path separators and dot) so // we can manually join it with them var rebaseName string if specifiesCurrentDir(path) && !specifiesCurrentDir(resolvedPath) { resolvedPath += string(filepath.Separator) + "." } if hasTrailingPathSeparator(path) && !hasTrailingPathSeparator(resolvedPath) { resolvedPath += string(filepath.Separator) } if filepath.Base(path) != filepath.Base(resolvedPath) { // In the case where the path had a trailing separator and a symlink // evaluation has changed the last path component, we will need to // rebase the name in the archive that is being copied to match the // originally requested name. rebaseName = filepath.Base(path) } return resolvedPath, rebaseName } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/copy_unix.go ================================================ //go:build !windows package archive import ( "path/filepath" ) func normalizePath(path string) string { return filepath.ToSlash(path) } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/copy_windows.go ================================================ package archive import ( "path/filepath" ) func normalizePath(path string) string { return filepath.FromSlash(path) } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/dev_freebsd.go ================================================ //go:build freebsd package archive import "golang.org/x/sys/unix" var mknod = unix.Mknod ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/dev_unix.go ================================================ //go:build !windows && !freebsd package archive import "golang.org/x/sys/unix" func mknod(path string, mode uint32, dev uint64) error { return unix.Mknod(path, mode, int(dev)) } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/diff.go ================================================ package archive import ( "archive/tar" "context" "fmt" "io" "os" "path/filepath" "runtime" "strings" "github.com/containerd/log" ) // UnpackLayer unpack `layer` to a `dest`. The stream `layer` can be // compressed or uncompressed. // Returns the size in bytes of the contents of the layer. func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, err error) { tr := tar.NewReader(layer) var dirs []*tar.Header unpackedPaths := make(map[string]struct{}) if options == nil { options = &TarOptions{} } if options.ExcludePatterns == nil { options.ExcludePatterns = []string{} } aufsTempdir := "" aufsHardlinks := make(map[string]*tar.Header) // Iterate through the files in the archive. for { hdr, err := tr.Next() if err == io.EOF { // end of tar archive break } if err != nil { return 0, err } size += hdr.Size // Normalize name, for safety and for a simple is-root check hdr.Name = filepath.Clean(hdr.Name) // Windows does not support filenames with colons in them. Ignore // these files. This is not a problem though (although it might // appear that it is). Let's suppose a client is running docker pull. // The daemon it points to is Windows. Would it make sense for the // client to be doing a docker pull Ubuntu for example (which has files // with colons in the name under /usr/share/man/man3)? No, absolutely // not as it would really only make sense that they were pulling a // Windows image. However, for development, it is necessary to be able // to pull Linux images which are in the repository. // // TODO Windows. Once the registry is aware of what images are Windows- // specific or Linux-specific, this warning should be changed to an error // to cater for the situation where someone does manage to upload a Linux // image but have it tagged as Windows inadvertently. if runtime.GOOS == "windows" { if strings.Contains(hdr.Name, ":") { log.G(context.TODO()).Warnf("Windows: Ignoring %s (is this a Linux image?)", hdr.Name) continue } } // Ensure that the parent directory exists. err = createImpliedDirectories(dest, hdr, options) if err != nil { return 0, err } // Skip AUFS metadata dirs if strings.HasPrefix(hdr.Name, WhiteoutMetaPrefix) { // Regular files inside /.wh..wh.plnk can be used as hardlink targets // We don't want this directory, but we need the files in them so that // such hardlinks can be resolved. if strings.HasPrefix(hdr.Name, WhiteoutLinkDir) && hdr.Typeflag == tar.TypeReg { basename := filepath.Base(hdr.Name) aufsHardlinks[basename] = hdr if aufsTempdir == "" { if aufsTempdir, err = os.MkdirTemp(dest, "dockerplnk"); err != nil { return 0, err } defer os.RemoveAll(aufsTempdir) } if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, options); err != nil { return 0, err } } if hdr.Name != WhiteoutOpaqueDir { continue } } // #nosec G305 -- The joined path is guarded against path traversal. path := filepath.Join(dest, hdr.Name) rel, err := filepath.Rel(dest, path) if err != nil { return 0, err } // Note as these operations are platform specific, so must the slash be. if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest)) } base := filepath.Base(path) if strings.HasPrefix(base, WhiteoutPrefix) { dir := filepath.Dir(path) if base == WhiteoutOpaqueDir { _, err := os.Lstat(dir) if err != nil { return 0, err } err = filepath.WalkDir(dir, func(path string, info os.DirEntry, err error) error { if err != nil { if os.IsNotExist(err) { err = nil // parent was deleted } return err } if path == dir { return nil } if _, exists := unpackedPaths[path]; !exists { return os.RemoveAll(path) } return nil }) if err != nil { return 0, err } } else { originalBase := base[len(WhiteoutPrefix):] originalPath := filepath.Join(dir, originalBase) if err := os.RemoveAll(originalPath); err != nil { return 0, err } } } else { // If path exits we almost always just want to remove and replace it. // The only exception is when it is a directory *and* the file from // the layer is also a directory. Then we want to merge them (i.e. // just apply the metadata from the layer). if fi, err := os.Lstat(path); err == nil { if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) { if err := os.RemoveAll(path); err != nil { return 0, err } } } srcData := io.Reader(tr) srcHdr := hdr // Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so // we manually retarget these into the temporary files we extracted them into if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), WhiteoutLinkDir) { linkBasename := filepath.Base(hdr.Linkname) srcHdr = aufsHardlinks[linkBasename] if srcHdr == nil { return 0, fmt.Errorf("Invalid aufs hardlink") } tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename)) if err != nil { return 0, err } defer tmpFile.Close() srcData = tmpFile } if err := remapIDs(options.IDMap, srcHdr); err != nil { return 0, err } if err := createTarFile(path, dest, srcHdr, srcData, options); err != nil { return 0, err } // Directory mtimes must be handled at the end to avoid further // file creation in them to modify the directory mtime if hdr.Typeflag == tar.TypeDir { dirs = append(dirs, hdr) } unpackedPaths[path] = struct{}{} } } for _, hdr := range dirs { // #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice. path := filepath.Join(dest, hdr.Name) if err := chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil { return 0, err } } return size, nil } // ApplyLayer parses a diff in the standard layer format from `layer`, // and applies it to the directory `dest`. The stream `layer` can be // compressed or uncompressed. // Returns the size in bytes of the contents of the layer. func ApplyLayer(dest string, layer io.Reader) (int64, error) { return applyLayerHandler(dest, layer, &TarOptions{}, true) } // ApplyUncompressedLayer parses a diff in the standard layer format from // `layer`, and applies it to the directory `dest`. The stream `layer` // can only be uncompressed. // Returns the size in bytes of the contents of the layer. func ApplyUncompressedLayer(dest string, layer io.Reader, options *TarOptions) (int64, error) { return applyLayerHandler(dest, layer, options, false) } // IsEmpty checks if the tar archive is empty (doesn't contain any entries). func IsEmpty(rd io.Reader) (bool, error) { decompRd, err := DecompressStream(rd) if err != nil { return true, fmt.Errorf("failed to decompress archive: %v", err) } defer decompRd.Close() tarReader := tar.NewReader(decompRd) if _, err := tarReader.Next(); err != nil { if err == io.EOF { return true, nil } return false, fmt.Errorf("failed to read next archive header: %v", err) } return false, nil } // do the bulk load of ApplyLayer, but allow for not calling DecompressStream func applyLayerHandler(dest string, layer io.Reader, options *TarOptions, decompress bool) (int64, error) { dest = filepath.Clean(dest) // We need to be able to set any perms restore := overrideUmask(0) defer restore() if decompress { decompLayer, err := DecompressStream(layer) if err != nil { return 0, err } defer decompLayer.Close() layer = decompLayer } return UnpackLayer(dest, layer, options) } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/diff_unix.go ================================================ //go:build !windows package archive import "golang.org/x/sys/unix" // overrideUmask sets current process's file mode creation mask to newmask // and returns a function to restore it. // // WARNING for readers stumbling upon this code. Changing umask in a multi- // threaded environment isn't safe. Don't use this without understanding the // risks, and don't export this function for others to use (we shouldn't even // be using this ourself). // // FIXME(thaJeztah): we should get rid of these hacks if possible. func overrideUmask(newMask int) func() { oldMask := unix.Umask(newMask) return func() { unix.Umask(oldMask) } } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/diff_windows.go ================================================ package archive // overrideUmask is a no-op on windows. func overrideUmask(newmask int) func() { return func() {} } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/path.go ================================================ package archive // CheckSystemDriveAndRemoveDriveLetter verifies that a path, if it includes a drive letter, // is the system drive. // On Linux: this is a no-op. // On Windows: this does the following> // CheckSystemDriveAndRemoveDriveLetter verifies and manipulates a Windows path. // This is used, for example, when validating a user provided path in docker cp. // If a drive letter is supplied, it must be the system drive. The drive letter // is always removed. Also, it translates it to OS semantics (IOW / to \). We // need the path in this syntax so that it can ultimately be concatenated with // a Windows long-path which doesn't support drive-letters. Examples: // C: --> Fail // C:\ --> \ // a --> a // /a --> \a // d:\ --> Fail func CheckSystemDriveAndRemoveDriveLetter(path string) (string, error) { return checkSystemDriveAndRemoveDriveLetter(path) } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/path_unix.go ================================================ //go:build !windows package archive // checkSystemDriveAndRemoveDriveLetter is the non-Windows implementation // of CheckSystemDriveAndRemoveDriveLetter func checkSystemDriveAndRemoveDriveLetter(path string) (string, error) { return path, nil } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/path_windows.go ================================================ package archive import ( "fmt" "path/filepath" "strings" ) // checkSystemDriveAndRemoveDriveLetter is the Windows implementation // of CheckSystemDriveAndRemoveDriveLetter func checkSystemDriveAndRemoveDriveLetter(path string) (string, error) { if len(path) == 2 && string(path[1]) == ":" { return "", fmt.Errorf("no relative path specified in %q", path) } if !filepath.IsAbs(path) || len(path) < 2 { return filepath.FromSlash(path), nil } if string(path[1]) == ":" && !strings.EqualFold(string(path[0]), "c") { return "", fmt.Errorf("the specified path is not on the system drive (C:)") } return filepath.FromSlash(path[2:]), nil } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/time.go ================================================ package archive import ( "syscall" "time" "unsafe" ) var ( minTime = time.Unix(0, 0) maxTime time.Time ) func init() { if unsafe.Sizeof(syscall.Timespec{}.Nsec) == 8 { // This is a 64 bit timespec // os.Chtimes limits time to the following maxTime = time.Unix(0, 1<<63-1) } else { // This is a 32 bit timespec maxTime = time.Unix(1<<31-1, 0) } } func boundTime(t time.Time) time.Time { if t.Before(minTime) || t.After(maxTime) { return minTime } return t } func latestTime(t1, t2 time.Time) time.Time { if t1.Before(t2) { return t2 } return t1 } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/time_nonwindows.go ================================================ //go:build !windows package archive import ( "os" "time" "golang.org/x/sys/unix" ) // chtimes changes the access time and modified time of a file at the given path. // If the modified time is prior to the Unix Epoch (unixMinTime), or after the // end of Unix Time (unixEpochTime), os.Chtimes has undefined behavior. In this // case, Chtimes defaults to Unix Epoch, just in case. func chtimes(name string, atime time.Time, mtime time.Time) error { return os.Chtimes(name, atime, mtime) } func timeToTimespec(time time.Time) (ts unix.Timespec) { if time.IsZero() { // Return UTIME_OMIT special value ts.Sec = 0 ts.Nsec = (1 << 30) - 2 return } return unix.NsecToTimespec(time.UnixNano()) } func lchtimes(name string, atime time.Time, mtime time.Time) error { utimes := [2]unix.Timespec{ timeToTimespec(atime), timeToTimespec(mtime), } err := unix.UtimesNanoAt(unix.AT_FDCWD, name, utimes[0:], unix.AT_SYMLINK_NOFOLLOW) if err != nil && err != unix.ENOSYS { return err } return err } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/time_windows.go ================================================ package archive import ( "os" "time" "golang.org/x/sys/windows" ) func chtimes(name string, atime time.Time, mtime time.Time) error { if err := os.Chtimes(name, atime, mtime); err != nil { return err } pathp, err := windows.UTF16PtrFromString(name) if err != nil { return err } h, err := windows.CreateFile(pathp, windows.FILE_WRITE_ATTRIBUTES, windows.FILE_SHARE_WRITE, nil, windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS, 0) if err != nil { return err } defer windows.Close(h) c := windows.NsecToFiletime(mtime.UnixNano()) return windows.SetFileTime(h, &c, nil, nil) } func lchtimes(name string, atime time.Time, mtime time.Time) error { return nil } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/whiteouts.go ================================================ package archive // Whiteouts are files with a special meaning for the layered filesystem. // Docker uses AUFS whiteout files inside exported archives. In other // filesystems these files are generated/handled on tar creation/extraction. // WhiteoutPrefix prefix means file is a whiteout. If this is followed by a // filename this means that file has been removed from the base layer. const WhiteoutPrefix = ".wh." // WhiteoutMetaPrefix prefix means whiteout has a special meaning and is not // for removing an actual file. Normally these files are excluded from exported // archives. const WhiteoutMetaPrefix = WhiteoutPrefix + WhiteoutPrefix // WhiteoutLinkDir is a directory AUFS uses for storing hardlink links to other // layers. Normally these should not go into exported archives and all changed // hardlinks should be copied to the top layer. const WhiteoutLinkDir = WhiteoutMetaPrefix + "plnk" // WhiteoutOpaqueDir file means directory has been made opaque - meaning // readdir calls to this directory do not follow to lower layers. const WhiteoutOpaqueDir = WhiteoutMetaPrefix + ".opq" ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/wrap.go ================================================ package archive import ( "archive/tar" "bytes" "io" ) // Generate generates a new archive from the content provided // as input. // // `files` is a sequence of path/content pairs. A new file is // added to the archive for each pair. // If the last pair is incomplete, the file is created with an // empty content. For example: // // Generate("foo.txt", "hello world", "emptyfile") // // The above call will return an archive with 2 files: // - ./foo.txt with content "hello world" // - ./empty with empty content // // FIXME: stream content instead of buffering // FIXME: specify permissions and other archive metadata func Generate(input ...string) (io.Reader, error) { files := parseStringPairs(input...) buf := new(bytes.Buffer) tw := tar.NewWriter(buf) for _, file := range files { name, content := file[0], file[1] hdr := &tar.Header{ Name: name, Size: int64(len(content)), } if err := tw.WriteHeader(hdr); err != nil { return nil, err } if _, err := tw.Write([]byte(content)); err != nil { return nil, err } } if err := tw.Close(); err != nil { return nil, err } return buf, nil } func parseStringPairs(input ...string) (output [][2]string) { output = make([][2]string, 0, len(input)/2+1) for i := 0; i < len(input); i += 2 { var pair [2]string pair[0] = input[i] if i+1 < len(input) { pair[1] = input[i+1] } output = append(output, pair) } return } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/xattr_supported.go ================================================ //go:build linux || darwin || freebsd || netbsd package archive import ( "errors" "fmt" "io/fs" "golang.org/x/sys/unix" ) // lgetxattr retrieves the value of the extended attribute identified by attr // and associated with the given path in the file system. // It returns a nil slice and nil error if the xattr is not set. func lgetxattr(path string, attr string) ([]byte, error) { // Start with a 128 length byte array dest := make([]byte, 128) sz, err := unix.Lgetxattr(path, attr, dest) for errors.Is(err, unix.ERANGE) { // Buffer too small, use zero-sized buffer to get the actual size sz, err = unix.Lgetxattr(path, attr, []byte{}) if err != nil { return nil, wrapPathError("lgetxattr", path, attr, err) } dest = make([]byte, sz) sz, err = unix.Lgetxattr(path, attr, dest) } if err != nil { if errors.Is(err, noattr) { return nil, nil } return nil, wrapPathError("lgetxattr", path, attr, err) } return dest[:sz], nil } // lsetxattr sets the value of the extended attribute identified by attr // and associated with the given path in the file system. func lsetxattr(path string, attr string, data []byte, flags int) error { return wrapPathError("lsetxattr", path, attr, unix.Lsetxattr(path, attr, data, flags)) } func wrapPathError(op, path, attr string, err error) error { if err == nil { return nil } return &fs.PathError{Op: op, Path: path, Err: fmt.Errorf("xattr %q: %w", attr, err)} } ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/xattr_supported_linux.go ================================================ package archive import "golang.org/x/sys/unix" var noattr = unix.ENODATA ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/xattr_supported_unix.go ================================================ //go:build !linux && !windows package archive import "golang.org/x/sys/unix" var noattr = unix.ENOATTR ================================================ FILE: vendor/github.com/docker/docker/pkg/archive/xattr_unsupported.go ================================================ //go:build !linux && !darwin && !freebsd && !netbsd package archive func lgetxattr(path string, attr string) ([]byte, error) { return nil, nil } func lsetxattr(path string, attr string, data []byte, flags int) error { return nil } ================================================ FILE: vendor/github.com/docker/docker/pkg/idtools/idtools.go ================================================ package idtools import ( "fmt" "os" ) // IDMap contains a single entry for user namespace range remapping. An array // of IDMap entries represents the structure that will be provided to the Linux // kernel for creating a user namespace. type IDMap struct { ContainerID int `json:"container_id"` HostID int `json:"host_id"` Size int `json:"size"` } // MkdirAllAndChown creates a directory (include any along the path) and then modifies // ownership to the requested uid/gid. If the directory already exists, this // function will still change ownership and permissions. func MkdirAllAndChown(path string, mode os.FileMode, owner Identity) error { return mkdirAs(path, mode, owner, true, true) } // MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid. // If the directory already exists, this function still changes ownership and permissions. // Note that unlike os.Mkdir(), this function does not return IsExist error // in case path already exists. func MkdirAndChown(path string, mode os.FileMode, owner Identity) error { return mkdirAs(path, mode, owner, false, true) } // MkdirAllAndChownNew creates a directory (include any along the path) and then modifies // ownership ONLY of newly created directories to the requested uid/gid. If the // directories along the path exist, no change of ownership or permissions will be performed func MkdirAllAndChownNew(path string, mode os.FileMode, owner Identity) error { return mkdirAs(path, mode, owner, true, false) } // GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps. // If the maps are empty, then the root uid/gid will default to "real" 0/0 func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) { uid, err := toHost(0, uidMap) if err != nil { return -1, -1, err } gid, err := toHost(0, gidMap) if err != nil { return -1, -1, err } return uid, gid, nil } // toContainer takes an id mapping, and uses it to translate a // host ID to the remapped ID. If no map is provided, then the translation // assumes a 1-to-1 mapping and returns the passed in id func toContainer(hostID int, idMap []IDMap) (int, error) { if idMap == nil { return hostID, nil } for _, m := range idMap { if (hostID >= m.HostID) && (hostID <= (m.HostID + m.Size - 1)) { contID := m.ContainerID + (hostID - m.HostID) return contID, nil } } return -1, fmt.Errorf("Host ID %d cannot be mapped to a container ID", hostID) } // toHost takes an id mapping and a remapped ID, and translates the // ID to the mapped host ID. If no map is provided, then the translation // assumes a 1-to-1 mapping and returns the passed in id # func toHost(contID int, idMap []IDMap) (int, error) { if idMap == nil { return contID, nil } for _, m := range idMap { if (contID >= m.ContainerID) && (contID <= (m.ContainerID + m.Size - 1)) { hostID := m.HostID + (contID - m.ContainerID) return hostID, nil } } return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID) } // Identity is either a UID and GID pair or a SID (but not both) type Identity struct { UID int GID int SID string } // Chown changes the numeric uid and gid of the named file to id.UID and id.GID. func (id Identity) Chown(name string) error { return os.Chown(name, id.UID, id.GID) } // IdentityMapping contains a mappings of UIDs and GIDs. // The zero value represents an empty mapping. type IdentityMapping struct { UIDMaps []IDMap `json:"UIDMaps"` GIDMaps []IDMap `json:"GIDMaps"` } // RootPair returns a uid and gid pair for the root user. The error is ignored // because a root user always exists, and the defaults are correct when the uid // and gid maps are empty. func (i IdentityMapping) RootPair() Identity { uid, gid, _ := GetRootUIDGID(i.UIDMaps, i.GIDMaps) return Identity{UID: uid, GID: gid} } // ToHost returns the host UID and GID for the container uid, gid. // Remapping is only performed if the ids aren't already the remapped root ids func (i IdentityMapping) ToHost(pair Identity) (Identity, error) { var err error target := i.RootPair() if pair.UID != target.UID { target.UID, err = toHost(pair.UID, i.UIDMaps) if err != nil { return target, err } } if pair.GID != target.GID { target.GID, err = toHost(pair.GID, i.GIDMaps) } return target, err } // ToContainer returns the container UID and GID for the host uid and gid func (i IdentityMapping) ToContainer(pair Identity) (int, int, error) { uid, err := toContainer(pair.UID, i.UIDMaps) if err != nil { return -1, -1, err } gid, err := toContainer(pair.GID, i.GIDMaps) return uid, gid, err } // Empty returns true if there are no id mappings func (i IdentityMapping) Empty() bool { return len(i.UIDMaps) == 0 && len(i.GIDMaps) == 0 } // CurrentIdentity returns the identity of the current process func CurrentIdentity() Identity { return Identity{UID: os.Getuid(), GID: os.Getegid()} } ================================================ FILE: vendor/github.com/docker/docker/pkg/idtools/idtools_unix.go ================================================ //go:build !windows package idtools import ( "fmt" "os" "path/filepath" "strconv" "syscall" "github.com/moby/sys/user" ) func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error { path, err := filepath.Abs(path) if err != nil { return err } stat, err := os.Stat(path) if err == nil { if !stat.IsDir() { return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR} } if !chownExisting { return nil } // short-circuit -- we were called with an existing directory and chown was requested return setPermissions(path, mode, owner, stat) } // make an array containing the original path asked for, plus (for mkAll == true) // all path components leading up to the complete path that don't exist before we MkdirAll // so that we can chown all of them properly at the end. If chownExisting is false, we won't // chown the full directory path if it exists var paths []string if os.IsNotExist(err) { paths = []string{path} } if mkAll { // walk back to "/" looking for directories which do not exist // and add them to the paths array for chown after creation dirPath := path for { dirPath = filepath.Dir(dirPath) if dirPath == "/" { break } if _, err = os.Stat(dirPath); err != nil && os.IsNotExist(err) { paths = append(paths, dirPath) } } if err = os.MkdirAll(path, mode); err != nil { return err } } else if err = os.Mkdir(path, mode); err != nil { return err } // even if it existed, we will chown the requested path + any subpaths that // didn't exist when we called MkdirAll for _, pathComponent := range paths { if err = setPermissions(pathComponent, mode, owner, nil); err != nil { return err } } return nil } // LookupUser uses traditional local system files lookup (from libcontainer/user) on a username // // Deprecated: use [user.LookupUser] instead func LookupUser(name string) (user.User, error) { return user.LookupUser(name) } // LookupUID uses traditional local system files lookup (from libcontainer/user) on a uid // // Deprecated: use [user.LookupUid] instead func LookupUID(uid int) (user.User, error) { return user.LookupUid(uid) } // LookupGroup uses traditional local system files lookup (from libcontainer/user) on a group name, // // Deprecated: use [user.LookupGroup] instead func LookupGroup(name string) (user.Group, error) { return user.LookupGroup(name) } // setPermissions performs a chown/chmod only if the uid/gid don't match what's requested // Normally a Chown is a no-op if uid/gid match, but in some cases this can still cause an error, e.g. if the // dir is on an NFS share, so don't call chown unless we absolutely must. // Likewise for setting permissions. func setPermissions(p string, mode os.FileMode, owner Identity, stat os.FileInfo) error { if stat == nil { var err error stat, err = os.Stat(p) if err != nil { return err } } if stat.Mode().Perm() != mode.Perm() { if err := os.Chmod(p, mode.Perm()); err != nil { return err } } ssi := stat.Sys().(*syscall.Stat_t) if ssi.Uid == uint32(owner.UID) && ssi.Gid == uint32(owner.GID) { return nil } return os.Chown(p, owner.UID, owner.GID) } // LoadIdentityMapping takes a requested username and // using the data from /etc/sub{uid,gid} ranges, creates the // proper uid and gid remapping ranges for that user/group pair func LoadIdentityMapping(name string) (IdentityMapping, error) { // TODO: Consider adding support for calling out to "getent" usr, err := user.LookupUser(name) if err != nil { return IdentityMapping{}, fmt.Errorf("could not get user for username %s: %v", name, err) } subuidRanges, err := lookupSubRangesFile("/etc/subuid", usr) if err != nil { return IdentityMapping{}, err } subgidRanges, err := lookupSubRangesFile("/etc/subgid", usr) if err != nil { return IdentityMapping{}, err } return IdentityMapping{ UIDMaps: subuidRanges, GIDMaps: subgidRanges, }, nil } func lookupSubRangesFile(path string, usr user.User) ([]IDMap, error) { uidstr := strconv.Itoa(usr.Uid) rangeList, err := user.ParseSubIDFileFilter(path, func(sid user.SubID) bool { return sid.Name == usr.Name || sid.Name == uidstr }) if err != nil { return nil, err } if len(rangeList) == 0 { return nil, fmt.Errorf("no subuid ranges found for user %q", usr.Name) } idMap := []IDMap{} containerID := 0 for _, idrange := range rangeList { idMap = append(idMap, IDMap{ ContainerID: containerID, HostID: int(idrange.SubID), Size: int(idrange.Count), }) containerID = containerID + int(idrange.Count) } return idMap, nil } ================================================ FILE: vendor/github.com/docker/docker/pkg/idtools/idtools_windows.go ================================================ package idtools import ( "os" ) const ( SeTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege" ) // TODO(thaJeztah): these magic consts need a source of reference, and should be defined in a canonical location const ( ContainerAdministratorSidString = "S-1-5-93-2-1" ContainerUserSidString = "S-1-5-93-2-2" ) // This is currently a wrapper around [os.MkdirAll] since currently // permissions aren't set through this path, the identity isn't utilized. // Ownership is handled elsewhere, but in the future could be support here // too. func mkdirAs(path string, _ os.FileMode, _ Identity, _, _ bool) error { return os.MkdirAll(path, 0) } ================================================ FILE: vendor/github.com/docker/docker/pkg/jsonmessage/jsonmessage.go ================================================ package jsonmessage // import "github.com/docker/docker/pkg/jsonmessage" import ( "encoding/json" "fmt" "io" "strings" "time" "github.com/docker/go-units" "github.com/moby/term" "github.com/morikuni/aec" ) // RFC3339NanoFixed is time.RFC3339Nano with nanoseconds padded using zeros to // ensure the formatted time isalways the same number of characters. const RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00" // JSONError wraps a concrete Code and Message, Code is // an integer error code, Message is the error message. type JSONError struct { Code int `json:"code,omitempty"` Message string `json:"message,omitempty"` } func (e *JSONError) Error() string { return e.Message } // JSONProgress describes a progress message in a JSON stream. type JSONProgress struct { // Current is the current status and value of the progress made towards Total. Current int64 `json:"current,omitempty"` // Total is the end value describing when we made 100% progress for an operation. Total int64 `json:"total,omitempty"` // Start is the initial value for the operation. Start int64 `json:"start,omitempty"` // HideCounts. if true, hides the progress count indicator (xB/yB). HideCounts bool `json:"hidecounts,omitempty"` // Units is the unit to print for progress. It defaults to "bytes" if empty. Units string `json:"units,omitempty"` // terminalFd is the fd of the current terminal, if any. It is used // to get the terminal width. terminalFd uintptr // nowFunc is used to override the current time in tests. nowFunc func() time.Time // winSize is used to override the terminal width in tests. winSize int } func (p *JSONProgress) String() string { var ( width = p.width() pbBox string numbersBox string ) if p.Current <= 0 && p.Total <= 0 { return "" } if p.Total <= 0 { switch p.Units { case "": return fmt.Sprintf("%8v", units.HumanSize(float64(p.Current))) default: return fmt.Sprintf("%d %s", p.Current, p.Units) } } percentage := int(float64(p.Current)/float64(p.Total)*100) / 2 if percentage > 50 { percentage = 50 } if width > 110 { // this number can't be negative gh#7136 numSpaces := 0 if 50-percentage > 0 { numSpaces = 50 - percentage } pbBox = fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces)) } switch { case p.HideCounts: case p.Units == "": // no units, use bytes current := units.HumanSize(float64(p.Current)) total := units.HumanSize(float64(p.Total)) numbersBox = fmt.Sprintf("%8v/%v", current, total) if p.Current > p.Total { // remove total display if the reported current is wonky. numbersBox = fmt.Sprintf("%8v", current) } default: numbersBox = fmt.Sprintf("%d/%d %s", p.Current, p.Total, p.Units) if p.Current > p.Total { // remove total display if the reported current is wonky. numbersBox = fmt.Sprintf("%d %s", p.Current, p.Units) } } // Show approximation of remaining time if there's enough width. var timeLeftBox string if width > 50 { if p.Current > 0 && p.Start > 0 && percentage < 50 { fromStart := p.now().Sub(time.Unix(p.Start, 0)) perEntry := fromStart / time.Duration(p.Current) left := time.Duration(p.Total-p.Current) * perEntry timeLeftBox = " " + left.Round(time.Second).String() } } return pbBox + numbersBox + timeLeftBox } // now returns the current time in UTC, but can be overridden in tests // by setting JSONProgress.nowFunc to a custom function. func (p *JSONProgress) now() time.Time { if p.nowFunc != nil { return p.nowFunc() } return time.Now().UTC() } // width returns the current terminal's width, but can be overridden // in tests by setting JSONProgress.winSize to a non-zero value. func (p *JSONProgress) width() int { if p.winSize != 0 { return p.winSize } ws, err := term.GetWinsize(p.terminalFd) if err == nil { return int(ws.Width) } return 200 } // JSONMessage defines a message struct. It describes // the created time, where it from, status, ID of the // message. It's used for docker events. type JSONMessage struct { Stream string `json:"stream,omitempty"` Status string `json:"status,omitempty"` Progress *JSONProgress `json:"progressDetail,omitempty"` // ProgressMessage is a pre-formatted presentation of [Progress]. // // Deprecated: this field is deprecated since docker v0.7.1 / API v1.8. Use the information in [Progress] instead. This field will be omitted in a future release. ProgressMessage string `json:"progress,omitempty"` ID string `json:"id,omitempty"` From string `json:"from,omitempty"` Time int64 `json:"time,omitempty"` TimeNano int64 `json:"timeNano,omitempty"` Error *JSONError `json:"errorDetail,omitempty"` // ErrorMessage contains errors encountered during the operation. // // Deprecated: this field is deprecated since docker v0.6.0 / API v1.4. Use [Error.Message] instead. This field will be omitted in a future release. ErrorMessage string `json:"error,omitempty"` // deprecated // Aux contains out-of-band data, such as digests for push signing and image id after building. Aux *json.RawMessage `json:"aux,omitempty"` } func clearLine(out io.Writer) { eraseMode := aec.EraseModes.All cl := aec.EraseLine(eraseMode) fmt.Fprint(out, cl) } func cursorUp(out io.Writer, l uint) { fmt.Fprint(out, aec.Up(l)) } func cursorDown(out io.Writer, l uint) { fmt.Fprint(out, aec.Down(l)) } // Display prints the JSONMessage to out. If isTerminal is true, it erases // the entire current line when displaying the progressbar. It returns an // error if the [JSONMessage.Error] field is non-nil. func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error { if jm.Error != nil { return jm.Error } var endl string if isTerminal && jm.Stream == "" && jm.Progress != nil { clearLine(out) endl = "\r" fmt.Fprint(out, endl) } else if jm.Progress != nil && jm.Progress.String() != "" { // disable progressbar in non-terminal return nil } if jm.TimeNano != 0 { fmt.Fprintf(out, "%s ", time.Unix(0, jm.TimeNano).Format(RFC3339NanoFixed)) } else if jm.Time != 0 { fmt.Fprintf(out, "%s ", time.Unix(jm.Time, 0).Format(RFC3339NanoFixed)) } if jm.ID != "" { fmt.Fprintf(out, "%s: ", jm.ID) } if jm.From != "" { fmt.Fprintf(out, "(from %s) ", jm.From) } if jm.Progress != nil && isTerminal { fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress.String(), endl) } else if jm.ProgressMessage != "" { // deprecated fmt.Fprintf(out, "%s %s%s", jm.Status, jm.ProgressMessage, endl) } else if jm.Stream != "" { fmt.Fprintf(out, "%s%s", jm.Stream, endl) } else { fmt.Fprintf(out, "%s%s\n", jm.Status, endl) } return nil } // DisplayJSONMessagesStream reads a JSON message stream from in, and writes // each [JSONMessage] to out. It returns an error if an invalid JSONMessage // is received, or if a JSONMessage containers a non-zero [JSONMessage.Error]. // // Presentation of the JSONMessage depends on whether a terminal is attached, // and on the terminal width. Progress bars ([JSONProgress]) are suppressed // on narrower terminals (< 110 characters). // // - isTerminal describes if out is a terminal, in which case it prints // a newline ("\n") at the end of each line and moves the cursor while // displaying. // - terminalFd is the fd of the current terminal (if any), and used // to get the terminal width. // - auxCallback allows handling the [JSONMessage.Aux] field. It is // called if a JSONMessage contains an Aux field, in which case // DisplayJSONMessagesStream does not present the JSONMessage. func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(JSONMessage)) error { var ( dec = json.NewDecoder(in) ids = make(map[string]uint) ) for { var diff uint var jm JSONMessage if err := dec.Decode(&jm); err != nil { if err == io.EOF { break } return err } if jm.Aux != nil { if auxCallback != nil { auxCallback(jm) } continue } if jm.Progress != nil { jm.Progress.terminalFd = terminalFd } if jm.ID != "" && (jm.Progress != nil || jm.ProgressMessage != "") { line, ok := ids[jm.ID] if !ok { // NOTE: This approach of using len(id) to // figure out the number of lines of history // only works as long as we clear the history // when we output something that's not // accounted for in the map, such as a line // with no ID. line = uint(len(ids)) ids[jm.ID] = line if isTerminal { fmt.Fprintf(out, "\n") } } diff = uint(len(ids)) - line if isTerminal { cursorUp(out, diff) } } else { // When outputting something that isn't progress // output, clear the history of previous lines. We // don't want progress entries from some previous // operation to be updated (for example, pull -a // with multiple tags). ids = make(map[string]uint) } err := jm.Display(out, isTerminal) if jm.ID != "" && isTerminal { cursorDown(out, diff) } if err != nil { return err } } return nil } // Stream is an io.Writer for output with utilities to get the output's file // descriptor and to detect whether it's a terminal. // // it is subset of the streams.Out type in // https://pkg.go.dev/github.com/docker/cli@v20.10.17+incompatible/cli/streams#Out type Stream interface { io.Writer FD() uintptr IsTerminal() bool } // DisplayJSONMessagesToStream prints json messages to the output Stream. It is // used by the Docker CLI to print JSONMessage streams. func DisplayJSONMessagesToStream(in io.Reader, stream Stream, auxCallback func(JSONMessage)) error { return DisplayJSONMessagesStream(in, stream, stream.FD(), stream.IsTerminal(), auxCallback) } ================================================ FILE: vendor/github.com/docker/docker/pkg/stdcopy/stdcopy.go ================================================ package stdcopy // import "github.com/docker/docker/pkg/stdcopy" import ( "bytes" "encoding/binary" "errors" "fmt" "io" "sync" ) // StdType is the type of standard stream // a writer can multiplex to. type StdType byte const ( // Stdin represents standard input stream type. Stdin StdType = iota // Stdout represents standard output stream type. Stdout // Stderr represents standard error steam type. Stderr // Systemerr represents errors originating from the system that make it // into the multiplexed stream. Systemerr stdWriterPrefixLen = 8 stdWriterFdIndex = 0 stdWriterSizeIndex = 4 startingBufLen = 32*1024 + stdWriterPrefixLen + 1 ) var bufPool = &sync.Pool{New: func() interface{} { return bytes.NewBuffer(nil) }} // stdWriter is wrapper of io.Writer with extra customized info. type stdWriter struct { io.Writer prefix byte } // Write sends the buffer to the underneath writer. // It inserts the prefix header before the buffer, // so stdcopy.StdCopy knows where to multiplex the output. // It makes stdWriter to implement io.Writer. func (w *stdWriter) Write(p []byte) (n int, err error) { if w == nil || w.Writer == nil { return 0, errors.New("Writer not instantiated") } if p == nil { return 0, nil } header := [stdWriterPrefixLen]byte{stdWriterFdIndex: w.prefix} binary.BigEndian.PutUint32(header[stdWriterSizeIndex:], uint32(len(p))) buf := bufPool.Get().(*bytes.Buffer) buf.Write(header[:]) buf.Write(p) n, err = w.Writer.Write(buf.Bytes()) n -= stdWriterPrefixLen if n < 0 { n = 0 } buf.Reset() bufPool.Put(buf) return } // NewStdWriter instantiates a new Writer. // Everything written to it will be encapsulated using a custom format, // and written to the underlying `w` stream. // This allows multiple write streams (e.g. stdout and stderr) to be muxed into a single connection. // `t` indicates the id of the stream to encapsulate. // It can be stdcopy.Stdin, stdcopy.Stdout, stdcopy.Stderr. func NewStdWriter(w io.Writer, t StdType) io.Writer { return &stdWriter{ Writer: w, prefix: byte(t), } } // StdCopy is a modified version of io.Copy. // // StdCopy will demultiplex `src`, assuming that it contains two streams, // previously multiplexed together using a StdWriter instance. // As it reads from `src`, StdCopy will write to `dstout` and `dsterr`. // // StdCopy will read until it hits EOF on `src`. It will then return a nil error. // In other words: if `err` is non nil, it indicates a real underlying error. // // `written` will hold the total number of bytes written to `dstout` and `dsterr`. func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) { var ( buf = make([]byte, startingBufLen) bufLen = len(buf) nr, nw int er, ew error out io.Writer frameSize int ) for { // Make sure we have at least a full header for nr < stdWriterPrefixLen { var nr2 int nr2, er = src.Read(buf[nr:]) nr += nr2 if er == io.EOF { if nr < stdWriterPrefixLen { return written, nil } break } if er != nil { return 0, er } } stream := StdType(buf[stdWriterFdIndex]) // Check the first byte to know where to write switch stream { case Stdin: fallthrough case Stdout: // Write on stdout out = dstout case Stderr: // Write on stderr out = dsterr case Systemerr: // If we're on Systemerr, we won't write anywhere. // NB: if this code changes later, make sure you don't try to write // to outstream if Systemerr is the stream out = nil default: return 0, fmt.Errorf("Unrecognized input header: %d", buf[stdWriterFdIndex]) } // Retrieve the size of the frame frameSize = int(binary.BigEndian.Uint32(buf[stdWriterSizeIndex : stdWriterSizeIndex+4])) // Check if the buffer is big enough to read the frame. // Extend it if necessary. if frameSize+stdWriterPrefixLen > bufLen { buf = append(buf, make([]byte, frameSize+stdWriterPrefixLen-bufLen+1)...) bufLen = len(buf) } // While the amount of bytes read is less than the size of the frame + header, we keep reading for nr < frameSize+stdWriterPrefixLen { var nr2 int nr2, er = src.Read(buf[nr:]) nr += nr2 if er == io.EOF { if nr < frameSize+stdWriterPrefixLen { return written, nil } break } if er != nil { return 0, er } } // we might have an error from the source mixed up in our multiplexed // stream. if we do, return it. if stream == Systemerr { return written, fmt.Errorf("error from daemon in stream: %s", string(buf[stdWriterPrefixLen:frameSize+stdWriterPrefixLen])) } // Write the retrieved frame (without header) nw, ew = out.Write(buf[stdWriterPrefixLen : frameSize+stdWriterPrefixLen]) if ew != nil { return 0, ew } // If the frame has not been fully written: error if nw != frameSize { return 0, io.ErrShortWrite } written += int64(nw) // Move the rest of the buffer to the beginning copy(buf, buf[frameSize+stdWriterPrefixLen:]) // Move the index nr -= frameSize + stdWriterPrefixLen } } ================================================ FILE: vendor/github.com/docker/go-connections/LICENSE ================================================ Apache License Version 2.0, January 2004 https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Copyright 2015 Docker, Inc. 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 https://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. ================================================ FILE: vendor/github.com/docker/go-connections/nat/nat.go ================================================ // Package nat is a convenience package for manipulation of strings describing network ports. package nat import ( "fmt" "net" "strconv" "strings" ) // PortBinding represents a binding between a Host IP address and a Host Port type PortBinding struct { // HostIP is the host IP Address HostIP string `json:"HostIp"` // HostPort is the host port number HostPort string } // PortMap is a collection of PortBinding indexed by Port type PortMap map[Port][]PortBinding // PortSet is a collection of structs indexed by Port type PortSet map[Port]struct{} // Port is a string containing port number and protocol in the format "80/tcp" type Port string // NewPort creates a new instance of a Port given a protocol and port number or port range func NewPort(proto, port string) (Port, error) { // Check for parsing issues on "port" now so we can avoid having // to check it later on. portStartInt, portEndInt, err := ParsePortRangeToInt(port) if err != nil { return "", err } if portStartInt == portEndInt { return Port(fmt.Sprintf("%d/%s", portStartInt, proto)), nil } return Port(fmt.Sprintf("%d-%d/%s", portStartInt, portEndInt, proto)), nil } // ParsePort parses the port number string and returns an int func ParsePort(rawPort string) (int, error) { if len(rawPort) == 0 { return 0, nil } port, err := strconv.ParseUint(rawPort, 10, 16) if err != nil { return 0, err } return int(port), nil } // ParsePortRangeToInt parses the port range string and returns start/end ints func ParsePortRangeToInt(rawPort string) (int, int, error) { if len(rawPort) == 0 { return 0, 0, nil } start, end, err := ParsePortRange(rawPort) if err != nil { return 0, 0, err } return int(start), int(end), nil } // Proto returns the protocol of a Port func (p Port) Proto() string { proto, _ := SplitProtoPort(string(p)) return proto } // Port returns the port number of a Port func (p Port) Port() string { _, port := SplitProtoPort(string(p)) return port } // Int returns the port number of a Port as an int func (p Port) Int() int { portStr := p.Port() // We don't need to check for an error because we're going to // assume that any error would have been found, and reported, in NewPort() port, _ := ParsePort(portStr) return port } // Range returns the start/end port numbers of a Port range as ints func (p Port) Range() (int, int, error) { return ParsePortRangeToInt(p.Port()) } // SplitProtoPort splits a port in the format of proto/port func SplitProtoPort(rawPort string) (string, string) { parts := strings.Split(rawPort, "/") l := len(parts) if len(rawPort) == 0 || l == 0 || len(parts[0]) == 0 { return "", "" } if l == 1 { return "tcp", rawPort } if len(parts[1]) == 0 { return "tcp", parts[0] } return parts[1], parts[0] } func validateProto(proto string) bool { for _, availableProto := range []string{"tcp", "udp", "sctp"} { if availableProto == proto { return true } } return false } // ParsePortSpecs receives port specs in the format of ip:public:private/proto and parses // these in to the internal types func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) { var ( exposedPorts = make(map[Port]struct{}, len(ports)) bindings = make(map[Port][]PortBinding) ) for _, rawPort := range ports { portMappings, err := ParsePortSpec(rawPort) if err != nil { return nil, nil, err } for _, portMapping := range portMappings { port := portMapping.Port if _, exists := exposedPorts[port]; !exists { exposedPorts[port] = struct{}{} } bslice, exists := bindings[port] if !exists { bslice = []PortBinding{} } bindings[port] = append(bslice, portMapping.Binding) } } return exposedPorts, bindings, nil } // PortMapping is a data object mapping a Port to a PortBinding type PortMapping struct { Port Port Binding PortBinding } func splitParts(rawport string) (string, string, string) { parts := strings.Split(rawport, ":") n := len(parts) containerPort := parts[n-1] switch n { case 1: return "", "", containerPort case 2: return "", parts[0], containerPort case 3: return parts[0], parts[1], containerPort default: return strings.Join(parts[:n-2], ":"), parts[n-2], containerPort } } // ParsePortSpec parses a port specification string into a slice of PortMappings func ParsePortSpec(rawPort string) ([]PortMapping, error) { var proto string ip, hostPort, containerPort := splitParts(rawPort) proto, containerPort = SplitProtoPort(containerPort) if ip != "" && ip[0] == '[' { // Strip [] from IPV6 addresses rawIP, _, err := net.SplitHostPort(ip + ":") if err != nil { return nil, fmt.Errorf("invalid IP address %v: %w", ip, err) } ip = rawIP } if ip != "" && net.ParseIP(ip) == nil { return nil, fmt.Errorf("invalid IP address: %s", ip) } if containerPort == "" { return nil, fmt.Errorf("no port specified: %s", rawPort) } startPort, endPort, err := ParsePortRange(containerPort) if err != nil { return nil, fmt.Errorf("invalid containerPort: %s", containerPort) } var startHostPort, endHostPort uint64 = 0, 0 if len(hostPort) > 0 { startHostPort, endHostPort, err = ParsePortRange(hostPort) if err != nil { return nil, fmt.Errorf("invalid hostPort: %s", hostPort) } } if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) { // Allow host port range iff containerPort is not a range. // In this case, use the host port range as the dynamic // host port range to allocate into. if endPort != startPort { return nil, fmt.Errorf("invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort) } } if !validateProto(strings.ToLower(proto)) { return nil, fmt.Errorf("invalid proto: %s", proto) } ports := []PortMapping{} for i := uint64(0); i <= (endPort - startPort); i++ { containerPort = strconv.FormatUint(startPort+i, 10) if len(hostPort) > 0 { hostPort = strconv.FormatUint(startHostPort+i, 10) } // Set hostPort to a range only if there is a single container port // and a dynamic host port. if startPort == endPort && startHostPort != endHostPort { hostPort = fmt.Sprintf("%s-%s", hostPort, strconv.FormatUint(endHostPort, 10)) } port, err := NewPort(strings.ToLower(proto), containerPort) if err != nil { return nil, err } binding := PortBinding{ HostIP: ip, HostPort: hostPort, } ports = append(ports, PortMapping{Port: port, Binding: binding}) } return ports, nil } ================================================ FILE: vendor/github.com/docker/go-connections/nat/parse.go ================================================ package nat import ( "fmt" "strconv" "strings" ) // ParsePortRange parses and validates the specified string as a port-range (8000-9000) func ParsePortRange(ports string) (uint64, uint64, error) { if ports == "" { return 0, 0, fmt.Errorf("empty string specified for ports") } if !strings.Contains(ports, "-") { start, err := strconv.ParseUint(ports, 10, 16) end := start return start, end, err } parts := strings.Split(ports, "-") start, err := strconv.ParseUint(parts[0], 10, 16) if err != nil { return 0, 0, err } end, err := strconv.ParseUint(parts[1], 10, 16) if err != nil { return 0, 0, err } if end < start { return 0, 0, fmt.Errorf("invalid range specified for port: %s", ports) } return start, end, nil } ================================================ FILE: vendor/github.com/docker/go-connections/nat/sort.go ================================================ package nat import ( "sort" "strings" ) type portSorter struct { ports []Port by func(i, j Port) bool } func (s *portSorter) Len() int { return len(s.ports) } func (s *portSorter) Swap(i, j int) { s.ports[i], s.ports[j] = s.ports[j], s.ports[i] } func (s *portSorter) Less(i, j int) bool { ip := s.ports[i] jp := s.ports[j] return s.by(ip, jp) } // Sort sorts a list of ports using the provided predicate // This function should compare `i` and `j`, returning true if `i` is // considered to be less than `j` func Sort(ports []Port, predicate func(i, j Port) bool) { s := &portSorter{ports, predicate} sort.Sort(s) } type portMapEntry struct { port Port binding PortBinding } type portMapSorter []portMapEntry func (s portMapSorter) Len() int { return len(s) } func (s portMapSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // Less sorts the port so that the order is: // 1. port with larger specified bindings // 2. larger port // 3. port with tcp protocol func (s portMapSorter) Less(i, j int) bool { pi, pj := s[i].port, s[j].port hpi, hpj := toInt(s[i].binding.HostPort), toInt(s[j].binding.HostPort) return hpi > hpj || pi.Int() > pj.Int() || (pi.Int() == pj.Int() && strings.ToLower(pi.Proto()) == "tcp") } // SortPortMap sorts the list of ports and their respected mapping. The ports // will explicit HostPort will be placed first. func SortPortMap(ports []Port, bindings PortMap) { s := portMapSorter{} for _, p := range ports { if binding, ok := bindings[p]; ok && len(binding) > 0 { for _, b := range binding { s = append(s, portMapEntry{port: p, binding: b}) } bindings[p] = []PortBinding{} } else { s = append(s, portMapEntry{port: p}) } } sort.Sort(s) var ( i int pm = make(map[Port]struct{}) ) // reorder ports for _, entry := range s { if _, ok := pm[entry.port]; !ok { ports[i] = entry.port pm[entry.port] = struct{}{} i++ } // reorder bindings for this port if _, ok := bindings[entry.port]; ok { bindings[entry.port] = append(bindings[entry.port], entry.binding) } } } func toInt(s string) uint64 { i, _, err := ParsePortRange(s) if err != nil { i = 0 } return i } ================================================ FILE: vendor/github.com/docker/go-connections/sockets/README.md ================================================ ================================================ FILE: vendor/github.com/docker/go-connections/sockets/inmem_socket.go ================================================ package sockets import ( "errors" "net" "sync" ) var errClosed = errors.New("use of closed network connection") // InmemSocket implements net.Listener using in-memory only connections. type InmemSocket struct { chConn chan net.Conn chClose chan struct{} addr string mu sync.Mutex } // dummyAddr is used to satisfy net.Addr for the in-mem socket // it is just stored as a string and returns the string for all calls type dummyAddr string // NewInmemSocket creates an in-memory only net.Listener // The addr argument can be any string, but is used to satisfy the `Addr()` part // of the net.Listener interface func NewInmemSocket(addr string, bufSize int) *InmemSocket { return &InmemSocket{ chConn: make(chan net.Conn, bufSize), chClose: make(chan struct{}), addr: addr, } } // Addr returns the socket's addr string to satisfy net.Listener func (s *InmemSocket) Addr() net.Addr { return dummyAddr(s.addr) } // Accept implements the Accept method in the Listener interface; it waits for the next call and returns a generic Conn. func (s *InmemSocket) Accept() (net.Conn, error) { select { case conn := <-s.chConn: return conn, nil case <-s.chClose: return nil, errClosed } } // Close closes the listener. It will be unavailable for use once closed. func (s *InmemSocket) Close() error { s.mu.Lock() defer s.mu.Unlock() select { case <-s.chClose: default: close(s.chClose) } return nil } // Dial is used to establish a connection with the in-mem server func (s *InmemSocket) Dial(network, addr string) (net.Conn, error) { srvConn, clientConn := net.Pipe() select { case s.chConn <- srvConn: case <-s.chClose: return nil, errClosed } return clientConn, nil } // Network returns the addr string, satisfies net.Addr func (a dummyAddr) Network() string { return string(a) } // String returns the string form func (a dummyAddr) String() string { return string(a) } ================================================ FILE: vendor/github.com/docker/go-connections/sockets/proxy.go ================================================ package sockets import ( "net" "os" "strings" ) // GetProxyEnv allows access to the uppercase and the lowercase forms of // proxy-related variables. See the Go specification for details on these // variables. https://golang.org/pkg/net/http/ func GetProxyEnv(key string) string { proxyValue := os.Getenv(strings.ToUpper(key)) if proxyValue == "" { return os.Getenv(strings.ToLower(key)) } return proxyValue } // DialerFromEnvironment was previously used to configure a net.Dialer to route // connections through a SOCKS proxy. // DEPRECATED: SOCKS proxies are now supported by configuring only // http.Transport.Proxy, and no longer require changing http.Transport.Dial. // Therefore, only sockets.ConfigureTransport() needs to be called, and any // sockets.DialerFromEnvironment() calls can be dropped. func DialerFromEnvironment(direct *net.Dialer) (*net.Dialer, error) { return direct, nil } ================================================ FILE: vendor/github.com/docker/go-connections/sockets/sockets.go ================================================ // Package sockets provides helper functions to create and configure Unix or TCP sockets. package sockets import ( "errors" "net" "net/http" "time" ) const defaultTimeout = 10 * time.Second // ErrProtocolNotAvailable is returned when a given transport protocol is not provided by the operating system. var ErrProtocolNotAvailable = errors.New("protocol not available") // ConfigureTransport configures the specified [http.Transport] according to the specified proto // and addr. // // If the proto is unix (using a unix socket to communicate) or npipe the compression is disabled. // For other protos, compression is enabled. If you want to manually enable/disable compression, // make sure you do it _after_ any subsequent calls to ConfigureTransport is made against the same // [http.Transport]. func ConfigureTransport(tr *http.Transport, proto, addr string) error { switch proto { case "unix": return configureUnixTransport(tr, proto, addr) case "npipe": return configureNpipeTransport(tr, proto, addr) default: tr.Proxy = http.ProxyFromEnvironment tr.DisableCompression = false tr.DialContext = (&net.Dialer{ Timeout: defaultTimeout, }).DialContext } return nil } ================================================ FILE: vendor/github.com/docker/go-connections/sockets/sockets_unix.go ================================================ //go:build !windows package sockets import ( "context" "fmt" "net" "net/http" "syscall" "time" ) const maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path) func configureUnixTransport(tr *http.Transport, proto, addr string) error { if len(addr) > maxUnixSocketPathSize { return fmt.Errorf("unix socket path %q is too long", addr) } // No need for compression in local communications. tr.DisableCompression = true dialer := &net.Dialer{ Timeout: defaultTimeout, } tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) { return dialer.DialContext(ctx, proto, addr) } return nil } func configureNpipeTransport(tr *http.Transport, proto, addr string) error { return ErrProtocolNotAvailable } // DialPipe connects to a Windows named pipe. // This is not supported on other OSes. func DialPipe(_ string, _ time.Duration) (net.Conn, error) { return nil, syscall.EAFNOSUPPORT } ================================================ FILE: vendor/github.com/docker/go-connections/sockets/sockets_windows.go ================================================ package sockets import ( "context" "net" "net/http" "time" "github.com/Microsoft/go-winio" ) func configureUnixTransport(tr *http.Transport, proto, addr string) error { return ErrProtocolNotAvailable } func configureNpipeTransport(tr *http.Transport, proto, addr string) error { // No need for compression in local communications. tr.DisableCompression = true tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) { return winio.DialPipeContext(ctx, addr) } return nil } // DialPipe connects to a Windows named pipe. func DialPipe(addr string, timeout time.Duration) (net.Conn, error) { return winio.DialPipe(addr, &timeout) } ================================================ FILE: vendor/github.com/docker/go-connections/sockets/tcp_socket.go ================================================ // Package sockets provides helper functions to create and configure Unix or TCP sockets. package sockets import ( "crypto/tls" "net" ) // NewTCPSocket creates a TCP socket listener with the specified address and // the specified tls configuration. If TLSConfig is set, will encapsulate the // TCP listener inside a TLS one. func NewTCPSocket(addr string, tlsConfig *tls.Config) (net.Listener, error) { l, err := net.Listen("tcp", addr) if err != nil { return nil, err } if tlsConfig != nil { tlsConfig.NextProtos = []string{"http/1.1"} l = tls.NewListener(l, tlsConfig) } return l, nil } ================================================ FILE: vendor/github.com/docker/go-connections/sockets/unix_socket.go ================================================ //go:build !windows /* Package sockets is a simple unix domain socket wrapper. # Usage For example: import( "fmt" "net" "os" "github.com/docker/go-connections/sockets" ) func main() { l, err := sockets.NewUnixSocketWithOpts("/path/to/sockets", sockets.WithChown(0,0),sockets.WithChmod(0660)) if err != nil { panic(err) } echoStr := "hello" go func() { for { conn, err := l.Accept() if err != nil { return } conn.Write([]byte(echoStr)) conn.Close() } }() conn, err := net.Dial("unix", path) if err != nil { t.Fatal(err) } buf := make([]byte, 5) if _, err := conn.Read(buf); err != nil { panic(err) } else if string(buf) != echoStr { panic(fmt.Errorf("msg may lost")) } } */ package sockets import ( "net" "os" "syscall" ) // SockOption sets up socket file's creating option type SockOption func(string) error // WithChown modifies the socket file's uid and gid func WithChown(uid, gid int) SockOption { return func(path string) error { if err := os.Chown(path, uid, gid); err != nil { return err } return nil } } // WithChmod modifies socket file's access mode. func WithChmod(mask os.FileMode) SockOption { return func(path string) error { if err := os.Chmod(path, mask); err != nil { return err } return nil } } // NewUnixSocketWithOpts creates a unix socket with the specified options. // By default, socket permissions are 0000 (i.e.: no access for anyone); pass // WithChmod() and WithChown() to set the desired ownership and permissions. // // This function temporarily changes the system's "umask" to 0777 to work around // a race condition between creating the socket and setting its permissions. While // this should only be for a short duration, it may affect other processes that // create files/directories during that period. func NewUnixSocketWithOpts(path string, opts ...SockOption) (net.Listener, error) { if err := syscall.Unlink(path); err != nil && !os.IsNotExist(err) { return nil, err } // net.Listen does not allow for permissions to be set. As a result, when // specifying custom permissions ("WithChmod()"), there is a short time // between creating the socket and applying the permissions, during which // the socket permissions are Less restrictive than desired. // // To work around this limitation of net.Listen(), we temporarily set the // umask to 0777, which forces the socket to be created with 000 permissions // (i.e.: no access for anyone). After that, WithChmod() must be used to set // the desired permissions. // // We don't use "defer" here, to reset the umask to its original value as soon // as possible. Ideally we'd be able to detect if WithChmod() was passed as // an option, and skip changing umask if default permissions are used. origUmask := syscall.Umask(0o777) l, err := net.Listen("unix", path) syscall.Umask(origUmask) if err != nil { return nil, err } for _, op := range opts { if err := op(path); err != nil { _ = l.Close() return nil, err } } return l, nil } // NewUnixSocket creates a unix socket with the specified path and group. func NewUnixSocket(path string, gid int) (net.Listener, error) { return NewUnixSocketWithOpts(path, WithChown(0, gid), WithChmod(0o660)) } ================================================ FILE: vendor/github.com/docker/go-connections/tlsconfig/certpool.go ================================================ package tlsconfig import ( "crypto/x509" "runtime" ) // SystemCertPool returns a copy of the system cert pool, // returns an error if failed to load or empty pool on windows. func SystemCertPool() (*x509.CertPool, error) { certpool, err := x509.SystemCertPool() if err != nil && runtime.GOOS == "windows" { return x509.NewCertPool(), nil } return certpool, err } ================================================ FILE: vendor/github.com/docker/go-connections/tlsconfig/config.go ================================================ // Package tlsconfig provides primitives to retrieve secure-enough TLS configurations for both clients and servers. // // As a reminder from https://golang.org/pkg/crypto/tls/#Config: // // A Config structure is used to configure a TLS client or server. After one has been passed to a TLS function it must not be modified. // A Config may be reused; the tls package will also not modify it. package tlsconfig import ( "crypto/tls" "crypto/x509" "encoding/pem" "errors" "fmt" "os" ) // Options represents the information needed to create client and server TLS configurations. type Options struct { CAFile string // If either CertFile or KeyFile is empty, Client() will not load them // preventing the client from authenticating to the server. // However, Server() requires them and will error out if they are empty. CertFile string KeyFile string // client-only option InsecureSkipVerify bool // server-only option ClientAuth tls.ClientAuthType // If ExclusiveRootPools is set, then if a CA file is provided, the root pool used for TLS // creds will include exclusively the roots in that CA file. If no CA file is provided, // the system pool will be used. ExclusiveRootPools bool MinVersion uint16 // If Passphrase is set, it will be used to decrypt a TLS private key // if the key is encrypted. // // Deprecated: Use of encrypted TLS private keys has been deprecated, and // will be removed in a future release. Golang has deprecated support for // legacy PEM encryption (as specified in RFC 1423), as it is insecure by // design (see https://go-review.googlesource.com/c/go/+/264159). Passphrase string } // Extra (server-side) accepted CBC cipher suites - will phase out in the future var acceptedCBCCiphers = []uint16{ tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, } // DefaultServerAcceptedCiphers should be uses by code which already has a crypto/tls // options struct but wants to use a commonly accepted set of TLS cipher suites, with // known weak algorithms removed. var DefaultServerAcceptedCiphers = append(clientCipherSuites, acceptedCBCCiphers...) // ServerDefault returns a secure-enough TLS configuration for the server TLS configuration. func ServerDefault(ops ...func(*tls.Config)) *tls.Config { tlsConfig := &tls.Config{ // Avoid fallback by default to SSL protocols < TLS1.2 MinVersion: tls.VersionTLS12, PreferServerCipherSuites: true, CipherSuites: DefaultServerAcceptedCiphers, } for _, op := range ops { op(tlsConfig) } return tlsConfig } // ClientDefault returns a secure-enough TLS configuration for the client TLS configuration. func ClientDefault(ops ...func(*tls.Config)) *tls.Config { tlsConfig := &tls.Config{ // Prefer TLS1.2 as the client minimum MinVersion: tls.VersionTLS12, CipherSuites: clientCipherSuites, } for _, op := range ops { op(tlsConfig) } return tlsConfig } // certPool returns an X.509 certificate pool from `caFile`, the certificate file. func certPool(caFile string, exclusivePool bool) (*x509.CertPool, error) { // If we should verify the server, we need to load a trusted ca var ( certPool *x509.CertPool err error ) if exclusivePool { certPool = x509.NewCertPool() } else { certPool, err = SystemCertPool() if err != nil { return nil, fmt.Errorf("failed to read system certificates: %v", err) } } pemData, err := os.ReadFile(caFile) if err != nil { return nil, fmt.Errorf("could not read CA certificate %q: %v", caFile, err) } if !certPool.AppendCertsFromPEM(pemData) { return nil, fmt.Errorf("failed to append certificates from PEM file: %q", caFile) } return certPool, nil } // allTLSVersions lists all the TLS versions and is used by the code that validates // a uint16 value as a TLS version. var allTLSVersions = map[uint16]struct{}{ tls.VersionTLS10: {}, tls.VersionTLS11: {}, tls.VersionTLS12: {}, tls.VersionTLS13: {}, } // isValidMinVersion checks that the input value is a valid tls minimum version func isValidMinVersion(version uint16) bool { _, ok := allTLSVersions[version] return ok } // adjustMinVersion sets the MinVersion on `config`, the input configuration. // It assumes the current MinVersion on the `config` is the lowest allowed. func adjustMinVersion(options Options, config *tls.Config) error { if options.MinVersion > 0 { if !isValidMinVersion(options.MinVersion) { return fmt.Errorf("invalid minimum TLS version: %x", options.MinVersion) } if options.MinVersion < config.MinVersion { return fmt.Errorf("requested minimum TLS version is too low. Should be at-least: %x", config.MinVersion) } config.MinVersion = options.MinVersion } return nil } // IsErrEncryptedKey returns true if the 'err' is an error of incorrect // password when trying to decrypt a TLS private key. // // Deprecated: Use of encrypted TLS private keys has been deprecated, and // will be removed in a future release. Golang has deprecated support for // legacy PEM encryption (as specified in RFC 1423), as it is insecure by // design (see https://go-review.googlesource.com/c/go/+/264159). func IsErrEncryptedKey(err error) bool { return errors.Is(err, x509.IncorrectPasswordError) } // getPrivateKey returns the private key in 'keyBytes', in PEM-encoded format. // If the private key is encrypted, 'passphrase' is used to decrypted the // private key. func getPrivateKey(keyBytes []byte, passphrase string) ([]byte, error) { // this section makes some small changes to code from notary/tuf/utils/x509.go pemBlock, _ := pem.Decode(keyBytes) if pemBlock == nil { return nil, fmt.Errorf("no valid private key found") } var err error if x509.IsEncryptedPEMBlock(pemBlock) { //nolint:staticcheck // Ignore SA1019 (IsEncryptedPEMBlock is deprecated) keyBytes, err = x509.DecryptPEMBlock(pemBlock, []byte(passphrase)) //nolint:staticcheck // Ignore SA1019 (DecryptPEMBlock is deprecated) if err != nil { return nil, fmt.Errorf("private key is encrypted, but could not decrypt it: %w", err) } keyBytes = pem.EncodeToMemory(&pem.Block{Type: pemBlock.Type, Bytes: keyBytes}) } return keyBytes, nil } // getCert returns a Certificate from the CertFile and KeyFile in 'options', // if the key is encrypted, the Passphrase in 'options' will be used to // decrypt it. func getCert(options Options) ([]tls.Certificate, error) { if options.CertFile == "" && options.KeyFile == "" { return nil, nil } cert, err := os.ReadFile(options.CertFile) if err != nil { return nil, err } prKeyBytes, err := os.ReadFile(options.KeyFile) if err != nil { return nil, err } prKeyBytes, err = getPrivateKey(prKeyBytes, options.Passphrase) if err != nil { return nil, err } tlsCert, err := tls.X509KeyPair(cert, prKeyBytes) if err != nil { return nil, err } return []tls.Certificate{tlsCert}, nil } // Client returns a TLS configuration meant to be used by a client. func Client(options Options) (*tls.Config, error) { tlsConfig := ClientDefault() tlsConfig.InsecureSkipVerify = options.InsecureSkipVerify if !options.InsecureSkipVerify && options.CAFile != "" { CAs, err := certPool(options.CAFile, options.ExclusiveRootPools) if err != nil { return nil, err } tlsConfig.RootCAs = CAs } tlsCerts, err := getCert(options) if err != nil { return nil, fmt.Errorf("could not load X509 key pair: %w", err) } tlsConfig.Certificates = tlsCerts if err := adjustMinVersion(options, tlsConfig); err != nil { return nil, err } return tlsConfig, nil } // Server returns a TLS configuration meant to be used by a server. func Server(options Options) (*tls.Config, error) { tlsConfig := ServerDefault() tlsConfig.ClientAuth = options.ClientAuth tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile) if err != nil { if os.IsNotExist(err) { return nil, fmt.Errorf("could not load X509 key pair (cert: %q, key: %q): %v", options.CertFile, options.KeyFile, err) } return nil, fmt.Errorf("error reading X509 key pair - make sure the key is not encrypted (cert: %q, key: %q): %v", options.CertFile, options.KeyFile, err) } tlsConfig.Certificates = []tls.Certificate{tlsCert} if options.ClientAuth >= tls.VerifyClientCertIfGiven && options.CAFile != "" { CAs, err := certPool(options.CAFile, options.ExclusiveRootPools) if err != nil { return nil, err } tlsConfig.ClientCAs = CAs } if err := adjustMinVersion(options, tlsConfig); err != nil { return nil, err } return tlsConfig, nil } ================================================ FILE: vendor/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go ================================================ // Package tlsconfig provides primitives to retrieve secure-enough TLS configurations for both clients and servers. package tlsconfig import ( "crypto/tls" ) // Client TLS cipher suites (dropping CBC ciphers for client preferred suite set) var clientCipherSuites = []uint16{ tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, } ================================================ FILE: vendor/github.com/docker/go-units/CONTRIBUTING.md ================================================ # Contributing to go-units Want to hack on go-units? Awesome! Here are instructions to get you started. go-units is a part of the [Docker](https://www.docker.com) project, and follows the same rules and principles. If you're already familiar with the way Docker does things, you'll feel right at home. Otherwise, go read Docker's [contributions guidelines](https://github.com/docker/docker/blob/master/CONTRIBUTING.md), [issue triaging](https://github.com/docker/docker/blob/master/project/ISSUE-TRIAGE.md), [review process](https://github.com/docker/docker/blob/master/project/REVIEWING.md) and [branches and tags](https://github.com/docker/docker/blob/master/project/BRANCHES-AND-TAGS.md). ### Sign your work The sign-off is a simple line at the end of the explanation for the patch. Your signature certifies that you wrote the patch or otherwise have the right to pass it on as an open-source patch. The rules are pretty simple: if you can certify the below (from [developercertificate.org](http://developercertificate.org/)): ``` Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 660 York Street, Suite 102, San Francisco, CA 94110 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. ``` Then you just add a line to every git commit message: Signed-off-by: Joe Smith Use your real name (sorry, no pseudonyms or anonymous contributions.) If you set your `user.name` and `user.email` git configs, you can sign your commit automatically with `git commit -s`. ================================================ FILE: vendor/github.com/docker/go-units/LICENSE ================================================ Apache License Version 2.0, January 2004 https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Copyright 2015 Docker, Inc. 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 https://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. ================================================ FILE: vendor/github.com/docker/go-units/MAINTAINERS ================================================ # go-units maintainers file # # This file describes who runs the docker/go-units project and how. # This is a living document - if you see something out of date or missing, speak up! # # It is structured to be consumable by both humans and programs. # To extract its contents programmatically, use any TOML-compliant parser. # # This file is compiled into the MAINTAINERS file in docker/opensource. # [Org] [Org."Core maintainers"] people = [ "akihirosuda", "dnephin", "thajeztah", "vdemeester", ] [people] # A reference list of all people associated with the project. # All other sections should refer to people by their canonical key # in the people section. # ADD YOURSELF HERE IN ALPHABETICAL ORDER [people.akihirosuda] Name = "Akihiro Suda" Email = "akihiro.suda.cz@hco.ntt.co.jp" GitHub = "AkihiroSuda" [people.dnephin] Name = "Daniel Nephin" Email = "dnephin@gmail.com" GitHub = "dnephin" [people.thajeztah] Name = "Sebastiaan van Stijn" Email = "github@gone.nl" GitHub = "thaJeztah" [people.vdemeester] Name = "Vincent Demeester" Email = "vincent@sbr.pm" GitHub = "vdemeester" ================================================ FILE: vendor/github.com/docker/go-units/README.md ================================================ [![GoDoc](https://godoc.org/github.com/docker/go-units?status.svg)](https://godoc.org/github.com/docker/go-units) # Introduction go-units is a library to transform human friendly measurements into machine friendly values. ## Usage See the [docs in godoc](https://godoc.org/github.com/docker/go-units) for examples and documentation. ## Copyright and license Copyright © 2015 Docker, Inc. go-units is licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full text of the license. ================================================ FILE: vendor/github.com/docker/go-units/circle.yml ================================================ dependencies: post: # install golint - go get golang.org/x/lint/golint test: pre: # run analysis before tests - go vet ./... - test -z "$(golint ./... | tee /dev/stderr)" - test -z "$(gofmt -s -l . | tee /dev/stderr)" ================================================ FILE: vendor/github.com/docker/go-units/duration.go ================================================ // Package units provides helper function to parse and print size and time units // in human-readable format. package units import ( "fmt" "time" ) // HumanDuration returns a human-readable approximation of a duration // (eg. "About a minute", "4 hours ago", etc.). func HumanDuration(d time.Duration) string { if seconds := int(d.Seconds()); seconds < 1 { return "Less than a second" } else if seconds == 1 { return "1 second" } else if seconds < 60 { return fmt.Sprintf("%d seconds", seconds) } else if minutes := int(d.Minutes()); minutes == 1 { return "About a minute" } else if minutes < 60 { return fmt.Sprintf("%d minutes", minutes) } else if hours := int(d.Hours() + 0.5); hours == 1 { return "About an hour" } else if hours < 48 { return fmt.Sprintf("%d hours", hours) } else if hours < 24*7*2 { return fmt.Sprintf("%d days", hours/24) } else if hours < 24*30*2 { return fmt.Sprintf("%d weeks", hours/24/7) } else if hours < 24*365*2 { return fmt.Sprintf("%d months", hours/24/30) } return fmt.Sprintf("%d years", int(d.Hours())/24/365) } ================================================ FILE: vendor/github.com/docker/go-units/size.go ================================================ package units import ( "fmt" "strconv" "strings" ) // See: http://en.wikipedia.org/wiki/Binary_prefix const ( // Decimal KB = 1000 MB = 1000 * KB GB = 1000 * MB TB = 1000 * GB PB = 1000 * TB // Binary KiB = 1024 MiB = 1024 * KiB GiB = 1024 * MiB TiB = 1024 * GiB PiB = 1024 * TiB ) type unitMap map[byte]int64 var ( decimalMap = unitMap{'k': KB, 'm': MB, 'g': GB, 't': TB, 'p': PB} binaryMap = unitMap{'k': KiB, 'm': MiB, 'g': GiB, 't': TiB, 'p': PiB} ) var ( decimapAbbrs = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"} binaryAbbrs = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"} ) func getSizeAndUnit(size float64, base float64, _map []string) (float64, string) { i := 0 unitsLimit := len(_map) - 1 for size >= base && i < unitsLimit { size = size / base i++ } return size, _map[i] } // CustomSize returns a human-readable approximation of a size // using custom format. func CustomSize(format string, size float64, base float64, _map []string) string { size, unit := getSizeAndUnit(size, base, _map) return fmt.Sprintf(format, size, unit) } // HumanSizeWithPrecision allows the size to be in any precision, // instead of 4 digit precision used in units.HumanSize. func HumanSizeWithPrecision(size float64, precision int) string { size, unit := getSizeAndUnit(size, 1000.0, decimapAbbrs) return fmt.Sprintf("%.*g%s", precision, size, unit) } // HumanSize returns a human-readable approximation of a size // capped at 4 valid numbers (eg. "2.746 MB", "796 KB"). func HumanSize(size float64) string { return HumanSizeWithPrecision(size, 4) } // BytesSize returns a human-readable size in bytes, kibibytes, // mebibytes, gibibytes, or tebibytes (eg. "44kiB", "17MiB"). func BytesSize(size float64) string { return CustomSize("%.4g%s", size, 1024.0, binaryAbbrs) } // FromHumanSize returns an integer from a human-readable specification of a // size using SI standard (eg. "44kB", "17MB"). func FromHumanSize(size string) (int64, error) { return parseSize(size, decimalMap) } // RAMInBytes parses a human-readable string representing an amount of RAM // in bytes, kibibytes, mebibytes, gibibytes, or tebibytes and // returns the number of bytes, or -1 if the string is unparseable. // Units are case-insensitive, and the 'b' suffix is optional. func RAMInBytes(size string) (int64, error) { return parseSize(size, binaryMap) } // Parses the human-readable size string into the amount it represents. func parseSize(sizeStr string, uMap unitMap) (int64, error) { // TODO: rewrite to use strings.Cut if there's a space // once Go < 1.18 is deprecated. sep := strings.LastIndexAny(sizeStr, "01234567890. ") if sep == -1 { // There should be at least a digit. return -1, fmt.Errorf("invalid size: '%s'", sizeStr) } var num, sfx string if sizeStr[sep] != ' ' { num = sizeStr[:sep+1] sfx = sizeStr[sep+1:] } else { // Omit the space separator. num = sizeStr[:sep] sfx = sizeStr[sep+1:] } size, err := strconv.ParseFloat(num, 64) if err != nil { return -1, err } // Backward compatibility: reject negative sizes. if size < 0 { return -1, fmt.Errorf("invalid size: '%s'", sizeStr) } if len(sfx) == 0 { return int64(size), nil } // Process the suffix. if len(sfx) > 3 { // Too long. goto badSuffix } sfx = strings.ToLower(sfx) // Trivial case: b suffix. if sfx[0] == 'b' { if len(sfx) > 1 { // no extra characters allowed after b. goto badSuffix } return int64(size), nil } // A suffix from the map. if mul, ok := uMap[sfx[0]]; ok { size *= float64(mul) } else { goto badSuffix } // The suffix may have extra "b" or "ib" (e.g. KiB or MB). switch { case len(sfx) == 2 && sfx[1] != 'b': goto badSuffix case len(sfx) == 3 && sfx[1:] != "ib": goto badSuffix } return int64(size), nil badSuffix: return -1, fmt.Errorf("invalid suffix: '%s'", sfx) } ================================================ FILE: vendor/github.com/docker/go-units/ulimit.go ================================================ package units import ( "fmt" "strconv" "strings" ) // Ulimit is a human friendly version of Rlimit. type Ulimit struct { Name string Hard int64 Soft int64 } // Rlimit specifies the resource limits, such as max open files. type Rlimit struct { Type int `json:"type,omitempty"` Hard uint64 `json:"hard,omitempty"` Soft uint64 `json:"soft,omitempty"` } const ( // magic numbers for making the syscall // some of these are defined in the syscall package, but not all. // Also since Windows client doesn't get access to the syscall package, need to // define these here rlimitAs = 9 rlimitCore = 4 rlimitCPU = 0 rlimitData = 2 rlimitFsize = 1 rlimitLocks = 10 rlimitMemlock = 8 rlimitMsgqueue = 12 rlimitNice = 13 rlimitNofile = 7 rlimitNproc = 6 rlimitRss = 5 rlimitRtprio = 14 rlimitRttime = 15 rlimitSigpending = 11 rlimitStack = 3 ) var ulimitNameMapping = map[string]int{ //"as": rlimitAs, // Disabled since this doesn't seem usable with the way Docker inits a container. "core": rlimitCore, "cpu": rlimitCPU, "data": rlimitData, "fsize": rlimitFsize, "locks": rlimitLocks, "memlock": rlimitMemlock, "msgqueue": rlimitMsgqueue, "nice": rlimitNice, "nofile": rlimitNofile, "nproc": rlimitNproc, "rss": rlimitRss, "rtprio": rlimitRtprio, "rttime": rlimitRttime, "sigpending": rlimitSigpending, "stack": rlimitStack, } // ParseUlimit parses and returns a Ulimit from the specified string. func ParseUlimit(val string) (*Ulimit, error) { parts := strings.SplitN(val, "=", 2) if len(parts) != 2 { return nil, fmt.Errorf("invalid ulimit argument: %s", val) } if _, exists := ulimitNameMapping[parts[0]]; !exists { return nil, fmt.Errorf("invalid ulimit type: %s", parts[0]) } var ( soft int64 hard = &soft // default to soft in case no hard was set temp int64 err error ) switch limitVals := strings.Split(parts[1], ":"); len(limitVals) { case 2: temp, err = strconv.ParseInt(limitVals[1], 10, 64) if err != nil { return nil, err } hard = &temp fallthrough case 1: soft, err = strconv.ParseInt(limitVals[0], 10, 64) if err != nil { return nil, err } default: return nil, fmt.Errorf("too many limit value arguments - %s, can only have up to two, `soft[:hard]`", parts[1]) } if *hard != -1 { if soft == -1 { return nil, fmt.Errorf("ulimit soft limit must be less than or equal to hard limit: soft: -1 (unlimited), hard: %d", *hard) } if soft > *hard { return nil, fmt.Errorf("ulimit soft limit must be less than or equal to hard limit: %d > %d", soft, *hard) } } return &Ulimit{Name: parts[0], Soft: soft, Hard: *hard}, nil } // GetRlimit returns the RLimit corresponding to Ulimit. func (u *Ulimit) GetRlimit() (*Rlimit, error) { t, exists := ulimitNameMapping[u.Name] if !exists { return nil, fmt.Errorf("invalid ulimit name %s", u.Name) } return &Rlimit{Type: t, Soft: uint64(u.Soft), Hard: uint64(u.Hard)}, nil } func (u *Ulimit) String() string { return fmt.Sprintf("%s=%d:%d", u.Name, u.Soft, u.Hard) } ================================================ FILE: vendor/github.com/ebitengine/purego/.gitignore ================================================ *~ ================================================ FILE: vendor/github.com/ebitengine/purego/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: vendor/github.com/ebitengine/purego/README.md ================================================ # purego [![Go Reference](https://pkg.go.dev/badge/github.com/ebitengine/purego?GOOS=darwin.svg)](https://pkg.go.dev/github.com/ebitengine/purego?GOOS=darwin) A library for calling C functions from Go without Cgo. > This is beta software so expect bugs and potentially API breaking changes > but each release will be tagged to avoid breaking people's code. > Bug reports are encouraged. ## Motivation The [Ebitengine](https://github.com/hajimehoshi/ebiten) game engine was ported to use only Go on Windows. This enabled cross-compiling to Windows from any other operating system simply by setting `GOOS=windows`. The purego project was born to bring that same vision to the other platforms supported by Ebitengine. ## Benefits - **Simple Cross-Compilation**: No C means you can build for other platforms easily without a C compiler. - **Faster Compilation**: Efficiently cache your entirely Go builds. - **Smaller Binaries**: Using Cgo generates a C wrapper function for each C function called. Purego doesn't! - **Dynamic Linking**: Load symbols at runtime and use it as a plugin system. - **Foreign Function Interface**: Call into other languages that are compiled into shared objects. - **Cgo Fallback**: Works even with CGO_ENABLED=1 so incremental porting is possible. This also means unsupported GOARCHs (freebsd/riscv64, linux/mips, etc.) will still work except for float arguments and return values. ## Supported Platforms - **FreeBSD**: amd64, arm64 - **Linux**: amd64, arm64 - **macOS / iOS**: amd64, arm64 - **Windows**: 386*, amd64, arm*, arm64 `*` These architectures only support SyscallN and NewCallback ## Example The example below only showcases purego use for macOS and Linux. The other platforms require special handling which can be seen in the complete example at [examples/libc](https://github.com/ebitengine/purego/tree/main/examples/libc) which supports Windows and FreeBSD. ```go package main import ( "fmt" "runtime" "github.com/ebitengine/purego" ) func getSystemLibrary() string { switch runtime.GOOS { case "darwin": return "/usr/lib/libSystem.B.dylib" case "linux": return "libc.so.6" default: panic(fmt.Errorf("GOOS=%s is not supported", runtime.GOOS)) } } func main() { libc, err := purego.Dlopen(getSystemLibrary(), purego.RTLD_NOW|purego.RTLD_GLOBAL) if err != nil { panic(err) } var puts func(string) purego.RegisterLibFunc(&puts, libc, "puts") puts("Calling C from Go without Cgo!") } ``` Then to run: `CGO_ENABLED=0 go run main.go` ## Questions If you have questions about how to incorporate purego in your project or want to discuss how it works join the [Discord](https://discord.gg/HzGZVD6BkY)! ### External Code Purego uses code that originates from the Go runtime. These files are under the BSD-3 License that can be found [in the Go Source](https://github.com/golang/go/blob/master/LICENSE). This is a list of the copied files: * `abi_*.h` from package `runtime/cgo` * `zcallback_darwin_*.s` from package `runtime` * `internal/fakecgo/abi_*.h` from package `runtime/cgo` * `internal/fakecgo/asm_GOARCH.s` from package `runtime/cgo` * `internal/fakecgo/callbacks.go` from package `runtime/cgo` * `internal/fakecgo/go_GOOS_GOARCH.go` from package `runtime/cgo` * `internal/fakecgo/iscgo.go` from package `runtime/cgo` * `internal/fakecgo/setenv.go` from package `runtime/cgo` * `internal/fakecgo/freebsd.go` from package `runtime/cgo` The files `abi_*.h` and `internal/fakecgo/abi_*.h` are the same because Bazel does not support cross-package use of `#include` so we need each one once per package. (cf. [issue](https://github.com/bazelbuild/rules_go/issues/3636)) ================================================ FILE: vendor/github.com/ebitengine/purego/abi_amd64.h ================================================ // Copyright 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Macros for transitioning from the host ABI to Go ABI0. // // These save the frame pointer, so in general, functions that use // these should have zero frame size to suppress the automatic frame // pointer, though it's harmless to not do this. #ifdef GOOS_windows // REGS_HOST_TO_ABI0_STACK is the stack bytes used by // PUSH_REGS_HOST_TO_ABI0. #define REGS_HOST_TO_ABI0_STACK (28*8 + 8) // PUSH_REGS_HOST_TO_ABI0 prepares for transitioning from // the host ABI to Go ABI0 code. It saves all registers that are // callee-save in the host ABI and caller-save in Go ABI0 and prepares // for entry to Go. // // Save DI SI BP BX R12 R13 R14 R15 X6-X15 registers and the DF flag. // Clear the DF flag for the Go ABI. // MXCSR matches the Go ABI, so we don't have to set that, // and Go doesn't modify it, so we don't have to save it. #define PUSH_REGS_HOST_TO_ABI0() \ PUSHFQ \ CLD \ ADJSP $(REGS_HOST_TO_ABI0_STACK - 8) \ MOVQ DI, (0*0)(SP) \ MOVQ SI, (1*8)(SP) \ MOVQ BP, (2*8)(SP) \ MOVQ BX, (3*8)(SP) \ MOVQ R12, (4*8)(SP) \ MOVQ R13, (5*8)(SP) \ MOVQ R14, (6*8)(SP) \ MOVQ R15, (7*8)(SP) \ MOVUPS X6, (8*8)(SP) \ MOVUPS X7, (10*8)(SP) \ MOVUPS X8, (12*8)(SP) \ MOVUPS X9, (14*8)(SP) \ MOVUPS X10, (16*8)(SP) \ MOVUPS X11, (18*8)(SP) \ MOVUPS X12, (20*8)(SP) \ MOVUPS X13, (22*8)(SP) \ MOVUPS X14, (24*8)(SP) \ MOVUPS X15, (26*8)(SP) #define POP_REGS_HOST_TO_ABI0() \ MOVQ (0*0)(SP), DI \ MOVQ (1*8)(SP), SI \ MOVQ (2*8)(SP), BP \ MOVQ (3*8)(SP), BX \ MOVQ (4*8)(SP), R12 \ MOVQ (5*8)(SP), R13 \ MOVQ (6*8)(SP), R14 \ MOVQ (7*8)(SP), R15 \ MOVUPS (8*8)(SP), X6 \ MOVUPS (10*8)(SP), X7 \ MOVUPS (12*8)(SP), X8 \ MOVUPS (14*8)(SP), X9 \ MOVUPS (16*8)(SP), X10 \ MOVUPS (18*8)(SP), X11 \ MOVUPS (20*8)(SP), X12 \ MOVUPS (22*8)(SP), X13 \ MOVUPS (24*8)(SP), X14 \ MOVUPS (26*8)(SP), X15 \ ADJSP $-(REGS_HOST_TO_ABI0_STACK - 8) \ POPFQ #else // SysV ABI #define REGS_HOST_TO_ABI0_STACK (6*8) // SysV MXCSR matches the Go ABI, so we don't have to set that, // and Go doesn't modify it, so we don't have to save it. // Both SysV and Go require DF to be cleared, so that's already clear. // The SysV and Go frame pointer conventions are compatible. #define PUSH_REGS_HOST_TO_ABI0() \ ADJSP $(REGS_HOST_TO_ABI0_STACK) \ MOVQ BP, (5*8)(SP) \ LEAQ (5*8)(SP), BP \ MOVQ BX, (0*8)(SP) \ MOVQ R12, (1*8)(SP) \ MOVQ R13, (2*8)(SP) \ MOVQ R14, (3*8)(SP) \ MOVQ R15, (4*8)(SP) #define POP_REGS_HOST_TO_ABI0() \ MOVQ (0*8)(SP), BX \ MOVQ (1*8)(SP), R12 \ MOVQ (2*8)(SP), R13 \ MOVQ (3*8)(SP), R14 \ MOVQ (4*8)(SP), R15 \ MOVQ (5*8)(SP), BP \ ADJSP $-(REGS_HOST_TO_ABI0_STACK) #endif ================================================ FILE: vendor/github.com/ebitengine/purego/abi_arm64.h ================================================ // Copyright 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Macros for transitioning from the host ABI to Go ABI0. // // These macros save and restore the callee-saved registers // from the stack, but they don't adjust stack pointer, so // the user should prepare stack space in advance. // SAVE_R19_TO_R28(offset) saves R19 ~ R28 to the stack space // of ((offset)+0*8)(RSP) ~ ((offset)+9*8)(RSP). // // SAVE_F8_TO_F15(offset) saves F8 ~ F15 to the stack space // of ((offset)+0*8)(RSP) ~ ((offset)+7*8)(RSP). // // R29 is not saved because Go will save and restore it. #define SAVE_R19_TO_R28(offset) \ STP (R19, R20), ((offset)+0*8)(RSP) \ STP (R21, R22), ((offset)+2*8)(RSP) \ STP (R23, R24), ((offset)+4*8)(RSP) \ STP (R25, R26), ((offset)+6*8)(RSP) \ STP (R27, g), ((offset)+8*8)(RSP) #define RESTORE_R19_TO_R28(offset) \ LDP ((offset)+0*8)(RSP), (R19, R20) \ LDP ((offset)+2*8)(RSP), (R21, R22) \ LDP ((offset)+4*8)(RSP), (R23, R24) \ LDP ((offset)+6*8)(RSP), (R25, R26) \ LDP ((offset)+8*8)(RSP), (R27, g) /* R28 */ #define SAVE_F8_TO_F15(offset) \ FSTPD (F8, F9), ((offset)+0*8)(RSP) \ FSTPD (F10, F11), ((offset)+2*8)(RSP) \ FSTPD (F12, F13), ((offset)+4*8)(RSP) \ FSTPD (F14, F15), ((offset)+6*8)(RSP) #define RESTORE_F8_TO_F15(offset) \ FLDPD ((offset)+0*8)(RSP), (F8, F9) \ FLDPD ((offset)+2*8)(RSP), (F10, F11) \ FLDPD ((offset)+4*8)(RSP), (F12, F13) \ FLDPD ((offset)+6*8)(RSP), (F14, F15) ================================================ FILE: vendor/github.com/ebitengine/purego/cgo.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build cgo && (darwin || freebsd || linux) package purego // if CGO_ENABLED=1 import the Cgo runtime to ensure that it is set up properly. // This is required since some frameworks need TLS setup the C way which Go doesn't do. // We currently don't support ios in fakecgo mode so force Cgo or fail // Even if CGO_ENABLED=1 the Cgo runtime is not imported unless `import "C"` is used. // which will import this package automatically. Normally this isn't an issue since it // usually isn't possible to call into C without using that import. However, with purego // it is since we don't use `import "C"`! import ( _ "runtime/cgo" _ "github.com/ebitengine/purego/internal/cgo" ) ================================================ FILE: vendor/github.com/ebitengine/purego/dlerror.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 The Ebitengine Authors //go:build darwin || freebsd || linux package purego // Dlerror represents an error value returned from Dlopen, Dlsym, or Dlclose. // // This type is not available on Windows as there is no counterpart to it on Windows. type Dlerror struct { s string } func (e Dlerror) Error() string { return e.s } ================================================ FILE: vendor/github.com/ebitengine/purego/dlfcn.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build (darwin || freebsd || linux) && !android && !faketime package purego import ( "unsafe" ) // Unix Specification for dlfcn.h: https://pubs.opengroup.org/onlinepubs/7908799/xsh/dlfcn.h.html var ( fnDlopen func(path string, mode int) uintptr fnDlsym func(handle uintptr, name string) uintptr fnDlerror func() string fnDlclose func(handle uintptr) bool ) func init() { RegisterFunc(&fnDlopen, dlopenABI0) RegisterFunc(&fnDlsym, dlsymABI0) RegisterFunc(&fnDlerror, dlerrorABI0) RegisterFunc(&fnDlclose, dlcloseABI0) } // Dlopen examines the dynamic library or bundle file specified by path. If the file is compatible // with the current process and has not already been loaded into the // current process, it is loaded and linked. After being linked, if it contains // any initializer functions, they are called, before Dlopen // returns. It returns a handle that can be used with Dlsym and Dlclose. // A second call to Dlopen with the same path will return the same handle, but the internal // reference count for the handle will be incremented. Therefore, all // Dlopen calls should be balanced with a Dlclose call. // // This function is not available on Windows. // Use [golang.org/x/sys/windows.LoadLibrary], [golang.org/x/sys/windows.LoadLibraryEx], // [golang.org/x/sys/windows.NewLazyDLL], or [golang.org/x/sys/windows.NewLazySystemDLL] for Windows instead. func Dlopen(path string, mode int) (uintptr, error) { u := fnDlopen(path, mode) if u == 0 { return 0, Dlerror{fnDlerror()} } return u, nil } // Dlsym takes a "handle" of a dynamic library returned by Dlopen and the symbol name. // It returns the address where that symbol is loaded into memory. If the symbol is not found, // in the specified library or any of the libraries that were automatically loaded by Dlopen // when that library was loaded, Dlsym returns zero. // // This function is not available on Windows. // Use [golang.org/x/sys/windows.GetProcAddress] for Windows instead. func Dlsym(handle uintptr, name string) (uintptr, error) { u := fnDlsym(handle, name) if u == 0 { return 0, Dlerror{fnDlerror()} } return u, nil } // Dlclose decrements the reference count on the dynamic library handle. // If the reference count drops to zero and no other loaded libraries // use symbols in it, then the dynamic library is unloaded. // // This function is not available on Windows. // Use [golang.org/x/sys/windows.FreeLibrary] for Windows instead. func Dlclose(handle uintptr) error { if fnDlclose(handle) { return Dlerror{fnDlerror()} } return nil } func loadSymbol(handle uintptr, name string) (uintptr, error) { return Dlsym(handle, name) } // these functions exist in dlfcn_stubs.s and are calling C functions linked to in dlfcn_GOOS.go // the indirection is necessary because a function is actually a pointer to the pointer to the code. // sadly, I do not know of anyway to remove the assembly stubs entirely because //go:linkname doesn't // appear to work if you link directly to the C function on darwin arm64. //go:linkname dlopen dlopen var dlopen uintptr var dlopenABI0 = uintptr(unsafe.Pointer(&dlopen)) //go:linkname dlsym dlsym var dlsym uintptr var dlsymABI0 = uintptr(unsafe.Pointer(&dlsym)) //go:linkname dlclose dlclose var dlclose uintptr var dlcloseABI0 = uintptr(unsafe.Pointer(&dlclose)) //go:linkname dlerror dlerror var dlerror uintptr var dlerrorABI0 = uintptr(unsafe.Pointer(&dlerror)) ================================================ FILE: vendor/github.com/ebitengine/purego/dlfcn_android.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors package purego import "github.com/ebitengine/purego/internal/cgo" // Source for constants: https://android.googlesource.com/platform/bionic/+/refs/heads/main/libc/include/dlfcn.h const ( is64bit = 1 << (^uintptr(0) >> 63) / 2 is32bit = 1 - is64bit RTLD_DEFAULT = is32bit * 0xffffffff RTLD_LAZY = 0x00000001 RTLD_NOW = is64bit * 0x00000002 RTLD_LOCAL = 0x00000000 RTLD_GLOBAL = is64bit*0x00100 | is32bit*0x00000002 ) func Dlopen(path string, mode int) (uintptr, error) { return cgo.Dlopen(path, mode) } func Dlsym(handle uintptr, name string) (uintptr, error) { return cgo.Dlsym(handle, name) } func Dlclose(handle uintptr) error { return cgo.Dlclose(handle) } func loadSymbol(handle uintptr, name string) (uintptr, error) { return Dlsym(handle, name) } ================================================ FILE: vendor/github.com/ebitengine/purego/dlfcn_darwin.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors package purego // Source for constants: https://opensource.apple.com/source/dyld/dyld-360.14/include/dlfcn.h.auto.html const ( RTLD_DEFAULT = 1<<64 - 2 // Pseudo-handle for dlsym so search for any loaded symbol RTLD_LAZY = 0x1 // Relocations are performed at an implementation-dependent time. RTLD_NOW = 0x2 // Relocations are performed when the object is loaded. RTLD_LOCAL = 0x4 // All symbols are not made available for relocation processing by other modules. RTLD_GLOBAL = 0x8 // All symbols are available for relocation processing of other modules. ) //go:cgo_import_dynamic purego_dlopen dlopen "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_dlsym dlsym "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_dlerror dlerror "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_dlclose dlclose "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_dlopen dlopen "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_dlsym dlsym "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_dlerror dlerror "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_dlclose dlclose "/usr/lib/libSystem.B.dylib" ================================================ FILE: vendor/github.com/ebitengine/purego/dlfcn_freebsd.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors package purego // Constants as defined in https://github.com/freebsd/freebsd-src/blob/main/include/dlfcn.h const ( intSize = 32 << (^uint(0) >> 63) // 32 or 64 RTLD_DEFAULT = 1< C) // // string <=> char* // bool <=> _Bool // uintptr <=> uintptr_t // uint <=> uint32_t or uint64_t // uint8 <=> uint8_t // uint16 <=> uint16_t // uint32 <=> uint32_t // uint64 <=> uint64_t // int <=> int32_t or int64_t // int8 <=> int8_t // int16 <=> int16_t // int32 <=> int32_t // int64 <=> int64_t // float32 <=> float // float64 <=> double // struct <=> struct (WIP - darwin only) // func <=> C function // unsafe.Pointer, *T <=> void* // []T => void* // // There is a special case when the last argument of fptr is a variadic interface (or []interface} // it will be expanded into a call to the C function as if it had the arguments in that slice. // This means that using arg ...interface{} is like a cast to the function with the arguments inside arg. // This is not the same as C variadic. // // # Memory // // In general it is not possible for purego to guarantee the lifetimes of objects returned or received from // calling functions using RegisterFunc. For arguments to a C function it is important that the C function doesn't // hold onto a reference to Go memory. This is the same as the [Cgo rules]. // // However, there are some special cases. When passing a string as an argument if the string does not end in a null // terminated byte (\x00) then the string will be copied into memory maintained by purego. The memory is only valid for // that specific call. Therefore, if the C code keeps a reference to that string it may become invalid at some // undefined time. However, if the string does already contain a null-terminated byte then no copy is done. // It is then the responsibility of the caller to ensure the string stays alive as long as it's needed in C memory. // This can be done using runtime.KeepAlive or allocating the string in C memory using malloc. When a C function // returns a null-terminated pointer to char a Go string can be used. Purego will allocate a new string in Go memory // and copy the data over. This string will be garbage collected whenever Go decides it's no longer referenced. // This C created string will not be freed by purego. If the pointer to char is not null-terminated or must continue // to point to C memory (because it's a buffer for example) then use a pointer to byte and then convert that to a slice // using unsafe.Slice. Doing this means that it becomes the responsibility of the caller to care about the lifetime // of the pointer // // # Structs // // Purego can handle the most common structs that have fields of builtin types like int8, uint16, float32, etc. However, // it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure // that all padding is added to the Go struct to match the C one. See `BoolStructFn` in struct_test.go for an example. // // # Example // // All functions below call this C function: // // char *foo(char *str); // // // Let purego convert types // var foo func(s string) string // goString := foo("copied") // // Go will garbage collect this string // // // Manually, handle allocations // var foo2 func(b string) *byte // mustFree := foo2("not copied\x00") // defer free(mustFree) // // [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C func RegisterFunc(fptr interface{}, cfn uintptr) { fn := reflect.ValueOf(fptr).Elem() ty := fn.Type() if ty.Kind() != reflect.Func { panic("purego: fptr must be a function pointer") } if ty.NumOut() > 1 { panic("purego: function can only return zero or one values") } if cfn == 0 { panic("purego: cfn is nil") } if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) && runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" { panic("purego: float returns are not supported") } { // this code checks how many registers and stack this function will use // to avoid crashing with too many arguments var ints int var floats int var stack int for i := 0; i < ty.NumIn(); i++ { arg := ty.In(i) switch arg.Kind() { case reflect.Func: // This only does preliminary testing to ensure the CDecl argument // is the first argument. Full testing is done when the callback is actually // created in NewCallback. for j := 0; j < arg.NumIn(); j++ { in := arg.In(j) if !in.AssignableTo(reflect.TypeOf(CDecl{})) { continue } if j != 0 { panic("purego: CDecl must be the first argument") } } case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer, reflect.Slice, reflect.Bool: if ints < numOfIntegerRegisters() { ints++ } else { stack++ } case reflect.Float32, reflect.Float64: const is32bit = unsafe.Sizeof(uintptr(0)) == 4 if is32bit { panic("purego: floats only supported on 64bit platforms") } if floats < numOfFloats { floats++ } else { stack++ } case reflect.Struct: if runtime.GOOS != "darwin" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64") { panic("purego: struct arguments are only supported on darwin amd64 & arm64") } if arg.Size() == 0 { continue } addInt := func(u uintptr) { ints++ } addFloat := func(u uintptr) { floats++ } addStack := func(u uintptr) { stack++ } _ = addStruct(reflect.New(arg).Elem(), &ints, &floats, &stack, addInt, addFloat, addStack, nil) default: panic("purego: unsupported kind " + arg.Kind().String()) } } if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { if runtime.GOOS != "darwin" { panic("purego: struct return values only supported on darwin arm64 & amd64") } outType := ty.Out(0) checkStructFieldsSupported(outType) if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize { // on amd64 if struct is bigger than 16 bytes allocate the return struct // and pass it in as a hidden first argument. ints++ } } sizeOfStack := maxArgs - numOfIntegerRegisters() if stack > sizeOfStack { panic("purego: too many arguments") } } v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { if len(args) > 0 { if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok { // subtract one from args bc the last argument in args is []interface{} // which we are currently expanding tmp := make([]reflect.Value, len(args)-1+len(variadic)) n := copy(tmp, args[:len(args)-1]) for i, v := range variadic { tmp[n+i] = reflect.ValueOf(v) } args = tmp } } var sysargs [maxArgs]uintptr stack := sysargs[numOfIntegerRegisters():] var floats [numOfFloats]uintptr var numInts int var numFloats int var numStack int var addStack, addInt, addFloat func(x uintptr) if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { // Windows arm64 uses the same calling convention as macOS and Linux addStack = func(x uintptr) { stack[numStack] = x numStack++ } addInt = func(x uintptr) { if numInts >= numOfIntegerRegisters() { addStack(x) } else { sysargs[numInts] = x numInts++ } } addFloat = func(x uintptr) { if numFloats < len(floats) { floats[numFloats] = x numFloats++ } else { addStack(x) } } } else { // On Windows amd64 the arguments are passed in the numbered registered. // So the first int is in the first integer register and the first float // is in the second floating register if there is already a first int. // This is in contrast to how macOS and Linux pass arguments which // tries to use as many registers as possible in the calling convention. addStack = func(x uintptr) { sysargs[numStack] = x numStack++ } addInt = addStack addFloat = addStack } var keepAlive []interface{} defer func() { runtime.KeepAlive(keepAlive) runtime.KeepAlive(args) }() var syscall syscall15Args if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { outType := ty.Out(0) if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize { val := reflect.New(outType) keepAlive = append(keepAlive, val) addInt(val.Pointer()) } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize { isAllFloats, numFields := isAllSameFloat(outType) if !isAllFloats || numFields > 4 { val := reflect.New(outType) keepAlive = append(keepAlive, val) syscall.arm64_r8 = val.Pointer() } } } for _, v := range args { switch v.Kind() { case reflect.String: ptr := strings.CString(v.String()) keepAlive = append(keepAlive, ptr) addInt(uintptr(unsafe.Pointer(ptr))) case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: addInt(uintptr(v.Uint())) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: addInt(uintptr(v.Int())) case reflect.Ptr, reflect.UnsafePointer, reflect.Slice: // There is no need to keepAlive this pointer separately because it is kept alive in the args variable addInt(v.Pointer()) case reflect.Func: addInt(NewCallback(v.Interface())) case reflect.Bool: if v.Bool() { addInt(1) } else { addInt(0) } case reflect.Float32: addFloat(uintptr(math.Float32bits(float32(v.Float())))) case reflect.Float64: addFloat(uintptr(math.Float64bits(v.Float()))) case reflect.Struct: keepAlive = addStruct(v, &numInts, &numFloats, &numStack, addInt, addFloat, addStack, keepAlive) default: panic("purego: unsupported kind: " + v.Kind().String()) } } if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { // Use the normal arm64 calling convention even on Windows syscall = syscall15Args{ cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], sysargs[12], sysargs[13], sysargs[14], floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7], syscall.arm64_r8, } runtime_cgocall(syscall15XABI0, unsafe.Pointer(&syscall)) } else { // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], sysargs[12], sysargs[13], sysargs[14]) syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support } if ty.NumOut() == 0 { return nil } outType := ty.Out(0) v := reflect.New(outType).Elem() switch outType.Kind() { case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: v.SetUint(uint64(syscall.a1)) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: v.SetInt(int64(syscall.a1)) case reflect.Bool: v.SetBool(byte(syscall.a1) != 0) case reflect.UnsafePointer: // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))) case reflect.Ptr: v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem() case reflect.Func: // wrap this C function in a nicely typed Go function v = reflect.New(outType) RegisterFunc(v.Interface(), syscall.a1) case reflect.String: v.SetString(strings.GoString(syscall.a1)) case reflect.Float32: // NOTE: syscall.r2 is only the floating return value on 64bit platforms. // On 32bit platforms syscall.r2 is the upper part of a 64bit return. v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) case reflect.Float64: // NOTE: syscall.r2 is only the floating return value on 64bit platforms. // On 32bit platforms syscall.r2 is the upper part of a 64bit return. v.SetFloat(math.Float64frombits(uint64(syscall.f1))) case reflect.Struct: v = getStruct(outType, syscall) default: panic("purego: unsupported return kind: " + outType.Kind().String()) } return []reflect.Value{v} }) fn.Set(v) } // maxRegAllocStructSize is the biggest a struct can be while still fitting in registers. // if it is bigger than this than enough space must be allocated on the heap and then passed into // the function as the first parameter on amd64 or in R8 on arm64. // // If you change this make sure to update it in objc_runtime_darwin.go const maxRegAllocStructSize = 16 func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) { allFloats = true root := ty.Field(0).Type for root.Kind() == reflect.Struct { root = root.Field(0).Type } first := root.Kind() if first != reflect.Float32 && first != reflect.Float64 { allFloats = false } for i := 0; i < ty.NumField(); i++ { f := ty.Field(i).Type if f.Kind() == reflect.Struct { var structNumFields int allFloats, structNumFields = isAllSameFloat(f) numFields += structNumFields continue } numFields++ if f.Kind() != first { allFloats = false } } return allFloats, numFields } func checkStructFieldsSupported(ty reflect.Type) { for i := 0; i < ty.NumField(); i++ { f := ty.Field(i).Type if f.Kind() == reflect.Array { f = f.Elem() } else if f.Kind() == reflect.Struct { checkStructFieldsSupported(f) continue } switch f.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32: default: panic(fmt.Sprintf("purego: struct field type %s is not supported", f)) } } } func roundUpTo8(val uintptr) uintptr { return (val + 7) &^ 7 } func numOfIntegerRegisters() int { switch runtime.GOARCH { case "arm64": return 8 case "amd64": return 6 default: // since this platform isn't supported and can therefore only access // integer registers it is fine to return the maxArgs return maxArgs } } ================================================ FILE: vendor/github.com/ebitengine/purego/go_runtime.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build darwin || freebsd || linux || windows package purego import ( "unsafe" ) //go:linkname runtime_cgocall runtime.cgocall func runtime_cgocall(fn uintptr, arg unsafe.Pointer) int32 // from runtime/sys_libc.go ================================================ FILE: vendor/github.com/ebitengine/purego/internal/cgo/dlfcn_cgo_unix.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors //go:build freebsd || linux package cgo /* #cgo LDFLAGS: -ldl #include #include */ import "C" import ( "errors" "unsafe" ) func Dlopen(filename string, flag int) (uintptr, error) { cfilename := C.CString(filename) defer C.free(unsafe.Pointer(cfilename)) handle := C.dlopen(cfilename, C.int(flag)) if handle == nil { return 0, errors.New(C.GoString(C.dlerror())) } return uintptr(handle), nil } func Dlsym(handle uintptr, symbol string) (uintptr, error) { csymbol := C.CString(symbol) defer C.free(unsafe.Pointer(csymbol)) symbolAddr := C.dlsym(*(*unsafe.Pointer)(unsafe.Pointer(&handle)), csymbol) if symbolAddr == nil { return 0, errors.New(C.GoString(C.dlerror())) } return uintptr(symbolAddr), nil } func Dlclose(handle uintptr) error { result := C.dlclose(*(*unsafe.Pointer)(unsafe.Pointer(&handle))) if result != 0 { return errors.New(C.GoString(C.dlerror())) } return nil } // all that is needed is to assign each dl function because then its // symbol will then be made available to the linker and linked to inside dlfcn.go var ( _ = C.dlopen _ = C.dlsym _ = C.dlerror _ = C.dlclose ) ================================================ FILE: vendor/github.com/ebitengine/purego/internal/cgo/empty.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors package cgo // Empty so that importing this package doesn't cause issue for certain platforms. ================================================ FILE: vendor/github.com/ebitengine/purego/internal/cgo/syscall_cgo_unix.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build freebsd || (linux && !(arm64 || amd64)) package cgo // this file is placed inside internal/cgo and not package purego // because Cgo and assembly files can't be in the same package. /* #cgo LDFLAGS: -ldl #include #include #include #include typedef struct syscall15Args { uintptr_t fn; uintptr_t a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15; uintptr_t f1, f2, f3, f4, f5, f6, f7, f8; uintptr_t err; } syscall15Args; void syscall15(struct syscall15Args *args) { assert((args->f1|args->f2|args->f3|args->f4|args->f5|args->f6|args->f7|args->f8) == 0); uintptr_t (*func_name)(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6, uintptr_t a7, uintptr_t a8, uintptr_t a9, uintptr_t a10, uintptr_t a11, uintptr_t a12, uintptr_t a13, uintptr_t a14, uintptr_t a15); *(void**)(&func_name) = (void*)(args->fn); uintptr_t r1 = func_name(args->a1,args->a2,args->a3,args->a4,args->a5,args->a6,args->a7,args->a8,args->a9, args->a10,args->a11,args->a12,args->a13,args->a14,args->a15); args->a1 = r1; args->err = errno; } */ import "C" import "unsafe" // assign purego.syscall15XABI0 to the C version of this function. var Syscall15XABI0 = unsafe.Pointer(C.syscall15) //go:nosplit func Syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { args := C.syscall15Args{ C.uintptr_t(fn), C.uintptr_t(a1), C.uintptr_t(a2), C.uintptr_t(a3), C.uintptr_t(a4), C.uintptr_t(a5), C.uintptr_t(a6), C.uintptr_t(a7), C.uintptr_t(a8), C.uintptr_t(a9), C.uintptr_t(a10), C.uintptr_t(a11), C.uintptr_t(a12), C.uintptr_t(a13), C.uintptr_t(a14), C.uintptr_t(a15), 0, 0, 0, 0, 0, 0, 0, 0, 0, } C.syscall15(&args) return uintptr(args.a1), 0, uintptr(args.err) } ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/abi_amd64.h ================================================ // Copyright 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Macros for transitioning from the host ABI to Go ABI0. // // These save the frame pointer, so in general, functions that use // these should have zero frame size to suppress the automatic frame // pointer, though it's harmless to not do this. #ifdef GOOS_windows // REGS_HOST_TO_ABI0_STACK is the stack bytes used by // PUSH_REGS_HOST_TO_ABI0. #define REGS_HOST_TO_ABI0_STACK (28*8 + 8) // PUSH_REGS_HOST_TO_ABI0 prepares for transitioning from // the host ABI to Go ABI0 code. It saves all registers that are // callee-save in the host ABI and caller-save in Go ABI0 and prepares // for entry to Go. // // Save DI SI BP BX R12 R13 R14 R15 X6-X15 registers and the DF flag. // Clear the DF flag for the Go ABI. // MXCSR matches the Go ABI, so we don't have to set that, // and Go doesn't modify it, so we don't have to save it. #define PUSH_REGS_HOST_TO_ABI0() \ PUSHFQ \ CLD \ ADJSP $(REGS_HOST_TO_ABI0_STACK - 8) \ MOVQ DI, (0*0)(SP) \ MOVQ SI, (1*8)(SP) \ MOVQ BP, (2*8)(SP) \ MOVQ BX, (3*8)(SP) \ MOVQ R12, (4*8)(SP) \ MOVQ R13, (5*8)(SP) \ MOVQ R14, (6*8)(SP) \ MOVQ R15, (7*8)(SP) \ MOVUPS X6, (8*8)(SP) \ MOVUPS X7, (10*8)(SP) \ MOVUPS X8, (12*8)(SP) \ MOVUPS X9, (14*8)(SP) \ MOVUPS X10, (16*8)(SP) \ MOVUPS X11, (18*8)(SP) \ MOVUPS X12, (20*8)(SP) \ MOVUPS X13, (22*8)(SP) \ MOVUPS X14, (24*8)(SP) \ MOVUPS X15, (26*8)(SP) #define POP_REGS_HOST_TO_ABI0() \ MOVQ (0*0)(SP), DI \ MOVQ (1*8)(SP), SI \ MOVQ (2*8)(SP), BP \ MOVQ (3*8)(SP), BX \ MOVQ (4*8)(SP), R12 \ MOVQ (5*8)(SP), R13 \ MOVQ (6*8)(SP), R14 \ MOVQ (7*8)(SP), R15 \ MOVUPS (8*8)(SP), X6 \ MOVUPS (10*8)(SP), X7 \ MOVUPS (12*8)(SP), X8 \ MOVUPS (14*8)(SP), X9 \ MOVUPS (16*8)(SP), X10 \ MOVUPS (18*8)(SP), X11 \ MOVUPS (20*8)(SP), X12 \ MOVUPS (22*8)(SP), X13 \ MOVUPS (24*8)(SP), X14 \ MOVUPS (26*8)(SP), X15 \ ADJSP $-(REGS_HOST_TO_ABI0_STACK - 8) \ POPFQ #else // SysV ABI #define REGS_HOST_TO_ABI0_STACK (6*8) // SysV MXCSR matches the Go ABI, so we don't have to set that, // and Go doesn't modify it, so we don't have to save it. // Both SysV and Go require DF to be cleared, so that's already clear. // The SysV and Go frame pointer conventions are compatible. #define PUSH_REGS_HOST_TO_ABI0() \ ADJSP $(REGS_HOST_TO_ABI0_STACK) \ MOVQ BP, (5*8)(SP) \ LEAQ (5*8)(SP), BP \ MOVQ BX, (0*8)(SP) \ MOVQ R12, (1*8)(SP) \ MOVQ R13, (2*8)(SP) \ MOVQ R14, (3*8)(SP) \ MOVQ R15, (4*8)(SP) #define POP_REGS_HOST_TO_ABI0() \ MOVQ (0*8)(SP), BX \ MOVQ (1*8)(SP), R12 \ MOVQ (2*8)(SP), R13 \ MOVQ (3*8)(SP), R14 \ MOVQ (4*8)(SP), R15 \ MOVQ (5*8)(SP), BP \ ADJSP $-(REGS_HOST_TO_ABI0_STACK) #endif ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/abi_arm64.h ================================================ // Copyright 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Macros for transitioning from the host ABI to Go ABI0. // // These macros save and restore the callee-saved registers // from the stack, but they don't adjust stack pointer, so // the user should prepare stack space in advance. // SAVE_R19_TO_R28(offset) saves R19 ~ R28 to the stack space // of ((offset)+0*8)(RSP) ~ ((offset)+9*8)(RSP). // // SAVE_F8_TO_F15(offset) saves F8 ~ F15 to the stack space // of ((offset)+0*8)(RSP) ~ ((offset)+7*8)(RSP). // // R29 is not saved because Go will save and restore it. #define SAVE_R19_TO_R28(offset) \ STP (R19, R20), ((offset)+0*8)(RSP) \ STP (R21, R22), ((offset)+2*8)(RSP) \ STP (R23, R24), ((offset)+4*8)(RSP) \ STP (R25, R26), ((offset)+6*8)(RSP) \ STP (R27, g), ((offset)+8*8)(RSP) #define RESTORE_R19_TO_R28(offset) \ LDP ((offset)+0*8)(RSP), (R19, R20) \ LDP ((offset)+2*8)(RSP), (R21, R22) \ LDP ((offset)+4*8)(RSP), (R23, R24) \ LDP ((offset)+6*8)(RSP), (R25, R26) \ LDP ((offset)+8*8)(RSP), (R27, g) /* R28 */ #define SAVE_F8_TO_F15(offset) \ FSTPD (F8, F9), ((offset)+0*8)(RSP) \ FSTPD (F10, F11), ((offset)+2*8)(RSP) \ FSTPD (F12, F13), ((offset)+4*8)(RSP) \ FSTPD (F14, F15), ((offset)+6*8)(RSP) #define RESTORE_F8_TO_F15(offset) \ FLDPD ((offset)+0*8)(RSP), (F8, F9) \ FLDPD ((offset)+2*8)(RSP), (F10, F11) \ FLDPD ((offset)+4*8)(RSP), (F12, F13) \ FLDPD ((offset)+6*8)(RSP), (F14, F15) ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/asm_amd64.s ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include "textflag.h" #include "abi_amd64.h" // Called by C code generated by cmd/cgo. // func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr) // Saves C callee-saved registers and calls cgocallback with three arguments. // fn is the PC of a func(a unsafe.Pointer) function. // This signature is known to SWIG, so we can't change it. TEXT crosscall2(SB), NOSPLIT, $0-0 PUSH_REGS_HOST_TO_ABI0() // Make room for arguments to cgocallback. ADJSP $0x18 #ifndef GOOS_windows MOVQ DI, 0x0(SP) // fn MOVQ SI, 0x8(SP) // arg // Skip n in DX. MOVQ CX, 0x10(SP) // ctxt #else MOVQ CX, 0x0(SP) // fn MOVQ DX, 0x8(SP) // arg // Skip n in R8. MOVQ R9, 0x10(SP) // ctxt #endif CALL runtime·cgocallback(SB) ADJSP $-0x18 POP_REGS_HOST_TO_ABI0() RET ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/asm_arm64.s ================================================ // Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include "textflag.h" #include "abi_arm64.h" // Called by C code generated by cmd/cgo. // func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr) // Saves C callee-saved registers and calls cgocallback with three arguments. // fn is the PC of a func(a unsafe.Pointer) function. TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 /* * We still need to save all callee save register as before, and then * push 3 args for fn (R0, R1, R3), skipping R2. * Also note that at procedure entry in gc world, 8(RSP) will be the * first arg. */ SUB $(8*24), RSP STP (R0, R1), (8*1)(RSP) MOVD R3, (8*3)(RSP) SAVE_R19_TO_R28(8*4) SAVE_F8_TO_F15(8*14) STP (R29, R30), (8*22)(RSP) // Initialize Go ABI environment BL runtime·load_g(SB) BL runtime·cgocallback(SB) RESTORE_R19_TO_R28(8*4) RESTORE_F8_TO_F15(8*14) LDP (8*22)(RSP), (R29, R30) ADD $(8*24), RSP RET ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/callbacks.go ================================================ // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build !cgo && (darwin || freebsd || linux) package fakecgo import ( _ "unsafe" ) // TODO: decide if we need _runtime_cgo_panic_internal //go:linkname x_cgo_init_trampoline x_cgo_init_trampoline //go:linkname _cgo_init _cgo_init var x_cgo_init_trampoline byte var _cgo_init = &x_cgo_init_trampoline // Creates a new system thread without updating any Go state. // // This method is invoked during shared library loading to create a new OS // thread to perform the runtime initialization. This method is similar to // _cgo_sys_thread_start except that it doesn't update any Go state. //go:linkname x_cgo_thread_start_trampoline x_cgo_thread_start_trampoline //go:linkname _cgo_thread_start _cgo_thread_start var x_cgo_thread_start_trampoline byte var _cgo_thread_start = &x_cgo_thread_start_trampoline // Notifies that the runtime has been initialized. // // We currently block at every CGO entry point (via _cgo_wait_runtime_init_done) // to ensure that the runtime has been initialized before the CGO call is // executed. This is necessary for shared libraries where we kickoff runtime // initialization in a separate thread and return without waiting for this // thread to complete the init. //go:linkname x_cgo_notify_runtime_init_done_trampoline x_cgo_notify_runtime_init_done_trampoline //go:linkname _cgo_notify_runtime_init_done _cgo_notify_runtime_init_done var x_cgo_notify_runtime_init_done_trampoline byte var _cgo_notify_runtime_init_done = &x_cgo_notify_runtime_init_done_trampoline // Indicates whether a dummy thread key has been created or not. // // When calling go exported function from C, we register a destructor // callback, for a dummy thread key, by using pthread_key_create. //go:linkname _cgo_pthread_key_created _cgo_pthread_key_created var x_cgo_pthread_key_created uintptr var _cgo_pthread_key_created = &x_cgo_pthread_key_created // Set the x_crosscall2_ptr C function pointer variable point to crosscall2. // It's for the runtime package to call at init time. func set_crosscall2() { // nothing needs to be done here for fakecgo // because it's possible to just call cgocallback directly } //go:linkname _set_crosscall2 runtime.set_crosscall2 var _set_crosscall2 = set_crosscall2 // Store the g into the thread-specific value. // So that pthread_key_destructor will dropm when the thread is exiting. //go:linkname x_cgo_bindm_trampoline x_cgo_bindm_trampoline //go:linkname _cgo_bindm _cgo_bindm var x_cgo_bindm_trampoline byte var _cgo_bindm = &x_cgo_bindm_trampoline // TODO: decide if we need x_cgo_set_context_function // TODO: decide if we need _cgo_yield var ( // In Go 1.20 the race detector was rewritten to pure Go // on darwin. This means that when CGO_ENABLED=0 is set // fakecgo is built with race detector code. This is not // good since this code is pretending to be C. The go:norace // pragma is not enough, since it only applies to the native // ABIInternal function. The ABIO wrapper (which is necessary, // since all references to text symbols from assembly will use it) // does not inherit the go:norace pragma, so it will still be // instrumented by the race detector. // // To circumvent this issue, using closure calls in the // assembly, which forces the compiler to use the ABIInternal // native implementation (which has go:norace) instead. threadentry_call = threadentry x_cgo_init_call = x_cgo_init x_cgo_setenv_call = x_cgo_setenv x_cgo_unsetenv_call = x_cgo_unsetenv x_cgo_thread_start_call = x_cgo_thread_start ) ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/doc.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build !cgo && (darwin || freebsd || linux) // Package fakecgo implements the Cgo runtime (runtime/cgo) entirely in Go. // This allows code that calls into C to function properly when CGO_ENABLED=0. // // # Goals // // fakecgo attempts to replicate the same naming structure as in the runtime. // For example, functions that have the prefix "gcc_*" are named "go_*". // This makes it easier to port other GOOSs and GOARCHs as well as to keep // it in sync with runtime/cgo. // // # Support // // Currently, fakecgo only supports macOS on amd64 & arm64. It also cannot // be used with -buildmode=c-archive because that requires special initialization // that fakecgo does not implement at the moment. // // # Usage // // Using fakecgo is easy just import _ "github.com/ebitengine/purego" and then // set the environment variable CGO_ENABLED=0. // The recommended usage for fakecgo is to prefer using runtime/cgo if possible // but if cross-compiling or fast build times are important fakecgo is available. // Purego will pick which ever Cgo runtime is available and prefer the one that // comes with Go (runtime/cgo). package fakecgo //go:generate go run gen.go ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/freebsd.go ================================================ // Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build freebsd && !cgo package fakecgo import _ "unsafe" // for go:linkname // Supply environ and __progname, because we don't // link against the standard FreeBSD crt0.o and the // libc dynamic library needs them. // Note: when building with cross-compiling or CGO_ENABLED=0, add // the following argument to `go` so that these symbols are defined by // making fakecgo the Cgo. // -gcflags="github.com/ebitengine/purego/internal/fakecgo=-std" //go:linkname _environ environ //go:linkname _progname __progname //go:cgo_export_dynamic environ //go:cgo_export_dynamic __progname var _environ uintptr var _progname uintptr ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/go_darwin_amd64.go ================================================ // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build !cgo package fakecgo import "unsafe" //go:nosplit //go:norace func _cgo_sys_thread_start(ts *ThreadStart) { var attr pthread_attr_t var ign, oset sigset_t var p pthread_t var size size_t var err int sigfillset(&ign) pthread_sigmask(SIG_SETMASK, &ign, &oset) size = pthread_get_stacksize_np(pthread_self()) pthread_attr_init(&attr) pthread_attr_setstacksize(&attr, size) // Leave stacklo=0 and set stackhi=size; mstart will do the rest. ts.g.stackhi = uintptr(size) err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) pthread_sigmask(SIG_SETMASK, &oset, nil) if err != 0 { print("fakecgo: pthread_create failed: ") println(err) abort() } } // threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function // //go:linkname x_threadentry_trampoline threadentry_trampoline var x_threadentry_trampoline byte var threadentry_trampolineABI0 = &x_threadentry_trampoline //go:nosplit //go:norace func threadentry(v unsafe.Pointer) unsafe.Pointer { ts := *(*ThreadStart)(v) free(v) setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) // faking funcs in go is a bit a... involved - but the following works :) fn := uintptr(unsafe.Pointer(&ts.fn)) (*(*func())(unsafe.Pointer(&fn)))() return nil } // here we will store a pointer to the provided setg func var setg_func uintptr //go:nosplit //go:norace func x_cgo_init(g *G, setg uintptr) { var size size_t setg_func = setg size = pthread_get_stacksize_np(pthread_self()) g.stacklo = uintptr(unsafe.Add(unsafe.Pointer(&size), -size+4096)) } ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/go_darwin_arm64.go ================================================ // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build !cgo package fakecgo import "unsafe" //go:nosplit //go:norace func _cgo_sys_thread_start(ts *ThreadStart) { var attr pthread_attr_t var ign, oset sigset_t var p pthread_t var size size_t var err int sigfillset(&ign) pthread_sigmask(SIG_SETMASK, &ign, &oset) size = pthread_get_stacksize_np(pthread_self()) pthread_attr_init(&attr) pthread_attr_setstacksize(&attr, size) // Leave stacklo=0 and set stackhi=size; mstart will do the rest. ts.g.stackhi = uintptr(size) err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) pthread_sigmask(SIG_SETMASK, &oset, nil) if err != 0 { print("fakecgo: pthread_create failed: ") println(err) abort() } } // threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function // //go:linkname x_threadentry_trampoline threadentry_trampoline var x_threadentry_trampoline byte var threadentry_trampolineABI0 = &x_threadentry_trampoline //go:nosplit //go:norace func threadentry(v unsafe.Pointer) unsafe.Pointer { ts := *(*ThreadStart)(v) free(v) // TODO: support ios //#if TARGET_OS_IPHONE // darwin_arm_init_thread_exception_port(); //#endif setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) // faking funcs in go is a bit a... involved - but the following works :) fn := uintptr(unsafe.Pointer(&ts.fn)) (*(*func())(unsafe.Pointer(&fn)))() return nil } // here we will store a pointer to the provided setg func var setg_func uintptr // x_cgo_init(G *g, void (*setg)(void*)) (runtime/cgo/gcc_linux_amd64.c) // This get's called during startup, adjusts stacklo, and provides a pointer to setg_gcc for us // Additionally, if we set _cgo_init to non-null, go won't do it's own TLS setup // This function can't be go:systemstack since go is not in a state where the systemcheck would work. // //go:nosplit //go:norace func x_cgo_init(g *G, setg uintptr) { var size size_t setg_func = setg size = pthread_get_stacksize_np(pthread_self()) g.stacklo = uintptr(unsafe.Add(unsafe.Pointer(&size), -size+4096)) //TODO: support ios //#if TARGET_OS_IPHONE // darwin_arm_init_mach_exception_handler(); // darwin_arm_init_thread_exception_port(); // init_working_dir(); //#endif } ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/go_freebsd_amd64.go ================================================ // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build !cgo package fakecgo import "unsafe" //go:nosplit func _cgo_sys_thread_start(ts *ThreadStart) { var attr pthread_attr_t var ign, oset sigset_t var p pthread_t var size size_t var err int //fprintf(stderr, "runtime/cgo: _cgo_sys_thread_start: fn=%p, g=%p\n", ts->fn, ts->g); // debug sigfillset(&ign) pthread_sigmask(SIG_SETMASK, &ign, &oset) pthread_attr_init(&attr) pthread_attr_getstacksize(&attr, &size) // Leave stacklo=0 and set stackhi=size; mstart will do the rest. ts.g.stackhi = uintptr(size) err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) pthread_sigmask(SIG_SETMASK, &oset, nil) if err != 0 { print("fakecgo: pthread_create failed: ") println(err) abort() } } // threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function // //go:linkname x_threadentry_trampoline threadentry_trampoline var x_threadentry_trampoline byte var threadentry_trampolineABI0 = &x_threadentry_trampoline //go:nosplit func threadentry(v unsafe.Pointer) unsafe.Pointer { ts := *(*ThreadStart)(v) free(v) setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) // faking funcs in go is a bit a... involved - but the following works :) fn := uintptr(unsafe.Pointer(&ts.fn)) (*(*func())(unsafe.Pointer(&fn)))() return nil } // here we will store a pointer to the provided setg func var setg_func uintptr //go:nosplit func x_cgo_init(g *G, setg uintptr) { var size size_t var attr *pthread_attr_t /* The memory sanitizer distributed with versions of clang before 3.8 has a bug: if you call mmap before malloc, mmap may return an address that is later overwritten by the msan library. Avoid this problem by forcing a call to malloc here, before we ever call malloc. This is only required for the memory sanitizer, so it's unfortunate that we always run it. It should be possible to remove this when we no longer care about versions of clang before 3.8. The test for this is misc/cgo/testsanitizers. GCC works hard to eliminate a seemingly unnecessary call to malloc, so we actually use the memory we allocate. */ setg_func = setg attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr))) if attr == nil { println("fakecgo: malloc failed") abort() } pthread_attr_init(attr) pthread_attr_getstacksize(attr, &size) // runtime/cgo uses __builtin_frame_address(0) instead of `uintptr(unsafe.Pointer(&size))` // but this should be OK since we are taking the address of the first variable in this function. g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096 pthread_attr_destroy(attr) free(unsafe.Pointer(attr)) } ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/go_freebsd_arm64.go ================================================ // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build !cgo package fakecgo import "unsafe" //go:nosplit func _cgo_sys_thread_start(ts *ThreadStart) { var attr pthread_attr_t var ign, oset sigset_t var p pthread_t var size size_t var err int // fprintf(stderr, "runtime/cgo: _cgo_sys_thread_start: fn=%p, g=%p\n", ts->fn, ts->g); // debug sigfillset(&ign) pthread_sigmask(SIG_SETMASK, &ign, &oset) pthread_attr_init(&attr) pthread_attr_getstacksize(&attr, &size) // Leave stacklo=0 and set stackhi=size; mstart will do the rest. ts.g.stackhi = uintptr(size) err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) pthread_sigmask(SIG_SETMASK, &oset, nil) if err != 0 { print("fakecgo: pthread_create failed: ") println(err) abort() } } // threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function // //go:linkname x_threadentry_trampoline threadentry_trampoline var x_threadentry_trampoline byte var threadentry_trampolineABI0 = &x_threadentry_trampoline //go:nosplit func threadentry(v unsafe.Pointer) unsafe.Pointer { ts := *(*ThreadStart)(v) free(v) setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) // faking funcs in go is a bit a... involved - but the following works :) fn := uintptr(unsafe.Pointer(&ts.fn)) (*(*func())(unsafe.Pointer(&fn)))() return nil } // here we will store a pointer to the provided setg func var setg_func uintptr // x_cgo_init(G *g, void (*setg)(void*)) (runtime/cgo/gcc_linux_amd64.c) // This get's called during startup, adjusts stacklo, and provides a pointer to setg_gcc for us // Additionally, if we set _cgo_init to non-null, go won't do it's own TLS setup // This function can't be go:systemstack since go is not in a state where the systemcheck would work. // //go:nosplit func x_cgo_init(g *G, setg uintptr) { var size size_t var attr *pthread_attr_t /* The memory sanitizer distributed with versions of clang before 3.8 has a bug: if you call mmap before malloc, mmap may return an address that is later overwritten by the msan library. Avoid this problem by forcing a call to malloc here, before we ever call malloc. This is only required for the memory sanitizer, so it's unfortunate that we always run it. It should be possible to remove this when we no longer care about versions of clang before 3.8. The test for this is misc/cgo/testsanitizers. GCC works hard to eliminate a seemingly unnecessary call to malloc, so we actually use the memory we allocate. */ setg_func = setg attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr))) if attr == nil { println("fakecgo: malloc failed") abort() } pthread_attr_init(attr) pthread_attr_getstacksize(attr, &size) g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096 pthread_attr_destroy(attr) free(unsafe.Pointer(attr)) } ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/go_libinit.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build !cgo && (darwin || freebsd || linux) package fakecgo import ( "syscall" "unsafe" ) var ( pthread_g pthread_key_t runtime_init_cond = PTHREAD_COND_INITIALIZER runtime_init_mu = PTHREAD_MUTEX_INITIALIZER runtime_init_done int ) //go:nosplit func x_cgo_notify_runtime_init_done() { pthread_mutex_lock(&runtime_init_mu) runtime_init_done = 1 pthread_cond_broadcast(&runtime_init_cond) pthread_mutex_unlock(&runtime_init_mu) } // Store the g into a thread-specific value associated with the pthread key pthread_g. // And pthread_key_destructor will dropm when the thread is exiting. func x_cgo_bindm(g unsafe.Pointer) { // We assume this will always succeed, otherwise, there might be extra M leaking, // when a C thread exits after a cgo call. // We only invoke this function once per thread in runtime.needAndBindM, // and the next calls just reuse the bound m. pthread_setspecific(pthread_g, g) } // _cgo_try_pthread_create retries pthread_create if it fails with // EAGAIN. // //go:nosplit //go:norace func _cgo_try_pthread_create(thread *pthread_t, attr *pthread_attr_t, pfn unsafe.Pointer, arg *ThreadStart) int { var ts syscall.Timespec // tries needs to be the same type as syscall.Timespec.Nsec // but the fields are int32 on 32bit and int64 on 64bit. // tries is assigned to syscall.Timespec.Nsec in order to match its type. tries := ts.Nsec var err int for tries = 0; tries < 20; tries++ { // inlined this call because it ran out of stack when inlining was disabled err = int(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(pfn), uintptr(unsafe.Pointer(arg)), 0)) if err == 0 { // inlined this call because it ran out of stack when inlining was disabled call5(pthread_detachABI0, uintptr(*thread), 0, 0, 0, 0) return 0 } if err != int(syscall.EAGAIN) { return err } ts.Sec = 0 ts.Nsec = (tries + 1) * 1000 * 1000 // Milliseconds. // inlined this call because it ran out of stack when inlining was disabled call5(nanosleepABI0, uintptr(unsafe.Pointer(&ts)), 0, 0, 0, 0) } return int(syscall.EAGAIN) } ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux_amd64.go ================================================ // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build !cgo package fakecgo import "unsafe" //go:nosplit func _cgo_sys_thread_start(ts *ThreadStart) { var attr pthread_attr_t var ign, oset sigset_t var p pthread_t var size size_t var err int //fprintf(stderr, "runtime/cgo: _cgo_sys_thread_start: fn=%p, g=%p\n", ts->fn, ts->g); // debug sigfillset(&ign) pthread_sigmask(SIG_SETMASK, &ign, &oset) pthread_attr_init(&attr) pthread_attr_getstacksize(&attr, &size) // Leave stacklo=0 and set stackhi=size; mstart will do the rest. ts.g.stackhi = uintptr(size) err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) pthread_sigmask(SIG_SETMASK, &oset, nil) if err != 0 { print("fakecgo: pthread_create failed: ") println(err) abort() } } // threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function // //go:linkname x_threadentry_trampoline threadentry_trampoline var x_threadentry_trampoline byte var threadentry_trampolineABI0 = &x_threadentry_trampoline //go:nosplit func threadentry(v unsafe.Pointer) unsafe.Pointer { ts := *(*ThreadStart)(v) free(v) setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) // faking funcs in go is a bit a... involved - but the following works :) fn := uintptr(unsafe.Pointer(&ts.fn)) (*(*func())(unsafe.Pointer(&fn)))() return nil } // here we will store a pointer to the provided setg func var setg_func uintptr //go:nosplit func x_cgo_init(g *G, setg uintptr) { var size size_t var attr *pthread_attr_t /* The memory sanitizer distributed with versions of clang before 3.8 has a bug: if you call mmap before malloc, mmap may return an address that is later overwritten by the msan library. Avoid this problem by forcing a call to malloc here, before we ever call malloc. This is only required for the memory sanitizer, so it's unfortunate that we always run it. It should be possible to remove this when we no longer care about versions of clang before 3.8. The test for this is misc/cgo/testsanitizers. GCC works hard to eliminate a seemingly unnecessary call to malloc, so we actually use the memory we allocate. */ setg_func = setg attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr))) if attr == nil { println("fakecgo: malloc failed") abort() } pthread_attr_init(attr) pthread_attr_getstacksize(attr, &size) // runtime/cgo uses __builtin_frame_address(0) instead of `uintptr(unsafe.Pointer(&size))` // but this should be OK since we are taking the address of the first variable in this function. g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096 pthread_attr_destroy(attr) free(unsafe.Pointer(attr)) } ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux_arm64.go ================================================ // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build !cgo package fakecgo import "unsafe" //go:nosplit func _cgo_sys_thread_start(ts *ThreadStart) { var attr pthread_attr_t var ign, oset sigset_t var p pthread_t var size size_t var err int //fprintf(stderr, "runtime/cgo: _cgo_sys_thread_start: fn=%p, g=%p\n", ts->fn, ts->g); // debug sigfillset(&ign) pthread_sigmask(SIG_SETMASK, &ign, &oset) pthread_attr_init(&attr) pthread_attr_getstacksize(&attr, &size) // Leave stacklo=0 and set stackhi=size; mstart will do the rest. ts.g.stackhi = uintptr(size) err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) pthread_sigmask(SIG_SETMASK, &oset, nil) if err != 0 { print("fakecgo: pthread_create failed: ") println(err) abort() } } // threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function // //go:linkname x_threadentry_trampoline threadentry_trampoline var x_threadentry_trampoline byte var threadentry_trampolineABI0 = &x_threadentry_trampoline //go:nosplit func threadentry(v unsafe.Pointer) unsafe.Pointer { ts := *(*ThreadStart)(v) free(v) setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) // faking funcs in go is a bit a... involved - but the following works :) fn := uintptr(unsafe.Pointer(&ts.fn)) (*(*func())(unsafe.Pointer(&fn)))() return nil } // here we will store a pointer to the provided setg func var setg_func uintptr // x_cgo_init(G *g, void (*setg)(void*)) (runtime/cgo/gcc_linux_amd64.c) // This get's called during startup, adjusts stacklo, and provides a pointer to setg_gcc for us // Additionally, if we set _cgo_init to non-null, go won't do it's own TLS setup // This function can't be go:systemstack since go is not in a state where the systemcheck would work. // //go:nosplit func x_cgo_init(g *G, setg uintptr) { var size size_t var attr *pthread_attr_t /* The memory sanitizer distributed with versions of clang before 3.8 has a bug: if you call mmap before malloc, mmap may return an address that is later overwritten by the msan library. Avoid this problem by forcing a call to malloc here, before we ever call malloc. This is only required for the memory sanitizer, so it's unfortunate that we always run it. It should be possible to remove this when we no longer care about versions of clang before 3.8. The test for this is misc/cgo/testsanitizers. GCC works hard to eliminate a seemingly unnecessary call to malloc, so we actually use the memory we allocate. */ setg_func = setg attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr))) if attr == nil { println("fakecgo: malloc failed") abort() } pthread_attr_init(attr) pthread_attr_getstacksize(attr, &size) g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096 pthread_attr_destroy(attr) free(unsafe.Pointer(attr)) } ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/go_setenv.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build !cgo && (darwin || freebsd || linux) package fakecgo //go:nosplit //go:norace func x_cgo_setenv(arg *[2]*byte) { setenv(arg[0], arg[1], 1) } //go:nosplit //go:norace func x_cgo_unsetenv(arg *[1]*byte) { unsetenv(arg[0]) } ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/go_util.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build !cgo && (darwin || freebsd || linux) package fakecgo import "unsafe" // _cgo_thread_start is split into three parts in cgo since only one part is system dependent (keep it here for easier handling) // _cgo_thread_start(ThreadStart *arg) (runtime/cgo/gcc_util.c) // This get's called instead of the go code for creating new threads // -> pthread_* stuff is used, so threads are setup correctly for C // If this is missing, TLS is only setup correctly on thread 1! // This function should be go:systemstack instead of go:nosplit (but that requires runtime) // //go:nosplit //go:norace func x_cgo_thread_start(arg *ThreadStart) { var ts *ThreadStart // Make our own copy that can persist after we return. // _cgo_tsan_acquire(); ts = (*ThreadStart)(malloc(unsafe.Sizeof(*ts))) // _cgo_tsan_release(); if ts == nil { println("fakecgo: out of memory in thread_start") abort() } // *ts = *arg would cause a writebarrier so copy using slices s1 := unsafe.Slice((*uintptr)(unsafe.Pointer(ts)), unsafe.Sizeof(*ts)/8) s2 := unsafe.Slice((*uintptr)(unsafe.Pointer(arg)), unsafe.Sizeof(*arg)/8) for i := range s2 { s1[i] = s2[i] } _cgo_sys_thread_start(ts) // OS-dependent half } ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/iscgo.go ================================================ // Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build !cgo && (darwin || freebsd || linux) // The runtime package contains an uninitialized definition // for runtime·iscgo. Override it to tell the runtime we're here. // There are various function pointers that should be set too, // but those depend on dynamic linker magic to get initialized // correctly, and sometimes they break. This variable is a // backup: it depends only on old C style static linking rules. package fakecgo import _ "unsafe" // for go:linkname //go:linkname _iscgo runtime.iscgo var _iscgo bool = true ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build !cgo && (darwin || freebsd || linux) package fakecgo type ( size_t uintptr // Sources: // Darwin (32 bytes) - https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/_types.h#L74 // FreeBSD (32 bytes) - https://github.com/DoctorWkt/xv6-freebsd/blob/d2a294c2a984baed27676068b15ed9a29b06ab6f/include/signal.h#L98C9-L98C21 // Linux (128 bytes) - https://github.com/torvalds/linux/blob/ab75170520d4964f3acf8bb1f91d34cbc650688e/arch/x86/include/asm/signal.h#L25 sigset_t [128]byte pthread_attr_t [64]byte pthread_t int pthread_key_t uint64 ) // for pthread_sigmask: type sighow int32 const ( SIG_BLOCK sighow = 0 SIG_UNBLOCK sighow = 1 SIG_SETMASK sighow = 2 ) type G struct { stacklo uintptr stackhi uintptr } type ThreadStart struct { g *G tls *uintptr fn uintptr } ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_darwin.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build !cgo package fakecgo type ( pthread_mutex_t struct { sig int64 opaque [56]byte } pthread_cond_t struct { sig int64 opaque [40]byte } ) var ( PTHREAD_COND_INITIALIZER = pthread_cond_t{sig: 0x3CB0B1BB} PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{sig: 0x32AAABA7} ) ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_freebsd.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build !cgo package fakecgo type ( pthread_cond_t uintptr pthread_mutex_t uintptr ) var ( PTHREAD_COND_INITIALIZER = pthread_cond_t(0) PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t(0) ) ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_linux.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build !cgo package fakecgo type ( pthread_cond_t [48]byte pthread_mutex_t [48]byte ) var ( PTHREAD_COND_INITIALIZER = pthread_cond_t{} PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{} ) ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/setenv.go ================================================ // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build !cgo && (darwin || freebsd || linux) package fakecgo import _ "unsafe" // for go:linkname //go:linkname x_cgo_setenv_trampoline x_cgo_setenv_trampoline //go:linkname _cgo_setenv runtime._cgo_setenv var x_cgo_setenv_trampoline byte var _cgo_setenv = &x_cgo_setenv_trampoline //go:linkname x_cgo_unsetenv_trampoline x_cgo_unsetenv_trampoline //go:linkname _cgo_unsetenv runtime._cgo_unsetenv var x_cgo_unsetenv_trampoline byte var _cgo_unsetenv = &x_cgo_unsetenv_trampoline ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/symbols.go ================================================ // Code generated by 'go generate' with gen.go. DO NOT EDIT. // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build !cgo && (darwin || freebsd || linux) package fakecgo import ( "syscall" "unsafe" ) // setg_trampoline calls setg with the G provided func setg_trampoline(setg uintptr, G uintptr) // call5 takes fn the C function and 5 arguments and calls the function with those arguments func call5(fn, a1, a2, a3, a4, a5 uintptr) uintptr //go:nosplit func malloc(size uintptr) unsafe.Pointer { ret := call5(mallocABI0, uintptr(size), 0, 0, 0, 0) // this indirection is to avoid go vet complaining about possible misuse of unsafe.Pointer return *(*unsafe.Pointer)(unsafe.Pointer(&ret)) } //go:nosplit func free(ptr unsafe.Pointer) { call5(freeABI0, uintptr(ptr), 0, 0, 0, 0) } //go:nosplit func setenv(name *byte, value *byte, overwrite int32) int32 { return int32(call5(setenvABI0, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(value)), uintptr(overwrite), 0, 0)) } //go:nosplit func unsetenv(name *byte) int32 { return int32(call5(unsetenvABI0, uintptr(unsafe.Pointer(name)), 0, 0, 0, 0)) } //go:nosplit func sigfillset(set *sigset_t) int32 { return int32(call5(sigfillsetABI0, uintptr(unsafe.Pointer(set)), 0, 0, 0, 0)) } //go:nosplit func nanosleep(ts *syscall.Timespec, rem *syscall.Timespec) int32 { return int32(call5(nanosleepABI0, uintptr(unsafe.Pointer(ts)), uintptr(unsafe.Pointer(rem)), 0, 0, 0)) } //go:nosplit func abort() { call5(abortABI0, 0, 0, 0, 0, 0) } //go:nosplit func pthread_attr_init(attr *pthread_attr_t) int32 { return int32(call5(pthread_attr_initABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0)) } //go:nosplit func pthread_create(thread *pthread_t, attr *pthread_attr_t, start unsafe.Pointer, arg unsafe.Pointer) int32 { return int32(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(start), uintptr(arg), 0)) } //go:nosplit func pthread_detach(thread pthread_t) int32 { return int32(call5(pthread_detachABI0, uintptr(thread), 0, 0, 0, 0)) } //go:nosplit func pthread_sigmask(how sighow, ign *sigset_t, oset *sigset_t) int32 { return int32(call5(pthread_sigmaskABI0, uintptr(how), uintptr(unsafe.Pointer(ign)), uintptr(unsafe.Pointer(oset)), 0, 0)) } //go:nosplit func pthread_self() pthread_t { return pthread_t(call5(pthread_selfABI0, 0, 0, 0, 0, 0)) } //go:nosplit func pthread_get_stacksize_np(thread pthread_t) size_t { return size_t(call5(pthread_get_stacksize_npABI0, uintptr(thread), 0, 0, 0, 0)) } //go:nosplit func pthread_attr_getstacksize(attr *pthread_attr_t, stacksize *size_t) int32 { return int32(call5(pthread_attr_getstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(unsafe.Pointer(stacksize)), 0, 0, 0)) } //go:nosplit func pthread_attr_setstacksize(attr *pthread_attr_t, size size_t) int32 { return int32(call5(pthread_attr_setstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(size), 0, 0, 0)) } //go:nosplit func pthread_attr_destroy(attr *pthread_attr_t) int32 { return int32(call5(pthread_attr_destroyABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0)) } //go:nosplit func pthread_mutex_lock(mutex *pthread_mutex_t) int32 { return int32(call5(pthread_mutex_lockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0)) } //go:nosplit func pthread_mutex_unlock(mutex *pthread_mutex_t) int32 { return int32(call5(pthread_mutex_unlockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0)) } //go:nosplit func pthread_cond_broadcast(cond *pthread_cond_t) int32 { return int32(call5(pthread_cond_broadcastABI0, uintptr(unsafe.Pointer(cond)), 0, 0, 0, 0)) } //go:nosplit func pthread_setspecific(key pthread_key_t, value unsafe.Pointer) int32 { return int32(call5(pthread_setspecificABI0, uintptr(key), uintptr(value), 0, 0, 0)) } //go:linkname _malloc _malloc var _malloc uintptr var mallocABI0 = uintptr(unsafe.Pointer(&_malloc)) //go:linkname _free _free var _free uintptr var freeABI0 = uintptr(unsafe.Pointer(&_free)) //go:linkname _setenv _setenv var _setenv uintptr var setenvABI0 = uintptr(unsafe.Pointer(&_setenv)) //go:linkname _unsetenv _unsetenv var _unsetenv uintptr var unsetenvABI0 = uintptr(unsafe.Pointer(&_unsetenv)) //go:linkname _sigfillset _sigfillset var _sigfillset uintptr var sigfillsetABI0 = uintptr(unsafe.Pointer(&_sigfillset)) //go:linkname _nanosleep _nanosleep var _nanosleep uintptr var nanosleepABI0 = uintptr(unsafe.Pointer(&_nanosleep)) //go:linkname _abort _abort var _abort uintptr var abortABI0 = uintptr(unsafe.Pointer(&_abort)) //go:linkname _pthread_attr_init _pthread_attr_init var _pthread_attr_init uintptr var pthread_attr_initABI0 = uintptr(unsafe.Pointer(&_pthread_attr_init)) //go:linkname _pthread_create _pthread_create var _pthread_create uintptr var pthread_createABI0 = uintptr(unsafe.Pointer(&_pthread_create)) //go:linkname _pthread_detach _pthread_detach var _pthread_detach uintptr var pthread_detachABI0 = uintptr(unsafe.Pointer(&_pthread_detach)) //go:linkname _pthread_sigmask _pthread_sigmask var _pthread_sigmask uintptr var pthread_sigmaskABI0 = uintptr(unsafe.Pointer(&_pthread_sigmask)) //go:linkname _pthread_self _pthread_self var _pthread_self uintptr var pthread_selfABI0 = uintptr(unsafe.Pointer(&_pthread_self)) //go:linkname _pthread_get_stacksize_np _pthread_get_stacksize_np var _pthread_get_stacksize_np uintptr var pthread_get_stacksize_npABI0 = uintptr(unsafe.Pointer(&_pthread_get_stacksize_np)) //go:linkname _pthread_attr_getstacksize _pthread_attr_getstacksize var _pthread_attr_getstacksize uintptr var pthread_attr_getstacksizeABI0 = uintptr(unsafe.Pointer(&_pthread_attr_getstacksize)) //go:linkname _pthread_attr_setstacksize _pthread_attr_setstacksize var _pthread_attr_setstacksize uintptr var pthread_attr_setstacksizeABI0 = uintptr(unsafe.Pointer(&_pthread_attr_setstacksize)) //go:linkname _pthread_attr_destroy _pthread_attr_destroy var _pthread_attr_destroy uintptr var pthread_attr_destroyABI0 = uintptr(unsafe.Pointer(&_pthread_attr_destroy)) //go:linkname _pthread_mutex_lock _pthread_mutex_lock var _pthread_mutex_lock uintptr var pthread_mutex_lockABI0 = uintptr(unsafe.Pointer(&_pthread_mutex_lock)) //go:linkname _pthread_mutex_unlock _pthread_mutex_unlock var _pthread_mutex_unlock uintptr var pthread_mutex_unlockABI0 = uintptr(unsafe.Pointer(&_pthread_mutex_unlock)) //go:linkname _pthread_cond_broadcast _pthread_cond_broadcast var _pthread_cond_broadcast uintptr var pthread_cond_broadcastABI0 = uintptr(unsafe.Pointer(&_pthread_cond_broadcast)) //go:linkname _pthread_setspecific _pthread_setspecific var _pthread_setspecific uintptr var pthread_setspecificABI0 = uintptr(unsafe.Pointer(&_pthread_setspecific)) ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_darwin.go ================================================ // Code generated by 'go generate' with gen.go. DO NOT EDIT. // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build !cgo package fakecgo //go:cgo_import_dynamic purego_malloc malloc "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_free free "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_setenv setenv "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_unsetenv unsetenv "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_sigfillset sigfillset "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_nanosleep nanosleep "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_abort abort "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_pthread_create pthread_create "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_pthread_detach pthread_detach "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_pthread_self pthread_self "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_pthread_get_stacksize_np pthread_get_stacksize_np "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_pthread_attr_setstacksize pthread_attr_setstacksize "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "/usr/lib/libSystem.B.dylib" ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_freebsd.go ================================================ // Code generated by 'go generate' with gen.go. DO NOT EDIT. // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build !cgo package fakecgo //go:cgo_import_dynamic purego_malloc malloc "libc.so.7" //go:cgo_import_dynamic purego_free free "libc.so.7" //go:cgo_import_dynamic purego_setenv setenv "libc.so.7" //go:cgo_import_dynamic purego_unsetenv unsetenv "libc.so.7" //go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.7" //go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.7" //go:cgo_import_dynamic purego_abort abort "libc.so.7" //go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so" //go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so" //go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so" //go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "libpthread.so" //go:cgo_import_dynamic purego_pthread_self pthread_self "libpthread.so" //go:cgo_import_dynamic purego_pthread_get_stacksize_np pthread_get_stacksize_np "libpthread.so" //go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "libpthread.so" //go:cgo_import_dynamic purego_pthread_attr_setstacksize pthread_attr_setstacksize "libpthread.so" //go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "libpthread.so" //go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "libpthread.so" //go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "libpthread.so" //go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "libpthread.so" //go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "libpthread.so" ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_linux.go ================================================ // Code generated by 'go generate' with gen.go. DO NOT EDIT. // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build !cgo package fakecgo //go:cgo_import_dynamic purego_malloc malloc "libc.so.6" //go:cgo_import_dynamic purego_free free "libc.so.6" //go:cgo_import_dynamic purego_setenv setenv "libc.so.6" //go:cgo_import_dynamic purego_unsetenv unsetenv "libc.so.6" //go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.6" //go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.6" //go:cgo_import_dynamic purego_abort abort "libc.so.6" //go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so.0" //go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so.0" //go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so.0" //go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "libpthread.so.0" //go:cgo_import_dynamic purego_pthread_self pthread_self "libpthread.so.0" //go:cgo_import_dynamic purego_pthread_get_stacksize_np pthread_get_stacksize_np "libpthread.so.0" //go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "libpthread.so.0" //go:cgo_import_dynamic purego_pthread_attr_setstacksize pthread_attr_setstacksize "libpthread.so.0" //go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "libpthread.so.0" //go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "libpthread.so.0" //go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "libpthread.so.0" //go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "libpthread.so.0" //go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "libpthread.so.0" ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_amd64.s ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build !cgo && (darwin || linux || freebsd) /* trampoline for emulating required C functions for cgo in go (see cgo.go) (we convert cdecl calling convention to go and vice-versa) Since we're called from go and call into C we can cheat a bit with the calling conventions: - in go all the registers are caller saved - in C we have a couple of callee saved registers => we can use BX, R12, R13, R14, R15 instead of the stack C Calling convention cdecl used here (we only need integer args): 1. arg: DI 2. arg: SI 3. arg: DX 4. arg: CX 5. arg: R8 6. arg: R9 We don't need floats with these functions -> AX=0 return value will be in AX */ #include "textflag.h" #include "go_asm.h" // these trampolines map the gcc ABI to Go ABI and then calls into the Go equivalent functions. TEXT x_cgo_init_trampoline(SB), NOSPLIT, $16 MOVQ DI, AX MOVQ SI, BX MOVQ ·x_cgo_init_call(SB), DX MOVQ (DX), CX CALL CX RET TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT, $8 MOVQ DI, AX MOVQ ·x_cgo_thread_start_call(SB), DX MOVQ (DX), CX CALL CX RET TEXT x_cgo_setenv_trampoline(SB), NOSPLIT, $8 MOVQ DI, AX MOVQ ·x_cgo_setenv_call(SB), DX MOVQ (DX), CX CALL CX RET TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT, $8 MOVQ DI, AX MOVQ ·x_cgo_unsetenv_call(SB), DX MOVQ (DX), CX CALL CX RET TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT, $0 CALL ·x_cgo_notify_runtime_init_done(SB) RET TEXT x_cgo_bindm_trampoline(SB), NOSPLIT, $0 CALL ·x_cgo_bindm(SB) RET // func setg_trampoline(setg uintptr, g uintptr) TEXT ·setg_trampoline(SB), NOSPLIT, $0-16 MOVQ G+8(FP), DI MOVQ setg+0(FP), BX XORL AX, AX CALL BX RET TEXT threadentry_trampoline(SB), NOSPLIT, $16 MOVQ DI, AX MOVQ ·threadentry_call(SB), DX MOVQ (DX), CX CALL CX RET TEXT ·call5(SB), NOSPLIT, $0-56 MOVQ fn+0(FP), BX MOVQ a1+8(FP), DI MOVQ a2+16(FP), SI MOVQ a3+24(FP), DX MOVQ a4+32(FP), CX MOVQ a5+40(FP), R8 XORL AX, AX // no floats PUSHQ BP // save BP MOVQ SP, BP // save SP inside BP bc BP is callee-saved SUBQ $16, SP // allocate space for alignment ANDQ $-16, SP // align on 16 bytes for SSE CALL BX MOVQ BP, SP // get SP back POPQ BP // restore BP MOVQ AX, ret+48(FP) RET ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_arm64.s ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build !cgo && (darwin || freebsd || linux) #include "textflag.h" #include "go_asm.h" // these trampolines map the gcc ABI to Go ABI and then calls into the Go equivalent functions. TEXT x_cgo_init_trampoline(SB), NOSPLIT, $0-0 MOVD R0, 8(RSP) MOVD R1, 16(RSP) MOVD ·x_cgo_init_call(SB), R26 MOVD (R26), R2 CALL (R2) RET TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT, $0-0 MOVD R0, 8(RSP) MOVD ·x_cgo_thread_start_call(SB), R26 MOVD (R26), R2 CALL (R2) RET TEXT x_cgo_setenv_trampoline(SB), NOSPLIT, $0-0 MOVD R0, 8(RSP) MOVD ·x_cgo_setenv_call(SB), R26 MOVD (R26), R2 CALL (R2) RET TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT, $0-0 MOVD R0, 8(RSP) MOVD ·x_cgo_unsetenv_call(SB), R26 MOVD (R26), R2 CALL (R2) RET TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT, $0-0 CALL ·x_cgo_notify_runtime_init_done(SB) RET TEXT x_cgo_bindm_trampoline(SB), NOSPLIT, $0 CALL ·x_cgo_bindm(SB) RET // func setg_trampoline(setg uintptr, g uintptr) TEXT ·setg_trampoline(SB), NOSPLIT, $0-16 MOVD G+8(FP), R0 MOVD setg+0(FP), R1 CALL R1 RET TEXT threadentry_trampoline(SB), NOSPLIT, $0-0 MOVD R0, 8(RSP) MOVD ·threadentry_call(SB), R26 MOVD (R26), R2 CALL (R2) MOVD $0, R0 // TODO: get the return value from threadentry RET TEXT ·call5(SB), NOSPLIT, $0-0 MOVD fn+0(FP), R6 MOVD a1+8(FP), R0 MOVD a2+16(FP), R1 MOVD a3+24(FP), R2 MOVD a4+32(FP), R3 MOVD a5+40(FP), R4 CALL R6 MOVD R0, ret+48(FP) RET ================================================ FILE: vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_stubs.s ================================================ // Code generated by 'go generate' with gen.go. DO NOT EDIT. // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build !cgo && (darwin || freebsd || linux) #include "textflag.h" // these stubs are here because it is not possible to go:linkname directly the C functions on darwin arm64 TEXT _malloc(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_malloc(SB) RET TEXT _free(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_free(SB) RET TEXT _setenv(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_setenv(SB) RET TEXT _unsetenv(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_unsetenv(SB) RET TEXT _sigfillset(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_sigfillset(SB) RET TEXT _nanosleep(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_nanosleep(SB) RET TEXT _abort(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_abort(SB) RET TEXT _pthread_attr_init(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_pthread_attr_init(SB) RET TEXT _pthread_create(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_pthread_create(SB) RET TEXT _pthread_detach(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_pthread_detach(SB) RET TEXT _pthread_sigmask(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_pthread_sigmask(SB) RET TEXT _pthread_self(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_pthread_self(SB) RET TEXT _pthread_get_stacksize_np(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_pthread_get_stacksize_np(SB) RET TEXT _pthread_attr_getstacksize(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_pthread_attr_getstacksize(SB) RET TEXT _pthread_attr_setstacksize(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_pthread_attr_setstacksize(SB) RET TEXT _pthread_attr_destroy(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_pthread_attr_destroy(SB) RET TEXT _pthread_mutex_lock(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_pthread_mutex_lock(SB) RET TEXT _pthread_mutex_unlock(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_pthread_mutex_unlock(SB) RET TEXT _pthread_cond_broadcast(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_pthread_cond_broadcast(SB) RET TEXT _pthread_setspecific(SB), NOSPLIT|NOFRAME, $0-0 JMP purego_pthread_setspecific(SB) RET ================================================ FILE: vendor/github.com/ebitengine/purego/internal/strings/strings.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors package strings import ( "unsafe" ) // hasSuffix tests whether the string s ends with suffix. func hasSuffix(s, suffix string) bool { return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix } // CString converts a go string to *byte that can be passed to C code. func CString(name string) *byte { if hasSuffix(name, "\x00") { return &(*(*[]byte)(unsafe.Pointer(&name)))[0] } b := make([]byte, len(name)+1) copy(b, name) return &b[0] } // GoString copies a null-terminated char* to a Go string. func GoString(c uintptr) string { // We take the address and then dereference it to trick go vet from creating a possible misuse of unsafe.Pointer ptr := *(*unsafe.Pointer)(unsafe.Pointer(&c)) if ptr == nil { return "" } var length int for { if *(*byte)(unsafe.Add(ptr, uintptr(length))) == '\x00' { break } length++ } return string(unsafe.Slice((*byte)(ptr), length)) } ================================================ FILE: vendor/github.com/ebitengine/purego/is_ios.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build !cgo package purego // if you are getting this error it means that you have // CGO_ENABLED=0 while trying to build for ios. // purego does not support this mode yet. // the fix is to set CGO_ENABLED=1 which will require // a C compiler. var _ = _PUREGO_REQUIRES_CGO_ON_IOS ================================================ FILE: vendor/github.com/ebitengine/purego/nocgo.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build !cgo && (darwin || freebsd || linux) package purego // if CGO_ENABLED=0 import fakecgo to setup the Cgo runtime correctly. // This is required since some frameworks need TLS setup the C way which Go doesn't do. // We currently don't support ios in fakecgo mode so force Cgo or fail // // The way that the Cgo runtime (runtime/cgo) works is by setting some variables found // in runtime with non-null GCC compiled functions. The variables that are replaced are // var ( // iscgo bool // in runtime/cgo.go // _cgo_init unsafe.Pointer // in runtime/cgo.go // _cgo_thread_start unsafe.Pointer // in runtime/cgo.go // _cgo_notify_runtime_init_done unsafe.Pointer // in runtime/cgo.go // _cgo_setenv unsafe.Pointer // in runtime/env_posix.go // _cgo_unsetenv unsafe.Pointer // in runtime/env_posix.go // ) // importing fakecgo will set these (using //go:linkname) with functions written // entirely in Go (except for some assembly trampolines to change GCC ABI to Go ABI). // Doing so makes it possible to build applications that call into C without CGO_ENABLED=1. import _ "github.com/ebitengine/purego/internal/fakecgo" ================================================ FILE: vendor/github.com/ebitengine/purego/struct_amd64.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors package purego import ( "math" "reflect" "unsafe" ) func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { outSize := outType.Size() switch { case outSize == 0: return reflect.New(outType).Elem() case outSize <= 8: if isAllFloats(outType) { // 2 float32s or 1 float64s are return in the float register return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.f1})).Elem() } // up to 8 bytes is returned in RAX return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem() case outSize <= 16: r1, r2 := syscall.a1, syscall.a2 if isAllFloats(outType) { r1 = syscall.f1 r2 = syscall.f2 } else { // check first 8 bytes if it's floats hasFirstFloat := false f1 := outType.Field(0).Type if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && outType.Field(1).Type.Kind() == reflect.Float32 { r1 = syscall.f1 hasFirstFloat = true } // find index of the field that starts the second 8 bytes var i int for i = 0; i < outType.NumField(); i++ { if outType.Field(i).Offset == 8 { break } } // check last 8 bytes if they are floats f1 = outType.Field(i).Type if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && i+1 == outType.NumField() { r2 = syscall.f1 } else if hasFirstFloat { // if the first field was a float then that means the second integer field // comes from the first integer register r2 = syscall.a1 } } return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() default: // create struct from the Go pointer created above // weird pointer dereference to circumvent go vet return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() } } func isAllFloats(ty reflect.Type) bool { for i := 0; i < ty.NumField(); i++ { f := ty.Field(i) switch f.Type.Kind() { case reflect.Float64, reflect.Float32: default: return false } } return true } // https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf // https://gitlab.com/x86-psABIs/x86-64-ABI // Class determines where the 8 byte value goes. // Higher value classes win over lower value classes const ( _NO_CLASS = 0b0000 _SSE = 0b0001 _X87 = 0b0011 // long double not used in Go _INTEGER = 0b0111 _MEMORY = 0b1111 ) func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} { if v.Type().Size() == 0 { return keepAlive } // if greater than 64 bytes place on stack if v.Type().Size() > 8*8 { placeStack(v, addStack) return keepAlive } var ( savedNumFloats = *numFloats savedNumInts = *numInts savedNumStack = *numStack ) placeOnStack := postMerger(v.Type()) || !tryPlaceRegister(v, addFloat, addInt) if placeOnStack { // reset any values placed in registers *numFloats = savedNumFloats *numInts = savedNumInts *numStack = savedNumStack placeStack(v, addStack) } return keepAlive } func postMerger(t reflect.Type) (passInMemory bool) { // (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isn’t SSE or any other // eightbyte isn’t SSEUP, the whole argument is passed in memory. if t.Kind() != reflect.Struct { return false } if t.Size() <= 2*8 { return false } return true // Go does not have an SSE/SEEUP type so this is always true } func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) (ok bool) { ok = true var val uint64 var shift byte // # of bits to shift var flushed bool class := _NO_CLASS flushIfNeeded := func() { if flushed { return } flushed = true if class == _SSE { addFloat(uintptr(val)) } else { addInt(uintptr(val)) } val = 0 shift = 0 class = _NO_CLASS } var place func(v reflect.Value) place = func(v reflect.Value) { var numFields int if v.Kind() == reflect.Struct { numFields = v.Type().NumField() } else { numFields = v.Type().Len() } for i := 0; i < numFields; i++ { flushed = false var f reflect.Value if v.Kind() == reflect.Struct { f = v.Field(i) } else { f = v.Index(i) } switch f.Kind() { case reflect.Struct: place(f) case reflect.Bool: if f.Bool() { val |= 1 } shift += 8 class |= _INTEGER case reflect.Pointer: ok = false return case reflect.Int8: val |= uint64(f.Int()&0xFF) << shift shift += 8 class |= _INTEGER case reflect.Int16: val |= uint64(f.Int()&0xFFFF) << shift shift += 16 class |= _INTEGER case reflect.Int32: val |= uint64(f.Int()&0xFFFF_FFFF) << shift shift += 32 class |= _INTEGER case reflect.Int64, reflect.Int: val = uint64(f.Int()) shift = 64 class = _INTEGER case reflect.Uint8: val |= f.Uint() << shift shift += 8 class |= _INTEGER case reflect.Uint16: val |= f.Uint() << shift shift += 16 class |= _INTEGER case reflect.Uint32: val |= f.Uint() << shift shift += 32 class |= _INTEGER case reflect.Uint64, reflect.Uint: val = f.Uint() shift = 64 class = _INTEGER case reflect.Float32: val |= uint64(math.Float32bits(float32(f.Float()))) << shift shift += 32 class |= _SSE case reflect.Float64: if v.Type().Size() > 16 { ok = false return } val = uint64(math.Float64bits(f.Float())) shift = 64 class = _SSE case reflect.Array: place(f) default: panic("purego: unsupported kind " + f.Kind().String()) } if shift == 64 { flushIfNeeded() } else if shift > 64 { // Should never happen, but may if we forget to reset shift after flush (or forget to flush), // better fall apart here, than corrupt arguments. panic("purego: tryPlaceRegisters shift > 64") } } } place(v) flushIfNeeded() return ok } func placeStack(v reflect.Value, addStack func(uintptr)) { for i := 0; i < v.Type().NumField(); i++ { f := v.Field(i) switch f.Kind() { case reflect.Pointer: addStack(f.Pointer()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: addStack(uintptr(f.Int())) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: addStack(uintptr(f.Uint())) case reflect.Float32: addStack(uintptr(math.Float32bits(float32(f.Float())))) case reflect.Float64: addStack(uintptr(math.Float64bits(f.Float()))) case reflect.Struct: placeStack(f, addStack) default: panic("purego: unsupported kind " + f.Kind().String()) } } } ================================================ FILE: vendor/github.com/ebitengine/purego/struct_arm64.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors package purego import ( "math" "reflect" "unsafe" ) func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { outSize := outType.Size() switch { case outSize == 0: return reflect.New(outType).Elem() case outSize <= 8: r1 := syscall.a1 if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { r1 = syscall.f1 if numFields == 2 { r1 = syscall.f2<<32 | syscall.f1 } } return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() case outSize <= 16: r1, r2 := syscall.a1, syscall.a2 if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { switch numFields { case 4: r1 = syscall.f2<<32 | syscall.f1 r2 = syscall.f4<<32 | syscall.f3 case 3: r1 = syscall.f2<<32 | syscall.f1 r2 = syscall.f3 case 2: r1 = syscall.f1 r2 = syscall.f2 default: panic("unreachable") } } return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() default: if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats && numFields <= 4 { switch numFields { case 4: return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{syscall.f1, syscall.f2, syscall.f3, syscall.f4})).Elem() case 3: return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c uintptr }{syscall.f1, syscall.f2, syscall.f3})).Elem() default: panic("unreachable") } } // create struct from the Go pointer created in arm64_r8 // weird pointer dereference to circumvent go vet return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.arm64_r8))).Elem() } } // https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst const ( _NO_CLASS = 0b00 _FLOAT = 0b01 _INT = 0b11 ) func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} { if v.Type().Size() == 0 { return keepAlive } if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 { // if this doesn't fit entirely in registers then // each element goes onto the stack if hfa && *numFloats+v.NumField() > numOfFloats { *numFloats = numOfFloats } else if hva && *numInts+v.NumField() > numOfIntegerRegisters() { *numInts = numOfIntegerRegisters() } placeRegisters(v, addFloat, addInt) } else { keepAlive = placeStack(v, keepAlive, addInt) } return keepAlive // the struct was allocated so don't panic } func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { var val uint64 var shift byte var flushed bool class := _NO_CLASS var place func(v reflect.Value) place = func(v reflect.Value) { var numFields int if v.Kind() == reflect.Struct { numFields = v.Type().NumField() } else { numFields = v.Type().Len() } for k := 0; k < numFields; k++ { flushed = false var f reflect.Value if v.Kind() == reflect.Struct { f = v.Field(k) } else { f = v.Index(k) } if shift >= 64 { shift = 0 flushed = true if class == _FLOAT { addFloat(uintptr(val)) } else { addInt(uintptr(val)) } } switch f.Type().Kind() { case reflect.Struct: place(f) case reflect.Bool: if f.Bool() { val |= 1 } shift += 8 class |= _INT case reflect.Uint8: val |= f.Uint() << shift shift += 8 class |= _INT case reflect.Uint16: val |= f.Uint() << shift shift += 16 class |= _INT case reflect.Uint32: val |= f.Uint() << shift shift += 32 class |= _INT case reflect.Uint64: addInt(uintptr(f.Uint())) shift = 0 flushed = true case reflect.Int8: val |= uint64(f.Int()&0xFF) << shift shift += 8 class |= _INT case reflect.Int16: val |= uint64(f.Int()&0xFFFF) << shift shift += 16 class |= _INT case reflect.Int32: val |= uint64(f.Int()&0xFFFF_FFFF) << shift shift += 32 class |= _INT case reflect.Int64: addInt(uintptr(f.Int())) shift = 0 flushed = true case reflect.Float32: if class == _FLOAT { addFloat(uintptr(val)) val = 0 shift = 0 } val |= uint64(math.Float32bits(float32(f.Float()))) << shift shift += 32 class |= _FLOAT case reflect.Float64: addFloat(uintptr(math.Float64bits(float64(f.Float())))) shift = 0 flushed = true case reflect.Array: place(f) default: panic("purego: unsupported kind " + f.Kind().String()) } } } place(v) if !flushed { if class == _FLOAT { addFloat(uintptr(val)) } else { addInt(uintptr(val)) } } } func placeStack(v reflect.Value, keepAlive []interface{}, addInt func(uintptr)) []interface{} { // Struct is too big to be placed in registers. // Copy to heap and place the pointer in register ptrStruct := reflect.New(v.Type()) ptrStruct.Elem().Set(v) ptr := ptrStruct.Elem().Addr().UnsafePointer() keepAlive = append(keepAlive, ptr) addInt(uintptr(ptr)) return keepAlive } // isHFA reports a Homogeneous Floating-point Aggregate (HFA) which is a Fundamental Data Type that is a // Floating-Point type and at most four uniquely addressable members (5.9.5.1 in [Arm64 Calling Convention]). // This type of struct will be placed more compactly than the individual fields. // // [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst func isHFA(t reflect.Type) bool { // round up struct size to nearest 8 see section B.4 structSize := roundUpTo8(t.Size()) if structSize == 0 || t.NumField() > 4 { return false } first := t.Field(0) switch first.Type.Kind() { case reflect.Float32, reflect.Float64: firstKind := first.Type.Kind() for i := 0; i < t.NumField(); i++ { if t.Field(i).Type.Kind() != firstKind { return false } } return true case reflect.Array: switch first.Type.Elem().Kind() { case reflect.Float32, reflect.Float64: return true default: return false } case reflect.Struct: for i := 0; i < first.Type.NumField(); i++ { if !isHFA(first.Type) { return false } } return true default: return false } } // isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type // and at most four uniquely addressable members (5.9.5.2 in [Arm64 Calling Convention]). // A short vector is a machine type that is composed of repeated instances of one fundamental integral or // floating-point type. It may be 8 or 16 bytes in total size (5.4 in [Arm64 Calling Convention]). // This type of struct will be placed more compactly than the individual fields. // // [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst func isHVA(t reflect.Type) bool { // round up struct size to nearest 8 see section B.4 structSize := roundUpTo8(t.Size()) if structSize == 0 || (structSize != 8 && structSize != 16) { return false } first := t.Field(0) switch first.Type.Kind() { case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32: firstKind := first.Type.Kind() for i := 0; i < t.NumField(); i++ { if t.Field(i).Type.Kind() != firstKind { return false } } return true case reflect.Array: switch first.Type.Elem().Kind() { case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32: return true default: return false } default: return false } } ================================================ FILE: vendor/github.com/ebitengine/purego/struct_other.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors //go:build !amd64 && !arm64 package purego import "reflect" func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} { panic("purego: struct arguments are not supported") } func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { panic("purego: struct returns are not supported") } ================================================ FILE: vendor/github.com/ebitengine/purego/sys_amd64.s ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build darwin || freebsd || linux #include "textflag.h" #include "abi_amd64.h" #include "go_asm.h" #include "funcdata.h" #define STACK_SIZE 80 #define PTR_ADDRESS (STACK_SIZE - 8) // syscall15X calls a function in libc on behalf of the syscall package. // syscall15X takes a pointer to a struct like: // struct { // fn uintptr // a1 uintptr // a2 uintptr // a3 uintptr // a4 uintptr // a5 uintptr // a6 uintptr // a7 uintptr // a8 uintptr // a9 uintptr // a10 uintptr // a11 uintptr // a12 uintptr // a13 uintptr // a14 uintptr // a15 uintptr // r1 uintptr // r2 uintptr // err uintptr // } // syscall15X must be called on the g0 stack with the // C calling convention (use libcCall). GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0 PUSHQ BP MOVQ SP, BP SUBQ $STACK_SIZE, SP MOVQ DI, PTR_ADDRESS(BP) // save the pointer MOVQ DI, R11 MOVQ syscall15Args_f1(R11), X0 // f1 MOVQ syscall15Args_f2(R11), X1 // f2 MOVQ syscall15Args_f3(R11), X2 // f3 MOVQ syscall15Args_f4(R11), X3 // f4 MOVQ syscall15Args_f5(R11), X4 // f5 MOVQ syscall15Args_f6(R11), X5 // f6 MOVQ syscall15Args_f7(R11), X6 // f7 MOVQ syscall15Args_f8(R11), X7 // f8 MOVQ syscall15Args_a1(R11), DI // a1 MOVQ syscall15Args_a2(R11), SI // a2 MOVQ syscall15Args_a3(R11), DX // a3 MOVQ syscall15Args_a4(R11), CX // a4 MOVQ syscall15Args_a5(R11), R8 // a5 MOVQ syscall15Args_a6(R11), R9 // a6 // push the remaining paramters onto the stack MOVQ syscall15Args_a7(R11), R12 MOVQ R12, 0(SP) // push a7 MOVQ syscall15Args_a8(R11), R12 MOVQ R12, 8(SP) // push a8 MOVQ syscall15Args_a9(R11), R12 MOVQ R12, 16(SP) // push a9 MOVQ syscall15Args_a10(R11), R12 MOVQ R12, 24(SP) // push a10 MOVQ syscall15Args_a11(R11), R12 MOVQ R12, 32(SP) // push a11 MOVQ syscall15Args_a12(R11), R12 MOVQ R12, 40(SP) // push a12 MOVQ syscall15Args_a13(R11), R12 MOVQ R12, 48(SP) // push a13 MOVQ syscall15Args_a14(R11), R12 MOVQ R12, 56(SP) // push a14 MOVQ syscall15Args_a15(R11), R12 MOVQ R12, 64(SP) // push a15 XORL AX, AX // vararg: say "no float args" MOVQ syscall15Args_fn(R11), R10 // fn CALL R10 MOVQ PTR_ADDRESS(BP), DI // get the pointer back MOVQ AX, syscall15Args_a1(DI) // r1 MOVQ DX, syscall15Args_a2(DI) // r3 MOVQ X0, syscall15Args_f1(DI) // f1 MOVQ X1, syscall15Args_f2(DI) // f2 XORL AX, AX // no error (it's ignored anyway) ADDQ $STACK_SIZE, SP MOVQ BP, SP POPQ BP RET TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 MOVQ 0(SP), AX // save the return address to calculate the cb index MOVQ 8(SP), R10 // get the return SP so that we can align register args with stack args ADDQ $8, SP // remove return address from stack, we are not returning to callbackasm, but to its caller. // make space for first six int and 8 float arguments below the frame ADJSP $14*8, SP MOVSD X0, (1*8)(SP) MOVSD X1, (2*8)(SP) MOVSD X2, (3*8)(SP) MOVSD X3, (4*8)(SP) MOVSD X4, (5*8)(SP) MOVSD X5, (6*8)(SP) MOVSD X6, (7*8)(SP) MOVSD X7, (8*8)(SP) MOVQ DI, (9*8)(SP) MOVQ SI, (10*8)(SP) MOVQ DX, (11*8)(SP) MOVQ CX, (12*8)(SP) MOVQ R8, (13*8)(SP) MOVQ R9, (14*8)(SP) LEAQ 8(SP), R8 // R8 = address of args vector PUSHQ R10 // push the stack pointer below registers // Switch from the host ABI to the Go ABI. PUSH_REGS_HOST_TO_ABI0() // determine index into runtime·cbs table MOVQ $callbackasm(SB), DX SUBQ DX, AX MOVQ $0, DX MOVQ $5, CX // divide by 5 because each call instruction in ·callbacks is 5 bytes long DIVL CX SUBQ $1, AX // subtract 1 because return PC is to the next slot // Create a struct callbackArgs on our stack to be passed as // the "frame" to cgocallback and on to callbackWrap. // $24 to make enough room for the arguments to runtime.cgocallback SUBQ $(24+callbackArgs__size), SP MOVQ AX, (24+callbackArgs_index)(SP) // callback index MOVQ R8, (24+callbackArgs_args)(SP) // address of args vector MOVQ $0, (24+callbackArgs_result)(SP) // result LEAQ 24(SP), AX // take the address of callbackArgs // Call cgocallback, which will call callbackWrap(frame). MOVQ ·callbackWrap_call(SB), DI // Get the ABIInternal function pointer MOVQ (DI), DI // without by using a closure. MOVQ AX, SI // frame (address of callbackArgs) MOVQ $0, CX // context CALL crosscall2(SB) // runtime.cgocallback(fn, frame, ctxt uintptr) // Get callback result. MOVQ (24+callbackArgs_result)(SP), AX ADDQ $(24+callbackArgs__size), SP // remove callbackArgs struct POP_REGS_HOST_TO_ABI0() POPQ R10 // get the SP back ADJSP $-14*8, SP // remove arguments MOVQ R10, 0(SP) RET ================================================ FILE: vendor/github.com/ebitengine/purego/sys_arm64.s ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build darwin || freebsd || linux || windows #include "textflag.h" #include "go_asm.h" #include "funcdata.h" #define STACK_SIZE 64 #define PTR_ADDRESS (STACK_SIZE - 8) // syscall15X calls a function in libc on behalf of the syscall package. // syscall15X takes a pointer to a struct like: // struct { // fn uintptr // a1 uintptr // a2 uintptr // a3 uintptr // a4 uintptr // a5 uintptr // a6 uintptr // a7 uintptr // a8 uintptr // a9 uintptr // a10 uintptr // a11 uintptr // a12 uintptr // a13 uintptr // a14 uintptr // a15 uintptr // r1 uintptr // r2 uintptr // err uintptr // } // syscall15X must be called on the g0 stack with the // C calling convention (use libcCall). GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) TEXT syscall15X(SB), NOSPLIT, $0 SUB $STACK_SIZE, RSP // push structure pointer MOVD R0, PTR_ADDRESS(RSP) MOVD R0, R9 FMOVD syscall15Args_f1(R9), F0 // f1 FMOVD syscall15Args_f2(R9), F1 // f2 FMOVD syscall15Args_f3(R9), F2 // f3 FMOVD syscall15Args_f4(R9), F3 // f4 FMOVD syscall15Args_f5(R9), F4 // f5 FMOVD syscall15Args_f6(R9), F5 // f6 FMOVD syscall15Args_f7(R9), F6 // f7 FMOVD syscall15Args_f8(R9), F7 // f8 MOVD syscall15Args_a1(R9), R0 // a1 MOVD syscall15Args_a2(R9), R1 // a2 MOVD syscall15Args_a3(R9), R2 // a3 MOVD syscall15Args_a4(R9), R3 // a4 MOVD syscall15Args_a5(R9), R4 // a5 MOVD syscall15Args_a6(R9), R5 // a6 MOVD syscall15Args_a7(R9), R6 // a7 MOVD syscall15Args_a8(R9), R7 // a8 MOVD syscall15Args_arm64_r8(R9), R8 // r8 MOVD syscall15Args_a9(R9), R10 MOVD R10, 0(RSP) // push a9 onto stack MOVD syscall15Args_a10(R9), R10 MOVD R10, 8(RSP) // push a10 onto stack MOVD syscall15Args_a11(R9), R10 MOVD R10, 16(RSP) // push a11 onto stack MOVD syscall15Args_a12(R9), R10 MOVD R10, 24(RSP) // push a12 onto stack MOVD syscall15Args_a13(R9), R10 MOVD R10, 32(RSP) // push a13 onto stack MOVD syscall15Args_a14(R9), R10 MOVD R10, 40(RSP) // push a14 onto stack MOVD syscall15Args_a15(R9), R10 MOVD R10, 48(RSP) // push a15 onto stack MOVD syscall15Args_fn(R9), R10 // fn BL (R10) MOVD PTR_ADDRESS(RSP), R2 // pop structure pointer ADD $STACK_SIZE, RSP MOVD R0, syscall15Args_a1(R2) // save r1 MOVD R1, syscall15Args_a2(R2) // save r3 FMOVD F0, syscall15Args_f1(R2) // save f0 FMOVD F1, syscall15Args_f2(R2) // save f1 FMOVD F2, syscall15Args_f3(R2) // save f2 FMOVD F3, syscall15Args_f4(R2) // save f3 RET ================================================ FILE: vendor/github.com/ebitengine/purego/sys_unix_arm64.s ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 The Ebitengine Authors //go:build darwin || freebsd || linux #include "textflag.h" #include "go_asm.h" #include "funcdata.h" #include "abi_arm64.h" TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 NO_LOCAL_POINTERS // On entry, the trampoline in zcallback_darwin_arm64.s left // the callback index in R12 (which is volatile in the C ABI). // Save callback register arguments R0-R7 and F0-F7. // We do this at the top of the frame so they're contiguous with stack arguments. SUB $(16*8), RSP, R14 FSTPD (F0, F1), (0*8)(R14) FSTPD (F2, F3), (2*8)(R14) FSTPD (F4, F5), (4*8)(R14) FSTPD (F6, F7), (6*8)(R14) STP (R0, R1), (8*8)(R14) STP (R2, R3), (10*8)(R14) STP (R4, R5), (12*8)(R14) STP (R6, R7), (14*8)(R14) // Adjust SP by frame size. SUB $(26*8), RSP // It is important to save R27 because the go assembler // uses it for move instructions for a variable. // This line: // MOVD ·callbackWrap_call(SB), R0 // Creates the instructions: // ADRP 14335(PC), R27 // MOVD 388(27), R0 // R27 is a callee saved register so we are responsible // for ensuring its value doesn't change. So save it and // restore it at the end of this function. // R30 is the link register. crosscall2 doesn't save it // so it's saved here. STP (R27, R30), 0(RSP) // Create a struct callbackArgs on our stack. MOVD $(callbackArgs__size)(RSP), R13 MOVD R12, callbackArgs_index(R13) // callback index MOVD R14, callbackArgs_args(R13) // address of args vector MOVD ZR, callbackArgs_result(R13) // result // Move parameters into registers // Get the ABIInternal function pointer // without by using a closure. MOVD ·callbackWrap_call(SB), R0 MOVD (R0), R0 // fn unsafe.Pointer MOVD R13, R1 // frame (&callbackArgs{...}) MOVD $0, R3 // ctxt uintptr BL crosscall2(SB) // Get callback result. MOVD $(callbackArgs__size)(RSP), R13 MOVD callbackArgs_result(R13), R0 // Restore LR and R27 LDP 0(RSP), (R27, R30) ADD $(26*8), RSP RET ================================================ FILE: vendor/github.com/ebitengine/purego/syscall.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build darwin || freebsd || linux || windows package purego // CDecl marks a function as being called using the __cdecl calling convention as defined in // the [MSDocs] when passed to NewCallback. It must be the first argument to the function. // This is only useful on 386 Windows, but it is safe to use on other platforms. // // [MSDocs]: https://learn.microsoft.com/en-us/cpp/cpp/cdecl?view=msvc-170 type CDecl struct{} const ( maxArgs = 15 numOfFloats = 8 // arm64 and amd64 both have 8 float registers ) type syscall15Args struct { fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr f1, f2, f3, f4, f5, f6, f7, f8 uintptr arm64_r8 uintptr } // SyscallN takes fn, a C function pointer and a list of arguments as uintptr. // There is an internal maximum number of arguments that SyscallN can take. It panics // when the maximum is exceeded. It returns the result and the libc error code if there is one. // // NOTE: SyscallN does not properly call functions that have both integer and float parameters. // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 // for an explanation of why that is. // // On amd64, if there are more than 8 floats the 9th and so on will be placed incorrectly on the // stack. // // The pragma go:nosplit is not needed at this function declaration because it uses go:uintptrescapes // which forces all the objects that the uintptrs point to onto the heap where a stack split won't affect // their memory location. // //go:uintptrescapes func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { if fn == 0 { panic("purego: fn is nil") } if len(args) > maxArgs { panic("purego: too many arguments to SyscallN") } // add padding so there is no out-of-bounds slicing var tmp [maxArgs]uintptr copy(tmp[:], args) return syscall_syscall15X(fn, tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14]) } ================================================ FILE: vendor/github.com/ebitengine/purego/syscall_cgo_linux.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build cgo && !(amd64 || arm64) package purego import ( "github.com/ebitengine/purego/internal/cgo" ) var syscall15XABI0 = uintptr(cgo.Syscall15XABI0) //go:nosplit func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { return cgo.Syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) } func NewCallback(_ interface{}) uintptr { panic("purego: NewCallback on Linux is only supported on amd64/arm64") } ================================================ FILE: vendor/github.com/ebitengine/purego/syscall_sysv.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors //go:build darwin || freebsd || (linux && (amd64 || arm64)) package purego import ( "reflect" "runtime" "sync" "unsafe" ) var syscall15XABI0 uintptr //go:nosplit func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { args := syscall15Args{ fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a1, a2, a3, a4, a5, a6, a7, a8, 0, } runtime_cgocall(syscall15XABI0, unsafe.Pointer(&args)) return args.a1, args.a2, 0 } // NewCallback converts a Go function to a function pointer conforming to the C calling convention. // This is useful when interoperating with C code requiring callbacks. The argument is expected to be a // function with zero or one uintptr-sized result. The function must not have arguments with size larger than the size // of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory allocated // for these callbacks is never released. At least 2000 callbacks can always be created. Although this function // provides similar functionality to windows.NewCallback it is distinct. func NewCallback(fn interface{}) uintptr { ty := reflect.TypeOf(fn) for i := 0; i < ty.NumIn(); i++ { in := ty.In(i) if !in.AssignableTo(reflect.TypeOf(CDecl{})) { continue } if i != 0 { panic("purego: CDecl must be the first argument") } } return compileCallback(fn) } // maxCb is the maximum number of callbacks // only increase this if you have added more to the callbackasm function const maxCB = 2000 var cbs struct { lock sync.Mutex numFn int // the number of functions currently in cbs.funcs funcs [maxCB]reflect.Value // the saved callbacks } type callbackArgs struct { index uintptr // args points to the argument block. // // The structure of the arguments goes // float registers followed by the // integer registers followed by the stack. // // This variable is treated as a continuous // block of memory containing all of the arguments // for this callback. args unsafe.Pointer // Below are out-args from callbackWrap result uintptr } func compileCallback(fn interface{}) uintptr { val := reflect.ValueOf(fn) if val.Kind() != reflect.Func { panic("purego: the type must be a function but was not") } if val.IsNil() { panic("purego: function must not be nil") } ty := val.Type() for i := 0; i < ty.NumIn(); i++ { in := ty.In(i) switch in.Kind() { case reflect.Struct: if i == 0 && in.AssignableTo(reflect.TypeOf(CDecl{})) { continue } fallthrough case reflect.Interface, reflect.Func, reflect.Slice, reflect.Chan, reflect.Complex64, reflect.Complex128, reflect.String, reflect.Map, reflect.Invalid: panic("purego: unsupported argument type: " + in.Kind().String()) } } output: switch { case ty.NumOut() == 1: switch ty.Out(0).Kind() { case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Bool, reflect.UnsafePointer: break output } panic("purego: unsupported return type: " + ty.String()) case ty.NumOut() > 1: panic("purego: callbacks can only have one return") } cbs.lock.Lock() defer cbs.lock.Unlock() if cbs.numFn >= maxCB { panic("purego: the maximum number of callbacks has been reached") } cbs.funcs[cbs.numFn] = val cbs.numFn++ return callbackasmAddr(cbs.numFn - 1) } const ptrSize = unsafe.Sizeof((*int)(nil)) const callbackMaxFrame = 64 * ptrSize // callbackasm is implemented in zcallback_GOOS_GOARCH.s // //go:linkname __callbackasm callbackasm var __callbackasm byte var callbackasmABI0 = uintptr(unsafe.Pointer(&__callbackasm)) // callbackWrap_call allows the calling of the ABIInternal wrapper // which is required for runtime.cgocallback without the // tag which is only allowed in the runtime. // This closure is used inside sys_darwin_GOARCH.s var callbackWrap_call = callbackWrap // callbackWrap is called by assembly code which determines which Go function to call. // This function takes the arguments and passes them to the Go function and returns the result. func callbackWrap(a *callbackArgs) { cbs.lock.Lock() fn := cbs.funcs[a.index] cbs.lock.Unlock() fnType := fn.Type() args := make([]reflect.Value, fnType.NumIn()) frame := (*[callbackMaxFrame]uintptr)(a.args) var floatsN int // floatsN represents the number of float arguments processed var intsN int // intsN represents the number of integer arguments processed // stack points to the index into frame of the current stack element. // The stack begins after the float and integer registers. stack := numOfIntegerRegisters() + numOfFloats for i := range args { var pos int switch fnType.In(i).Kind() { case reflect.Float32, reflect.Float64: if floatsN >= numOfFloats { pos = stack stack++ } else { pos = floatsN } floatsN++ case reflect.Struct: // This is the CDecl field args[i] = reflect.Zero(fnType.In(i)) continue default: if intsN >= numOfIntegerRegisters() { pos = stack stack++ } else { // the integers begin after the floats in frame pos = intsN + numOfFloats } intsN++ } args[i] = reflect.NewAt(fnType.In(i), unsafe.Pointer(&frame[pos])).Elem() } ret := fn.Call(args) if len(ret) > 0 { switch k := ret[0].Kind(); k { case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr: a.result = uintptr(ret[0].Uint()) case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: a.result = uintptr(ret[0].Int()) case reflect.Bool: if ret[0].Bool() { a.result = 1 } else { a.result = 0 } case reflect.Pointer: a.result = ret[0].Pointer() case reflect.UnsafePointer: a.result = ret[0].Pointer() default: panic("purego: unsupported kind: " + k.String()) } } } // callbackasmAddr returns address of runtime.callbackasm // function adjusted by i. // On x86 and amd64, runtime.callbackasm is a series of CALL instructions, // and we want callback to arrive at // correspondent call instruction instead of start of // runtime.callbackasm. // On ARM, runtime.callbackasm is a series of mov and branch instructions. // R12 is loaded with the callback index. Each entry is two instructions, // hence 8 bytes. func callbackasmAddr(i int) uintptr { var entrySize int switch runtime.GOARCH { default: panic("purego: unsupported architecture") case "386", "amd64": entrySize = 5 case "arm", "arm64": // On ARM and ARM64, each entry is a MOV instruction // followed by a branch instruction entrySize = 8 } return callbackasmABI0 + uintptr(i*entrySize) } ================================================ FILE: vendor/github.com/ebitengine/purego/syscall_windows.go ================================================ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors package purego import ( "reflect" "syscall" ) var syscall15XABI0 uintptr func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { r1, r2, errno := syscall.Syscall15(fn, 15, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) return r1, r2, uintptr(errno) } // NewCallback converts a Go function to a function pointer conforming to the stdcall calling convention. // This is useful when interoperating with Windows code requiring callbacks. The argument is expected to be a // function with one uintptr-sized result. The function must not have arguments with size larger than the // size of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory // allocated for these callbacks is never released. Between NewCallback and NewCallbackCDecl, at least 1024 // callbacks can always be created. Although this function is similiar to the darwin version it may act // differently. func NewCallback(fn interface{}) uintptr { isCDecl := false ty := reflect.TypeOf(fn) for i := 0; i < ty.NumIn(); i++ { in := ty.In(i) if !in.AssignableTo(reflect.TypeOf(CDecl{})) { continue } if i != 0 { panic("purego: CDecl must be the first argument") } isCDecl = true } if isCDecl { return syscall.NewCallbackCDecl(fn) } return syscall.NewCallback(fn) } func loadSymbol(handle uintptr, name string) (uintptr, error) { return syscall.GetProcAddress(syscall.Handle(handle), name) } ================================================ FILE: vendor/github.com/ebitengine/purego/zcallback_amd64.s ================================================ // Code generated by wincallback.go using 'go generate'. DO NOT EDIT. //go:build darwin || freebsd || linux // runtime·callbackasm is called by external code to // execute Go implemented callback function. It is not // called from the start, instead runtime·compilecallback // always returns address into runtime·callbackasm offset // appropriately so different callbacks start with different // CALL instruction in runtime·callbackasm. This determines // which Go callback function is executed later on. #include "textflag.h" TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) CALL callbackasm1(SB) ================================================ FILE: vendor/github.com/ebitengine/purego/zcallback_arm64.s ================================================ // Code generated by wincallback.go using 'go generate'. DO NOT EDIT. //go:build darwin || freebsd || linux // External code calls into callbackasm at an offset corresponding // to the callback index. Callbackasm is a table of MOV and B instructions. // The MOV instruction loads R12 with the callback index, and the // B instruction branches to callbackasm1. // callbackasm1 takes the callback index from R12 and // indexes into an array that stores information about each callback. // It then calls the Go implementation for that callback. #include "textflag.h" TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 MOVD $0, R12 B callbackasm1(SB) MOVD $1, R12 B callbackasm1(SB) MOVD $2, R12 B callbackasm1(SB) MOVD $3, R12 B callbackasm1(SB) MOVD $4, R12 B callbackasm1(SB) MOVD $5, R12 B callbackasm1(SB) MOVD $6, R12 B callbackasm1(SB) MOVD $7, R12 B callbackasm1(SB) MOVD $8, R12 B callbackasm1(SB) MOVD $9, R12 B callbackasm1(SB) MOVD $10, R12 B callbackasm1(SB) MOVD $11, R12 B callbackasm1(SB) MOVD $12, R12 B callbackasm1(SB) MOVD $13, R12 B callbackasm1(SB) MOVD $14, R12 B callbackasm1(SB) MOVD $15, R12 B callbackasm1(SB) MOVD $16, R12 B callbackasm1(SB) MOVD $17, R12 B callbackasm1(SB) MOVD $18, R12 B callbackasm1(SB) MOVD $19, R12 B callbackasm1(SB) MOVD $20, R12 B callbackasm1(SB) MOVD $21, R12 B callbackasm1(SB) MOVD $22, R12 B callbackasm1(SB) MOVD $23, R12 B callbackasm1(SB) MOVD $24, R12 B callbackasm1(SB) MOVD $25, R12 B callbackasm1(SB) MOVD $26, R12 B callbackasm1(SB) MOVD $27, R12 B callbackasm1(SB) MOVD $28, R12 B callbackasm1(SB) MOVD $29, R12 B callbackasm1(SB) MOVD $30, R12 B callbackasm1(SB) MOVD $31, R12 B callbackasm1(SB) MOVD $32, R12 B callbackasm1(SB) MOVD $33, R12 B callbackasm1(SB) MOVD $34, R12 B callbackasm1(SB) MOVD $35, R12 B callbackasm1(SB) MOVD $36, R12 B callbackasm1(SB) MOVD $37, R12 B callbackasm1(SB) MOVD $38, R12 B callbackasm1(SB) MOVD $39, R12 B callbackasm1(SB) MOVD $40, R12 B callbackasm1(SB) MOVD $41, R12 B callbackasm1(SB) MOVD $42, R12 B callbackasm1(SB) MOVD $43, R12 B callbackasm1(SB) MOVD $44, R12 B callbackasm1(SB) MOVD $45, R12 B callbackasm1(SB) MOVD $46, R12 B callbackasm1(SB) MOVD $47, R12 B callbackasm1(SB) MOVD $48, R12 B callbackasm1(SB) MOVD $49, R12 B callbackasm1(SB) MOVD $50, R12 B callbackasm1(SB) MOVD $51, R12 B callbackasm1(SB) MOVD $52, R12 B callbackasm1(SB) MOVD $53, R12 B callbackasm1(SB) MOVD $54, R12 B callbackasm1(SB) MOVD $55, R12 B callbackasm1(SB) MOVD $56, R12 B callbackasm1(SB) MOVD $57, R12 B callbackasm1(SB) MOVD $58, R12 B callbackasm1(SB) MOVD $59, R12 B callbackasm1(SB) MOVD $60, R12 B callbackasm1(SB) MOVD $61, R12 B callbackasm1(SB) MOVD $62, R12 B callbackasm1(SB) MOVD $63, R12 B callbackasm1(SB) MOVD $64, R12 B callbackasm1(SB) MOVD $65, R12 B callbackasm1(SB) MOVD $66, R12 B callbackasm1(SB) MOVD $67, R12 B callbackasm1(SB) MOVD $68, R12 B callbackasm1(SB) MOVD $69, R12 B callbackasm1(SB) MOVD $70, R12 B callbackasm1(SB) MOVD $71, R12 B callbackasm1(SB) MOVD $72, R12 B callbackasm1(SB) MOVD $73, R12 B callbackasm1(SB) MOVD $74, R12 B callbackasm1(SB) MOVD $75, R12 B callbackasm1(SB) MOVD $76, R12 B callbackasm1(SB) MOVD $77, R12 B callbackasm1(SB) MOVD $78, R12 B callbackasm1(SB) MOVD $79, R12 B callbackasm1(SB) MOVD $80, R12 B callbackasm1(SB) MOVD $81, R12 B callbackasm1(SB) MOVD $82, R12 B callbackasm1(SB) MOVD $83, R12 B callbackasm1(SB) MOVD $84, R12 B callbackasm1(SB) MOVD $85, R12 B callbackasm1(SB) MOVD $86, R12 B callbackasm1(SB) MOVD $87, R12 B callbackasm1(SB) MOVD $88, R12 B callbackasm1(SB) MOVD $89, R12 B callbackasm1(SB) MOVD $90, R12 B callbackasm1(SB) MOVD $91, R12 B callbackasm1(SB) MOVD $92, R12 B callbackasm1(SB) MOVD $93, R12 B callbackasm1(SB) MOVD $94, R12 B callbackasm1(SB) MOVD $95, R12 B callbackasm1(SB) MOVD $96, R12 B callbackasm1(SB) MOVD $97, R12 B callbackasm1(SB) MOVD $98, R12 B callbackasm1(SB) MOVD $99, R12 B callbackasm1(SB) MOVD $100, R12 B callbackasm1(SB) MOVD $101, R12 B callbackasm1(SB) MOVD $102, R12 B callbackasm1(SB) MOVD $103, R12 B callbackasm1(SB) MOVD $104, R12 B callbackasm1(SB) MOVD $105, R12 B callbackasm1(SB) MOVD $106, R12 B callbackasm1(SB) MOVD $107, R12 B callbackasm1(SB) MOVD $108, R12 B callbackasm1(SB) MOVD $109, R12 B callbackasm1(SB) MOVD $110, R12 B callbackasm1(SB) MOVD $111, R12 B callbackasm1(SB) MOVD $112, R12 B callbackasm1(SB) MOVD $113, R12 B callbackasm1(SB) MOVD $114, R12 B callbackasm1(SB) MOVD $115, R12 B callbackasm1(SB) MOVD $116, R12 B callbackasm1(SB) MOVD $117, R12 B callbackasm1(SB) MOVD $118, R12 B callbackasm1(SB) MOVD $119, R12 B callbackasm1(SB) MOVD $120, R12 B callbackasm1(SB) MOVD $121, R12 B callbackasm1(SB) MOVD $122, R12 B callbackasm1(SB) MOVD $123, R12 B callbackasm1(SB) MOVD $124, R12 B callbackasm1(SB) MOVD $125, R12 B callbackasm1(SB) MOVD $126, R12 B callbackasm1(SB) MOVD $127, R12 B callbackasm1(SB) MOVD $128, R12 B callbackasm1(SB) MOVD $129, R12 B callbackasm1(SB) MOVD $130, R12 B callbackasm1(SB) MOVD $131, R12 B callbackasm1(SB) MOVD $132, R12 B callbackasm1(SB) MOVD $133, R12 B callbackasm1(SB) MOVD $134, R12 B callbackasm1(SB) MOVD $135, R12 B callbackasm1(SB) MOVD $136, R12 B callbackasm1(SB) MOVD $137, R12 B callbackasm1(SB) MOVD $138, R12 B callbackasm1(SB) MOVD $139, R12 B callbackasm1(SB) MOVD $140, R12 B callbackasm1(SB) MOVD $141, R12 B callbackasm1(SB) MOVD $142, R12 B callbackasm1(SB) MOVD $143, R12 B callbackasm1(SB) MOVD $144, R12 B callbackasm1(SB) MOVD $145, R12 B callbackasm1(SB) MOVD $146, R12 B callbackasm1(SB) MOVD $147, R12 B callbackasm1(SB) MOVD $148, R12 B callbackasm1(SB) MOVD $149, R12 B callbackasm1(SB) MOVD $150, R12 B callbackasm1(SB) MOVD $151, R12 B callbackasm1(SB) MOVD $152, R12 B callbackasm1(SB) MOVD $153, R12 B callbackasm1(SB) MOVD $154, R12 B callbackasm1(SB) MOVD $155, R12 B callbackasm1(SB) MOVD $156, R12 B callbackasm1(SB) MOVD $157, R12 B callbackasm1(SB) MOVD $158, R12 B callbackasm1(SB) MOVD $159, R12 B callbackasm1(SB) MOVD $160, R12 B callbackasm1(SB) MOVD $161, R12 B callbackasm1(SB) MOVD $162, R12 B callbackasm1(SB) MOVD $163, R12 B callbackasm1(SB) MOVD $164, R12 B callbackasm1(SB) MOVD $165, R12 B callbackasm1(SB) MOVD $166, R12 B callbackasm1(SB) MOVD $167, R12 B callbackasm1(SB) MOVD $168, R12 B callbackasm1(SB) MOVD $169, R12 B callbackasm1(SB) MOVD $170, R12 B callbackasm1(SB) MOVD $171, R12 B callbackasm1(SB) MOVD $172, R12 B callbackasm1(SB) MOVD $173, R12 B callbackasm1(SB) MOVD $174, R12 B callbackasm1(SB) MOVD $175, R12 B callbackasm1(SB) MOVD $176, R12 B callbackasm1(SB) MOVD $177, R12 B callbackasm1(SB) MOVD $178, R12 B callbackasm1(SB) MOVD $179, R12 B callbackasm1(SB) MOVD $180, R12 B callbackasm1(SB) MOVD $181, R12 B callbackasm1(SB) MOVD $182, R12 B callbackasm1(SB) MOVD $183, R12 B callbackasm1(SB) MOVD $184, R12 B callbackasm1(SB) MOVD $185, R12 B callbackasm1(SB) MOVD $186, R12 B callbackasm1(SB) MOVD $187, R12 B callbackasm1(SB) MOVD $188, R12 B callbackasm1(SB) MOVD $189, R12 B callbackasm1(SB) MOVD $190, R12 B callbackasm1(SB) MOVD $191, R12 B callbackasm1(SB) MOVD $192, R12 B callbackasm1(SB) MOVD $193, R12 B callbackasm1(SB) MOVD $194, R12 B callbackasm1(SB) MOVD $195, R12 B callbackasm1(SB) MOVD $196, R12 B callbackasm1(SB) MOVD $197, R12 B callbackasm1(SB) MOVD $198, R12 B callbackasm1(SB) MOVD $199, R12 B callbackasm1(SB) MOVD $200, R12 B callbackasm1(SB) MOVD $201, R12 B callbackasm1(SB) MOVD $202, R12 B callbackasm1(SB) MOVD $203, R12 B callbackasm1(SB) MOVD $204, R12 B callbackasm1(SB) MOVD $205, R12 B callbackasm1(SB) MOVD $206, R12 B callbackasm1(SB) MOVD $207, R12 B callbackasm1(SB) MOVD $208, R12 B callbackasm1(SB) MOVD $209, R12 B callbackasm1(SB) MOVD $210, R12 B callbackasm1(SB) MOVD $211, R12 B callbackasm1(SB) MOVD $212, R12 B callbackasm1(SB) MOVD $213, R12 B callbackasm1(SB) MOVD $214, R12 B callbackasm1(SB) MOVD $215, R12 B callbackasm1(SB) MOVD $216, R12 B callbackasm1(SB) MOVD $217, R12 B callbackasm1(SB) MOVD $218, R12 B callbackasm1(SB) MOVD $219, R12 B callbackasm1(SB) MOVD $220, R12 B callbackasm1(SB) MOVD $221, R12 B callbackasm1(SB) MOVD $222, R12 B callbackasm1(SB) MOVD $223, R12 B callbackasm1(SB) MOVD $224, R12 B callbackasm1(SB) MOVD $225, R12 B callbackasm1(SB) MOVD $226, R12 B callbackasm1(SB) MOVD $227, R12 B callbackasm1(SB) MOVD $228, R12 B callbackasm1(SB) MOVD $229, R12 B callbackasm1(SB) MOVD $230, R12 B callbackasm1(SB) MOVD $231, R12 B callbackasm1(SB) MOVD $232, R12 B callbackasm1(SB) MOVD $233, R12 B callbackasm1(SB) MOVD $234, R12 B callbackasm1(SB) MOVD $235, R12 B callbackasm1(SB) MOVD $236, R12 B callbackasm1(SB) MOVD $237, R12 B callbackasm1(SB) MOVD $238, R12 B callbackasm1(SB) MOVD $239, R12 B callbackasm1(SB) MOVD $240, R12 B callbackasm1(SB) MOVD $241, R12 B callbackasm1(SB) MOVD $242, R12 B callbackasm1(SB) MOVD $243, R12 B callbackasm1(SB) MOVD $244, R12 B callbackasm1(SB) MOVD $245, R12 B callbackasm1(SB) MOVD $246, R12 B callbackasm1(SB) MOVD $247, R12 B callbackasm1(SB) MOVD $248, R12 B callbackasm1(SB) MOVD $249, R12 B callbackasm1(SB) MOVD $250, R12 B callbackasm1(SB) MOVD $251, R12 B callbackasm1(SB) MOVD $252, R12 B callbackasm1(SB) MOVD $253, R12 B callbackasm1(SB) MOVD $254, R12 B callbackasm1(SB) MOVD $255, R12 B callbackasm1(SB) MOVD $256, R12 B callbackasm1(SB) MOVD $257, R12 B callbackasm1(SB) MOVD $258, R12 B callbackasm1(SB) MOVD $259, R12 B callbackasm1(SB) MOVD $260, R12 B callbackasm1(SB) MOVD $261, R12 B callbackasm1(SB) MOVD $262, R12 B callbackasm1(SB) MOVD $263, R12 B callbackasm1(SB) MOVD $264, R12 B callbackasm1(SB) MOVD $265, R12 B callbackasm1(SB) MOVD $266, R12 B callbackasm1(SB) MOVD $267, R12 B callbackasm1(SB) MOVD $268, R12 B callbackasm1(SB) MOVD $269, R12 B callbackasm1(SB) MOVD $270, R12 B callbackasm1(SB) MOVD $271, R12 B callbackasm1(SB) MOVD $272, R12 B callbackasm1(SB) MOVD $273, R12 B callbackasm1(SB) MOVD $274, R12 B callbackasm1(SB) MOVD $275, R12 B callbackasm1(SB) MOVD $276, R12 B callbackasm1(SB) MOVD $277, R12 B callbackasm1(SB) MOVD $278, R12 B callbackasm1(SB) MOVD $279, R12 B callbackasm1(SB) MOVD $280, R12 B callbackasm1(SB) MOVD $281, R12 B callbackasm1(SB) MOVD $282, R12 B callbackasm1(SB) MOVD $283, R12 B callbackasm1(SB) MOVD $284, R12 B callbackasm1(SB) MOVD $285, R12 B callbackasm1(SB) MOVD $286, R12 B callbackasm1(SB) MOVD $287, R12 B callbackasm1(SB) MOVD $288, R12 B callbackasm1(SB) MOVD $289, R12 B callbackasm1(SB) MOVD $290, R12 B callbackasm1(SB) MOVD $291, R12 B callbackasm1(SB) MOVD $292, R12 B callbackasm1(SB) MOVD $293, R12 B callbackasm1(SB) MOVD $294, R12 B callbackasm1(SB) MOVD $295, R12 B callbackasm1(SB) MOVD $296, R12 B callbackasm1(SB) MOVD $297, R12 B callbackasm1(SB) MOVD $298, R12 B callbackasm1(SB) MOVD $299, R12 B callbackasm1(SB) MOVD $300, R12 B callbackasm1(SB) MOVD $301, R12 B callbackasm1(SB) MOVD $302, R12 B callbackasm1(SB) MOVD $303, R12 B callbackasm1(SB) MOVD $304, R12 B callbackasm1(SB) MOVD $305, R12 B callbackasm1(SB) MOVD $306, R12 B callbackasm1(SB) MOVD $307, R12 B callbackasm1(SB) MOVD $308, R12 B callbackasm1(SB) MOVD $309, R12 B callbackasm1(SB) MOVD $310, R12 B callbackasm1(SB) MOVD $311, R12 B callbackasm1(SB) MOVD $312, R12 B callbackasm1(SB) MOVD $313, R12 B callbackasm1(SB) MOVD $314, R12 B callbackasm1(SB) MOVD $315, R12 B callbackasm1(SB) MOVD $316, R12 B callbackasm1(SB) MOVD $317, R12 B callbackasm1(SB) MOVD $318, R12 B callbackasm1(SB) MOVD $319, R12 B callbackasm1(SB) MOVD $320, R12 B callbackasm1(SB) MOVD $321, R12 B callbackasm1(SB) MOVD $322, R12 B callbackasm1(SB) MOVD $323, R12 B callbackasm1(SB) MOVD $324, R12 B callbackasm1(SB) MOVD $325, R12 B callbackasm1(SB) MOVD $326, R12 B callbackasm1(SB) MOVD $327, R12 B callbackasm1(SB) MOVD $328, R12 B callbackasm1(SB) MOVD $329, R12 B callbackasm1(SB) MOVD $330, R12 B callbackasm1(SB) MOVD $331, R12 B callbackasm1(SB) MOVD $332, R12 B callbackasm1(SB) MOVD $333, R12 B callbackasm1(SB) MOVD $334, R12 B callbackasm1(SB) MOVD $335, R12 B callbackasm1(SB) MOVD $336, R12 B callbackasm1(SB) MOVD $337, R12 B callbackasm1(SB) MOVD $338, R12 B callbackasm1(SB) MOVD $339, R12 B callbackasm1(SB) MOVD $340, R12 B callbackasm1(SB) MOVD $341, R12 B callbackasm1(SB) MOVD $342, R12 B callbackasm1(SB) MOVD $343, R12 B callbackasm1(SB) MOVD $344, R12 B callbackasm1(SB) MOVD $345, R12 B callbackasm1(SB) MOVD $346, R12 B callbackasm1(SB) MOVD $347, R12 B callbackasm1(SB) MOVD $348, R12 B callbackasm1(SB) MOVD $349, R12 B callbackasm1(SB) MOVD $350, R12 B callbackasm1(SB) MOVD $351, R12 B callbackasm1(SB) MOVD $352, R12 B callbackasm1(SB) MOVD $353, R12 B callbackasm1(SB) MOVD $354, R12 B callbackasm1(SB) MOVD $355, R12 B callbackasm1(SB) MOVD $356, R12 B callbackasm1(SB) MOVD $357, R12 B callbackasm1(SB) MOVD $358, R12 B callbackasm1(SB) MOVD $359, R12 B callbackasm1(SB) MOVD $360, R12 B callbackasm1(SB) MOVD $361, R12 B callbackasm1(SB) MOVD $362, R12 B callbackasm1(SB) MOVD $363, R12 B callbackasm1(SB) MOVD $364, R12 B callbackasm1(SB) MOVD $365, R12 B callbackasm1(SB) MOVD $366, R12 B callbackasm1(SB) MOVD $367, R12 B callbackasm1(SB) MOVD $368, R12 B callbackasm1(SB) MOVD $369, R12 B callbackasm1(SB) MOVD $370, R12 B callbackasm1(SB) MOVD $371, R12 B callbackasm1(SB) MOVD $372, R12 B callbackasm1(SB) MOVD $373, R12 B callbackasm1(SB) MOVD $374, R12 B callbackasm1(SB) MOVD $375, R12 B callbackasm1(SB) MOVD $376, R12 B callbackasm1(SB) MOVD $377, R12 B callbackasm1(SB) MOVD $378, R12 B callbackasm1(SB) MOVD $379, R12 B callbackasm1(SB) MOVD $380, R12 B callbackasm1(SB) MOVD $381, R12 B callbackasm1(SB) MOVD $382, R12 B callbackasm1(SB) MOVD $383, R12 B callbackasm1(SB) MOVD $384, R12 B callbackasm1(SB) MOVD $385, R12 B callbackasm1(SB) MOVD $386, R12 B callbackasm1(SB) MOVD $387, R12 B callbackasm1(SB) MOVD $388, R12 B callbackasm1(SB) MOVD $389, R12 B callbackasm1(SB) MOVD $390, R12 B callbackasm1(SB) MOVD $391, R12 B callbackasm1(SB) MOVD $392, R12 B callbackasm1(SB) MOVD $393, R12 B callbackasm1(SB) MOVD $394, R12 B callbackasm1(SB) MOVD $395, R12 B callbackasm1(SB) MOVD $396, R12 B callbackasm1(SB) MOVD $397, R12 B callbackasm1(SB) MOVD $398, R12 B callbackasm1(SB) MOVD $399, R12 B callbackasm1(SB) MOVD $400, R12 B callbackasm1(SB) MOVD $401, R12 B callbackasm1(SB) MOVD $402, R12 B callbackasm1(SB) MOVD $403, R12 B callbackasm1(SB) MOVD $404, R12 B callbackasm1(SB) MOVD $405, R12 B callbackasm1(SB) MOVD $406, R12 B callbackasm1(SB) MOVD $407, R12 B callbackasm1(SB) MOVD $408, R12 B callbackasm1(SB) MOVD $409, R12 B callbackasm1(SB) MOVD $410, R12 B callbackasm1(SB) MOVD $411, R12 B callbackasm1(SB) MOVD $412, R12 B callbackasm1(SB) MOVD $413, R12 B callbackasm1(SB) MOVD $414, R12 B callbackasm1(SB) MOVD $415, R12 B callbackasm1(SB) MOVD $416, R12 B callbackasm1(SB) MOVD $417, R12 B callbackasm1(SB) MOVD $418, R12 B callbackasm1(SB) MOVD $419, R12 B callbackasm1(SB) MOVD $420, R12 B callbackasm1(SB) MOVD $421, R12 B callbackasm1(SB) MOVD $422, R12 B callbackasm1(SB) MOVD $423, R12 B callbackasm1(SB) MOVD $424, R12 B callbackasm1(SB) MOVD $425, R12 B callbackasm1(SB) MOVD $426, R12 B callbackasm1(SB) MOVD $427, R12 B callbackasm1(SB) MOVD $428, R12 B callbackasm1(SB) MOVD $429, R12 B callbackasm1(SB) MOVD $430, R12 B callbackasm1(SB) MOVD $431, R12 B callbackasm1(SB) MOVD $432, R12 B callbackasm1(SB) MOVD $433, R12 B callbackasm1(SB) MOVD $434, R12 B callbackasm1(SB) MOVD $435, R12 B callbackasm1(SB) MOVD $436, R12 B callbackasm1(SB) MOVD $437, R12 B callbackasm1(SB) MOVD $438, R12 B callbackasm1(SB) MOVD $439, R12 B callbackasm1(SB) MOVD $440, R12 B callbackasm1(SB) MOVD $441, R12 B callbackasm1(SB) MOVD $442, R12 B callbackasm1(SB) MOVD $443, R12 B callbackasm1(SB) MOVD $444, R12 B callbackasm1(SB) MOVD $445, R12 B callbackasm1(SB) MOVD $446, R12 B callbackasm1(SB) MOVD $447, R12 B callbackasm1(SB) MOVD $448, R12 B callbackasm1(SB) MOVD $449, R12 B callbackasm1(SB) MOVD $450, R12 B callbackasm1(SB) MOVD $451, R12 B callbackasm1(SB) MOVD $452, R12 B callbackasm1(SB) MOVD $453, R12 B callbackasm1(SB) MOVD $454, R12 B callbackasm1(SB) MOVD $455, R12 B callbackasm1(SB) MOVD $456, R12 B callbackasm1(SB) MOVD $457, R12 B callbackasm1(SB) MOVD $458, R12 B callbackasm1(SB) MOVD $459, R12 B callbackasm1(SB) MOVD $460, R12 B callbackasm1(SB) MOVD $461, R12 B callbackasm1(SB) MOVD $462, R12 B callbackasm1(SB) MOVD $463, R12 B callbackasm1(SB) MOVD $464, R12 B callbackasm1(SB) MOVD $465, R12 B callbackasm1(SB) MOVD $466, R12 B callbackasm1(SB) MOVD $467, R12 B callbackasm1(SB) MOVD $468, R12 B callbackasm1(SB) MOVD $469, R12 B callbackasm1(SB) MOVD $470, R12 B callbackasm1(SB) MOVD $471, R12 B callbackasm1(SB) MOVD $472, R12 B callbackasm1(SB) MOVD $473, R12 B callbackasm1(SB) MOVD $474, R12 B callbackasm1(SB) MOVD $475, R12 B callbackasm1(SB) MOVD $476, R12 B callbackasm1(SB) MOVD $477, R12 B callbackasm1(SB) MOVD $478, R12 B callbackasm1(SB) MOVD $479, R12 B callbackasm1(SB) MOVD $480, R12 B callbackasm1(SB) MOVD $481, R12 B callbackasm1(SB) MOVD $482, R12 B callbackasm1(SB) MOVD $483, R12 B callbackasm1(SB) MOVD $484, R12 B callbackasm1(SB) MOVD $485, R12 B callbackasm1(SB) MOVD $486, R12 B callbackasm1(SB) MOVD $487, R12 B callbackasm1(SB) MOVD $488, R12 B callbackasm1(SB) MOVD $489, R12 B callbackasm1(SB) MOVD $490, R12 B callbackasm1(SB) MOVD $491, R12 B callbackasm1(SB) MOVD $492, R12 B callbackasm1(SB) MOVD $493, R12 B callbackasm1(SB) MOVD $494, R12 B callbackasm1(SB) MOVD $495, R12 B callbackasm1(SB) MOVD $496, R12 B callbackasm1(SB) MOVD $497, R12 B callbackasm1(SB) MOVD $498, R12 B callbackasm1(SB) MOVD $499, R12 B callbackasm1(SB) MOVD $500, R12 B callbackasm1(SB) MOVD $501, R12 B callbackasm1(SB) MOVD $502, R12 B callbackasm1(SB) MOVD $503, R12 B callbackasm1(SB) MOVD $504, R12 B callbackasm1(SB) MOVD $505, R12 B callbackasm1(SB) MOVD $506, R12 B callbackasm1(SB) MOVD $507, R12 B callbackasm1(SB) MOVD $508, R12 B callbackasm1(SB) MOVD $509, R12 B callbackasm1(SB) MOVD $510, R12 B callbackasm1(SB) MOVD $511, R12 B callbackasm1(SB) MOVD $512, R12 B callbackasm1(SB) MOVD $513, R12 B callbackasm1(SB) MOVD $514, R12 B callbackasm1(SB) MOVD $515, R12 B callbackasm1(SB) MOVD $516, R12 B callbackasm1(SB) MOVD $517, R12 B callbackasm1(SB) MOVD $518, R12 B callbackasm1(SB) MOVD $519, R12 B callbackasm1(SB) MOVD $520, R12 B callbackasm1(SB) MOVD $521, R12 B callbackasm1(SB) MOVD $522, R12 B callbackasm1(SB) MOVD $523, R12 B callbackasm1(SB) MOVD $524, R12 B callbackasm1(SB) MOVD $525, R12 B callbackasm1(SB) MOVD $526, R12 B callbackasm1(SB) MOVD $527, R12 B callbackasm1(SB) MOVD $528, R12 B callbackasm1(SB) MOVD $529, R12 B callbackasm1(SB) MOVD $530, R12 B callbackasm1(SB) MOVD $531, R12 B callbackasm1(SB) MOVD $532, R12 B callbackasm1(SB) MOVD $533, R12 B callbackasm1(SB) MOVD $534, R12 B callbackasm1(SB) MOVD $535, R12 B callbackasm1(SB) MOVD $536, R12 B callbackasm1(SB) MOVD $537, R12 B callbackasm1(SB) MOVD $538, R12 B callbackasm1(SB) MOVD $539, R12 B callbackasm1(SB) MOVD $540, R12 B callbackasm1(SB) MOVD $541, R12 B callbackasm1(SB) MOVD $542, R12 B callbackasm1(SB) MOVD $543, R12 B callbackasm1(SB) MOVD $544, R12 B callbackasm1(SB) MOVD $545, R12 B callbackasm1(SB) MOVD $546, R12 B callbackasm1(SB) MOVD $547, R12 B callbackasm1(SB) MOVD $548, R12 B callbackasm1(SB) MOVD $549, R12 B callbackasm1(SB) MOVD $550, R12 B callbackasm1(SB) MOVD $551, R12 B callbackasm1(SB) MOVD $552, R12 B callbackasm1(SB) MOVD $553, R12 B callbackasm1(SB) MOVD $554, R12 B callbackasm1(SB) MOVD $555, R12 B callbackasm1(SB) MOVD $556, R12 B callbackasm1(SB) MOVD $557, R12 B callbackasm1(SB) MOVD $558, R12 B callbackasm1(SB) MOVD $559, R12 B callbackasm1(SB) MOVD $560, R12 B callbackasm1(SB) MOVD $561, R12 B callbackasm1(SB) MOVD $562, R12 B callbackasm1(SB) MOVD $563, R12 B callbackasm1(SB) MOVD $564, R12 B callbackasm1(SB) MOVD $565, R12 B callbackasm1(SB) MOVD $566, R12 B callbackasm1(SB) MOVD $567, R12 B callbackasm1(SB) MOVD $568, R12 B callbackasm1(SB) MOVD $569, R12 B callbackasm1(SB) MOVD $570, R12 B callbackasm1(SB) MOVD $571, R12 B callbackasm1(SB) MOVD $572, R12 B callbackasm1(SB) MOVD $573, R12 B callbackasm1(SB) MOVD $574, R12 B callbackasm1(SB) MOVD $575, R12 B callbackasm1(SB) MOVD $576, R12 B callbackasm1(SB) MOVD $577, R12 B callbackasm1(SB) MOVD $578, R12 B callbackasm1(SB) MOVD $579, R12 B callbackasm1(SB) MOVD $580, R12 B callbackasm1(SB) MOVD $581, R12 B callbackasm1(SB) MOVD $582, R12 B callbackasm1(SB) MOVD $583, R12 B callbackasm1(SB) MOVD $584, R12 B callbackasm1(SB) MOVD $585, R12 B callbackasm1(SB) MOVD $586, R12 B callbackasm1(SB) MOVD $587, R12 B callbackasm1(SB) MOVD $588, R12 B callbackasm1(SB) MOVD $589, R12 B callbackasm1(SB) MOVD $590, R12 B callbackasm1(SB) MOVD $591, R12 B callbackasm1(SB) MOVD $592, R12 B callbackasm1(SB) MOVD $593, R12 B callbackasm1(SB) MOVD $594, R12 B callbackasm1(SB) MOVD $595, R12 B callbackasm1(SB) MOVD $596, R12 B callbackasm1(SB) MOVD $597, R12 B callbackasm1(SB) MOVD $598, R12 B callbackasm1(SB) MOVD $599, R12 B callbackasm1(SB) MOVD $600, R12 B callbackasm1(SB) MOVD $601, R12 B callbackasm1(SB) MOVD $602, R12 B callbackasm1(SB) MOVD $603, R12 B callbackasm1(SB) MOVD $604, R12 B callbackasm1(SB) MOVD $605, R12 B callbackasm1(SB) MOVD $606, R12 B callbackasm1(SB) MOVD $607, R12 B callbackasm1(SB) MOVD $608, R12 B callbackasm1(SB) MOVD $609, R12 B callbackasm1(SB) MOVD $610, R12 B callbackasm1(SB) MOVD $611, R12 B callbackasm1(SB) MOVD $612, R12 B callbackasm1(SB) MOVD $613, R12 B callbackasm1(SB) MOVD $614, R12 B callbackasm1(SB) MOVD $615, R12 B callbackasm1(SB) MOVD $616, R12 B callbackasm1(SB) MOVD $617, R12 B callbackasm1(SB) MOVD $618, R12 B callbackasm1(SB) MOVD $619, R12 B callbackasm1(SB) MOVD $620, R12 B callbackasm1(SB) MOVD $621, R12 B callbackasm1(SB) MOVD $622, R12 B callbackasm1(SB) MOVD $623, R12 B callbackasm1(SB) MOVD $624, R12 B callbackasm1(SB) MOVD $625, R12 B callbackasm1(SB) MOVD $626, R12 B callbackasm1(SB) MOVD $627, R12 B callbackasm1(SB) MOVD $628, R12 B callbackasm1(SB) MOVD $629, R12 B callbackasm1(SB) MOVD $630, R12 B callbackasm1(SB) MOVD $631, R12 B callbackasm1(SB) MOVD $632, R12 B callbackasm1(SB) MOVD $633, R12 B callbackasm1(SB) MOVD $634, R12 B callbackasm1(SB) MOVD $635, R12 B callbackasm1(SB) MOVD $636, R12 B callbackasm1(SB) MOVD $637, R12 B callbackasm1(SB) MOVD $638, R12 B callbackasm1(SB) MOVD $639, R12 B callbackasm1(SB) MOVD $640, R12 B callbackasm1(SB) MOVD $641, R12 B callbackasm1(SB) MOVD $642, R12 B callbackasm1(SB) MOVD $643, R12 B callbackasm1(SB) MOVD $644, R12 B callbackasm1(SB) MOVD $645, R12 B callbackasm1(SB) MOVD $646, R12 B callbackasm1(SB) MOVD $647, R12 B callbackasm1(SB) MOVD $648, R12 B callbackasm1(SB) MOVD $649, R12 B callbackasm1(SB) MOVD $650, R12 B callbackasm1(SB) MOVD $651, R12 B callbackasm1(SB) MOVD $652, R12 B callbackasm1(SB) MOVD $653, R12 B callbackasm1(SB) MOVD $654, R12 B callbackasm1(SB) MOVD $655, R12 B callbackasm1(SB) MOVD $656, R12 B callbackasm1(SB) MOVD $657, R12 B callbackasm1(SB) MOVD $658, R12 B callbackasm1(SB) MOVD $659, R12 B callbackasm1(SB) MOVD $660, R12 B callbackasm1(SB) MOVD $661, R12 B callbackasm1(SB) MOVD $662, R12 B callbackasm1(SB) MOVD $663, R12 B callbackasm1(SB) MOVD $664, R12 B callbackasm1(SB) MOVD $665, R12 B callbackasm1(SB) MOVD $666, R12 B callbackasm1(SB) MOVD $667, R12 B callbackasm1(SB) MOVD $668, R12 B callbackasm1(SB) MOVD $669, R12 B callbackasm1(SB) MOVD $670, R12 B callbackasm1(SB) MOVD $671, R12 B callbackasm1(SB) MOVD $672, R12 B callbackasm1(SB) MOVD $673, R12 B callbackasm1(SB) MOVD $674, R12 B callbackasm1(SB) MOVD $675, R12 B callbackasm1(SB) MOVD $676, R12 B callbackasm1(SB) MOVD $677, R12 B callbackasm1(SB) MOVD $678, R12 B callbackasm1(SB) MOVD $679, R12 B callbackasm1(SB) MOVD $680, R12 B callbackasm1(SB) MOVD $681, R12 B callbackasm1(SB) MOVD $682, R12 B callbackasm1(SB) MOVD $683, R12 B callbackasm1(SB) MOVD $684, R12 B callbackasm1(SB) MOVD $685, R12 B callbackasm1(SB) MOVD $686, R12 B callbackasm1(SB) MOVD $687, R12 B callbackasm1(SB) MOVD $688, R12 B callbackasm1(SB) MOVD $689, R12 B callbackasm1(SB) MOVD $690, R12 B callbackasm1(SB) MOVD $691, R12 B callbackasm1(SB) MOVD $692, R12 B callbackasm1(SB) MOVD $693, R12 B callbackasm1(SB) MOVD $694, R12 B callbackasm1(SB) MOVD $695, R12 B callbackasm1(SB) MOVD $696, R12 B callbackasm1(SB) MOVD $697, R12 B callbackasm1(SB) MOVD $698, R12 B callbackasm1(SB) MOVD $699, R12 B callbackasm1(SB) MOVD $700, R12 B callbackasm1(SB) MOVD $701, R12 B callbackasm1(SB) MOVD $702, R12 B callbackasm1(SB) MOVD $703, R12 B callbackasm1(SB) MOVD $704, R12 B callbackasm1(SB) MOVD $705, R12 B callbackasm1(SB) MOVD $706, R12 B callbackasm1(SB) MOVD $707, R12 B callbackasm1(SB) MOVD $708, R12 B callbackasm1(SB) MOVD $709, R12 B callbackasm1(SB) MOVD $710, R12 B callbackasm1(SB) MOVD $711, R12 B callbackasm1(SB) MOVD $712, R12 B callbackasm1(SB) MOVD $713, R12 B callbackasm1(SB) MOVD $714, R12 B callbackasm1(SB) MOVD $715, R12 B callbackasm1(SB) MOVD $716, R12 B callbackasm1(SB) MOVD $717, R12 B callbackasm1(SB) MOVD $718, R12 B callbackasm1(SB) MOVD $719, R12 B callbackasm1(SB) MOVD $720, R12 B callbackasm1(SB) MOVD $721, R12 B callbackasm1(SB) MOVD $722, R12 B callbackasm1(SB) MOVD $723, R12 B callbackasm1(SB) MOVD $724, R12 B callbackasm1(SB) MOVD $725, R12 B callbackasm1(SB) MOVD $726, R12 B callbackasm1(SB) MOVD $727, R12 B callbackasm1(SB) MOVD $728, R12 B callbackasm1(SB) MOVD $729, R12 B callbackasm1(SB) MOVD $730, R12 B callbackasm1(SB) MOVD $731, R12 B callbackasm1(SB) MOVD $732, R12 B callbackasm1(SB) MOVD $733, R12 B callbackasm1(SB) MOVD $734, R12 B callbackasm1(SB) MOVD $735, R12 B callbackasm1(SB) MOVD $736, R12 B callbackasm1(SB) MOVD $737, R12 B callbackasm1(SB) MOVD $738, R12 B callbackasm1(SB) MOVD $739, R12 B callbackasm1(SB) MOVD $740, R12 B callbackasm1(SB) MOVD $741, R12 B callbackasm1(SB) MOVD $742, R12 B callbackasm1(SB) MOVD $743, R12 B callbackasm1(SB) MOVD $744, R12 B callbackasm1(SB) MOVD $745, R12 B callbackasm1(SB) MOVD $746, R12 B callbackasm1(SB) MOVD $747, R12 B callbackasm1(SB) MOVD $748, R12 B callbackasm1(SB) MOVD $749, R12 B callbackasm1(SB) MOVD $750, R12 B callbackasm1(SB) MOVD $751, R12 B callbackasm1(SB) MOVD $752, R12 B callbackasm1(SB) MOVD $753, R12 B callbackasm1(SB) MOVD $754, R12 B callbackasm1(SB) MOVD $755, R12 B callbackasm1(SB) MOVD $756, R12 B callbackasm1(SB) MOVD $757, R12 B callbackasm1(SB) MOVD $758, R12 B callbackasm1(SB) MOVD $759, R12 B callbackasm1(SB) MOVD $760, R12 B callbackasm1(SB) MOVD $761, R12 B callbackasm1(SB) MOVD $762, R12 B callbackasm1(SB) MOVD $763, R12 B callbackasm1(SB) MOVD $764, R12 B callbackasm1(SB) MOVD $765, R12 B callbackasm1(SB) MOVD $766, R12 B callbackasm1(SB) MOVD $767, R12 B callbackasm1(SB) MOVD $768, R12 B callbackasm1(SB) MOVD $769, R12 B callbackasm1(SB) MOVD $770, R12 B callbackasm1(SB) MOVD $771, R12 B callbackasm1(SB) MOVD $772, R12 B callbackasm1(SB) MOVD $773, R12 B callbackasm1(SB) MOVD $774, R12 B callbackasm1(SB) MOVD $775, R12 B callbackasm1(SB) MOVD $776, R12 B callbackasm1(SB) MOVD $777, R12 B callbackasm1(SB) MOVD $778, R12 B callbackasm1(SB) MOVD $779, R12 B callbackasm1(SB) MOVD $780, R12 B callbackasm1(SB) MOVD $781, R12 B callbackasm1(SB) MOVD $782, R12 B callbackasm1(SB) MOVD $783, R12 B callbackasm1(SB) MOVD $784, R12 B callbackasm1(SB) MOVD $785, R12 B callbackasm1(SB) MOVD $786, R12 B callbackasm1(SB) MOVD $787, R12 B callbackasm1(SB) MOVD $788, R12 B callbackasm1(SB) MOVD $789, R12 B callbackasm1(SB) MOVD $790, R12 B callbackasm1(SB) MOVD $791, R12 B callbackasm1(SB) MOVD $792, R12 B callbackasm1(SB) MOVD $793, R12 B callbackasm1(SB) MOVD $794, R12 B callbackasm1(SB) MOVD $795, R12 B callbackasm1(SB) MOVD $796, R12 B callbackasm1(SB) MOVD $797, R12 B callbackasm1(SB) MOVD $798, R12 B callbackasm1(SB) MOVD $799, R12 B callbackasm1(SB) MOVD $800, R12 B callbackasm1(SB) MOVD $801, R12 B callbackasm1(SB) MOVD $802, R12 B callbackasm1(SB) MOVD $803, R12 B callbackasm1(SB) MOVD $804, R12 B callbackasm1(SB) MOVD $805, R12 B callbackasm1(SB) MOVD $806, R12 B callbackasm1(SB) MOVD $807, R12 B callbackasm1(SB) MOVD $808, R12 B callbackasm1(SB) MOVD $809, R12 B callbackasm1(SB) MOVD $810, R12 B callbackasm1(SB) MOVD $811, R12 B callbackasm1(SB) MOVD $812, R12 B callbackasm1(SB) MOVD $813, R12 B callbackasm1(SB) MOVD $814, R12 B callbackasm1(SB) MOVD $815, R12 B callbackasm1(SB) MOVD $816, R12 B callbackasm1(SB) MOVD $817, R12 B callbackasm1(SB) MOVD $818, R12 B callbackasm1(SB) MOVD $819, R12 B callbackasm1(SB) MOVD $820, R12 B callbackasm1(SB) MOVD $821, R12 B callbackasm1(SB) MOVD $822, R12 B callbackasm1(SB) MOVD $823, R12 B callbackasm1(SB) MOVD $824, R12 B callbackasm1(SB) MOVD $825, R12 B callbackasm1(SB) MOVD $826, R12 B callbackasm1(SB) MOVD $827, R12 B callbackasm1(SB) MOVD $828, R12 B callbackasm1(SB) MOVD $829, R12 B callbackasm1(SB) MOVD $830, R12 B callbackasm1(SB) MOVD $831, R12 B callbackasm1(SB) MOVD $832, R12 B callbackasm1(SB) MOVD $833, R12 B callbackasm1(SB) MOVD $834, R12 B callbackasm1(SB) MOVD $835, R12 B callbackasm1(SB) MOVD $836, R12 B callbackasm1(SB) MOVD $837, R12 B callbackasm1(SB) MOVD $838, R12 B callbackasm1(SB) MOVD $839, R12 B callbackasm1(SB) MOVD $840, R12 B callbackasm1(SB) MOVD $841, R12 B callbackasm1(SB) MOVD $842, R12 B callbackasm1(SB) MOVD $843, R12 B callbackasm1(SB) MOVD $844, R12 B callbackasm1(SB) MOVD $845, R12 B callbackasm1(SB) MOVD $846, R12 B callbackasm1(SB) MOVD $847, R12 B callbackasm1(SB) MOVD $848, R12 B callbackasm1(SB) MOVD $849, R12 B callbackasm1(SB) MOVD $850, R12 B callbackasm1(SB) MOVD $851, R12 B callbackasm1(SB) MOVD $852, R12 B callbackasm1(SB) MOVD $853, R12 B callbackasm1(SB) MOVD $854, R12 B callbackasm1(SB) MOVD $855, R12 B callbackasm1(SB) MOVD $856, R12 B callbackasm1(SB) MOVD $857, R12 B callbackasm1(SB) MOVD $858, R12 B callbackasm1(SB) MOVD $859, R12 B callbackasm1(SB) MOVD $860, R12 B callbackasm1(SB) MOVD $861, R12 B callbackasm1(SB) MOVD $862, R12 B callbackasm1(SB) MOVD $863, R12 B callbackasm1(SB) MOVD $864, R12 B callbackasm1(SB) MOVD $865, R12 B callbackasm1(SB) MOVD $866, R12 B callbackasm1(SB) MOVD $867, R12 B callbackasm1(SB) MOVD $868, R12 B callbackasm1(SB) MOVD $869, R12 B callbackasm1(SB) MOVD $870, R12 B callbackasm1(SB) MOVD $871, R12 B callbackasm1(SB) MOVD $872, R12 B callbackasm1(SB) MOVD $873, R12 B callbackasm1(SB) MOVD $874, R12 B callbackasm1(SB) MOVD $875, R12 B callbackasm1(SB) MOVD $876, R12 B callbackasm1(SB) MOVD $877, R12 B callbackasm1(SB) MOVD $878, R12 B callbackasm1(SB) MOVD $879, R12 B callbackasm1(SB) MOVD $880, R12 B callbackasm1(SB) MOVD $881, R12 B callbackasm1(SB) MOVD $882, R12 B callbackasm1(SB) MOVD $883, R12 B callbackasm1(SB) MOVD $884, R12 B callbackasm1(SB) MOVD $885, R12 B callbackasm1(SB) MOVD $886, R12 B callbackasm1(SB) MOVD $887, R12 B callbackasm1(SB) MOVD $888, R12 B callbackasm1(SB) MOVD $889, R12 B callbackasm1(SB) MOVD $890, R12 B callbackasm1(SB) MOVD $891, R12 B callbackasm1(SB) MOVD $892, R12 B callbackasm1(SB) MOVD $893, R12 B callbackasm1(SB) MOVD $894, R12 B callbackasm1(SB) MOVD $895, R12 B callbackasm1(SB) MOVD $896, R12 B callbackasm1(SB) MOVD $897, R12 B callbackasm1(SB) MOVD $898, R12 B callbackasm1(SB) MOVD $899, R12 B callbackasm1(SB) MOVD $900, R12 B callbackasm1(SB) MOVD $901, R12 B callbackasm1(SB) MOVD $902, R12 B callbackasm1(SB) MOVD $903, R12 B callbackasm1(SB) MOVD $904, R12 B callbackasm1(SB) MOVD $905, R12 B callbackasm1(SB) MOVD $906, R12 B callbackasm1(SB) MOVD $907, R12 B callbackasm1(SB) MOVD $908, R12 B callbackasm1(SB) MOVD $909, R12 B callbackasm1(SB) MOVD $910, R12 B callbackasm1(SB) MOVD $911, R12 B callbackasm1(SB) MOVD $912, R12 B callbackasm1(SB) MOVD $913, R12 B callbackasm1(SB) MOVD $914, R12 B callbackasm1(SB) MOVD $915, R12 B callbackasm1(SB) MOVD $916, R12 B callbackasm1(SB) MOVD $917, R12 B callbackasm1(SB) MOVD $918, R12 B callbackasm1(SB) MOVD $919, R12 B callbackasm1(SB) MOVD $920, R12 B callbackasm1(SB) MOVD $921, R12 B callbackasm1(SB) MOVD $922, R12 B callbackasm1(SB) MOVD $923, R12 B callbackasm1(SB) MOVD $924, R12 B callbackasm1(SB) MOVD $925, R12 B callbackasm1(SB) MOVD $926, R12 B callbackasm1(SB) MOVD $927, R12 B callbackasm1(SB) MOVD $928, R12 B callbackasm1(SB) MOVD $929, R12 B callbackasm1(SB) MOVD $930, R12 B callbackasm1(SB) MOVD $931, R12 B callbackasm1(SB) MOVD $932, R12 B callbackasm1(SB) MOVD $933, R12 B callbackasm1(SB) MOVD $934, R12 B callbackasm1(SB) MOVD $935, R12 B callbackasm1(SB) MOVD $936, R12 B callbackasm1(SB) MOVD $937, R12 B callbackasm1(SB) MOVD $938, R12 B callbackasm1(SB) MOVD $939, R12 B callbackasm1(SB) MOVD $940, R12 B callbackasm1(SB) MOVD $941, R12 B callbackasm1(SB) MOVD $942, R12 B callbackasm1(SB) MOVD $943, R12 B callbackasm1(SB) MOVD $944, R12 B callbackasm1(SB) MOVD $945, R12 B callbackasm1(SB) MOVD $946, R12 B callbackasm1(SB) MOVD $947, R12 B callbackasm1(SB) MOVD $948, R12 B callbackasm1(SB) MOVD $949, R12 B callbackasm1(SB) MOVD $950, R12 B callbackasm1(SB) MOVD $951, R12 B callbackasm1(SB) MOVD $952, R12 B callbackasm1(SB) MOVD $953, R12 B callbackasm1(SB) MOVD $954, R12 B callbackasm1(SB) MOVD $955, R12 B callbackasm1(SB) MOVD $956, R12 B callbackasm1(SB) MOVD $957, R12 B callbackasm1(SB) MOVD $958, R12 B callbackasm1(SB) MOVD $959, R12 B callbackasm1(SB) MOVD $960, R12 B callbackasm1(SB) MOVD $961, R12 B callbackasm1(SB) MOVD $962, R12 B callbackasm1(SB) MOVD $963, R12 B callbackasm1(SB) MOVD $964, R12 B callbackasm1(SB) MOVD $965, R12 B callbackasm1(SB) MOVD $966, R12 B callbackasm1(SB) MOVD $967, R12 B callbackasm1(SB) MOVD $968, R12 B callbackasm1(SB) MOVD $969, R12 B callbackasm1(SB) MOVD $970, R12 B callbackasm1(SB) MOVD $971, R12 B callbackasm1(SB) MOVD $972, R12 B callbackasm1(SB) MOVD $973, R12 B callbackasm1(SB) MOVD $974, R12 B callbackasm1(SB) MOVD $975, R12 B callbackasm1(SB) MOVD $976, R12 B callbackasm1(SB) MOVD $977, R12 B callbackasm1(SB) MOVD $978, R12 B callbackasm1(SB) MOVD $979, R12 B callbackasm1(SB) MOVD $980, R12 B callbackasm1(SB) MOVD $981, R12 B callbackasm1(SB) MOVD $982, R12 B callbackasm1(SB) MOVD $983, R12 B callbackasm1(SB) MOVD $984, R12 B callbackasm1(SB) MOVD $985, R12 B callbackasm1(SB) MOVD $986, R12 B callbackasm1(SB) MOVD $987, R12 B callbackasm1(SB) MOVD $988, R12 B callbackasm1(SB) MOVD $989, R12 B callbackasm1(SB) MOVD $990, R12 B callbackasm1(SB) MOVD $991, R12 B callbackasm1(SB) MOVD $992, R12 B callbackasm1(SB) MOVD $993, R12 B callbackasm1(SB) MOVD $994, R12 B callbackasm1(SB) MOVD $995, R12 B callbackasm1(SB) MOVD $996, R12 B callbackasm1(SB) MOVD $997, R12 B callbackasm1(SB) MOVD $998, R12 B callbackasm1(SB) MOVD $999, R12 B callbackasm1(SB) MOVD $1000, R12 B callbackasm1(SB) MOVD $1001, R12 B callbackasm1(SB) MOVD $1002, R12 B callbackasm1(SB) MOVD $1003, R12 B callbackasm1(SB) MOVD $1004, R12 B callbackasm1(SB) MOVD $1005, R12 B callbackasm1(SB) MOVD $1006, R12 B callbackasm1(SB) MOVD $1007, R12 B callbackasm1(SB) MOVD $1008, R12 B callbackasm1(SB) MOVD $1009, R12 B callbackasm1(SB) MOVD $1010, R12 B callbackasm1(SB) MOVD $1011, R12 B callbackasm1(SB) MOVD $1012, R12 B callbackasm1(SB) MOVD $1013, R12 B callbackasm1(SB) MOVD $1014, R12 B callbackasm1(SB) MOVD $1015, R12 B callbackasm1(SB) MOVD $1016, R12 B callbackasm1(SB) MOVD $1017, R12 B callbackasm1(SB) MOVD $1018, R12 B callbackasm1(SB) MOVD $1019, R12 B callbackasm1(SB) MOVD $1020, R12 B callbackasm1(SB) MOVD $1021, R12 B callbackasm1(SB) MOVD $1022, R12 B callbackasm1(SB) MOVD $1023, R12 B callbackasm1(SB) MOVD $1024, R12 B callbackasm1(SB) MOVD $1025, R12 B callbackasm1(SB) MOVD $1026, R12 B callbackasm1(SB) MOVD $1027, R12 B callbackasm1(SB) MOVD $1028, R12 B callbackasm1(SB) MOVD $1029, R12 B callbackasm1(SB) MOVD $1030, R12 B callbackasm1(SB) MOVD $1031, R12 B callbackasm1(SB) MOVD $1032, R12 B callbackasm1(SB) MOVD $1033, R12 B callbackasm1(SB) MOVD $1034, R12 B callbackasm1(SB) MOVD $1035, R12 B callbackasm1(SB) MOVD $1036, R12 B callbackasm1(SB) MOVD $1037, R12 B callbackasm1(SB) MOVD $1038, R12 B callbackasm1(SB) MOVD $1039, R12 B callbackasm1(SB) MOVD $1040, R12 B callbackasm1(SB) MOVD $1041, R12 B callbackasm1(SB) MOVD $1042, R12 B callbackasm1(SB) MOVD $1043, R12 B callbackasm1(SB) MOVD $1044, R12 B callbackasm1(SB) MOVD $1045, R12 B callbackasm1(SB) MOVD $1046, R12 B callbackasm1(SB) MOVD $1047, R12 B callbackasm1(SB) MOVD $1048, R12 B callbackasm1(SB) MOVD $1049, R12 B callbackasm1(SB) MOVD $1050, R12 B callbackasm1(SB) MOVD $1051, R12 B callbackasm1(SB) MOVD $1052, R12 B callbackasm1(SB) MOVD $1053, R12 B callbackasm1(SB) MOVD $1054, R12 B callbackasm1(SB) MOVD $1055, R12 B callbackasm1(SB) MOVD $1056, R12 B callbackasm1(SB) MOVD $1057, R12 B callbackasm1(SB) MOVD $1058, R12 B callbackasm1(SB) MOVD $1059, R12 B callbackasm1(SB) MOVD $1060, R12 B callbackasm1(SB) MOVD $1061, R12 B callbackasm1(SB) MOVD $1062, R12 B callbackasm1(SB) MOVD $1063, R12 B callbackasm1(SB) MOVD $1064, R12 B callbackasm1(SB) MOVD $1065, R12 B callbackasm1(SB) MOVD $1066, R12 B callbackasm1(SB) MOVD $1067, R12 B callbackasm1(SB) MOVD $1068, R12 B callbackasm1(SB) MOVD $1069, R12 B callbackasm1(SB) MOVD $1070, R12 B callbackasm1(SB) MOVD $1071, R12 B callbackasm1(SB) MOVD $1072, R12 B callbackasm1(SB) MOVD $1073, R12 B callbackasm1(SB) MOVD $1074, R12 B callbackasm1(SB) MOVD $1075, R12 B callbackasm1(SB) MOVD $1076, R12 B callbackasm1(SB) MOVD $1077, R12 B callbackasm1(SB) MOVD $1078, R12 B callbackasm1(SB) MOVD $1079, R12 B callbackasm1(SB) MOVD $1080, R12 B callbackasm1(SB) MOVD $1081, R12 B callbackasm1(SB) MOVD $1082, R12 B callbackasm1(SB) MOVD $1083, R12 B callbackasm1(SB) MOVD $1084, R12 B callbackasm1(SB) MOVD $1085, R12 B callbackasm1(SB) MOVD $1086, R12 B callbackasm1(SB) MOVD $1087, R12 B callbackasm1(SB) MOVD $1088, R12 B callbackasm1(SB) MOVD $1089, R12 B callbackasm1(SB) MOVD $1090, R12 B callbackasm1(SB) MOVD $1091, R12 B callbackasm1(SB) MOVD $1092, R12 B callbackasm1(SB) MOVD $1093, R12 B callbackasm1(SB) MOVD $1094, R12 B callbackasm1(SB) MOVD $1095, R12 B callbackasm1(SB) MOVD $1096, R12 B callbackasm1(SB) MOVD $1097, R12 B callbackasm1(SB) MOVD $1098, R12 B callbackasm1(SB) MOVD $1099, R12 B callbackasm1(SB) MOVD $1100, R12 B callbackasm1(SB) MOVD $1101, R12 B callbackasm1(SB) MOVD $1102, R12 B callbackasm1(SB) MOVD $1103, R12 B callbackasm1(SB) MOVD $1104, R12 B callbackasm1(SB) MOVD $1105, R12 B callbackasm1(SB) MOVD $1106, R12 B callbackasm1(SB) MOVD $1107, R12 B callbackasm1(SB) MOVD $1108, R12 B callbackasm1(SB) MOVD $1109, R12 B callbackasm1(SB) MOVD $1110, R12 B callbackasm1(SB) MOVD $1111, R12 B callbackasm1(SB) MOVD $1112, R12 B callbackasm1(SB) MOVD $1113, R12 B callbackasm1(SB) MOVD $1114, R12 B callbackasm1(SB) MOVD $1115, R12 B callbackasm1(SB) MOVD $1116, R12 B callbackasm1(SB) MOVD $1117, R12 B callbackasm1(SB) MOVD $1118, R12 B callbackasm1(SB) MOVD $1119, R12 B callbackasm1(SB) MOVD $1120, R12 B callbackasm1(SB) MOVD $1121, R12 B callbackasm1(SB) MOVD $1122, R12 B callbackasm1(SB) MOVD $1123, R12 B callbackasm1(SB) MOVD $1124, R12 B callbackasm1(SB) MOVD $1125, R12 B callbackasm1(SB) MOVD $1126, R12 B callbackasm1(SB) MOVD $1127, R12 B callbackasm1(SB) MOVD $1128, R12 B callbackasm1(SB) MOVD $1129, R12 B callbackasm1(SB) MOVD $1130, R12 B callbackasm1(SB) MOVD $1131, R12 B callbackasm1(SB) MOVD $1132, R12 B callbackasm1(SB) MOVD $1133, R12 B callbackasm1(SB) MOVD $1134, R12 B callbackasm1(SB) MOVD $1135, R12 B callbackasm1(SB) MOVD $1136, R12 B callbackasm1(SB) MOVD $1137, R12 B callbackasm1(SB) MOVD $1138, R12 B callbackasm1(SB) MOVD $1139, R12 B callbackasm1(SB) MOVD $1140, R12 B callbackasm1(SB) MOVD $1141, R12 B callbackasm1(SB) MOVD $1142, R12 B callbackasm1(SB) MOVD $1143, R12 B callbackasm1(SB) MOVD $1144, R12 B callbackasm1(SB) MOVD $1145, R12 B callbackasm1(SB) MOVD $1146, R12 B callbackasm1(SB) MOVD $1147, R12 B callbackasm1(SB) MOVD $1148, R12 B callbackasm1(SB) MOVD $1149, R12 B callbackasm1(SB) MOVD $1150, R12 B callbackasm1(SB) MOVD $1151, R12 B callbackasm1(SB) MOVD $1152, R12 B callbackasm1(SB) MOVD $1153, R12 B callbackasm1(SB) MOVD $1154, R12 B callbackasm1(SB) MOVD $1155, R12 B callbackasm1(SB) MOVD $1156, R12 B callbackasm1(SB) MOVD $1157, R12 B callbackasm1(SB) MOVD $1158, R12 B callbackasm1(SB) MOVD $1159, R12 B callbackasm1(SB) MOVD $1160, R12 B callbackasm1(SB) MOVD $1161, R12 B callbackasm1(SB) MOVD $1162, R12 B callbackasm1(SB) MOVD $1163, R12 B callbackasm1(SB) MOVD $1164, R12 B callbackasm1(SB) MOVD $1165, R12 B callbackasm1(SB) MOVD $1166, R12 B callbackasm1(SB) MOVD $1167, R12 B callbackasm1(SB) MOVD $1168, R12 B callbackasm1(SB) MOVD $1169, R12 B callbackasm1(SB) MOVD $1170, R12 B callbackasm1(SB) MOVD $1171, R12 B callbackasm1(SB) MOVD $1172, R12 B callbackasm1(SB) MOVD $1173, R12 B callbackasm1(SB) MOVD $1174, R12 B callbackasm1(SB) MOVD $1175, R12 B callbackasm1(SB) MOVD $1176, R12 B callbackasm1(SB) MOVD $1177, R12 B callbackasm1(SB) MOVD $1178, R12 B callbackasm1(SB) MOVD $1179, R12 B callbackasm1(SB) MOVD $1180, R12 B callbackasm1(SB) MOVD $1181, R12 B callbackasm1(SB) MOVD $1182, R12 B callbackasm1(SB) MOVD $1183, R12 B callbackasm1(SB) MOVD $1184, R12 B callbackasm1(SB) MOVD $1185, R12 B callbackasm1(SB) MOVD $1186, R12 B callbackasm1(SB) MOVD $1187, R12 B callbackasm1(SB) MOVD $1188, R12 B callbackasm1(SB) MOVD $1189, R12 B callbackasm1(SB) MOVD $1190, R12 B callbackasm1(SB) MOVD $1191, R12 B callbackasm1(SB) MOVD $1192, R12 B callbackasm1(SB) MOVD $1193, R12 B callbackasm1(SB) MOVD $1194, R12 B callbackasm1(SB) MOVD $1195, R12 B callbackasm1(SB) MOVD $1196, R12 B callbackasm1(SB) MOVD $1197, R12 B callbackasm1(SB) MOVD $1198, R12 B callbackasm1(SB) MOVD $1199, R12 B callbackasm1(SB) MOVD $1200, R12 B callbackasm1(SB) MOVD $1201, R12 B callbackasm1(SB) MOVD $1202, R12 B callbackasm1(SB) MOVD $1203, R12 B callbackasm1(SB) MOVD $1204, R12 B callbackasm1(SB) MOVD $1205, R12 B callbackasm1(SB) MOVD $1206, R12 B callbackasm1(SB) MOVD $1207, R12 B callbackasm1(SB) MOVD $1208, R12 B callbackasm1(SB) MOVD $1209, R12 B callbackasm1(SB) MOVD $1210, R12 B callbackasm1(SB) MOVD $1211, R12 B callbackasm1(SB) MOVD $1212, R12 B callbackasm1(SB) MOVD $1213, R12 B callbackasm1(SB) MOVD $1214, R12 B callbackasm1(SB) MOVD $1215, R12 B callbackasm1(SB) MOVD $1216, R12 B callbackasm1(SB) MOVD $1217, R12 B callbackasm1(SB) MOVD $1218, R12 B callbackasm1(SB) MOVD $1219, R12 B callbackasm1(SB) MOVD $1220, R12 B callbackasm1(SB) MOVD $1221, R12 B callbackasm1(SB) MOVD $1222, R12 B callbackasm1(SB) MOVD $1223, R12 B callbackasm1(SB) MOVD $1224, R12 B callbackasm1(SB) MOVD $1225, R12 B callbackasm1(SB) MOVD $1226, R12 B callbackasm1(SB) MOVD $1227, R12 B callbackasm1(SB) MOVD $1228, R12 B callbackasm1(SB) MOVD $1229, R12 B callbackasm1(SB) MOVD $1230, R12 B callbackasm1(SB) MOVD $1231, R12 B callbackasm1(SB) MOVD $1232, R12 B callbackasm1(SB) MOVD $1233, R12 B callbackasm1(SB) MOVD $1234, R12 B callbackasm1(SB) MOVD $1235, R12 B callbackasm1(SB) MOVD $1236, R12 B callbackasm1(SB) MOVD $1237, R12 B callbackasm1(SB) MOVD $1238, R12 B callbackasm1(SB) MOVD $1239, R12 B callbackasm1(SB) MOVD $1240, R12 B callbackasm1(SB) MOVD $1241, R12 B callbackasm1(SB) MOVD $1242, R12 B callbackasm1(SB) MOVD $1243, R12 B callbackasm1(SB) MOVD $1244, R12 B callbackasm1(SB) MOVD $1245, R12 B callbackasm1(SB) MOVD $1246, R12 B callbackasm1(SB) MOVD $1247, R12 B callbackasm1(SB) MOVD $1248, R12 B callbackasm1(SB) MOVD $1249, R12 B callbackasm1(SB) MOVD $1250, R12 B callbackasm1(SB) MOVD $1251, R12 B callbackasm1(SB) MOVD $1252, R12 B callbackasm1(SB) MOVD $1253, R12 B callbackasm1(SB) MOVD $1254, R12 B callbackasm1(SB) MOVD $1255, R12 B callbackasm1(SB) MOVD $1256, R12 B callbackasm1(SB) MOVD $1257, R12 B callbackasm1(SB) MOVD $1258, R12 B callbackasm1(SB) MOVD $1259, R12 B callbackasm1(SB) MOVD $1260, R12 B callbackasm1(SB) MOVD $1261, R12 B callbackasm1(SB) MOVD $1262, R12 B callbackasm1(SB) MOVD $1263, R12 B callbackasm1(SB) MOVD $1264, R12 B callbackasm1(SB) MOVD $1265, R12 B callbackasm1(SB) MOVD $1266, R12 B callbackasm1(SB) MOVD $1267, R12 B callbackasm1(SB) MOVD $1268, R12 B callbackasm1(SB) MOVD $1269, R12 B callbackasm1(SB) MOVD $1270, R12 B callbackasm1(SB) MOVD $1271, R12 B callbackasm1(SB) MOVD $1272, R12 B callbackasm1(SB) MOVD $1273, R12 B callbackasm1(SB) MOVD $1274, R12 B callbackasm1(SB) MOVD $1275, R12 B callbackasm1(SB) MOVD $1276, R12 B callbackasm1(SB) MOVD $1277, R12 B callbackasm1(SB) MOVD $1278, R12 B callbackasm1(SB) MOVD $1279, R12 B callbackasm1(SB) MOVD $1280, R12 B callbackasm1(SB) MOVD $1281, R12 B callbackasm1(SB) MOVD $1282, R12 B callbackasm1(SB) MOVD $1283, R12 B callbackasm1(SB) MOVD $1284, R12 B callbackasm1(SB) MOVD $1285, R12 B callbackasm1(SB) MOVD $1286, R12 B callbackasm1(SB) MOVD $1287, R12 B callbackasm1(SB) MOVD $1288, R12 B callbackasm1(SB) MOVD $1289, R12 B callbackasm1(SB) MOVD $1290, R12 B callbackasm1(SB) MOVD $1291, R12 B callbackasm1(SB) MOVD $1292, R12 B callbackasm1(SB) MOVD $1293, R12 B callbackasm1(SB) MOVD $1294, R12 B callbackasm1(SB) MOVD $1295, R12 B callbackasm1(SB) MOVD $1296, R12 B callbackasm1(SB) MOVD $1297, R12 B callbackasm1(SB) MOVD $1298, R12 B callbackasm1(SB) MOVD $1299, R12 B callbackasm1(SB) MOVD $1300, R12 B callbackasm1(SB) MOVD $1301, R12 B callbackasm1(SB) MOVD $1302, R12 B callbackasm1(SB) MOVD $1303, R12 B callbackasm1(SB) MOVD $1304, R12 B callbackasm1(SB) MOVD $1305, R12 B callbackasm1(SB) MOVD $1306, R12 B callbackasm1(SB) MOVD $1307, R12 B callbackasm1(SB) MOVD $1308, R12 B callbackasm1(SB) MOVD $1309, R12 B callbackasm1(SB) MOVD $1310, R12 B callbackasm1(SB) MOVD $1311, R12 B callbackasm1(SB) MOVD $1312, R12 B callbackasm1(SB) MOVD $1313, R12 B callbackasm1(SB) MOVD $1314, R12 B callbackasm1(SB) MOVD $1315, R12 B callbackasm1(SB) MOVD $1316, R12 B callbackasm1(SB) MOVD $1317, R12 B callbackasm1(SB) MOVD $1318, R12 B callbackasm1(SB) MOVD $1319, R12 B callbackasm1(SB) MOVD $1320, R12 B callbackasm1(SB) MOVD $1321, R12 B callbackasm1(SB) MOVD $1322, R12 B callbackasm1(SB) MOVD $1323, R12 B callbackasm1(SB) MOVD $1324, R12 B callbackasm1(SB) MOVD $1325, R12 B callbackasm1(SB) MOVD $1326, R12 B callbackasm1(SB) MOVD $1327, R12 B callbackasm1(SB) MOVD $1328, R12 B callbackasm1(SB) MOVD $1329, R12 B callbackasm1(SB) MOVD $1330, R12 B callbackasm1(SB) MOVD $1331, R12 B callbackasm1(SB) MOVD $1332, R12 B callbackasm1(SB) MOVD $1333, R12 B callbackasm1(SB) MOVD $1334, R12 B callbackasm1(SB) MOVD $1335, R12 B callbackasm1(SB) MOVD $1336, R12 B callbackasm1(SB) MOVD $1337, R12 B callbackasm1(SB) MOVD $1338, R12 B callbackasm1(SB) MOVD $1339, R12 B callbackasm1(SB) MOVD $1340, R12 B callbackasm1(SB) MOVD $1341, R12 B callbackasm1(SB) MOVD $1342, R12 B callbackasm1(SB) MOVD $1343, R12 B callbackasm1(SB) MOVD $1344, R12 B callbackasm1(SB) MOVD $1345, R12 B callbackasm1(SB) MOVD $1346, R12 B callbackasm1(SB) MOVD $1347, R12 B callbackasm1(SB) MOVD $1348, R12 B callbackasm1(SB) MOVD $1349, R12 B callbackasm1(SB) MOVD $1350, R12 B callbackasm1(SB) MOVD $1351, R12 B callbackasm1(SB) MOVD $1352, R12 B callbackasm1(SB) MOVD $1353, R12 B callbackasm1(SB) MOVD $1354, R12 B callbackasm1(SB) MOVD $1355, R12 B callbackasm1(SB) MOVD $1356, R12 B callbackasm1(SB) MOVD $1357, R12 B callbackasm1(SB) MOVD $1358, R12 B callbackasm1(SB) MOVD $1359, R12 B callbackasm1(SB) MOVD $1360, R12 B callbackasm1(SB) MOVD $1361, R12 B callbackasm1(SB) MOVD $1362, R12 B callbackasm1(SB) MOVD $1363, R12 B callbackasm1(SB) MOVD $1364, R12 B callbackasm1(SB) MOVD $1365, R12 B callbackasm1(SB) MOVD $1366, R12 B callbackasm1(SB) MOVD $1367, R12 B callbackasm1(SB) MOVD $1368, R12 B callbackasm1(SB) MOVD $1369, R12 B callbackasm1(SB) MOVD $1370, R12 B callbackasm1(SB) MOVD $1371, R12 B callbackasm1(SB) MOVD $1372, R12 B callbackasm1(SB) MOVD $1373, R12 B callbackasm1(SB) MOVD $1374, R12 B callbackasm1(SB) MOVD $1375, R12 B callbackasm1(SB) MOVD $1376, R12 B callbackasm1(SB) MOVD $1377, R12 B callbackasm1(SB) MOVD $1378, R12 B callbackasm1(SB) MOVD $1379, R12 B callbackasm1(SB) MOVD $1380, R12 B callbackasm1(SB) MOVD $1381, R12 B callbackasm1(SB) MOVD $1382, R12 B callbackasm1(SB) MOVD $1383, R12 B callbackasm1(SB) MOVD $1384, R12 B callbackasm1(SB) MOVD $1385, R12 B callbackasm1(SB) MOVD $1386, R12 B callbackasm1(SB) MOVD $1387, R12 B callbackasm1(SB) MOVD $1388, R12 B callbackasm1(SB) MOVD $1389, R12 B callbackasm1(SB) MOVD $1390, R12 B callbackasm1(SB) MOVD $1391, R12 B callbackasm1(SB) MOVD $1392, R12 B callbackasm1(SB) MOVD $1393, R12 B callbackasm1(SB) MOVD $1394, R12 B callbackasm1(SB) MOVD $1395, R12 B callbackasm1(SB) MOVD $1396, R12 B callbackasm1(SB) MOVD $1397, R12 B callbackasm1(SB) MOVD $1398, R12 B callbackasm1(SB) MOVD $1399, R12 B callbackasm1(SB) MOVD $1400, R12 B callbackasm1(SB) MOVD $1401, R12 B callbackasm1(SB) MOVD $1402, R12 B callbackasm1(SB) MOVD $1403, R12 B callbackasm1(SB) MOVD $1404, R12 B callbackasm1(SB) MOVD $1405, R12 B callbackasm1(SB) MOVD $1406, R12 B callbackasm1(SB) MOVD $1407, R12 B callbackasm1(SB) MOVD $1408, R12 B callbackasm1(SB) MOVD $1409, R12 B callbackasm1(SB) MOVD $1410, R12 B callbackasm1(SB) MOVD $1411, R12 B callbackasm1(SB) MOVD $1412, R12 B callbackasm1(SB) MOVD $1413, R12 B callbackasm1(SB) MOVD $1414, R12 B callbackasm1(SB) MOVD $1415, R12 B callbackasm1(SB) MOVD $1416, R12 B callbackasm1(SB) MOVD $1417, R12 B callbackasm1(SB) MOVD $1418, R12 B callbackasm1(SB) MOVD $1419, R12 B callbackasm1(SB) MOVD $1420, R12 B callbackasm1(SB) MOVD $1421, R12 B callbackasm1(SB) MOVD $1422, R12 B callbackasm1(SB) MOVD $1423, R12 B callbackasm1(SB) MOVD $1424, R12 B callbackasm1(SB) MOVD $1425, R12 B callbackasm1(SB) MOVD $1426, R12 B callbackasm1(SB) MOVD $1427, R12 B callbackasm1(SB) MOVD $1428, R12 B callbackasm1(SB) MOVD $1429, R12 B callbackasm1(SB) MOVD $1430, R12 B callbackasm1(SB) MOVD $1431, R12 B callbackasm1(SB) MOVD $1432, R12 B callbackasm1(SB) MOVD $1433, R12 B callbackasm1(SB) MOVD $1434, R12 B callbackasm1(SB) MOVD $1435, R12 B callbackasm1(SB) MOVD $1436, R12 B callbackasm1(SB) MOVD $1437, R12 B callbackasm1(SB) MOVD $1438, R12 B callbackasm1(SB) MOVD $1439, R12 B callbackasm1(SB) MOVD $1440, R12 B callbackasm1(SB) MOVD $1441, R12 B callbackasm1(SB) MOVD $1442, R12 B callbackasm1(SB) MOVD $1443, R12 B callbackasm1(SB) MOVD $1444, R12 B callbackasm1(SB) MOVD $1445, R12 B callbackasm1(SB) MOVD $1446, R12 B callbackasm1(SB) MOVD $1447, R12 B callbackasm1(SB) MOVD $1448, R12 B callbackasm1(SB) MOVD $1449, R12 B callbackasm1(SB) MOVD $1450, R12 B callbackasm1(SB) MOVD $1451, R12 B callbackasm1(SB) MOVD $1452, R12 B callbackasm1(SB) MOVD $1453, R12 B callbackasm1(SB) MOVD $1454, R12 B callbackasm1(SB) MOVD $1455, R12 B callbackasm1(SB) MOVD $1456, R12 B callbackasm1(SB) MOVD $1457, R12 B callbackasm1(SB) MOVD $1458, R12 B callbackasm1(SB) MOVD $1459, R12 B callbackasm1(SB) MOVD $1460, R12 B callbackasm1(SB) MOVD $1461, R12 B callbackasm1(SB) MOVD $1462, R12 B callbackasm1(SB) MOVD $1463, R12 B callbackasm1(SB) MOVD $1464, R12 B callbackasm1(SB) MOVD $1465, R12 B callbackasm1(SB) MOVD $1466, R12 B callbackasm1(SB) MOVD $1467, R12 B callbackasm1(SB) MOVD $1468, R12 B callbackasm1(SB) MOVD $1469, R12 B callbackasm1(SB) MOVD $1470, R12 B callbackasm1(SB) MOVD $1471, R12 B callbackasm1(SB) MOVD $1472, R12 B callbackasm1(SB) MOVD $1473, R12 B callbackasm1(SB) MOVD $1474, R12 B callbackasm1(SB) MOVD $1475, R12 B callbackasm1(SB) MOVD $1476, R12 B callbackasm1(SB) MOVD $1477, R12 B callbackasm1(SB) MOVD $1478, R12 B callbackasm1(SB) MOVD $1479, R12 B callbackasm1(SB) MOVD $1480, R12 B callbackasm1(SB) MOVD $1481, R12 B callbackasm1(SB) MOVD $1482, R12 B callbackasm1(SB) MOVD $1483, R12 B callbackasm1(SB) MOVD $1484, R12 B callbackasm1(SB) MOVD $1485, R12 B callbackasm1(SB) MOVD $1486, R12 B callbackasm1(SB) MOVD $1487, R12 B callbackasm1(SB) MOVD $1488, R12 B callbackasm1(SB) MOVD $1489, R12 B callbackasm1(SB) MOVD $1490, R12 B callbackasm1(SB) MOVD $1491, R12 B callbackasm1(SB) MOVD $1492, R12 B callbackasm1(SB) MOVD $1493, R12 B callbackasm1(SB) MOVD $1494, R12 B callbackasm1(SB) MOVD $1495, R12 B callbackasm1(SB) MOVD $1496, R12 B callbackasm1(SB) MOVD $1497, R12 B callbackasm1(SB) MOVD $1498, R12 B callbackasm1(SB) MOVD $1499, R12 B callbackasm1(SB) MOVD $1500, R12 B callbackasm1(SB) MOVD $1501, R12 B callbackasm1(SB) MOVD $1502, R12 B callbackasm1(SB) MOVD $1503, R12 B callbackasm1(SB) MOVD $1504, R12 B callbackasm1(SB) MOVD $1505, R12 B callbackasm1(SB) MOVD $1506, R12 B callbackasm1(SB) MOVD $1507, R12 B callbackasm1(SB) MOVD $1508, R12 B callbackasm1(SB) MOVD $1509, R12 B callbackasm1(SB) MOVD $1510, R12 B callbackasm1(SB) MOVD $1511, R12 B callbackasm1(SB) MOVD $1512, R12 B callbackasm1(SB) MOVD $1513, R12 B callbackasm1(SB) MOVD $1514, R12 B callbackasm1(SB) MOVD $1515, R12 B callbackasm1(SB) MOVD $1516, R12 B callbackasm1(SB) MOVD $1517, R12 B callbackasm1(SB) MOVD $1518, R12 B callbackasm1(SB) MOVD $1519, R12 B callbackasm1(SB) MOVD $1520, R12 B callbackasm1(SB) MOVD $1521, R12 B callbackasm1(SB) MOVD $1522, R12 B callbackasm1(SB) MOVD $1523, R12 B callbackasm1(SB) MOVD $1524, R12 B callbackasm1(SB) MOVD $1525, R12 B callbackasm1(SB) MOVD $1526, R12 B callbackasm1(SB) MOVD $1527, R12 B callbackasm1(SB) MOVD $1528, R12 B callbackasm1(SB) MOVD $1529, R12 B callbackasm1(SB) MOVD $1530, R12 B callbackasm1(SB) MOVD $1531, R12 B callbackasm1(SB) MOVD $1532, R12 B callbackasm1(SB) MOVD $1533, R12 B callbackasm1(SB) MOVD $1534, R12 B callbackasm1(SB) MOVD $1535, R12 B callbackasm1(SB) MOVD $1536, R12 B callbackasm1(SB) MOVD $1537, R12 B callbackasm1(SB) MOVD $1538, R12 B callbackasm1(SB) MOVD $1539, R12 B callbackasm1(SB) MOVD $1540, R12 B callbackasm1(SB) MOVD $1541, R12 B callbackasm1(SB) MOVD $1542, R12 B callbackasm1(SB) MOVD $1543, R12 B callbackasm1(SB) MOVD $1544, R12 B callbackasm1(SB) MOVD $1545, R12 B callbackasm1(SB) MOVD $1546, R12 B callbackasm1(SB) MOVD $1547, R12 B callbackasm1(SB) MOVD $1548, R12 B callbackasm1(SB) MOVD $1549, R12 B callbackasm1(SB) MOVD $1550, R12 B callbackasm1(SB) MOVD $1551, R12 B callbackasm1(SB) MOVD $1552, R12 B callbackasm1(SB) MOVD $1553, R12 B callbackasm1(SB) MOVD $1554, R12 B callbackasm1(SB) MOVD $1555, R12 B callbackasm1(SB) MOVD $1556, R12 B callbackasm1(SB) MOVD $1557, R12 B callbackasm1(SB) MOVD $1558, R12 B callbackasm1(SB) MOVD $1559, R12 B callbackasm1(SB) MOVD $1560, R12 B callbackasm1(SB) MOVD $1561, R12 B callbackasm1(SB) MOVD $1562, R12 B callbackasm1(SB) MOVD $1563, R12 B callbackasm1(SB) MOVD $1564, R12 B callbackasm1(SB) MOVD $1565, R12 B callbackasm1(SB) MOVD $1566, R12 B callbackasm1(SB) MOVD $1567, R12 B callbackasm1(SB) MOVD $1568, R12 B callbackasm1(SB) MOVD $1569, R12 B callbackasm1(SB) MOVD $1570, R12 B callbackasm1(SB) MOVD $1571, R12 B callbackasm1(SB) MOVD $1572, R12 B callbackasm1(SB) MOVD $1573, R12 B callbackasm1(SB) MOVD $1574, R12 B callbackasm1(SB) MOVD $1575, R12 B callbackasm1(SB) MOVD $1576, R12 B callbackasm1(SB) MOVD $1577, R12 B callbackasm1(SB) MOVD $1578, R12 B callbackasm1(SB) MOVD $1579, R12 B callbackasm1(SB) MOVD $1580, R12 B callbackasm1(SB) MOVD $1581, R12 B callbackasm1(SB) MOVD $1582, R12 B callbackasm1(SB) MOVD $1583, R12 B callbackasm1(SB) MOVD $1584, R12 B callbackasm1(SB) MOVD $1585, R12 B callbackasm1(SB) MOVD $1586, R12 B callbackasm1(SB) MOVD $1587, R12 B callbackasm1(SB) MOVD $1588, R12 B callbackasm1(SB) MOVD $1589, R12 B callbackasm1(SB) MOVD $1590, R12 B callbackasm1(SB) MOVD $1591, R12 B callbackasm1(SB) MOVD $1592, R12 B callbackasm1(SB) MOVD $1593, R12 B callbackasm1(SB) MOVD $1594, R12 B callbackasm1(SB) MOVD $1595, R12 B callbackasm1(SB) MOVD $1596, R12 B callbackasm1(SB) MOVD $1597, R12 B callbackasm1(SB) MOVD $1598, R12 B callbackasm1(SB) MOVD $1599, R12 B callbackasm1(SB) MOVD $1600, R12 B callbackasm1(SB) MOVD $1601, R12 B callbackasm1(SB) MOVD $1602, R12 B callbackasm1(SB) MOVD $1603, R12 B callbackasm1(SB) MOVD $1604, R12 B callbackasm1(SB) MOVD $1605, R12 B callbackasm1(SB) MOVD $1606, R12 B callbackasm1(SB) MOVD $1607, R12 B callbackasm1(SB) MOVD $1608, R12 B callbackasm1(SB) MOVD $1609, R12 B callbackasm1(SB) MOVD $1610, R12 B callbackasm1(SB) MOVD $1611, R12 B callbackasm1(SB) MOVD $1612, R12 B callbackasm1(SB) MOVD $1613, R12 B callbackasm1(SB) MOVD $1614, R12 B callbackasm1(SB) MOVD $1615, R12 B callbackasm1(SB) MOVD $1616, R12 B callbackasm1(SB) MOVD $1617, R12 B callbackasm1(SB) MOVD $1618, R12 B callbackasm1(SB) MOVD $1619, R12 B callbackasm1(SB) MOVD $1620, R12 B callbackasm1(SB) MOVD $1621, R12 B callbackasm1(SB) MOVD $1622, R12 B callbackasm1(SB) MOVD $1623, R12 B callbackasm1(SB) MOVD $1624, R12 B callbackasm1(SB) MOVD $1625, R12 B callbackasm1(SB) MOVD $1626, R12 B callbackasm1(SB) MOVD $1627, R12 B callbackasm1(SB) MOVD $1628, R12 B callbackasm1(SB) MOVD $1629, R12 B callbackasm1(SB) MOVD $1630, R12 B callbackasm1(SB) MOVD $1631, R12 B callbackasm1(SB) MOVD $1632, R12 B callbackasm1(SB) MOVD $1633, R12 B callbackasm1(SB) MOVD $1634, R12 B callbackasm1(SB) MOVD $1635, R12 B callbackasm1(SB) MOVD $1636, R12 B callbackasm1(SB) MOVD $1637, R12 B callbackasm1(SB) MOVD $1638, R12 B callbackasm1(SB) MOVD $1639, R12 B callbackasm1(SB) MOVD $1640, R12 B callbackasm1(SB) MOVD $1641, R12 B callbackasm1(SB) MOVD $1642, R12 B callbackasm1(SB) MOVD $1643, R12 B callbackasm1(SB) MOVD $1644, R12 B callbackasm1(SB) MOVD $1645, R12 B callbackasm1(SB) MOVD $1646, R12 B callbackasm1(SB) MOVD $1647, R12 B callbackasm1(SB) MOVD $1648, R12 B callbackasm1(SB) MOVD $1649, R12 B callbackasm1(SB) MOVD $1650, R12 B callbackasm1(SB) MOVD $1651, R12 B callbackasm1(SB) MOVD $1652, R12 B callbackasm1(SB) MOVD $1653, R12 B callbackasm1(SB) MOVD $1654, R12 B callbackasm1(SB) MOVD $1655, R12 B callbackasm1(SB) MOVD $1656, R12 B callbackasm1(SB) MOVD $1657, R12 B callbackasm1(SB) MOVD $1658, R12 B callbackasm1(SB) MOVD $1659, R12 B callbackasm1(SB) MOVD $1660, R12 B callbackasm1(SB) MOVD $1661, R12 B callbackasm1(SB) MOVD $1662, R12 B callbackasm1(SB) MOVD $1663, R12 B callbackasm1(SB) MOVD $1664, R12 B callbackasm1(SB) MOVD $1665, R12 B callbackasm1(SB) MOVD $1666, R12 B callbackasm1(SB) MOVD $1667, R12 B callbackasm1(SB) MOVD $1668, R12 B callbackasm1(SB) MOVD $1669, R12 B callbackasm1(SB) MOVD $1670, R12 B callbackasm1(SB) MOVD $1671, R12 B callbackasm1(SB) MOVD $1672, R12 B callbackasm1(SB) MOVD $1673, R12 B callbackasm1(SB) MOVD $1674, R12 B callbackasm1(SB) MOVD $1675, R12 B callbackasm1(SB) MOVD $1676, R12 B callbackasm1(SB) MOVD $1677, R12 B callbackasm1(SB) MOVD $1678, R12 B callbackasm1(SB) MOVD $1679, R12 B callbackasm1(SB) MOVD $1680, R12 B callbackasm1(SB) MOVD $1681, R12 B callbackasm1(SB) MOVD $1682, R12 B callbackasm1(SB) MOVD $1683, R12 B callbackasm1(SB) MOVD $1684, R12 B callbackasm1(SB) MOVD $1685, R12 B callbackasm1(SB) MOVD $1686, R12 B callbackasm1(SB) MOVD $1687, R12 B callbackasm1(SB) MOVD $1688, R12 B callbackasm1(SB) MOVD $1689, R12 B callbackasm1(SB) MOVD $1690, R12 B callbackasm1(SB) MOVD $1691, R12 B callbackasm1(SB) MOVD $1692, R12 B callbackasm1(SB) MOVD $1693, R12 B callbackasm1(SB) MOVD $1694, R12 B callbackasm1(SB) MOVD $1695, R12 B callbackasm1(SB) MOVD $1696, R12 B callbackasm1(SB) MOVD $1697, R12 B callbackasm1(SB) MOVD $1698, R12 B callbackasm1(SB) MOVD $1699, R12 B callbackasm1(SB) MOVD $1700, R12 B callbackasm1(SB) MOVD $1701, R12 B callbackasm1(SB) MOVD $1702, R12 B callbackasm1(SB) MOVD $1703, R12 B callbackasm1(SB) MOVD $1704, R12 B callbackasm1(SB) MOVD $1705, R12 B callbackasm1(SB) MOVD $1706, R12 B callbackasm1(SB) MOVD $1707, R12 B callbackasm1(SB) MOVD $1708, R12 B callbackasm1(SB) MOVD $1709, R12 B callbackasm1(SB) MOVD $1710, R12 B callbackasm1(SB) MOVD $1711, R12 B callbackasm1(SB) MOVD $1712, R12 B callbackasm1(SB) MOVD $1713, R12 B callbackasm1(SB) MOVD $1714, R12 B callbackasm1(SB) MOVD $1715, R12 B callbackasm1(SB) MOVD $1716, R12 B callbackasm1(SB) MOVD $1717, R12 B callbackasm1(SB) MOVD $1718, R12 B callbackasm1(SB) MOVD $1719, R12 B callbackasm1(SB) MOVD $1720, R12 B callbackasm1(SB) MOVD $1721, R12 B callbackasm1(SB) MOVD $1722, R12 B callbackasm1(SB) MOVD $1723, R12 B callbackasm1(SB) MOVD $1724, R12 B callbackasm1(SB) MOVD $1725, R12 B callbackasm1(SB) MOVD $1726, R12 B callbackasm1(SB) MOVD $1727, R12 B callbackasm1(SB) MOVD $1728, R12 B callbackasm1(SB) MOVD $1729, R12 B callbackasm1(SB) MOVD $1730, R12 B callbackasm1(SB) MOVD $1731, R12 B callbackasm1(SB) MOVD $1732, R12 B callbackasm1(SB) MOVD $1733, R12 B callbackasm1(SB) MOVD $1734, R12 B callbackasm1(SB) MOVD $1735, R12 B callbackasm1(SB) MOVD $1736, R12 B callbackasm1(SB) MOVD $1737, R12 B callbackasm1(SB) MOVD $1738, R12 B callbackasm1(SB) MOVD $1739, R12 B callbackasm1(SB) MOVD $1740, R12 B callbackasm1(SB) MOVD $1741, R12 B callbackasm1(SB) MOVD $1742, R12 B callbackasm1(SB) MOVD $1743, R12 B callbackasm1(SB) MOVD $1744, R12 B callbackasm1(SB) MOVD $1745, R12 B callbackasm1(SB) MOVD $1746, R12 B callbackasm1(SB) MOVD $1747, R12 B callbackasm1(SB) MOVD $1748, R12 B callbackasm1(SB) MOVD $1749, R12 B callbackasm1(SB) MOVD $1750, R12 B callbackasm1(SB) MOVD $1751, R12 B callbackasm1(SB) MOVD $1752, R12 B callbackasm1(SB) MOVD $1753, R12 B callbackasm1(SB) MOVD $1754, R12 B callbackasm1(SB) MOVD $1755, R12 B callbackasm1(SB) MOVD $1756, R12 B callbackasm1(SB) MOVD $1757, R12 B callbackasm1(SB) MOVD $1758, R12 B callbackasm1(SB) MOVD $1759, R12 B callbackasm1(SB) MOVD $1760, R12 B callbackasm1(SB) MOVD $1761, R12 B callbackasm1(SB) MOVD $1762, R12 B callbackasm1(SB) MOVD $1763, R12 B callbackasm1(SB) MOVD $1764, R12 B callbackasm1(SB) MOVD $1765, R12 B callbackasm1(SB) MOVD $1766, R12 B callbackasm1(SB) MOVD $1767, R12 B callbackasm1(SB) MOVD $1768, R12 B callbackasm1(SB) MOVD $1769, R12 B callbackasm1(SB) MOVD $1770, R12 B callbackasm1(SB) MOVD $1771, R12 B callbackasm1(SB) MOVD $1772, R12 B callbackasm1(SB) MOVD $1773, R12 B callbackasm1(SB) MOVD $1774, R12 B callbackasm1(SB) MOVD $1775, R12 B callbackasm1(SB) MOVD $1776, R12 B callbackasm1(SB) MOVD $1777, R12 B callbackasm1(SB) MOVD $1778, R12 B callbackasm1(SB) MOVD $1779, R12 B callbackasm1(SB) MOVD $1780, R12 B callbackasm1(SB) MOVD $1781, R12 B callbackasm1(SB) MOVD $1782, R12 B callbackasm1(SB) MOVD $1783, R12 B callbackasm1(SB) MOVD $1784, R12 B callbackasm1(SB) MOVD $1785, R12 B callbackasm1(SB) MOVD $1786, R12 B callbackasm1(SB) MOVD $1787, R12 B callbackasm1(SB) MOVD $1788, R12 B callbackasm1(SB) MOVD $1789, R12 B callbackasm1(SB) MOVD $1790, R12 B callbackasm1(SB) MOVD $1791, R12 B callbackasm1(SB) MOVD $1792, R12 B callbackasm1(SB) MOVD $1793, R12 B callbackasm1(SB) MOVD $1794, R12 B callbackasm1(SB) MOVD $1795, R12 B callbackasm1(SB) MOVD $1796, R12 B callbackasm1(SB) MOVD $1797, R12 B callbackasm1(SB) MOVD $1798, R12 B callbackasm1(SB) MOVD $1799, R12 B callbackasm1(SB) MOVD $1800, R12 B callbackasm1(SB) MOVD $1801, R12 B callbackasm1(SB) MOVD $1802, R12 B callbackasm1(SB) MOVD $1803, R12 B callbackasm1(SB) MOVD $1804, R12 B callbackasm1(SB) MOVD $1805, R12 B callbackasm1(SB) MOVD $1806, R12 B callbackasm1(SB) MOVD $1807, R12 B callbackasm1(SB) MOVD $1808, R12 B callbackasm1(SB) MOVD $1809, R12 B callbackasm1(SB) MOVD $1810, R12 B callbackasm1(SB) MOVD $1811, R12 B callbackasm1(SB) MOVD $1812, R12 B callbackasm1(SB) MOVD $1813, R12 B callbackasm1(SB) MOVD $1814, R12 B callbackasm1(SB) MOVD $1815, R12 B callbackasm1(SB) MOVD $1816, R12 B callbackasm1(SB) MOVD $1817, R12 B callbackasm1(SB) MOVD $1818, R12 B callbackasm1(SB) MOVD $1819, R12 B callbackasm1(SB) MOVD $1820, R12 B callbackasm1(SB) MOVD $1821, R12 B callbackasm1(SB) MOVD $1822, R12 B callbackasm1(SB) MOVD $1823, R12 B callbackasm1(SB) MOVD $1824, R12 B callbackasm1(SB) MOVD $1825, R12 B callbackasm1(SB) MOVD $1826, R12 B callbackasm1(SB) MOVD $1827, R12 B callbackasm1(SB) MOVD $1828, R12 B callbackasm1(SB) MOVD $1829, R12 B callbackasm1(SB) MOVD $1830, R12 B callbackasm1(SB) MOVD $1831, R12 B callbackasm1(SB) MOVD $1832, R12 B callbackasm1(SB) MOVD $1833, R12 B callbackasm1(SB) MOVD $1834, R12 B callbackasm1(SB) MOVD $1835, R12 B callbackasm1(SB) MOVD $1836, R12 B callbackasm1(SB) MOVD $1837, R12 B callbackasm1(SB) MOVD $1838, R12 B callbackasm1(SB) MOVD $1839, R12 B callbackasm1(SB) MOVD $1840, R12 B callbackasm1(SB) MOVD $1841, R12 B callbackasm1(SB) MOVD $1842, R12 B callbackasm1(SB) MOVD $1843, R12 B callbackasm1(SB) MOVD $1844, R12 B callbackasm1(SB) MOVD $1845, R12 B callbackasm1(SB) MOVD $1846, R12 B callbackasm1(SB) MOVD $1847, R12 B callbackasm1(SB) MOVD $1848, R12 B callbackasm1(SB) MOVD $1849, R12 B callbackasm1(SB) MOVD $1850, R12 B callbackasm1(SB) MOVD $1851, R12 B callbackasm1(SB) MOVD $1852, R12 B callbackasm1(SB) MOVD $1853, R12 B callbackasm1(SB) MOVD $1854, R12 B callbackasm1(SB) MOVD $1855, R12 B callbackasm1(SB) MOVD $1856, R12 B callbackasm1(SB) MOVD $1857, R12 B callbackasm1(SB) MOVD $1858, R12 B callbackasm1(SB) MOVD $1859, R12 B callbackasm1(SB) MOVD $1860, R12 B callbackasm1(SB) MOVD $1861, R12 B callbackasm1(SB) MOVD $1862, R12 B callbackasm1(SB) MOVD $1863, R12 B callbackasm1(SB) MOVD $1864, R12 B callbackasm1(SB) MOVD $1865, R12 B callbackasm1(SB) MOVD $1866, R12 B callbackasm1(SB) MOVD $1867, R12 B callbackasm1(SB) MOVD $1868, R12 B callbackasm1(SB) MOVD $1869, R12 B callbackasm1(SB) MOVD $1870, R12 B callbackasm1(SB) MOVD $1871, R12 B callbackasm1(SB) MOVD $1872, R12 B callbackasm1(SB) MOVD $1873, R12 B callbackasm1(SB) MOVD $1874, R12 B callbackasm1(SB) MOVD $1875, R12 B callbackasm1(SB) MOVD $1876, R12 B callbackasm1(SB) MOVD $1877, R12 B callbackasm1(SB) MOVD $1878, R12 B callbackasm1(SB) MOVD $1879, R12 B callbackasm1(SB) MOVD $1880, R12 B callbackasm1(SB) MOVD $1881, R12 B callbackasm1(SB) MOVD $1882, R12 B callbackasm1(SB) MOVD $1883, R12 B callbackasm1(SB) MOVD $1884, R12 B callbackasm1(SB) MOVD $1885, R12 B callbackasm1(SB) MOVD $1886, R12 B callbackasm1(SB) MOVD $1887, R12 B callbackasm1(SB) MOVD $1888, R12 B callbackasm1(SB) MOVD $1889, R12 B callbackasm1(SB) MOVD $1890, R12 B callbackasm1(SB) MOVD $1891, R12 B callbackasm1(SB) MOVD $1892, R12 B callbackasm1(SB) MOVD $1893, R12 B callbackasm1(SB) MOVD $1894, R12 B callbackasm1(SB) MOVD $1895, R12 B callbackasm1(SB) MOVD $1896, R12 B callbackasm1(SB) MOVD $1897, R12 B callbackasm1(SB) MOVD $1898, R12 B callbackasm1(SB) MOVD $1899, R12 B callbackasm1(SB) MOVD $1900, R12 B callbackasm1(SB) MOVD $1901, R12 B callbackasm1(SB) MOVD $1902, R12 B callbackasm1(SB) MOVD $1903, R12 B callbackasm1(SB) MOVD $1904, R12 B callbackasm1(SB) MOVD $1905, R12 B callbackasm1(SB) MOVD $1906, R12 B callbackasm1(SB) MOVD $1907, R12 B callbackasm1(SB) MOVD $1908, R12 B callbackasm1(SB) MOVD $1909, R12 B callbackasm1(SB) MOVD $1910, R12 B callbackasm1(SB) MOVD $1911, R12 B callbackasm1(SB) MOVD $1912, R12 B callbackasm1(SB) MOVD $1913, R12 B callbackasm1(SB) MOVD $1914, R12 B callbackasm1(SB) MOVD $1915, R12 B callbackasm1(SB) MOVD $1916, R12 B callbackasm1(SB) MOVD $1917, R12 B callbackasm1(SB) MOVD $1918, R12 B callbackasm1(SB) MOVD $1919, R12 B callbackasm1(SB) MOVD $1920, R12 B callbackasm1(SB) MOVD $1921, R12 B callbackasm1(SB) MOVD $1922, R12 B callbackasm1(SB) MOVD $1923, R12 B callbackasm1(SB) MOVD $1924, R12 B callbackasm1(SB) MOVD $1925, R12 B callbackasm1(SB) MOVD $1926, R12 B callbackasm1(SB) MOVD $1927, R12 B callbackasm1(SB) MOVD $1928, R12 B callbackasm1(SB) MOVD $1929, R12 B callbackasm1(SB) MOVD $1930, R12 B callbackasm1(SB) MOVD $1931, R12 B callbackasm1(SB) MOVD $1932, R12 B callbackasm1(SB) MOVD $1933, R12 B callbackasm1(SB) MOVD $1934, R12 B callbackasm1(SB) MOVD $1935, R12 B callbackasm1(SB) MOVD $1936, R12 B callbackasm1(SB) MOVD $1937, R12 B callbackasm1(SB) MOVD $1938, R12 B callbackasm1(SB) MOVD $1939, R12 B callbackasm1(SB) MOVD $1940, R12 B callbackasm1(SB) MOVD $1941, R12 B callbackasm1(SB) MOVD $1942, R12 B callbackasm1(SB) MOVD $1943, R12 B callbackasm1(SB) MOVD $1944, R12 B callbackasm1(SB) MOVD $1945, R12 B callbackasm1(SB) MOVD $1946, R12 B callbackasm1(SB) MOVD $1947, R12 B callbackasm1(SB) MOVD $1948, R12 B callbackasm1(SB) MOVD $1949, R12 B callbackasm1(SB) MOVD $1950, R12 B callbackasm1(SB) MOVD $1951, R12 B callbackasm1(SB) MOVD $1952, R12 B callbackasm1(SB) MOVD $1953, R12 B callbackasm1(SB) MOVD $1954, R12 B callbackasm1(SB) MOVD $1955, R12 B callbackasm1(SB) MOVD $1956, R12 B callbackasm1(SB) MOVD $1957, R12 B callbackasm1(SB) MOVD $1958, R12 B callbackasm1(SB) MOVD $1959, R12 B callbackasm1(SB) MOVD $1960, R12 B callbackasm1(SB) MOVD $1961, R12 B callbackasm1(SB) MOVD $1962, R12 B callbackasm1(SB) MOVD $1963, R12 B callbackasm1(SB) MOVD $1964, R12 B callbackasm1(SB) MOVD $1965, R12 B callbackasm1(SB) MOVD $1966, R12 B callbackasm1(SB) MOVD $1967, R12 B callbackasm1(SB) MOVD $1968, R12 B callbackasm1(SB) MOVD $1969, R12 B callbackasm1(SB) MOVD $1970, R12 B callbackasm1(SB) MOVD $1971, R12 B callbackasm1(SB) MOVD $1972, R12 B callbackasm1(SB) MOVD $1973, R12 B callbackasm1(SB) MOVD $1974, R12 B callbackasm1(SB) MOVD $1975, R12 B callbackasm1(SB) MOVD $1976, R12 B callbackasm1(SB) MOVD $1977, R12 B callbackasm1(SB) MOVD $1978, R12 B callbackasm1(SB) MOVD $1979, R12 B callbackasm1(SB) MOVD $1980, R12 B callbackasm1(SB) MOVD $1981, R12 B callbackasm1(SB) MOVD $1982, R12 B callbackasm1(SB) MOVD $1983, R12 B callbackasm1(SB) MOVD $1984, R12 B callbackasm1(SB) MOVD $1985, R12 B callbackasm1(SB) MOVD $1986, R12 B callbackasm1(SB) MOVD $1987, R12 B callbackasm1(SB) MOVD $1988, R12 B callbackasm1(SB) MOVD $1989, R12 B callbackasm1(SB) MOVD $1990, R12 B callbackasm1(SB) MOVD $1991, R12 B callbackasm1(SB) MOVD $1992, R12 B callbackasm1(SB) MOVD $1993, R12 B callbackasm1(SB) MOVD $1994, R12 B callbackasm1(SB) MOVD $1995, R12 B callbackasm1(SB) MOVD $1996, R12 B callbackasm1(SB) MOVD $1997, R12 B callbackasm1(SB) MOVD $1998, R12 B callbackasm1(SB) MOVD $1999, R12 B callbackasm1(SB) ================================================ FILE: vendor/github.com/felixge/httpsnoop/.gitignore ================================================ ================================================ FILE: vendor/github.com/felixge/httpsnoop/LICENSE.txt ================================================ Copyright (c) 2016 Felix Geisendörfer (felix@debuggable.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: vendor/github.com/felixge/httpsnoop/Makefile ================================================ .PHONY: ci generate clean ci: clean generate go test -race -v ./... generate: go generate . clean: rm -rf *_generated*.go ================================================ FILE: vendor/github.com/felixge/httpsnoop/README.md ================================================ # httpsnoop Package httpsnoop provides an easy way to capture http related metrics (i.e. response time, bytes written, and http status code) from your application's http.Handlers. Doing this requires non-trivial wrapping of the http.ResponseWriter interface, which is also exposed for users interested in a more low-level API. [![Go Reference](https://pkg.go.dev/badge/github.com/felixge/httpsnoop.svg)](https://pkg.go.dev/github.com/felixge/httpsnoop) [![Build Status](https://github.com/felixge/httpsnoop/actions/workflows/main.yaml/badge.svg)](https://github.com/felixge/httpsnoop/actions/workflows/main.yaml) ## Usage Example ```go // myH is your app's http handler, perhaps a http.ServeMux or similar. var myH http.Handler // wrappedH wraps myH in order to log every request. wrappedH := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { m := httpsnoop.CaptureMetrics(myH, w, r) log.Printf( "%s %s (code=%d dt=%s written=%d)", r.Method, r.URL, m.Code, m.Duration, m.Written, ) }) http.ListenAndServe(":8080", wrappedH) ``` ## Why this package exists Instrumenting an application's http.Handler is surprisingly difficult. However if you google for e.g. "capture ResponseWriter status code" you'll find lots of advise and code examples that suggest it to be a fairly trivial undertaking. Unfortunately everything I've seen so far has a high chance of breaking your application. The main problem is that a `http.ResponseWriter` often implements additional interfaces such as `http.Flusher`, `http.CloseNotifier`, `http.Hijacker`, `http.Pusher`, and `io.ReaderFrom`. So the naive approach of just wrapping `http.ResponseWriter` in your own struct that also implements the `http.ResponseWriter` interface will hide the additional interfaces mentioned above. This has a high change of introducing subtle bugs into any non-trivial application. Another approach I've seen people take is to return a struct that implements all of the interfaces above. However, that's also problematic, because it's difficult to fake some of these interfaces behaviors when the underlying `http.ResponseWriter` doesn't have an implementation. It's also dangerous, because an application may choose to operate differently, merely because it detects the presence of these additional interfaces. This package solves this problem by checking which additional interfaces a `http.ResponseWriter` implements, returning a wrapped version implementing the exact same set of interfaces. Additionally this package properly handles edge cases such as `WriteHeader` not being called, or called more than once, as well as concurrent calls to `http.ResponseWriter` methods, and even calls happening after the wrapped `ServeHTTP` has already returned. Unfortunately this package is not perfect either. It's possible that it is still missing some interfaces provided by the go core (let me know if you find one), and it won't work for applications adding their own interfaces into the mix. You can however use `httpsnoop.Unwrap(w)` to access the underlying `http.ResponseWriter` and type-assert the result to its other interfaces. However, hopefully the explanation above has sufficiently scared you of rolling your own solution to this problem. httpsnoop may still break your application, but at least it tries to avoid it as much as possible. Anyway, the real problem here is that smuggling additional interfaces inside `http.ResponseWriter` is a problematic design choice, but it probably goes as deep as the Go language specification itself. But that's okay, I still prefer Go over the alternatives ;). ## Performance ``` BenchmarkBaseline-8 20000 94912 ns/op BenchmarkCaptureMetrics-8 20000 95461 ns/op ``` As you can see, using `CaptureMetrics` on a vanilla http.Handler introduces an overhead of ~500 ns per http request on my machine. However, the margin of error appears to be larger than that, therefor it should be reasonable to assume that the overhead introduced by `CaptureMetrics` is absolutely negligible. ## License MIT ================================================ FILE: vendor/github.com/felixge/httpsnoop/capture_metrics.go ================================================ package httpsnoop import ( "io" "net/http" "time" ) // Metrics holds metrics captured from CaptureMetrics. type Metrics struct { // Code is the first http response code passed to the WriteHeader func of // the ResponseWriter. If no such call is made, a default code of 200 is // assumed instead. Code int // Duration is the time it took to execute the handler. Duration time.Duration // Written is the number of bytes successfully written by the Write or // ReadFrom function of the ResponseWriter. ResponseWriters may also write // data to their underlaying connection directly (e.g. headers), but those // are not tracked. Therefor the number of Written bytes will usually match // the size of the response body. Written int64 } // CaptureMetrics wraps the given hnd, executes it with the given w and r, and // returns the metrics it captured from it. func CaptureMetrics(hnd http.Handler, w http.ResponseWriter, r *http.Request) Metrics { return CaptureMetricsFn(w, func(ww http.ResponseWriter) { hnd.ServeHTTP(ww, r) }) } // CaptureMetricsFn wraps w and calls fn with the wrapped w and returns the // resulting metrics. This is very similar to CaptureMetrics (which is just // sugar on top of this func), but is a more usable interface if your // application doesn't use the Go http.Handler interface. func CaptureMetricsFn(w http.ResponseWriter, fn func(http.ResponseWriter)) Metrics { m := Metrics{Code: http.StatusOK} m.CaptureMetrics(w, fn) return m } // CaptureMetrics wraps w and calls fn with the wrapped w and updates // Metrics m with the resulting metrics. This is similar to CaptureMetricsFn, // but allows one to customize starting Metrics object. func (m *Metrics) CaptureMetrics(w http.ResponseWriter, fn func(http.ResponseWriter)) { var ( start = time.Now() headerWritten bool hooks = Hooks{ WriteHeader: func(next WriteHeaderFunc) WriteHeaderFunc { return func(code int) { next(code) if !(code >= 100 && code <= 199) && !headerWritten { m.Code = code headerWritten = true } } }, Write: func(next WriteFunc) WriteFunc { return func(p []byte) (int, error) { n, err := next(p) m.Written += int64(n) headerWritten = true return n, err } }, ReadFrom: func(next ReadFromFunc) ReadFromFunc { return func(src io.Reader) (int64, error) { n, err := next(src) headerWritten = true m.Written += n return n, err } }, } ) fn(Wrap(w, hooks)) m.Duration += time.Since(start) } ================================================ FILE: vendor/github.com/felixge/httpsnoop/docs.go ================================================ // Package httpsnoop provides an easy way to capture http related metrics (i.e. // response time, bytes written, and http status code) from your application's // http.Handlers. // // Doing this requires non-trivial wrapping of the http.ResponseWriter // interface, which is also exposed for users interested in a more low-level // API. package httpsnoop //go:generate go run codegen/main.go ================================================ FILE: vendor/github.com/felixge/httpsnoop/wrap_generated_gteq_1.8.go ================================================ // +build go1.8 // Code generated by "httpsnoop/codegen"; DO NOT EDIT. package httpsnoop import ( "bufio" "io" "net" "net/http" ) // HeaderFunc is part of the http.ResponseWriter interface. type HeaderFunc func() http.Header // WriteHeaderFunc is part of the http.ResponseWriter interface. type WriteHeaderFunc func(code int) // WriteFunc is part of the http.ResponseWriter interface. type WriteFunc func(b []byte) (int, error) // FlushFunc is part of the http.Flusher interface. type FlushFunc func() // CloseNotifyFunc is part of the http.CloseNotifier interface. type CloseNotifyFunc func() <-chan bool // HijackFunc is part of the http.Hijacker interface. type HijackFunc func() (net.Conn, *bufio.ReadWriter, error) // ReadFromFunc is part of the io.ReaderFrom interface. type ReadFromFunc func(src io.Reader) (int64, error) // PushFunc is part of the http.Pusher interface. type PushFunc func(target string, opts *http.PushOptions) error // Hooks defines a set of method interceptors for methods included in // http.ResponseWriter as well as some others. You can think of them as // middleware for the function calls they target. See Wrap for more details. type Hooks struct { Header func(HeaderFunc) HeaderFunc WriteHeader func(WriteHeaderFunc) WriteHeaderFunc Write func(WriteFunc) WriteFunc Flush func(FlushFunc) FlushFunc CloseNotify func(CloseNotifyFunc) CloseNotifyFunc Hijack func(HijackFunc) HijackFunc ReadFrom func(ReadFromFunc) ReadFromFunc Push func(PushFunc) PushFunc } // Wrap returns a wrapped version of w that provides the exact same interface // as w. Specifically if w implements any combination of: // // - http.Flusher // - http.CloseNotifier // - http.Hijacker // - io.ReaderFrom // - http.Pusher // // The wrapped version will implement the exact same combination. If no hooks // are set, the wrapped version also behaves exactly as w. Hooks targeting // methods not supported by w are ignored. Any other hooks will intercept the // method they target and may modify the call's arguments and/or return values. // The CaptureMetrics implementation serves as a working example for how the // hooks can be used. func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter { rw := &rw{w: w, h: hooks} _, i0 := w.(http.Flusher) _, i1 := w.(http.CloseNotifier) _, i2 := w.(http.Hijacker) _, i3 := w.(io.ReaderFrom) _, i4 := w.(http.Pusher) switch { // combination 1/32 case !i0 && !i1 && !i2 && !i3 && !i4: return struct { Unwrapper http.ResponseWriter }{rw, rw} // combination 2/32 case !i0 && !i1 && !i2 && !i3 && i4: return struct { Unwrapper http.ResponseWriter http.Pusher }{rw, rw, rw} // combination 3/32 case !i0 && !i1 && !i2 && i3 && !i4: return struct { Unwrapper http.ResponseWriter io.ReaderFrom }{rw, rw, rw} // combination 4/32 case !i0 && !i1 && !i2 && i3 && i4: return struct { Unwrapper http.ResponseWriter io.ReaderFrom http.Pusher }{rw, rw, rw, rw} // combination 5/32 case !i0 && !i1 && i2 && !i3 && !i4: return struct { Unwrapper http.ResponseWriter http.Hijacker }{rw, rw, rw} // combination 6/32 case !i0 && !i1 && i2 && !i3 && i4: return struct { Unwrapper http.ResponseWriter http.Hijacker http.Pusher }{rw, rw, rw, rw} // combination 7/32 case !i0 && !i1 && i2 && i3 && !i4: return struct { Unwrapper http.ResponseWriter http.Hijacker io.ReaderFrom }{rw, rw, rw, rw} // combination 8/32 case !i0 && !i1 && i2 && i3 && i4: return struct { Unwrapper http.ResponseWriter http.Hijacker io.ReaderFrom http.Pusher }{rw, rw, rw, rw, rw} // combination 9/32 case !i0 && i1 && !i2 && !i3 && !i4: return struct { Unwrapper http.ResponseWriter http.CloseNotifier }{rw, rw, rw} // combination 10/32 case !i0 && i1 && !i2 && !i3 && i4: return struct { Unwrapper http.ResponseWriter http.CloseNotifier http.Pusher }{rw, rw, rw, rw} // combination 11/32 case !i0 && i1 && !i2 && i3 && !i4: return struct { Unwrapper http.ResponseWriter http.CloseNotifier io.ReaderFrom }{rw, rw, rw, rw} // combination 12/32 case !i0 && i1 && !i2 && i3 && i4: return struct { Unwrapper http.ResponseWriter http.CloseNotifier io.ReaderFrom http.Pusher }{rw, rw, rw, rw, rw} // combination 13/32 case !i0 && i1 && i2 && !i3 && !i4: return struct { Unwrapper http.ResponseWriter http.CloseNotifier http.Hijacker }{rw, rw, rw, rw} // combination 14/32 case !i0 && i1 && i2 && !i3 && i4: return struct { Unwrapper http.ResponseWriter http.CloseNotifier http.Hijacker http.Pusher }{rw, rw, rw, rw, rw} // combination 15/32 case !i0 && i1 && i2 && i3 && !i4: return struct { Unwrapper http.ResponseWriter http.CloseNotifier http.Hijacker io.ReaderFrom }{rw, rw, rw, rw, rw} // combination 16/32 case !i0 && i1 && i2 && i3 && i4: return struct { Unwrapper http.ResponseWriter http.CloseNotifier http.Hijacker io.ReaderFrom http.Pusher }{rw, rw, rw, rw, rw, rw} // combination 17/32 case i0 && !i1 && !i2 && !i3 && !i4: return struct { Unwrapper http.ResponseWriter http.Flusher }{rw, rw, rw} // combination 18/32 case i0 && !i1 && !i2 && !i3 && i4: return struct { Unwrapper http.ResponseWriter http.Flusher http.Pusher }{rw, rw, rw, rw} // combination 19/32 case i0 && !i1 && !i2 && i3 && !i4: return struct { Unwrapper http.ResponseWriter http.Flusher io.ReaderFrom }{rw, rw, rw, rw} // combination 20/32 case i0 && !i1 && !i2 && i3 && i4: return struct { Unwrapper http.ResponseWriter http.Flusher io.ReaderFrom http.Pusher }{rw, rw, rw, rw, rw} // combination 21/32 case i0 && !i1 && i2 && !i3 && !i4: return struct { Unwrapper http.ResponseWriter http.Flusher http.Hijacker }{rw, rw, rw, rw} // combination 22/32 case i0 && !i1 && i2 && !i3 && i4: return struct { Unwrapper http.ResponseWriter http.Flusher http.Hijacker http.Pusher }{rw, rw, rw, rw, rw} // combination 23/32 case i0 && !i1 && i2 && i3 && !i4: return struct { Unwrapper http.ResponseWriter http.Flusher http.Hijacker io.ReaderFrom }{rw, rw, rw, rw, rw} // combination 24/32 case i0 && !i1 && i2 && i3 && i4: return struct { Unwrapper http.ResponseWriter http.Flusher http.Hijacker io.ReaderFrom http.Pusher }{rw, rw, rw, rw, rw, rw} // combination 25/32 case i0 && i1 && !i2 && !i3 && !i4: return struct { Unwrapper http.ResponseWriter http.Flusher http.CloseNotifier }{rw, rw, rw, rw} // combination 26/32 case i0 && i1 && !i2 && !i3 && i4: return struct { Unwrapper http.ResponseWriter http.Flusher http.CloseNotifier http.Pusher }{rw, rw, rw, rw, rw} // combination 27/32 case i0 && i1 && !i2 && i3 && !i4: return struct { Unwrapper http.ResponseWriter http.Flusher http.CloseNotifier io.ReaderFrom }{rw, rw, rw, rw, rw} // combination 28/32 case i0 && i1 && !i2 && i3 && i4: return struct { Unwrapper http.ResponseWriter http.Flusher http.CloseNotifier io.ReaderFrom http.Pusher }{rw, rw, rw, rw, rw, rw} // combination 29/32 case i0 && i1 && i2 && !i3 && !i4: return struct { Unwrapper http.ResponseWriter http.Flusher http.CloseNotifier http.Hijacker }{rw, rw, rw, rw, rw} // combination 30/32 case i0 && i1 && i2 && !i3 && i4: return struct { Unwrapper http.ResponseWriter http.Flusher http.CloseNotifier http.Hijacker http.Pusher }{rw, rw, rw, rw, rw, rw} // combination 31/32 case i0 && i1 && i2 && i3 && !i4: return struct { Unwrapper http.ResponseWriter http.Flusher http.CloseNotifier http.Hijacker io.ReaderFrom }{rw, rw, rw, rw, rw, rw} // combination 32/32 case i0 && i1 && i2 && i3 && i4: return struct { Unwrapper http.ResponseWriter http.Flusher http.CloseNotifier http.Hijacker io.ReaderFrom http.Pusher }{rw, rw, rw, rw, rw, rw, rw} } panic("unreachable") } type rw struct { w http.ResponseWriter h Hooks } func (w *rw) Unwrap() http.ResponseWriter { return w.w } func (w *rw) Header() http.Header { f := w.w.(http.ResponseWriter).Header if w.h.Header != nil { f = w.h.Header(f) } return f() } func (w *rw) WriteHeader(code int) { f := w.w.(http.ResponseWriter).WriteHeader if w.h.WriteHeader != nil { f = w.h.WriteHeader(f) } f(code) } func (w *rw) Write(b []byte) (int, error) { f := w.w.(http.ResponseWriter).Write if w.h.Write != nil { f = w.h.Write(f) } return f(b) } func (w *rw) Flush() { f := w.w.(http.Flusher).Flush if w.h.Flush != nil { f = w.h.Flush(f) } f() } func (w *rw) CloseNotify() <-chan bool { f := w.w.(http.CloseNotifier).CloseNotify if w.h.CloseNotify != nil { f = w.h.CloseNotify(f) } return f() } func (w *rw) Hijack() (net.Conn, *bufio.ReadWriter, error) { f := w.w.(http.Hijacker).Hijack if w.h.Hijack != nil { f = w.h.Hijack(f) } return f() } func (w *rw) ReadFrom(src io.Reader) (int64, error) { f := w.w.(io.ReaderFrom).ReadFrom if w.h.ReadFrom != nil { f = w.h.ReadFrom(f) } return f(src) } func (w *rw) Push(target string, opts *http.PushOptions) error { f := w.w.(http.Pusher).Push if w.h.Push != nil { f = w.h.Push(f) } return f(target, opts) } type Unwrapper interface { Unwrap() http.ResponseWriter } // Unwrap returns the underlying http.ResponseWriter from within zero or more // layers of httpsnoop wrappers. func Unwrap(w http.ResponseWriter) http.ResponseWriter { if rw, ok := w.(Unwrapper); ok { // recurse until rw.Unwrap() returns a non-Unwrapper return Unwrap(rw.Unwrap()) } else { return w } } ================================================ FILE: vendor/github.com/felixge/httpsnoop/wrap_generated_lt_1.8.go ================================================ // +build !go1.8 // Code generated by "httpsnoop/codegen"; DO NOT EDIT. package httpsnoop import ( "bufio" "io" "net" "net/http" ) // HeaderFunc is part of the http.ResponseWriter interface. type HeaderFunc func() http.Header // WriteHeaderFunc is part of the http.ResponseWriter interface. type WriteHeaderFunc func(code int) // WriteFunc is part of the http.ResponseWriter interface. type WriteFunc func(b []byte) (int, error) // FlushFunc is part of the http.Flusher interface. type FlushFunc func() // CloseNotifyFunc is part of the http.CloseNotifier interface. type CloseNotifyFunc func() <-chan bool // HijackFunc is part of the http.Hijacker interface. type HijackFunc func() (net.Conn, *bufio.ReadWriter, error) // ReadFromFunc is part of the io.ReaderFrom interface. type ReadFromFunc func(src io.Reader) (int64, error) // Hooks defines a set of method interceptors for methods included in // http.ResponseWriter as well as some others. You can think of them as // middleware for the function calls they target. See Wrap for more details. type Hooks struct { Header func(HeaderFunc) HeaderFunc WriteHeader func(WriteHeaderFunc) WriteHeaderFunc Write func(WriteFunc) WriteFunc Flush func(FlushFunc) FlushFunc CloseNotify func(CloseNotifyFunc) CloseNotifyFunc Hijack func(HijackFunc) HijackFunc ReadFrom func(ReadFromFunc) ReadFromFunc } // Wrap returns a wrapped version of w that provides the exact same interface // as w. Specifically if w implements any combination of: // // - http.Flusher // - http.CloseNotifier // - http.Hijacker // - io.ReaderFrom // // The wrapped version will implement the exact same combination. If no hooks // are set, the wrapped version also behaves exactly as w. Hooks targeting // methods not supported by w are ignored. Any other hooks will intercept the // method they target and may modify the call's arguments and/or return values. // The CaptureMetrics implementation serves as a working example for how the // hooks can be used. func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter { rw := &rw{w: w, h: hooks} _, i0 := w.(http.Flusher) _, i1 := w.(http.CloseNotifier) _, i2 := w.(http.Hijacker) _, i3 := w.(io.ReaderFrom) switch { // combination 1/16 case !i0 && !i1 && !i2 && !i3: return struct { Unwrapper http.ResponseWriter }{rw, rw} // combination 2/16 case !i0 && !i1 && !i2 && i3: return struct { Unwrapper http.ResponseWriter io.ReaderFrom }{rw, rw, rw} // combination 3/16 case !i0 && !i1 && i2 && !i3: return struct { Unwrapper http.ResponseWriter http.Hijacker }{rw, rw, rw} // combination 4/16 case !i0 && !i1 && i2 && i3: return struct { Unwrapper http.ResponseWriter http.Hijacker io.ReaderFrom }{rw, rw, rw, rw} // combination 5/16 case !i0 && i1 && !i2 && !i3: return struct { Unwrapper http.ResponseWriter http.CloseNotifier }{rw, rw, rw} // combination 6/16 case !i0 && i1 && !i2 && i3: return struct { Unwrapper http.ResponseWriter http.CloseNotifier io.ReaderFrom }{rw, rw, rw, rw} // combination 7/16 case !i0 && i1 && i2 && !i3: return struct { Unwrapper http.ResponseWriter http.CloseNotifier http.Hijacker }{rw, rw, rw, rw} // combination 8/16 case !i0 && i1 && i2 && i3: return struct { Unwrapper http.ResponseWriter http.CloseNotifier http.Hijacker io.ReaderFrom }{rw, rw, rw, rw, rw} // combination 9/16 case i0 && !i1 && !i2 && !i3: return struct { Unwrapper http.ResponseWriter http.Flusher }{rw, rw, rw} // combination 10/16 case i0 && !i1 && !i2 && i3: return struct { Unwrapper http.ResponseWriter http.Flusher io.ReaderFrom }{rw, rw, rw, rw} // combination 11/16 case i0 && !i1 && i2 && !i3: return struct { Unwrapper http.ResponseWriter http.Flusher http.Hijacker }{rw, rw, rw, rw} // combination 12/16 case i0 && !i1 && i2 && i3: return struct { Unwrapper http.ResponseWriter http.Flusher http.Hijacker io.ReaderFrom }{rw, rw, rw, rw, rw} // combination 13/16 case i0 && i1 && !i2 && !i3: return struct { Unwrapper http.ResponseWriter http.Flusher http.CloseNotifier }{rw, rw, rw, rw} // combination 14/16 case i0 && i1 && !i2 && i3: return struct { Unwrapper http.ResponseWriter http.Flusher http.CloseNotifier io.ReaderFrom }{rw, rw, rw, rw, rw} // combination 15/16 case i0 && i1 && i2 && !i3: return struct { Unwrapper http.ResponseWriter http.Flusher http.CloseNotifier http.Hijacker }{rw, rw, rw, rw, rw} // combination 16/16 case i0 && i1 && i2 && i3: return struct { Unwrapper http.ResponseWriter http.Flusher http.CloseNotifier http.Hijacker io.ReaderFrom }{rw, rw, rw, rw, rw, rw} } panic("unreachable") } type rw struct { w http.ResponseWriter h Hooks } func (w *rw) Unwrap() http.ResponseWriter { return w.w } func (w *rw) Header() http.Header { f := w.w.(http.ResponseWriter).Header if w.h.Header != nil { f = w.h.Header(f) } return f() } func (w *rw) WriteHeader(code int) { f := w.w.(http.ResponseWriter).WriteHeader if w.h.WriteHeader != nil { f = w.h.WriteHeader(f) } f(code) } func (w *rw) Write(b []byte) (int, error) { f := w.w.(http.ResponseWriter).Write if w.h.Write != nil { f = w.h.Write(f) } return f(b) } func (w *rw) Flush() { f := w.w.(http.Flusher).Flush if w.h.Flush != nil { f = w.h.Flush(f) } f() } func (w *rw) CloseNotify() <-chan bool { f := w.w.(http.CloseNotifier).CloseNotify if w.h.CloseNotify != nil { f = w.h.CloseNotify(f) } return f() } func (w *rw) Hijack() (net.Conn, *bufio.ReadWriter, error) { f := w.w.(http.Hijacker).Hijack if w.h.Hijack != nil { f = w.h.Hijack(f) } return f() } func (w *rw) ReadFrom(src io.Reader) (int64, error) { f := w.w.(io.ReaderFrom).ReadFrom if w.h.ReadFrom != nil { f = w.h.ReadFrom(f) } return f(src) } type Unwrapper interface { Unwrap() http.ResponseWriter } // Unwrap returns the underlying http.ResponseWriter from within zero or more // layers of httpsnoop wrappers. func Unwrap(w http.ResponseWriter) http.ResponseWriter { if rw, ok := w.(Unwrapper); ok { // recurse until rw.Unwrap() returns a non-Unwrapper return Unwrap(rw.Unwrap()) } else { return w } } ================================================ FILE: vendor/github.com/go-ini/ini/.editorconfig ================================================ # http://editorconfig.org root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*_test.go] trim_trailing_whitespace = false ================================================ FILE: vendor/github.com/go-ini/ini/.gitignore ================================================ testdata/conf_out.ini ini.sublime-project ini.sublime-workspace testdata/conf_reflect.ini .idea /.vscode .DS_Store ================================================ FILE: vendor/github.com/go-ini/ini/.golangci.yml ================================================ linters-settings: staticcheck: checks: [ "all", "-SA1019" # There are valid use cases of strings.Title ] nakedret: max-func-lines: 0 # Disallow any unnamed return statement linters: enable: - deadcode - errcheck - gosimple - govet - ineffassign - staticcheck - structcheck - typecheck - unused - varcheck - nakedret - gofmt - rowserrcheck - unconvert - goimports - unparam ================================================ FILE: vendor/github.com/go-ini/ini/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: You must give any other recipients of the Work or Derivative Works a copy of this License; and You must cause any modified files to carry prominent notices stating that You changed the files; and You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2014 Unknwon 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. ================================================ FILE: vendor/github.com/go-ini/ini/Makefile ================================================ .PHONY: build test bench vet coverage build: vet bench test: go test -v -cover -race bench: go test -v -cover -test.bench=. -test.benchmem vet: go vet coverage: go test -coverprofile=c.out && go tool cover -html=c.out && rm c.out ================================================ FILE: vendor/github.com/go-ini/ini/README.md ================================================ # INI [![GitHub Workflow Status](https://img.shields.io/github/checks-status/go-ini/ini/main?logo=github&style=for-the-badge)](https://github.com/go-ini/ini/actions?query=branch%3Amain) [![codecov](https://img.shields.io/codecov/c/github/go-ini/ini/master?logo=codecov&style=for-the-badge)](https://codecov.io/gh/go-ini/ini) [![GoDoc](https://img.shields.io/badge/GoDoc-Reference-blue?style=for-the-badge&logo=go)](https://pkg.go.dev/github.com/go-ini/ini?tab=doc) [![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg?style=for-the-badge&logo=sourcegraph)](https://sourcegraph.com/github.com/go-ini/ini) ![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200) Package ini provides INI file read and write functionality in Go. ## Features - Load from multiple data sources(file, `[]byte`, `io.Reader` and `io.ReadCloser`) with overwrites. - Read with recursion values. - Read with parent-child sections. - Read with auto-increment key names. - Read with multiple-line values. - Read with tons of helper methods. - Read and convert values to Go types. - Read and **WRITE** comments of sections and keys. - Manipulate sections, keys and comments with ease. - Keep sections and keys in order as you parse and save. ## Installation The minimum requirement of Go is **1.13**. ```sh $ go get gopkg.in/ini.v1 ``` Please add `-u` flag to update in the future. ## Getting Help - [Getting Started](https://ini.unknwon.io/docs/intro/getting_started) - [API Documentation](https://gowalker.org/gopkg.in/ini.v1) - 中国大陆镜像:https://ini.unknwon.cn ## License This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text. ================================================ FILE: vendor/github.com/go-ini/ini/codecov.yml ================================================ coverage: range: "60...95" status: project: default: threshold: 1% informational: true patch: defualt: only_pulls: true informational: true comment: layout: 'diff' github_checks: false ================================================ FILE: vendor/github.com/go-ini/ini/data_source.go ================================================ // Copyright 2019 Unknwon // // 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. package ini import ( "bytes" "fmt" "io" "io/ioutil" "os" ) var ( _ dataSource = (*sourceFile)(nil) _ dataSource = (*sourceData)(nil) _ dataSource = (*sourceReadCloser)(nil) ) // dataSource is an interface that returns object which can be read and closed. type dataSource interface { ReadCloser() (io.ReadCloser, error) } // sourceFile represents an object that contains content on the local file system. type sourceFile struct { name string } func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) { return os.Open(s.name) } // sourceData represents an object that contains content in memory. type sourceData struct { data []byte } func (s *sourceData) ReadCloser() (io.ReadCloser, error) { return ioutil.NopCloser(bytes.NewReader(s.data)), nil } // sourceReadCloser represents an input stream with Close method. type sourceReadCloser struct { reader io.ReadCloser } func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) { return s.reader, nil } func parseDataSource(source interface{}) (dataSource, error) { switch s := source.(type) { case string: return sourceFile{s}, nil case []byte: return &sourceData{s}, nil case io.ReadCloser: return &sourceReadCloser{s}, nil case io.Reader: return &sourceReadCloser{ioutil.NopCloser(s)}, nil default: return nil, fmt.Errorf("error parsing data source: unknown type %q", s) } } ================================================ FILE: vendor/github.com/go-ini/ini/deprecated.go ================================================ // Copyright 2019 Unknwon // // 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. package ini var ( // Deprecated: Use "DefaultSection" instead. DEFAULT_SECTION = DefaultSection // Deprecated: AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE. AllCapsUnderscore = SnackCase ) ================================================ FILE: vendor/github.com/go-ini/ini/error.go ================================================ // Copyright 2016 Unknwon // // 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. package ini import ( "fmt" ) // ErrDelimiterNotFound indicates the error type of no delimiter is found which there should be one. type ErrDelimiterNotFound struct { Line string } // IsErrDelimiterNotFound returns true if the given error is an instance of ErrDelimiterNotFound. func IsErrDelimiterNotFound(err error) bool { _, ok := err.(ErrDelimiterNotFound) return ok } func (err ErrDelimiterNotFound) Error() string { return fmt.Sprintf("key-value delimiter not found: %s", err.Line) } // ErrEmptyKeyName indicates the error type of no key name is found which there should be one. type ErrEmptyKeyName struct { Line string } // IsErrEmptyKeyName returns true if the given error is an instance of ErrEmptyKeyName. func IsErrEmptyKeyName(err error) bool { _, ok := err.(ErrEmptyKeyName) return ok } func (err ErrEmptyKeyName) Error() string { return fmt.Sprintf("empty key name: %s", err.Line) } ================================================ FILE: vendor/github.com/go-ini/ini/file.go ================================================ // Copyright 2017 Unknwon // // 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. package ini import ( "bytes" "errors" "fmt" "io" "io/ioutil" "os" "strings" "sync" ) // File represents a combination of one or more INI files in memory. type File struct { options LoadOptions dataSources []dataSource // Should make things safe, but sometimes doesn't matter. BlockMode bool lock sync.RWMutex // To keep data in order. sectionList []string // To keep track of the index of a section with same name. // This meta list is only used with non-unique section names are allowed. sectionIndexes []int // Actual data is stored here. sections map[string][]*Section NameMapper ValueMapper } // newFile initializes File object with given data sources. func newFile(dataSources []dataSource, opts LoadOptions) *File { if len(opts.KeyValueDelimiters) == 0 { opts.KeyValueDelimiters = "=:" } if len(opts.KeyValueDelimiterOnWrite) == 0 { opts.KeyValueDelimiterOnWrite = "=" } if len(opts.ChildSectionDelimiter) == 0 { opts.ChildSectionDelimiter = "." } return &File{ BlockMode: true, dataSources: dataSources, sections: make(map[string][]*Section), options: opts, } } // Empty returns an empty file object. func Empty(opts ...LoadOptions) *File { var opt LoadOptions if len(opts) > 0 { opt = opts[0] } // Ignore error here, we are sure our data is good. f, _ := LoadSources(opt, []byte("")) return f } // NewSection creates a new section. func (f *File) NewSection(name string) (*Section, error) { if len(name) == 0 { return nil, errors.New("empty section name") } if (f.options.Insensitive || f.options.InsensitiveSections) && name != DefaultSection { name = strings.ToLower(name) } if f.BlockMode { f.lock.Lock() defer f.lock.Unlock() } if !f.options.AllowNonUniqueSections && inSlice(name, f.sectionList) { return f.sections[name][0], nil } f.sectionList = append(f.sectionList, name) // NOTE: Append to indexes must happen before appending to sections, // otherwise index will have off-by-one problem. f.sectionIndexes = append(f.sectionIndexes, len(f.sections[name])) sec := newSection(f, name) f.sections[name] = append(f.sections[name], sec) return sec, nil } // NewRawSection creates a new section with an unparseable body. func (f *File) NewRawSection(name, body string) (*Section, error) { section, err := f.NewSection(name) if err != nil { return nil, err } section.isRawSection = true section.rawBody = body return section, nil } // NewSections creates a list of sections. func (f *File) NewSections(names ...string) (err error) { for _, name := range names { if _, err = f.NewSection(name); err != nil { return err } } return nil } // GetSection returns section by given name. func (f *File) GetSection(name string) (*Section, error) { secs, err := f.SectionsByName(name) if err != nil { return nil, err } return secs[0], err } // HasSection returns true if the file contains a section with given name. func (f *File) HasSection(name string) bool { section, _ := f.GetSection(name) return section != nil } // SectionsByName returns all sections with given name. func (f *File) SectionsByName(name string) ([]*Section, error) { if len(name) == 0 { name = DefaultSection } if f.options.Insensitive || f.options.InsensitiveSections { name = strings.ToLower(name) } if f.BlockMode { f.lock.RLock() defer f.lock.RUnlock() } secs := f.sections[name] if len(secs) == 0 { return nil, fmt.Errorf("section %q does not exist", name) } return secs, nil } // Section assumes named section exists and returns a zero-value when not. func (f *File) Section(name string) *Section { sec, err := f.GetSection(name) if err != nil { if name == "" { name = DefaultSection } sec, _ = f.NewSection(name) return sec } return sec } // SectionWithIndex assumes named section exists and returns a new section when not. func (f *File) SectionWithIndex(name string, index int) *Section { secs, err := f.SectionsByName(name) if err != nil || len(secs) <= index { // NOTE: It's OK here because the only possible error is empty section name, // but if it's empty, this piece of code won't be executed. newSec, _ := f.NewSection(name) return newSec } return secs[index] } // Sections returns a list of Section stored in the current instance. func (f *File) Sections() []*Section { if f.BlockMode { f.lock.RLock() defer f.lock.RUnlock() } sections := make([]*Section, len(f.sectionList)) for i, name := range f.sectionList { sections[i] = f.sections[name][f.sectionIndexes[i]] } return sections } // ChildSections returns a list of child sections of given section name. func (f *File) ChildSections(name string) []*Section { return f.Section(name).ChildSections() } // SectionStrings returns list of section names. func (f *File) SectionStrings() []string { list := make([]string, len(f.sectionList)) copy(list, f.sectionList) return list } // DeleteSection deletes a section or all sections with given name. func (f *File) DeleteSection(name string) { secs, err := f.SectionsByName(name) if err != nil { return } for i := 0; i < len(secs); i++ { // For non-unique sections, it is always needed to remove the first one so // in the next iteration, the subsequent section continue having index 0. // Ignoring the error as index 0 never returns an error. _ = f.DeleteSectionWithIndex(name, 0) } } // DeleteSectionWithIndex deletes a section with given name and index. func (f *File) DeleteSectionWithIndex(name string, index int) error { if !f.options.AllowNonUniqueSections && index != 0 { return fmt.Errorf("delete section with non-zero index is only allowed when non-unique sections is enabled") } if len(name) == 0 { name = DefaultSection } if f.options.Insensitive || f.options.InsensitiveSections { name = strings.ToLower(name) } if f.BlockMode { f.lock.Lock() defer f.lock.Unlock() } // Count occurrences of the sections occurrences := 0 sectionListCopy := make([]string, len(f.sectionList)) copy(sectionListCopy, f.sectionList) for i, s := range sectionListCopy { if s != name { continue } if occurrences == index { if len(f.sections[name]) <= 1 { delete(f.sections, name) // The last one in the map } else { f.sections[name] = append(f.sections[name][:index], f.sections[name][index+1:]...) } // Fix section lists f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...) f.sectionIndexes = append(f.sectionIndexes[:i], f.sectionIndexes[i+1:]...) } else if occurrences > index { // Fix the indices of all following sections with this name. f.sectionIndexes[i-1]-- } occurrences++ } return nil } func (f *File) reload(s dataSource) error { r, err := s.ReadCloser() if err != nil { return err } defer r.Close() return f.parse(r) } // Reload reloads and parses all data sources. func (f *File) Reload() (err error) { for _, s := range f.dataSources { if err = f.reload(s); err != nil { // In loose mode, we create an empty default section for nonexistent files. if os.IsNotExist(err) && f.options.Loose { _ = f.parse(bytes.NewBuffer(nil)) continue } return err } if f.options.ShortCircuit { return nil } } return nil } // Append appends one or more data sources and reloads automatically. func (f *File) Append(source interface{}, others ...interface{}) error { ds, err := parseDataSource(source) if err != nil { return err } f.dataSources = append(f.dataSources, ds) for _, s := range others { ds, err = parseDataSource(s) if err != nil { return err } f.dataSources = append(f.dataSources, ds) } return f.Reload() } func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) { equalSign := DefaultFormatLeft + f.options.KeyValueDelimiterOnWrite + DefaultFormatRight if PrettyFormat || PrettyEqual { equalSign = fmt.Sprintf(" %s ", f.options.KeyValueDelimiterOnWrite) } // Use buffer to make sure target is safe until finish encoding. buf := bytes.NewBuffer(nil) lastSectionIdx := len(f.sectionList) - 1 for i, sname := range f.sectionList { sec := f.SectionWithIndex(sname, f.sectionIndexes[i]) if len(sec.Comment) > 0 { // Support multiline comments lines := strings.Split(sec.Comment, LineBreak) for i := range lines { if lines[i][0] != '#' && lines[i][0] != ';' { lines[i] = "; " + lines[i] } else { lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:]) } if _, err := buf.WriteString(lines[i] + LineBreak); err != nil { return nil, err } } } if i > 0 || DefaultHeader || (i == 0 && strings.ToUpper(sec.name) != DefaultSection) { if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil { return nil, err } } else { // Write nothing if default section is empty if len(sec.keyList) == 0 { continue } } isLastSection := i == lastSectionIdx if sec.isRawSection { if _, err := buf.WriteString(sec.rawBody); err != nil { return nil, err } if PrettySection && !isLastSection { // Put a line between sections if _, err := buf.WriteString(LineBreak); err != nil { return nil, err } } continue } // Count and generate alignment length and buffer spaces using the // longest key. Keys may be modified if they contain certain characters so // we need to take that into account in our calculation. alignLength := 0 if PrettyFormat { for _, kname := range sec.keyList { keyLength := len(kname) // First case will surround key by ` and second by """ if strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters) { keyLength += 2 } else if strings.Contains(kname, "`") { keyLength += 6 } if keyLength > alignLength { alignLength = keyLength } } } alignSpaces := bytes.Repeat([]byte(" "), alignLength) KeyList: for _, kname := range sec.keyList { key := sec.Key(kname) if len(key.Comment) > 0 { if len(indent) > 0 && sname != DefaultSection { buf.WriteString(indent) } // Support multiline comments lines := strings.Split(key.Comment, LineBreak) for i := range lines { if lines[i][0] != '#' && lines[i][0] != ';' { lines[i] = "; " + strings.TrimSpace(lines[i]) } else { lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:]) } if _, err := buf.WriteString(lines[i] + LineBreak); err != nil { return nil, err } } } if len(indent) > 0 && sname != DefaultSection { buf.WriteString(indent) } switch { case key.isAutoIncrement: kname = "-" case strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters): kname = "`" + kname + "`" case strings.Contains(kname, "`"): kname = `"""` + kname + `"""` } writeKeyValue := func(val string) (bool, error) { if _, err := buf.WriteString(kname); err != nil { return false, err } if key.isBooleanType { buf.WriteString(LineBreak) return true, nil } // Write out alignment spaces before "=" sign if PrettyFormat { buf.Write(alignSpaces[:alignLength-len(kname)]) } // In case key value contains "\n", "`", "\"", "#" or ";" if strings.ContainsAny(val, "\n`") { val = `"""` + val + `"""` } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") { val = "`" + val + "`" } else if len(strings.TrimSpace(val)) != len(val) { val = `"` + val + `"` } if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil { return false, err } return false, nil } shadows := key.ValueWithShadows() if len(shadows) == 0 { if _, err := writeKeyValue(""); err != nil { return nil, err } } for _, val := range shadows { exitLoop, err := writeKeyValue(val) if err != nil { return nil, err } else if exitLoop { continue KeyList } } for _, val := range key.nestedValues { if _, err := buf.WriteString(indent + " " + val + LineBreak); err != nil { return nil, err } } } if PrettySection && !isLastSection { // Put a line between sections if _, err := buf.WriteString(LineBreak); err != nil { return nil, err } } } return buf, nil } // WriteToIndent writes content into io.Writer with given indention. // If PrettyFormat has been set to be true, // it will align "=" sign with spaces under each section. func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) { buf, err := f.writeToBuffer(indent) if err != nil { return 0, err } return buf.WriteTo(w) } // WriteTo writes file content into io.Writer. func (f *File) WriteTo(w io.Writer) (int64, error) { return f.WriteToIndent(w, "") } // SaveToIndent writes content to file system with given value indention. func (f *File) SaveToIndent(filename, indent string) error { // Note: Because we are truncating with os.Create, // so it's safer to save to a temporary file location and rename after done. buf, err := f.writeToBuffer(indent) if err != nil { return err } return ioutil.WriteFile(filename, buf.Bytes(), 0666) } // SaveTo writes content to file system. func (f *File) SaveTo(filename string) error { return f.SaveToIndent(filename, "") } ================================================ FILE: vendor/github.com/go-ini/ini/helper.go ================================================ // Copyright 2019 Unknwon // // 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. package ini func inSlice(str string, s []string) bool { for _, v := range s { if str == v { return true } } return false } ================================================ FILE: vendor/github.com/go-ini/ini/ini.go ================================================ // Copyright 2014 Unknwon // // 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. // Package ini provides INI file read and write functionality in Go. package ini import ( "os" "regexp" "runtime" "strings" ) const ( // Maximum allowed depth when recursively substituing variable names. depthValues = 99 ) var ( // DefaultSection is the name of default section. You can use this var or the string literal. // In most of cases, an empty string is all you need to access the section. DefaultSection = "DEFAULT" // LineBreak is the delimiter to determine or compose a new line. // This variable will be changed to "\r\n" automatically on Windows at package init time. LineBreak = "\n" // Variable regexp pattern: %(variable)s varPattern = regexp.MustCompile(`%\(([^)]+)\)s`) // DefaultHeader explicitly writes default section header. DefaultHeader = false // PrettySection indicates whether to put a line between sections. PrettySection = true // PrettyFormat indicates whether to align "=" sign with spaces to produce pretty output // or reduce all possible spaces for compact format. PrettyFormat = true // PrettyEqual places spaces around "=" sign even when PrettyFormat is false. PrettyEqual = false // DefaultFormatLeft places custom spaces on the left when PrettyFormat and PrettyEqual are both disabled. DefaultFormatLeft = "" // DefaultFormatRight places custom spaces on the right when PrettyFormat and PrettyEqual are both disabled. DefaultFormatRight = "" ) var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test") func init() { if runtime.GOOS == "windows" && !inTest { LineBreak = "\r\n" } } // LoadOptions contains all customized options used for load data source(s). type LoadOptions struct { // Loose indicates whether the parser should ignore nonexistent files or return error. Loose bool // Insensitive indicates whether the parser forces all section and key names to lowercase. Insensitive bool // InsensitiveSections indicates whether the parser forces all section to lowercase. InsensitiveSections bool // InsensitiveKeys indicates whether the parser forces all key names to lowercase. InsensitiveKeys bool // IgnoreContinuation indicates whether to ignore continuation lines while parsing. IgnoreContinuation bool // IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value. IgnoreInlineComment bool // SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs. SkipUnrecognizableLines bool // ShortCircuit indicates whether to ignore other configuration sources after loaded the first available configuration source. ShortCircuit bool // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing. // This type of keys are mostly used in my.cnf. AllowBooleanKeys bool // AllowShadows indicates whether to keep track of keys with same name under same section. AllowShadows bool // AllowNestedValues indicates whether to allow AWS-like nested values. // Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values AllowNestedValues bool // AllowPythonMultilineValues indicates whether to allow Python-like multi-line values. // Docs: https://docs.python.org/3/library/configparser.html#supported-ini-file-structure // Relevant quote: Values can also span multiple lines, as long as they are indented deeper // than the first line of the value. AllowPythonMultilineValues bool // SpaceBeforeInlineComment indicates whether to allow comment symbols (\# and \;) inside value. // Docs: https://docs.python.org/2/library/configparser.html // Quote: Comments may appear on their own in an otherwise empty line, or may be entered in lines holding values or section names. // In the latter case, they need to be preceded by a whitespace character to be recognized as a comment. SpaceBeforeInlineComment bool // UnescapeValueDoubleQuotes indicates whether to unescape double quotes inside value to regular format // when value is surrounded by double quotes, e.g. key="a \"value\"" => key=a "value" UnescapeValueDoubleQuotes bool // UnescapeValueCommentSymbols indicates to unescape comment symbols (\# and \;) inside value to regular format // when value is NOT surrounded by any quotes. // Note: UNSTABLE, behavior might change to only unescape inside double quotes but may noy necessary at all. UnescapeValueCommentSymbols bool // UnparseableSections stores a list of blocks that are allowed with raw content which do not otherwise // conform to key/value pairs. Specify the names of those blocks here. UnparseableSections []string // KeyValueDelimiters is the sequence of delimiters that are used to separate key and value. By default, it is "=:". KeyValueDelimiters string // KeyValueDelimiterOnWrite is the delimiter that are used to separate key and value output. By default, it is "=". KeyValueDelimiterOnWrite string // ChildSectionDelimiter is the delimiter that is used to separate child sections. By default, it is ".". ChildSectionDelimiter string // PreserveSurroundedQuote indicates whether to preserve surrounded quote (single and double quotes). PreserveSurroundedQuote bool // DebugFunc is called to collect debug information (currently only useful to debug parsing Python-style multiline values). DebugFunc DebugFunc // ReaderBufferSize is the buffer size of the reader in bytes. ReaderBufferSize int // AllowNonUniqueSections indicates whether to allow sections with the same name multiple times. AllowNonUniqueSections bool // AllowDuplicateShadowValues indicates whether values for shadowed keys should be deduplicated. AllowDuplicateShadowValues bool } // DebugFunc is the type of function called to log parse events. type DebugFunc func(message string) // LoadSources allows caller to apply customized options for loading from data source(s). func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) { sources := make([]dataSource, len(others)+1) sources[0], err = parseDataSource(source) if err != nil { return nil, err } for i := range others { sources[i+1], err = parseDataSource(others[i]) if err != nil { return nil, err } } f := newFile(sources, opts) if err = f.Reload(); err != nil { return nil, err } return f, nil } // Load loads and parses from INI data sources. // Arguments can be mixed of file name with string type, or raw data in []byte. // It will return error if list contains nonexistent files. func Load(source interface{}, others ...interface{}) (*File, error) { return LoadSources(LoadOptions{}, source, others...) } // LooseLoad has exactly same functionality as Load function // except it ignores nonexistent files instead of returning error. func LooseLoad(source interface{}, others ...interface{}) (*File, error) { return LoadSources(LoadOptions{Loose: true}, source, others...) } // InsensitiveLoad has exactly same functionality as Load function // except it forces all section and key names to be lowercased. func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) { return LoadSources(LoadOptions{Insensitive: true}, source, others...) } // ShadowLoad has exactly same functionality as Load function // except it allows have shadow keys. func ShadowLoad(source interface{}, others ...interface{}) (*File, error) { return LoadSources(LoadOptions{AllowShadows: true}, source, others...) } ================================================ FILE: vendor/github.com/go-ini/ini/key.go ================================================ // Copyright 2014 Unknwon // // 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. package ini import ( "bytes" "errors" "fmt" "strconv" "strings" "time" ) // Key represents a key under a section. type Key struct { s *Section Comment string name string value string isAutoIncrement bool isBooleanType bool isShadow bool shadows []*Key nestedValues []string } // newKey simply return a key object with given values. func newKey(s *Section, name, val string) *Key { return &Key{ s: s, name: name, value: val, } } func (k *Key) addShadow(val string) error { if k.isShadow { return errors.New("cannot add shadow to another shadow key") } else if k.isAutoIncrement || k.isBooleanType { return errors.New("cannot add shadow to auto-increment or boolean key") } if !k.s.f.options.AllowDuplicateShadowValues { // Deduplicate shadows based on their values. if k.value == val { return nil } for i := range k.shadows { if k.shadows[i].value == val { return nil } } } shadow := newKey(k.s, k.name, val) shadow.isShadow = true k.shadows = append(k.shadows, shadow) return nil } // AddShadow adds a new shadow key to itself. func (k *Key) AddShadow(val string) error { if !k.s.f.options.AllowShadows { return errors.New("shadow key is not allowed") } return k.addShadow(val) } func (k *Key) addNestedValue(val string) error { if k.isAutoIncrement || k.isBooleanType { return errors.New("cannot add nested value to auto-increment or boolean key") } k.nestedValues = append(k.nestedValues, val) return nil } // AddNestedValue adds a nested value to the key. func (k *Key) AddNestedValue(val string) error { if !k.s.f.options.AllowNestedValues { return errors.New("nested value is not allowed") } return k.addNestedValue(val) } // ValueMapper represents a mapping function for values, e.g. os.ExpandEnv type ValueMapper func(string) string // Name returns name of key. func (k *Key) Name() string { return k.name } // Value returns raw value of key for performance purpose. func (k *Key) Value() string { return k.value } // ValueWithShadows returns raw values of key and its shadows if any. Shadow // keys with empty values are ignored from the returned list. func (k *Key) ValueWithShadows() []string { if len(k.shadows) == 0 { if k.value == "" { return []string{} } return []string{k.value} } vals := make([]string, 0, len(k.shadows)+1) if k.value != "" { vals = append(vals, k.value) } for _, s := range k.shadows { if s.value != "" { vals = append(vals, s.value) } } return vals } // NestedValues returns nested values stored in the key. // It is possible returned value is nil if no nested values stored in the key. func (k *Key) NestedValues() []string { return k.nestedValues } // transformValue takes a raw value and transforms to its final string. func (k *Key) transformValue(val string) string { if k.s.f.ValueMapper != nil { val = k.s.f.ValueMapper(val) } // Fail-fast if no indicate char found for recursive value if !strings.Contains(val, "%") { return val } for i := 0; i < depthValues; i++ { vr := varPattern.FindString(val) if len(vr) == 0 { break } // Take off leading '%(' and trailing ')s'. noption := vr[2 : len(vr)-2] // Search in the same section. // If not found or found the key itself, then search again in default section. nk, err := k.s.GetKey(noption) if err != nil || k == nk { nk, _ = k.s.f.Section("").GetKey(noption) if nk == nil { // Stop when no results found in the default section, // and returns the value as-is. break } } // Substitute by new value and take off leading '%(' and trailing ')s'. val = strings.Replace(val, vr, nk.value, -1) } return val } // String returns string representation of value. func (k *Key) String() string { return k.transformValue(k.value) } // Validate accepts a validate function which can // return modifed result as key value. func (k *Key) Validate(fn func(string) string) string { return fn(k.String()) } // parseBool returns the boolean value represented by the string. // // It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On, // 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off. // Any other value returns an error. func parseBool(str string) (value bool, err error) { switch str { case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On": return true, nil case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off": return false, nil } return false, fmt.Errorf("parsing \"%s\": invalid syntax", str) } // Bool returns bool type value. func (k *Key) Bool() (bool, error) { return parseBool(k.String()) } // Float64 returns float64 type value. func (k *Key) Float64() (float64, error) { return strconv.ParseFloat(k.String(), 64) } // Int returns int type value. func (k *Key) Int() (int, error) { v, err := strconv.ParseInt(k.String(), 0, 64) return int(v), err } // Int64 returns int64 type value. func (k *Key) Int64() (int64, error) { return strconv.ParseInt(k.String(), 0, 64) } // Uint returns uint type valued. func (k *Key) Uint() (uint, error) { u, e := strconv.ParseUint(k.String(), 0, 64) return uint(u), e } // Uint64 returns uint64 type value. func (k *Key) Uint64() (uint64, error) { return strconv.ParseUint(k.String(), 0, 64) } // Duration returns time.Duration type value. func (k *Key) Duration() (time.Duration, error) { return time.ParseDuration(k.String()) } // TimeFormat parses with given format and returns time.Time type value. func (k *Key) TimeFormat(format string) (time.Time, error) { return time.Parse(format, k.String()) } // Time parses with RFC3339 format and returns time.Time type value. func (k *Key) Time() (time.Time, error) { return k.TimeFormat(time.RFC3339) } // MustString returns default value if key value is empty. func (k *Key) MustString(defaultVal string) string { val := k.String() if len(val) == 0 { k.value = defaultVal return defaultVal } return val } // MustBool always returns value without error, // it returns false if error occurs. func (k *Key) MustBool(defaultVal ...bool) bool { val, err := k.Bool() if len(defaultVal) > 0 && err != nil { k.value = strconv.FormatBool(defaultVal[0]) return defaultVal[0] } return val } // MustFloat64 always returns value without error, // it returns 0.0 if error occurs. func (k *Key) MustFloat64(defaultVal ...float64) float64 { val, err := k.Float64() if len(defaultVal) > 0 && err != nil { k.value = strconv.FormatFloat(defaultVal[0], 'f', -1, 64) return defaultVal[0] } return val } // MustInt always returns value without error, // it returns 0 if error occurs. func (k *Key) MustInt(defaultVal ...int) int { val, err := k.Int() if len(defaultVal) > 0 && err != nil { k.value = strconv.FormatInt(int64(defaultVal[0]), 10) return defaultVal[0] } return val } // MustInt64 always returns value without error, // it returns 0 if error occurs. func (k *Key) MustInt64(defaultVal ...int64) int64 { val, err := k.Int64() if len(defaultVal) > 0 && err != nil { k.value = strconv.FormatInt(defaultVal[0], 10) return defaultVal[0] } return val } // MustUint always returns value without error, // it returns 0 if error occurs. func (k *Key) MustUint(defaultVal ...uint) uint { val, err := k.Uint() if len(defaultVal) > 0 && err != nil { k.value = strconv.FormatUint(uint64(defaultVal[0]), 10) return defaultVal[0] } return val } // MustUint64 always returns value without error, // it returns 0 if error occurs. func (k *Key) MustUint64(defaultVal ...uint64) uint64 { val, err := k.Uint64() if len(defaultVal) > 0 && err != nil { k.value = strconv.FormatUint(defaultVal[0], 10) return defaultVal[0] } return val } // MustDuration always returns value without error, // it returns zero value if error occurs. func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration { val, err := k.Duration() if len(defaultVal) > 0 && err != nil { k.value = defaultVal[0].String() return defaultVal[0] } return val } // MustTimeFormat always parses with given format and returns value without error, // it returns zero value if error occurs. func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time { val, err := k.TimeFormat(format) if len(defaultVal) > 0 && err != nil { k.value = defaultVal[0].Format(format) return defaultVal[0] } return val } // MustTime always parses with RFC3339 format and returns value without error, // it returns zero value if error occurs. func (k *Key) MustTime(defaultVal ...time.Time) time.Time { return k.MustTimeFormat(time.RFC3339, defaultVal...) } // In always returns value without error, // it returns default value if error occurs or doesn't fit into candidates. func (k *Key) In(defaultVal string, candidates []string) string { val := k.String() for _, cand := range candidates { if val == cand { return val } } return defaultVal } // InFloat64 always returns value without error, // it returns default value if error occurs or doesn't fit into candidates. func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 { val := k.MustFloat64() for _, cand := range candidates { if val == cand { return val } } return defaultVal } // InInt always returns value without error, // it returns default value if error occurs or doesn't fit into candidates. func (k *Key) InInt(defaultVal int, candidates []int) int { val := k.MustInt() for _, cand := range candidates { if val == cand { return val } } return defaultVal } // InInt64 always returns value without error, // it returns default value if error occurs or doesn't fit into candidates. func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 { val := k.MustInt64() for _, cand := range candidates { if val == cand { return val } } return defaultVal } // InUint always returns value without error, // it returns default value if error occurs or doesn't fit into candidates. func (k *Key) InUint(defaultVal uint, candidates []uint) uint { val := k.MustUint() for _, cand := range candidates { if val == cand { return val } } return defaultVal } // InUint64 always returns value without error, // it returns default value if error occurs or doesn't fit into candidates. func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 { val := k.MustUint64() for _, cand := range candidates { if val == cand { return val } } return defaultVal } // InTimeFormat always parses with given format and returns value without error, // it returns default value if error occurs or doesn't fit into candidates. func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time { val := k.MustTimeFormat(format) for _, cand := range candidates { if val == cand { return val } } return defaultVal } // InTime always parses with RFC3339 format and returns value without error, // it returns default value if error occurs or doesn't fit into candidates. func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time { return k.InTimeFormat(time.RFC3339, defaultVal, candidates) } // RangeFloat64 checks if value is in given range inclusively, // and returns default value if it's not. func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 { val := k.MustFloat64() if val < min || val > max { return defaultVal } return val } // RangeInt checks if value is in given range inclusively, // and returns default value if it's not. func (k *Key) RangeInt(defaultVal, min, max int) int { val := k.MustInt() if val < min || val > max { return defaultVal } return val } // RangeInt64 checks if value is in given range inclusively, // and returns default value if it's not. func (k *Key) RangeInt64(defaultVal, min, max int64) int64 { val := k.MustInt64() if val < min || val > max { return defaultVal } return val } // RangeTimeFormat checks if value with given format is in given range inclusively, // and returns default value if it's not. func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time { val := k.MustTimeFormat(format) if val.Unix() < min.Unix() || val.Unix() > max.Unix() { return defaultVal } return val } // RangeTime checks if value with RFC3339 format is in given range inclusively, // and returns default value if it's not. func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time { return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max) } // Strings returns list of string divided by given delimiter. func (k *Key) Strings(delim string) []string { str := k.String() if len(str) == 0 { return []string{} } runes := []rune(str) vals := make([]string, 0, 2) var buf bytes.Buffer escape := false idx := 0 for { if escape { escape = false if runes[idx] != '\\' && !strings.HasPrefix(string(runes[idx:]), delim) { buf.WriteRune('\\') } buf.WriteRune(runes[idx]) } else { if runes[idx] == '\\' { escape = true } else if strings.HasPrefix(string(runes[idx:]), delim) { idx += len(delim) - 1 vals = append(vals, strings.TrimSpace(buf.String())) buf.Reset() } else { buf.WriteRune(runes[idx]) } } idx++ if idx == len(runes) { break } } if buf.Len() > 0 { vals = append(vals, strings.TrimSpace(buf.String())) } return vals } // StringsWithShadows returns list of string divided by given delimiter. // Shadows will also be appended if any. func (k *Key) StringsWithShadows(delim string) []string { vals := k.ValueWithShadows() results := make([]string, 0, len(vals)*2) for i := range vals { if len(vals) == 0 { continue } results = append(results, strings.Split(vals[i], delim)...) } for i := range results { results[i] = k.transformValue(strings.TrimSpace(results[i])) } return results } // Float64s returns list of float64 divided by given delimiter. Any invalid input will be treated as zero value. func (k *Key) Float64s(delim string) []float64 { vals, _ := k.parseFloat64s(k.Strings(delim), true, false) return vals } // Ints returns list of int divided by given delimiter. Any invalid input will be treated as zero value. func (k *Key) Ints(delim string) []int { vals, _ := k.parseInts(k.Strings(delim), true, false) return vals } // Int64s returns list of int64 divided by given delimiter. Any invalid input will be treated as zero value. func (k *Key) Int64s(delim string) []int64 { vals, _ := k.parseInt64s(k.Strings(delim), true, false) return vals } // Uints returns list of uint divided by given delimiter. Any invalid input will be treated as zero value. func (k *Key) Uints(delim string) []uint { vals, _ := k.parseUints(k.Strings(delim), true, false) return vals } // Uint64s returns list of uint64 divided by given delimiter. Any invalid input will be treated as zero value. func (k *Key) Uint64s(delim string) []uint64 { vals, _ := k.parseUint64s(k.Strings(delim), true, false) return vals } // Bools returns list of bool divided by given delimiter. Any invalid input will be treated as zero value. func (k *Key) Bools(delim string) []bool { vals, _ := k.parseBools(k.Strings(delim), true, false) return vals } // TimesFormat parses with given format and returns list of time.Time divided by given delimiter. // Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC). func (k *Key) TimesFormat(format, delim string) []time.Time { vals, _ := k.parseTimesFormat(format, k.Strings(delim), true, false) return vals } // Times parses with RFC3339 format and returns list of time.Time divided by given delimiter. // Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC). func (k *Key) Times(delim string) []time.Time { return k.TimesFormat(time.RFC3339, delim) } // ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then // it will not be included to result list. func (k *Key) ValidFloat64s(delim string) []float64 { vals, _ := k.parseFloat64s(k.Strings(delim), false, false) return vals } // ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will // not be included to result list. func (k *Key) ValidInts(delim string) []int { vals, _ := k.parseInts(k.Strings(delim), false, false) return vals } // ValidInt64s returns list of int64 divided by given delimiter. If some value is not 64-bit integer, // then it will not be included to result list. func (k *Key) ValidInt64s(delim string) []int64 { vals, _ := k.parseInt64s(k.Strings(delim), false, false) return vals } // ValidUints returns list of uint divided by given delimiter. If some value is not unsigned integer, // then it will not be included to result list. func (k *Key) ValidUints(delim string) []uint { vals, _ := k.parseUints(k.Strings(delim), false, false) return vals } // ValidUint64s returns list of uint64 divided by given delimiter. If some value is not 64-bit unsigned // integer, then it will not be included to result list. func (k *Key) ValidUint64s(delim string) []uint64 { vals, _ := k.parseUint64s(k.Strings(delim), false, false) return vals } // ValidBools returns list of bool divided by given delimiter. If some value is not 64-bit unsigned // integer, then it will not be included to result list. func (k *Key) ValidBools(delim string) []bool { vals, _ := k.parseBools(k.Strings(delim), false, false) return vals } // ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter. func (k *Key) ValidTimesFormat(format, delim string) []time.Time { vals, _ := k.parseTimesFormat(format, k.Strings(delim), false, false) return vals } // ValidTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter. func (k *Key) ValidTimes(delim string) []time.Time { return k.ValidTimesFormat(time.RFC3339, delim) } // StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input. func (k *Key) StrictFloat64s(delim string) ([]float64, error) { return k.parseFloat64s(k.Strings(delim), false, true) } // StrictInts returns list of int divided by given delimiter or error on first invalid input. func (k *Key) StrictInts(delim string) ([]int, error) { return k.parseInts(k.Strings(delim), false, true) } // StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input. func (k *Key) StrictInt64s(delim string) ([]int64, error) { return k.parseInt64s(k.Strings(delim), false, true) } // StrictUints returns list of uint divided by given delimiter or error on first invalid input. func (k *Key) StrictUints(delim string) ([]uint, error) { return k.parseUints(k.Strings(delim), false, true) } // StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input. func (k *Key) StrictUint64s(delim string) ([]uint64, error) { return k.parseUint64s(k.Strings(delim), false, true) } // StrictBools returns list of bool divided by given delimiter or error on first invalid input. func (k *Key) StrictBools(delim string) ([]bool, error) { return k.parseBools(k.Strings(delim), false, true) } // StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter // or error on first invalid input. func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) { return k.parseTimesFormat(format, k.Strings(delim), false, true) } // StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter // or error on first invalid input. func (k *Key) StrictTimes(delim string) ([]time.Time, error) { return k.StrictTimesFormat(time.RFC3339, delim) } // parseBools transforms strings to bools. func (k *Key) parseBools(strs []string, addInvalid, returnOnInvalid bool) ([]bool, error) { vals := make([]bool, 0, len(strs)) parser := func(str string) (interface{}, error) { val, err := parseBool(str) return val, err } rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser) if err == nil { for _, val := range rawVals { vals = append(vals, val.(bool)) } } return vals, err } // parseFloat64s transforms strings to float64s. func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) { vals := make([]float64, 0, len(strs)) parser := func(str string) (interface{}, error) { val, err := strconv.ParseFloat(str, 64) return val, err } rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser) if err == nil { for _, val := range rawVals { vals = append(vals, val.(float64)) } } return vals, err } // parseInts transforms strings to ints. func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) { vals := make([]int, 0, len(strs)) parser := func(str string) (interface{}, error) { val, err := strconv.ParseInt(str, 0, 64) return val, err } rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser) if err == nil { for _, val := range rawVals { vals = append(vals, int(val.(int64))) } } return vals, err } // parseInt64s transforms strings to int64s. func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) { vals := make([]int64, 0, len(strs)) parser := func(str string) (interface{}, error) { val, err := strconv.ParseInt(str, 0, 64) return val, err } rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser) if err == nil { for _, val := range rawVals { vals = append(vals, val.(int64)) } } return vals, err } // parseUints transforms strings to uints. func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) { vals := make([]uint, 0, len(strs)) parser := func(str string) (interface{}, error) { val, err := strconv.ParseUint(str, 0, 64) return val, err } rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser) if err == nil { for _, val := range rawVals { vals = append(vals, uint(val.(uint64))) } } return vals, err } // parseUint64s transforms strings to uint64s. func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) { vals := make([]uint64, 0, len(strs)) parser := func(str string) (interface{}, error) { val, err := strconv.ParseUint(str, 0, 64) return val, err } rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser) if err == nil { for _, val := range rawVals { vals = append(vals, val.(uint64)) } } return vals, err } type Parser func(str string) (interface{}, error) // parseTimesFormat transforms strings to times in given format. func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) { vals := make([]time.Time, 0, len(strs)) parser := func(str string) (interface{}, error) { val, err := time.Parse(format, str) return val, err } rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser) if err == nil { for _, val := range rawVals { vals = append(vals, val.(time.Time)) } } return vals, err } // doParse transforms strings to different types func (k *Key) doParse(strs []string, addInvalid, returnOnInvalid bool, parser Parser) ([]interface{}, error) { vals := make([]interface{}, 0, len(strs)) for _, str := range strs { val, err := parser(str) if err != nil && returnOnInvalid { return nil, err } if err == nil || addInvalid { vals = append(vals, val) } } return vals, nil } // SetValue changes key value. func (k *Key) SetValue(v string) { if k.s.f.BlockMode { k.s.f.lock.Lock() defer k.s.f.lock.Unlock() } k.value = v k.s.keysHash[k.name] = v } ================================================ FILE: vendor/github.com/go-ini/ini/parser.go ================================================ // Copyright 2015 Unknwon // // 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. package ini import ( "bufio" "bytes" "fmt" "io" "regexp" "strconv" "strings" "unicode" ) const minReaderBufferSize = 4096 var pythonMultiline = regexp.MustCompile(`^([\t\f ]+)(.*)`) type parserOptions struct { IgnoreContinuation bool IgnoreInlineComment bool AllowPythonMultilineValues bool SpaceBeforeInlineComment bool UnescapeValueDoubleQuotes bool UnescapeValueCommentSymbols bool PreserveSurroundedQuote bool DebugFunc DebugFunc ReaderBufferSize int } type parser struct { buf *bufio.Reader options parserOptions isEOF bool count int comment *bytes.Buffer } func (p *parser) debug(format string, args ...interface{}) { if p.options.DebugFunc != nil { p.options.DebugFunc(fmt.Sprintf(format, args...)) } } func newParser(r io.Reader, opts parserOptions) *parser { size := opts.ReaderBufferSize if size < minReaderBufferSize { size = minReaderBufferSize } return &parser{ buf: bufio.NewReaderSize(r, size), options: opts, count: 1, comment: &bytes.Buffer{}, } } // BOM handles header of UTF-8, UTF-16 LE and UTF-16 BE's BOM format. // http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding func (p *parser) BOM() error { mask, err := p.buf.Peek(2) if err != nil && err != io.EOF { return err } else if len(mask) < 2 { return nil } switch { case mask[0] == 254 && mask[1] == 255: fallthrough case mask[0] == 255 && mask[1] == 254: _, err = p.buf.Read(mask) if err != nil { return err } case mask[0] == 239 && mask[1] == 187: mask, err := p.buf.Peek(3) if err != nil && err != io.EOF { return err } else if len(mask) < 3 { return nil } if mask[2] == 191 { _, err = p.buf.Read(mask) if err != nil { return err } } } return nil } func (p *parser) readUntil(delim byte) ([]byte, error) { data, err := p.buf.ReadBytes(delim) if err != nil { if err == io.EOF { p.isEOF = true } else { return nil, err } } return data, nil } func cleanComment(in []byte) ([]byte, bool) { i := bytes.IndexAny(in, "#;") if i == -1 { return nil, false } return in[i:], true } func readKeyName(delimiters string, in []byte) (string, int, error) { line := string(in) // Check if key name surrounded by quotes. var keyQuote string if line[0] == '"' { if len(line) > 6 && line[0:3] == `"""` { keyQuote = `"""` } else { keyQuote = `"` } } else if line[0] == '`' { keyQuote = "`" } // Get out key name var endIdx int if len(keyQuote) > 0 { startIdx := len(keyQuote) // FIXME: fail case -> """"""name"""=value pos := strings.Index(line[startIdx:], keyQuote) if pos == -1 { return "", -1, fmt.Errorf("missing closing key quote: %s", line) } pos += startIdx // Find key-value delimiter i := strings.IndexAny(line[pos+startIdx:], delimiters) if i < 0 { return "", -1, ErrDelimiterNotFound{line} } endIdx = pos + i return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil } endIdx = strings.IndexAny(line, delimiters) if endIdx < 0 { return "", -1, ErrDelimiterNotFound{line} } if endIdx == 0 { return "", -1, ErrEmptyKeyName{line} } return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil } func (p *parser) readMultilines(line, val, valQuote string) (string, error) { for { data, err := p.readUntil('\n') if err != nil { return "", err } next := string(data) pos := strings.LastIndex(next, valQuote) if pos > -1 { val += next[:pos] comment, has := cleanComment([]byte(next[pos:])) if has { p.comment.Write(bytes.TrimSpace(comment)) } break } val += next if p.isEOF { return "", fmt.Errorf("missing closing key quote from %q to %q", line, next) } } return val, nil } func (p *parser) readContinuationLines(val string) (string, error) { for { data, err := p.readUntil('\n') if err != nil { return "", err } next := strings.TrimSpace(string(data)) if len(next) == 0 { break } val += next if val[len(val)-1] != '\\' { break } val = val[:len(val)-1] } return val, nil } // hasSurroundedQuote check if and only if the first and last characters // are quotes \" or \'. // It returns false if any other parts also contain same kind of quotes. func hasSurroundedQuote(in string, quote byte) bool { return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote && strings.IndexByte(in[1:], quote) == len(in)-2 } func (p *parser) readValue(in []byte, bufferSize int) (string, error) { line := strings.TrimLeftFunc(string(in), unicode.IsSpace) if len(line) == 0 { if p.options.AllowPythonMultilineValues && len(in) > 0 && in[len(in)-1] == '\n' { return p.readPythonMultilines(line, bufferSize) } return "", nil } var valQuote string if len(line) > 3 && line[0:3] == `"""` { valQuote = `"""` } else if line[0] == '`' { valQuote = "`" } else if p.options.UnescapeValueDoubleQuotes && line[0] == '"' { valQuote = `"` } if len(valQuote) > 0 { startIdx := len(valQuote) pos := strings.LastIndex(line[startIdx:], valQuote) // Check for multi-line value if pos == -1 { return p.readMultilines(line, line[startIdx:], valQuote) } if p.options.UnescapeValueDoubleQuotes && valQuote == `"` { return strings.Replace(line[startIdx:pos+startIdx], `\"`, `"`, -1), nil } return line[startIdx : pos+startIdx], nil } lastChar := line[len(line)-1] // Won't be able to reach here if value only contains whitespace line = strings.TrimSpace(line) trimmedLastChar := line[len(line)-1] // Check continuation lines when desired if !p.options.IgnoreContinuation && trimmedLastChar == '\\' { return p.readContinuationLines(line[:len(line)-1]) } // Check if ignore inline comment if !p.options.IgnoreInlineComment { var i int if p.options.SpaceBeforeInlineComment { i = strings.Index(line, " #") if i == -1 { i = strings.Index(line, " ;") } } else { i = strings.IndexAny(line, "#;") } if i > -1 { p.comment.WriteString(line[i:]) line = strings.TrimSpace(line[:i]) } } // Trim single and double quotes if (hasSurroundedQuote(line, '\'') || hasSurroundedQuote(line, '"')) && !p.options.PreserveSurroundedQuote { line = line[1 : len(line)-1] } else if len(valQuote) == 0 && p.options.UnescapeValueCommentSymbols { line = strings.ReplaceAll(line, `\;`, ";") line = strings.ReplaceAll(line, `\#`, "#") } else if p.options.AllowPythonMultilineValues && lastChar == '\n' { return p.readPythonMultilines(line, bufferSize) } return line, nil } func (p *parser) readPythonMultilines(line string, bufferSize int) (string, error) { parserBufferPeekResult, _ := p.buf.Peek(bufferSize) peekBuffer := bytes.NewBuffer(parserBufferPeekResult) for { peekData, peekErr := peekBuffer.ReadBytes('\n') if peekErr != nil && peekErr != io.EOF { p.debug("readPythonMultilines: failed to peek with error: %v", peekErr) return "", peekErr } p.debug("readPythonMultilines: parsing %q", string(peekData)) peekMatches := pythonMultiline.FindStringSubmatch(string(peekData)) p.debug("readPythonMultilines: matched %d parts", len(peekMatches)) for n, v := range peekMatches { p.debug(" %d: %q", n, v) } // Return if not a Python multiline value. if len(peekMatches) != 3 { p.debug("readPythonMultilines: end of value, got: %q", line) return line, nil } // Advance the parser reader (buffer) in-sync with the peek buffer. _, err := p.buf.Discard(len(peekData)) if err != nil { p.debug("readPythonMultilines: failed to skip to the end, returning error") return "", err } line += "\n" + peekMatches[0] } } // parse parses data through an io.Reader. func (f *File) parse(reader io.Reader) (err error) { p := newParser(reader, parserOptions{ IgnoreContinuation: f.options.IgnoreContinuation, IgnoreInlineComment: f.options.IgnoreInlineComment, AllowPythonMultilineValues: f.options.AllowPythonMultilineValues, SpaceBeforeInlineComment: f.options.SpaceBeforeInlineComment, UnescapeValueDoubleQuotes: f.options.UnescapeValueDoubleQuotes, UnescapeValueCommentSymbols: f.options.UnescapeValueCommentSymbols, PreserveSurroundedQuote: f.options.PreserveSurroundedQuote, DebugFunc: f.options.DebugFunc, ReaderBufferSize: f.options.ReaderBufferSize, }) if err = p.BOM(); err != nil { return fmt.Errorf("BOM: %v", err) } // Ignore error because default section name is never empty string. name := DefaultSection if f.options.Insensitive || f.options.InsensitiveSections { name = strings.ToLower(DefaultSection) } section, _ := f.NewSection(name) // This "last" is not strictly equivalent to "previous one" if current key is not the first nested key var isLastValueEmpty bool var lastRegularKey *Key var line []byte var inUnparseableSection bool // NOTE: Iterate and increase `currentPeekSize` until // the size of the parser buffer is found. // TODO(unknwon): When Golang 1.10 is the lowest version supported, replace with `parserBufferSize := p.buf.Size()`. parserBufferSize := 0 // NOTE: Peek 4kb at a time. currentPeekSize := minReaderBufferSize if f.options.AllowPythonMultilineValues { for { peekBytes, _ := p.buf.Peek(currentPeekSize) peekBytesLength := len(peekBytes) if parserBufferSize >= peekBytesLength { break } currentPeekSize *= 2 parserBufferSize = peekBytesLength } } for !p.isEOF { line, err = p.readUntil('\n') if err != nil { return err } if f.options.AllowNestedValues && isLastValueEmpty && len(line) > 0 { if line[0] == ' ' || line[0] == '\t' { err = lastRegularKey.addNestedValue(string(bytes.TrimSpace(line))) if err != nil { return err } continue } } line = bytes.TrimLeftFunc(line, unicode.IsSpace) if len(line) == 0 { continue } // Comments if line[0] == '#' || line[0] == ';' { // Note: we do not care ending line break, // it is needed for adding second line, // so just clean it once at the end when set to value. p.comment.Write(line) continue } // Section if line[0] == '[' { // Read to the next ']' (TODO: support quoted strings) closeIdx := bytes.LastIndexByte(line, ']') if closeIdx == -1 { return fmt.Errorf("unclosed section: %s", line) } name := string(line[1:closeIdx]) section, err = f.NewSection(name) if err != nil { return err } comment, has := cleanComment(line[closeIdx+1:]) if has { p.comment.Write(comment) } section.Comment = strings.TrimSpace(p.comment.String()) // Reset auto-counter and comments p.comment.Reset() p.count = 1 // Nested values can't span sections isLastValueEmpty = false inUnparseableSection = false for i := range f.options.UnparseableSections { if f.options.UnparseableSections[i] == name || ((f.options.Insensitive || f.options.InsensitiveSections) && strings.EqualFold(f.options.UnparseableSections[i], name)) { inUnparseableSection = true continue } } continue } if inUnparseableSection { section.isRawSection = true section.rawBody += string(line) continue } kname, offset, err := readKeyName(f.options.KeyValueDelimiters, line) if err != nil { switch { // Treat as boolean key when desired, and whole line is key name. case IsErrDelimiterNotFound(err): switch { case f.options.AllowBooleanKeys: kname, err := p.readValue(line, parserBufferSize) if err != nil { return err } key, err := section.NewBooleanKey(kname) if err != nil { return err } key.Comment = strings.TrimSpace(p.comment.String()) p.comment.Reset() continue case f.options.SkipUnrecognizableLines: continue } case IsErrEmptyKeyName(err) && f.options.SkipUnrecognizableLines: continue } return err } // Auto increment. isAutoIncr := false if kname == "-" { isAutoIncr = true kname = "#" + strconv.Itoa(p.count) p.count++ } value, err := p.readValue(line[offset:], parserBufferSize) if err != nil { return err } isLastValueEmpty = len(value) == 0 key, err := section.NewKey(kname, value) if err != nil { return err } key.isAutoIncrement = isAutoIncr key.Comment = strings.TrimSpace(p.comment.String()) p.comment.Reset() lastRegularKey = key } return nil } ================================================ FILE: vendor/github.com/go-ini/ini/section.go ================================================ // Copyright 2014 Unknwon // // 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. package ini import ( "errors" "fmt" "strings" ) // Section represents a config section. type Section struct { f *File Comment string name string keys map[string]*Key keyList []string keysHash map[string]string isRawSection bool rawBody string } func newSection(f *File, name string) *Section { return &Section{ f: f, name: name, keys: make(map[string]*Key), keyList: make([]string, 0, 10), keysHash: make(map[string]string), } } // Name returns name of Section. func (s *Section) Name() string { return s.name } // Body returns rawBody of Section if the section was marked as unparseable. // It still follows the other rules of the INI format surrounding leading/trailing whitespace. func (s *Section) Body() string { return strings.TrimSpace(s.rawBody) } // SetBody updates body content only if section is raw. func (s *Section) SetBody(body string) { if !s.isRawSection { return } s.rawBody = body } // NewKey creates a new key to given section. func (s *Section) NewKey(name, val string) (*Key, error) { if len(name) == 0 { return nil, errors.New("error creating new key: empty key name") } else if s.f.options.Insensitive || s.f.options.InsensitiveKeys { name = strings.ToLower(name) } if s.f.BlockMode { s.f.lock.Lock() defer s.f.lock.Unlock() } if inSlice(name, s.keyList) { if s.f.options.AllowShadows { if err := s.keys[name].addShadow(val); err != nil { return nil, err } } else { s.keys[name].value = val s.keysHash[name] = val } return s.keys[name], nil } s.keyList = append(s.keyList, name) s.keys[name] = newKey(s, name, val) s.keysHash[name] = val return s.keys[name], nil } // NewBooleanKey creates a new boolean type key to given section. func (s *Section) NewBooleanKey(name string) (*Key, error) { key, err := s.NewKey(name, "true") if err != nil { return nil, err } key.isBooleanType = true return key, nil } // GetKey returns key in section by given name. func (s *Section) GetKey(name string) (*Key, error) { if s.f.BlockMode { s.f.lock.RLock() } if s.f.options.Insensitive || s.f.options.InsensitiveKeys { name = strings.ToLower(name) } key := s.keys[name] if s.f.BlockMode { s.f.lock.RUnlock() } if key == nil { // Check if it is a child-section. sname := s.name for { if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 { sname = sname[:i] sec, err := s.f.GetSection(sname) if err != nil { continue } return sec.GetKey(name) } break } return nil, fmt.Errorf("error when getting key of section %q: key %q not exists", s.name, name) } return key, nil } // HasKey returns true if section contains a key with given name. func (s *Section) HasKey(name string) bool { key, _ := s.GetKey(name) return key != nil } // Deprecated: Use "HasKey" instead. func (s *Section) Haskey(name string) bool { return s.HasKey(name) } // HasValue returns true if section contains given raw value. func (s *Section) HasValue(value string) bool { if s.f.BlockMode { s.f.lock.RLock() defer s.f.lock.RUnlock() } for _, k := range s.keys { if value == k.value { return true } } return false } // Key assumes named Key exists in section and returns a zero-value when not. func (s *Section) Key(name string) *Key { key, err := s.GetKey(name) if err != nil { // It's OK here because the only possible error is empty key name, // but if it's empty, this piece of code won't be executed. key, _ = s.NewKey(name, "") return key } return key } // Keys returns list of keys of section. func (s *Section) Keys() []*Key { keys := make([]*Key, len(s.keyList)) for i := range s.keyList { keys[i] = s.Key(s.keyList[i]) } return keys } // ParentKeys returns list of keys of parent section. func (s *Section) ParentKeys() []*Key { var parentKeys []*Key sname := s.name for { if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 { sname = sname[:i] sec, err := s.f.GetSection(sname) if err != nil { continue } parentKeys = append(parentKeys, sec.Keys()...) } else { break } } return parentKeys } // KeyStrings returns list of key names of section. func (s *Section) KeyStrings() []string { list := make([]string, len(s.keyList)) copy(list, s.keyList) return list } // KeysHash returns keys hash consisting of names and values. func (s *Section) KeysHash() map[string]string { if s.f.BlockMode { s.f.lock.RLock() defer s.f.lock.RUnlock() } hash := make(map[string]string, len(s.keysHash)) for key, value := range s.keysHash { hash[key] = value } return hash } // DeleteKey deletes a key from section. func (s *Section) DeleteKey(name string) { if s.f.BlockMode { s.f.lock.Lock() defer s.f.lock.Unlock() } for i, k := range s.keyList { if k == name { s.keyList = append(s.keyList[:i], s.keyList[i+1:]...) delete(s.keys, name) delete(s.keysHash, name) return } } } // ChildSections returns a list of child sections of current section. // For example, "[parent.child1]" and "[parent.child12]" are child sections // of section "[parent]". func (s *Section) ChildSections() []*Section { prefix := s.name + s.f.options.ChildSectionDelimiter children := make([]*Section, 0, 3) for _, name := range s.f.sectionList { if strings.HasPrefix(name, prefix) { children = append(children, s.f.sections[name]...) } } return children } ================================================ FILE: vendor/github.com/go-ini/ini/struct.go ================================================ // Copyright 2014 Unknwon // // 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. package ini import ( "bytes" "errors" "fmt" "reflect" "strings" "time" "unicode" ) // NameMapper represents a ini tag name mapper. type NameMapper func(string) string // Built-in name getters. var ( // SnackCase converts to format SNACK_CASE. SnackCase NameMapper = func(raw string) string { newstr := make([]rune, 0, len(raw)) for i, chr := range raw { if isUpper := 'A' <= chr && chr <= 'Z'; isUpper { if i > 0 { newstr = append(newstr, '_') } } newstr = append(newstr, unicode.ToUpper(chr)) } return string(newstr) } // TitleUnderscore converts to format title_underscore. TitleUnderscore NameMapper = func(raw string) string { newstr := make([]rune, 0, len(raw)) for i, chr := range raw { if isUpper := 'A' <= chr && chr <= 'Z'; isUpper { if i > 0 { newstr = append(newstr, '_') } chr -= 'A' - 'a' } newstr = append(newstr, chr) } return string(newstr) } ) func (s *Section) parseFieldName(raw, actual string) string { if len(actual) > 0 { return actual } if s.f.NameMapper != nil { return s.f.NameMapper(raw) } return raw } func parseDelim(actual string) string { if len(actual) > 0 { return actual } return "," } var reflectTime = reflect.TypeOf(time.Now()).Kind() // setSliceWithProperType sets proper values to slice based on its type. func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error { var strs []string if allowShadow { strs = key.StringsWithShadows(delim) } else { strs = key.Strings(delim) } numVals := len(strs) if numVals == 0 { return nil } var vals interface{} var err error sliceOf := field.Type().Elem().Kind() switch sliceOf { case reflect.String: vals = strs case reflect.Int: vals, err = key.parseInts(strs, true, false) case reflect.Int64: vals, err = key.parseInt64s(strs, true, false) case reflect.Uint: vals, err = key.parseUints(strs, true, false) case reflect.Uint64: vals, err = key.parseUint64s(strs, true, false) case reflect.Float64: vals, err = key.parseFloat64s(strs, true, false) case reflect.Bool: vals, err = key.parseBools(strs, true, false) case reflectTime: vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false) default: return fmt.Errorf("unsupported type '[]%s'", sliceOf) } if err != nil && isStrict { return err } slice := reflect.MakeSlice(field.Type(), numVals, numVals) for i := 0; i < numVals; i++ { switch sliceOf { case reflect.String: slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i])) case reflect.Int: slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i])) case reflect.Int64: slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i])) case reflect.Uint: slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i])) case reflect.Uint64: slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i])) case reflect.Float64: slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i])) case reflect.Bool: slice.Index(i).Set(reflect.ValueOf(vals.([]bool)[i])) case reflectTime: slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i])) } } field.Set(slice) return nil } func wrapStrictError(err error, isStrict bool) error { if isStrict { return err } return nil } // setWithProperType sets proper value to field based on its type, // but it does not return error for failing parsing, // because we want to use default value that is already assigned to struct. func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error { vt := t isPtr := t.Kind() == reflect.Ptr if isPtr { vt = t.Elem() } switch vt.Kind() { case reflect.String: stringVal := key.String() if isPtr { field.Set(reflect.ValueOf(&stringVal)) } else if len(stringVal) > 0 { field.SetString(key.String()) } case reflect.Bool: boolVal, err := key.Bool() if err != nil { return wrapStrictError(err, isStrict) } if isPtr { field.Set(reflect.ValueOf(&boolVal)) } else { field.SetBool(boolVal) } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: // ParseDuration will not return err for `0`, so check the type name if vt.Name() == "Duration" { durationVal, err := key.Duration() if err != nil { if intVal, err := key.Int64(); err == nil { field.SetInt(intVal) return nil } return wrapStrictError(err, isStrict) } if isPtr { field.Set(reflect.ValueOf(&durationVal)) } else if int64(durationVal) > 0 { field.Set(reflect.ValueOf(durationVal)) } return nil } intVal, err := key.Int64() if err != nil { return wrapStrictError(err, isStrict) } if isPtr { pv := reflect.New(t.Elem()) pv.Elem().SetInt(intVal) field.Set(pv) } else { field.SetInt(intVal) } // byte is an alias for uint8, so supporting uint8 breaks support for byte case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: durationVal, err := key.Duration() // Skip zero value if err == nil && uint64(durationVal) > 0 { if isPtr { field.Set(reflect.ValueOf(&durationVal)) } else { field.Set(reflect.ValueOf(durationVal)) } return nil } uintVal, err := key.Uint64() if err != nil { return wrapStrictError(err, isStrict) } if isPtr { pv := reflect.New(t.Elem()) pv.Elem().SetUint(uintVal) field.Set(pv) } else { field.SetUint(uintVal) } case reflect.Float32, reflect.Float64: floatVal, err := key.Float64() if err != nil { return wrapStrictError(err, isStrict) } if isPtr { pv := reflect.New(t.Elem()) pv.Elem().SetFloat(floatVal) field.Set(pv) } else { field.SetFloat(floatVal) } case reflectTime: timeVal, err := key.Time() if err != nil { return wrapStrictError(err, isStrict) } if isPtr { field.Set(reflect.ValueOf(&timeVal)) } else { field.Set(reflect.ValueOf(timeVal)) } case reflect.Slice: return setSliceWithProperType(key, field, delim, allowShadow, isStrict) default: return fmt.Errorf("unsupported type %q", t) } return nil } func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool, allowNonUnique bool, extends bool) { opts := strings.SplitN(tag, ",", 5) rawName = opts[0] for _, opt := range opts[1:] { omitEmpty = omitEmpty || (opt == "omitempty") allowShadow = allowShadow || (opt == "allowshadow") allowNonUnique = allowNonUnique || (opt == "nonunique") extends = extends || (opt == "extends") } return rawName, omitEmpty, allowShadow, allowNonUnique, extends } // mapToField maps the given value to the matching field of the given section. // The sectionIndex is the index (if non unique sections are enabled) to which the value should be added. func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int, sectionName string) error { if val.Kind() == reflect.Ptr { val = val.Elem() } typ := val.Type() for i := 0; i < typ.NumField(); i++ { field := val.Field(i) tpField := typ.Field(i) tag := tpField.Tag.Get("ini") if tag == "-" { continue } rawName, _, allowShadow, allowNonUnique, extends := parseTagOptions(tag) fieldName := s.parseFieldName(tpField.Name, rawName) if len(fieldName) == 0 || !field.CanSet() { continue } isStruct := tpField.Type.Kind() == reflect.Struct isStructPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct isAnonymousPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous if isAnonymousPtr { field.Set(reflect.New(tpField.Type.Elem())) } if extends && (isAnonymousPtr || (isStruct && tpField.Anonymous)) { if isStructPtr && field.IsNil() { field.Set(reflect.New(tpField.Type.Elem())) } fieldSection := s if rawName != "" { sectionName = s.name + s.f.options.ChildSectionDelimiter + rawName if secs, err := s.f.SectionsByName(sectionName); err == nil && sectionIndex < len(secs) { fieldSection = secs[sectionIndex] } } if err := fieldSection.mapToField(field, isStrict, sectionIndex, sectionName); err != nil { return fmt.Errorf("map to field %q: %v", fieldName, err) } } else if isAnonymousPtr || isStruct || isStructPtr { if secs, err := s.f.SectionsByName(fieldName); err == nil { if len(secs) <= sectionIndex { return fmt.Errorf("there are not enough sections (%d <= %d) for the field %q", len(secs), sectionIndex, fieldName) } // Only set the field to non-nil struct value if we have a section for it. // Otherwise, we end up with a non-nil struct ptr even though there is no data. if isStructPtr && field.IsNil() { field.Set(reflect.New(tpField.Type.Elem())) } if err = secs[sectionIndex].mapToField(field, isStrict, sectionIndex, fieldName); err != nil { return fmt.Errorf("map to field %q: %v", fieldName, err) } continue } } // Map non-unique sections if allowNonUnique && tpField.Type.Kind() == reflect.Slice { newField, err := s.mapToSlice(fieldName, field, isStrict) if err != nil { return fmt.Errorf("map to slice %q: %v", fieldName, err) } field.Set(newField) continue } if key, err := s.GetKey(fieldName); err == nil { delim := parseDelim(tpField.Tag.Get("delim")) if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil { return fmt.Errorf("set field %q: %v", fieldName, err) } } } return nil } // mapToSlice maps all sections with the same name and returns the new value. // The type of the Value must be a slice. func (s *Section) mapToSlice(secName string, val reflect.Value, isStrict bool) (reflect.Value, error) { secs, err := s.f.SectionsByName(secName) if err != nil { return reflect.Value{}, err } typ := val.Type().Elem() for i, sec := range secs { elem := reflect.New(typ) if err = sec.mapToField(elem, isStrict, i, sec.name); err != nil { return reflect.Value{}, fmt.Errorf("map to field from section %q: %v", secName, err) } val = reflect.Append(val, elem.Elem()) } return val, nil } // mapTo maps a section to object v. func (s *Section) mapTo(v interface{}, isStrict bool) error { typ := reflect.TypeOf(v) val := reflect.ValueOf(v) if typ.Kind() == reflect.Ptr { typ = typ.Elem() val = val.Elem() } else { return errors.New("not a pointer to a struct") } if typ.Kind() == reflect.Slice { newField, err := s.mapToSlice(s.name, val, isStrict) if err != nil { return err } val.Set(newField) return nil } return s.mapToField(val, isStrict, 0, s.name) } // MapTo maps section to given struct. func (s *Section) MapTo(v interface{}) error { return s.mapTo(v, false) } // StrictMapTo maps section to given struct in strict mode, // which returns all possible error including value parsing error. func (s *Section) StrictMapTo(v interface{}) error { return s.mapTo(v, true) } // MapTo maps file to given struct. func (f *File) MapTo(v interface{}) error { return f.Section("").MapTo(v) } // StrictMapTo maps file to given struct in strict mode, // which returns all possible error including value parsing error. func (f *File) StrictMapTo(v interface{}) error { return f.Section("").StrictMapTo(v) } // MapToWithMapper maps data sources to given struct with name mapper. func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error { cfg, err := Load(source, others...) if err != nil { return err } cfg.NameMapper = mapper return cfg.MapTo(v) } // StrictMapToWithMapper maps data sources to given struct with name mapper in strict mode, // which returns all possible error including value parsing error. func StrictMapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error { cfg, err := Load(source, others...) if err != nil { return err } cfg.NameMapper = mapper return cfg.StrictMapTo(v) } // MapTo maps data sources to given struct. func MapTo(v, source interface{}, others ...interface{}) error { return MapToWithMapper(v, nil, source, others...) } // StrictMapTo maps data sources to given struct in strict mode, // which returns all possible error including value parsing error. func StrictMapTo(v, source interface{}, others ...interface{}) error { return StrictMapToWithMapper(v, nil, source, others...) } // reflectSliceWithProperType does the opposite thing as setSliceWithProperType. func reflectSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow bool) error { slice := field.Slice(0, field.Len()) if field.Len() == 0 { return nil } sliceOf := field.Type().Elem().Kind() if allowShadow { var keyWithShadows *Key for i := 0; i < field.Len(); i++ { var val string switch sliceOf { case reflect.String: val = slice.Index(i).String() case reflect.Int, reflect.Int64: val = fmt.Sprint(slice.Index(i).Int()) case reflect.Uint, reflect.Uint64: val = fmt.Sprint(slice.Index(i).Uint()) case reflect.Float64: val = fmt.Sprint(slice.Index(i).Float()) case reflect.Bool: val = fmt.Sprint(slice.Index(i).Bool()) case reflectTime: val = slice.Index(i).Interface().(time.Time).Format(time.RFC3339) default: return fmt.Errorf("unsupported type '[]%s'", sliceOf) } if i == 0 { keyWithShadows = newKey(key.s, key.name, val) } else { _ = keyWithShadows.AddShadow(val) } } *key = *keyWithShadows return nil } var buf bytes.Buffer for i := 0; i < field.Len(); i++ { switch sliceOf { case reflect.String: buf.WriteString(slice.Index(i).String()) case reflect.Int, reflect.Int64: buf.WriteString(fmt.Sprint(slice.Index(i).Int())) case reflect.Uint, reflect.Uint64: buf.WriteString(fmt.Sprint(slice.Index(i).Uint())) case reflect.Float64: buf.WriteString(fmt.Sprint(slice.Index(i).Float())) case reflect.Bool: buf.WriteString(fmt.Sprint(slice.Index(i).Bool())) case reflectTime: buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339)) default: return fmt.Errorf("unsupported type '[]%s'", sliceOf) } buf.WriteString(delim) } key.SetValue(buf.String()[:buf.Len()-len(delim)]) return nil } // reflectWithProperType does the opposite thing as setWithProperType. func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow bool) error { switch t.Kind() { case reflect.String: key.SetValue(field.String()) case reflect.Bool: key.SetValue(fmt.Sprint(field.Bool())) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: key.SetValue(fmt.Sprint(field.Int())) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: key.SetValue(fmt.Sprint(field.Uint())) case reflect.Float32, reflect.Float64: key.SetValue(fmt.Sprint(field.Float())) case reflectTime: key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339))) case reflect.Slice: return reflectSliceWithProperType(key, field, delim, allowShadow) case reflect.Ptr: if !field.IsNil() { return reflectWithProperType(t.Elem(), key, field.Elem(), delim, allowShadow) } default: return fmt.Errorf("unsupported type %q", t) } return nil } // CR: copied from encoding/json/encode.go with modifications of time.Time support. // TODO: add more test coverage. func isEmptyValue(v reflect.Value) bool { switch v.Kind() { case reflect.Array, reflect.Map, reflect.Slice, reflect.String: return v.Len() == 0 case reflect.Bool: return !v.Bool() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v.Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return v.Uint() == 0 case reflect.Float32, reflect.Float64: return v.Float() == 0 case reflect.Interface, reflect.Ptr: return v.IsNil() case reflectTime: t, ok := v.Interface().(time.Time) return ok && t.IsZero() } return false } // StructReflector is the interface implemented by struct types that can extract themselves into INI objects. type StructReflector interface { ReflectINIStruct(*File) error } func (s *Section) reflectFrom(val reflect.Value) error { if val.Kind() == reflect.Ptr { val = val.Elem() } typ := val.Type() for i := 0; i < typ.NumField(); i++ { if !val.Field(i).CanInterface() { continue } field := val.Field(i) tpField := typ.Field(i) tag := tpField.Tag.Get("ini") if tag == "-" { continue } rawName, omitEmpty, allowShadow, allowNonUnique, extends := parseTagOptions(tag) if omitEmpty && isEmptyValue(field) { continue } if r, ok := field.Interface().(StructReflector); ok { return r.ReflectINIStruct(s.f) } fieldName := s.parseFieldName(tpField.Name, rawName) if len(fieldName) == 0 || !field.CanSet() { continue } if extends && tpField.Anonymous && (tpField.Type.Kind() == reflect.Ptr || tpField.Type.Kind() == reflect.Struct) { if err := s.reflectFrom(field); err != nil { return fmt.Errorf("reflect from field %q: %v", fieldName, err) } continue } if (tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct) || (tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") { // Note: The only error here is section doesn't exist. sec, err := s.f.GetSection(fieldName) if err != nil { // Note: fieldName can never be empty here, ignore error. sec, _ = s.f.NewSection(fieldName) } // Add comment from comment tag if len(sec.Comment) == 0 { sec.Comment = tpField.Tag.Get("comment") } if err = sec.reflectFrom(field); err != nil { return fmt.Errorf("reflect from field %q: %v", fieldName, err) } continue } if allowNonUnique && tpField.Type.Kind() == reflect.Slice { slice := field.Slice(0, field.Len()) if field.Len() == 0 { return nil } sliceOf := field.Type().Elem().Kind() for i := 0; i < field.Len(); i++ { if sliceOf != reflect.Struct && sliceOf != reflect.Ptr { return fmt.Errorf("field %q is not a slice of pointer or struct", fieldName) } sec, err := s.f.NewSection(fieldName) if err != nil { return err } // Add comment from comment tag if len(sec.Comment) == 0 { sec.Comment = tpField.Tag.Get("comment") } if err := sec.reflectFrom(slice.Index(i)); err != nil { return fmt.Errorf("reflect from field %q: %v", fieldName, err) } } continue } // Note: Same reason as section. key, err := s.GetKey(fieldName) if err != nil { key, _ = s.NewKey(fieldName, "") } // Add comment from comment tag if len(key.Comment) == 0 { key.Comment = tpField.Tag.Get("comment") } delim := parseDelim(tpField.Tag.Get("delim")) if err = reflectWithProperType(tpField.Type, key, field, delim, allowShadow); err != nil { return fmt.Errorf("reflect field %q: %v", fieldName, err) } } return nil } // ReflectFrom reflects section from given struct. It overwrites existing ones. func (s *Section) ReflectFrom(v interface{}) error { typ := reflect.TypeOf(v) val := reflect.ValueOf(v) if s.name != DefaultSection && s.f.options.AllowNonUniqueSections && (typ.Kind() == reflect.Slice || typ.Kind() == reflect.Ptr) { // Clear sections to make sure none exists before adding the new ones s.f.DeleteSection(s.name) if typ.Kind() == reflect.Ptr { sec, err := s.f.NewSection(s.name) if err != nil { return err } return sec.reflectFrom(val.Elem()) } slice := val.Slice(0, val.Len()) sliceOf := val.Type().Elem().Kind() if sliceOf != reflect.Ptr { return fmt.Errorf("not a slice of pointers") } for i := 0; i < slice.Len(); i++ { sec, err := s.f.NewSection(s.name) if err != nil { return err } err = sec.reflectFrom(slice.Index(i)) if err != nil { return fmt.Errorf("reflect from %dth field: %v", i, err) } } return nil } if typ.Kind() == reflect.Ptr { val = val.Elem() } else { return errors.New("not a pointer to a struct") } return s.reflectFrom(val) } // ReflectFrom reflects file from given struct. func (f *File) ReflectFrom(v interface{}) error { return f.Section("").ReflectFrom(v) } // ReflectFromWithMapper reflects data sources from given struct with name mapper. func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error { cfg.NameMapper = mapper return cfg.ReflectFrom(v) } // ReflectFrom reflects data sources from given struct. func ReflectFrom(cfg *File, v interface{}) error { return ReflectFromWithMapper(cfg, v, nil) } ================================================ FILE: vendor/github.com/go-logr/logr/.golangci.yaml ================================================ run: timeout: 1m tests: true linters: disable-all: true enable: - asciicheck - errcheck - forcetypeassert - gocritic - gofmt - goimports - gosimple - govet - ineffassign - misspell - revive - staticcheck - typecheck - unused issues: exclude-use-default: false max-issues-per-linter: 0 max-same-issues: 10 ================================================ FILE: vendor/github.com/go-logr/logr/CHANGELOG.md ================================================ # CHANGELOG ## v1.0.0-rc1 This is the first logged release. Major changes (including breaking changes) have occurred since earlier tags. ================================================ FILE: vendor/github.com/go-logr/logr/CONTRIBUTING.md ================================================ # Contributing Logr is open to pull-requests, provided they fit within the intended scope of the project. Specifically, this library aims to be VERY small and minimalist, with no external dependencies. ## Compatibility This project intends to follow [semantic versioning](http://semver.org) and is very strict about compatibility. Any proposed changes MUST follow those rules. ## Performance As a logging library, logr must be as light-weight as possible. Any proposed code change must include results of running the [benchmark](./benchmark) before and after the change. ================================================ FILE: vendor/github.com/go-logr/logr/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: vendor/github.com/go-logr/logr/README.md ================================================ # A minimal logging API for Go [![Go Reference](https://pkg.go.dev/badge/github.com/go-logr/logr.svg)](https://pkg.go.dev/github.com/go-logr/logr) [![Go Report Card](https://goreportcard.com/badge/github.com/go-logr/logr)](https://goreportcard.com/report/github.com/go-logr/logr) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/go-logr/logr/badge)](https://securityscorecards.dev/viewer/?platform=github.com&org=go-logr&repo=logr) logr offers an(other) opinion on how Go programs and libraries can do logging without becoming coupled to a particular logging implementation. This is not an implementation of logging - it is an API. In fact it is two APIs with two different sets of users. The `Logger` type is intended for application and library authors. It provides a relatively small API which can be used everywhere you want to emit logs. It defers the actual act of writing logs (to files, to stdout, or whatever) to the `LogSink` interface. The `LogSink` interface is intended for logging library implementers. It is a pure interface which can be implemented by logging frameworks to provide the actual logging functionality. This decoupling allows application and library developers to write code in terms of `logr.Logger` (which has very low dependency fan-out) while the implementation of logging is managed "up stack" (e.g. in or near `main()`.) Application developers can then switch out implementations as necessary. Many people assert that libraries should not be logging, and as such efforts like this are pointless. Those people are welcome to convince the authors of the tens-of-thousands of libraries that *DO* write logs that they are all wrong. In the meantime, logr takes a more practical approach. ## Typical usage Somewhere, early in an application's life, it will make a decision about which logging library (implementation) it actually wants to use. Something like: ``` func main() { // ... other setup code ... // Create the "root" logger. We have chosen the "logimpl" implementation, // which takes some initial parameters and returns a logr.Logger. logger := logimpl.New(param1, param2) // ... other setup code ... ``` Most apps will call into other libraries, create structures to govern the flow, etc. The `logr.Logger` object can be passed to these other libraries, stored in structs, or even used as a package-global variable, if needed. For example: ``` app := createTheAppObject(logger) app.Run() ``` Outside of this early setup, no other packages need to know about the choice of implementation. They write logs in terms of the `logr.Logger` that they received: ``` type appObject struct { // ... other fields ... logger logr.Logger // ... other fields ... } func (app *appObject) Run() { app.logger.Info("starting up", "timestamp", time.Now()) // ... app code ... ``` ## Background If the Go standard library had defined an interface for logging, this project probably would not be needed. Alas, here we are. When the Go developers started developing such an interface with [slog](https://github.com/golang/go/issues/56345), they adopted some of the logr design but also left out some parts and changed others: | Feature | logr | slog | |---------|------|------| | High-level API | `Logger` (passed by value) | `Logger` (passed by [pointer](https://github.com/golang/go/issues/59126)) | | Low-level API | `LogSink` | `Handler` | | Stack unwinding | done by `LogSink` | done by `Logger` | | Skipping helper functions | `WithCallDepth`, `WithCallStackHelper` | [not supported by Logger](https://github.com/golang/go/issues/59145) | | Generating a value for logging on demand | `Marshaler` | `LogValuer` | | Log levels | >= 0, higher meaning "less important" | positive and negative, with 0 for "info" and higher meaning "more important" | | Error log entries | always logged, don't have a verbosity level | normal log entries with level >= `LevelError` | | Passing logger via context | `NewContext`, `FromContext` | no API | | Adding a name to a logger | `WithName` | no API | | Modify verbosity of log entries in a call chain | `V` | no API | | Grouping of key/value pairs | not supported | `WithGroup`, `GroupValue` | | Pass context for extracting additional values | no API | API variants like `InfoCtx` | The high-level slog API is explicitly meant to be one of many different APIs that can be layered on top of a shared `slog.Handler`. logr is one such alternative API, with [interoperability](#slog-interoperability) provided by some conversion functions. ### Inspiration Before you consider this package, please read [this blog post by the inimitable Dave Cheney][warning-makes-no-sense]. We really appreciate what he has to say, and it largely aligns with our own experiences. ### Differences from Dave's ideas The main differences are: 1. Dave basically proposes doing away with the notion of a logging API in favor of `fmt.Printf()`. We disagree, especially when you consider things like output locations, timestamps, file and line decorations, and structured logging. This package restricts the logging API to just 2 types of logs: info and error. Info logs are things you want to tell the user which are not errors. Error logs are, well, errors. If your code receives an `error` from a subordinate function call and is logging that `error` *and not returning it*, use error logs. 2. Verbosity-levels on info logs. This gives developers a chance to indicate arbitrary grades of importance for info logs, without assigning names with semantic meaning such as "warning", "trace", and "debug." Superficially this may feel very similar, but the primary difference is the lack of semantics. Because verbosity is a numerical value, it's safe to assume that an app running with higher verbosity means more (and less important) logs will be generated. ## Implementations (non-exhaustive) There are implementations for the following logging libraries: - **a function** (can bridge to non-structured libraries): [funcr](https://github.com/go-logr/logr/tree/master/funcr) - **a testing.T** (for use in Go tests, with JSON-like output): [testr](https://github.com/go-logr/logr/tree/master/testr) - **github.com/google/glog**: [glogr](https://github.com/go-logr/glogr) - **k8s.io/klog** (for Kubernetes): [klogr](https://git.k8s.io/klog/klogr) - **a testing.T** (with klog-like text output): [ktesting](https://git.k8s.io/klog/ktesting) - **go.uber.org/zap**: [zapr](https://github.com/go-logr/zapr) - **log** (the Go standard library logger): [stdr](https://github.com/go-logr/stdr) - **github.com/sirupsen/logrus**: [logrusr](https://github.com/bombsimon/logrusr) - **github.com/wojas/genericr**: [genericr](https://github.com/wojas/genericr) (makes it easy to implement your own backend) - **logfmt** (Heroku style [logging](https://www.brandur.org/logfmt)): [logfmtr](https://github.com/iand/logfmtr) - **github.com/rs/zerolog**: [zerologr](https://github.com/go-logr/zerologr) - **github.com/go-kit/log**: [gokitlogr](https://github.com/tonglil/gokitlogr) (also compatible with github.com/go-kit/kit/log since v0.12.0) - **bytes.Buffer** (writing to a buffer): [bufrlogr](https://github.com/tonglil/buflogr) (useful for ensuring values were logged, like during testing) ## slog interoperability Interoperability goes both ways, using the `logr.Logger` API with a `slog.Handler` and using the `slog.Logger` API with a `logr.LogSink`. `FromSlogHandler` and `ToSlogHandler` convert between a `logr.Logger` and a `slog.Handler`. As usual, `slog.New` can be used to wrap such a `slog.Handler` in the high-level slog API. ### Using a `logr.LogSink` as backend for slog Ideally, a logr sink implementation should support both logr and slog by implementing both the normal logr interface(s) and `SlogSink`. Because of a conflict in the parameters of the common `Enabled` method, it is [not possible to implement both slog.Handler and logr.Sink in the same type](https://github.com/golang/go/issues/59110). If both are supported, log calls can go from the high-level APIs to the backend without the need to convert parameters. `FromSlogHandler` and `ToSlogHandler` can convert back and forth without adding additional wrappers, with one exception: when `Logger.V` was used to adjust the verbosity for a `slog.Handler`, then `ToSlogHandler` has to use a wrapper which adjusts the verbosity for future log calls. Such an implementation should also support values that implement specific interfaces from both packages for logging (`logr.Marshaler`, `slog.LogValuer`, `slog.GroupValue`). logr does not convert those. Not supporting slog has several drawbacks: - Recording source code locations works correctly if the handler gets called through `slog.Logger`, but may be wrong in other cases. That's because a `logr.Sink` does its own stack unwinding instead of using the program counter provided by the high-level API. - slog levels <= 0 can be mapped to logr levels by negating the level without a loss of information. But all slog levels > 0 (e.g. `slog.LevelWarning` as used by `slog.Logger.Warn`) must be mapped to 0 before calling the sink because logr does not support "more important than info" levels. - The slog group concept is supported by prefixing each key in a key/value pair with the group names, separated by a dot. For structured output like JSON it would be better to group the key/value pairs inside an object. - Special slog values and interfaces don't work as expected. - The overhead is likely to be higher. These drawbacks are severe enough that applications using a mixture of slog and logr should switch to a different backend. ### Using a `slog.Handler` as backend for logr Using a plain `slog.Handler` without support for logr works better than the other direction: - All logr verbosity levels can be mapped 1:1 to their corresponding slog level by negating them. - Stack unwinding is done by the `SlogSink` and the resulting program counter is passed to the `slog.Handler`. - Names added via `Logger.WithName` are gathered and recorded in an additional attribute with `logger` as key and the names separated by slash as value. - `Logger.Error` is turned into a log record with `slog.LevelError` as level and an additional attribute with `err` as key, if an error was provided. The main drawback is that `logr.Marshaler` will not be supported. Types should ideally support both `logr.Marshaler` and `slog.Valuer`. If compatibility with logr implementations without slog support is not important, then `slog.Valuer` is sufficient. ### Context support for slog Storing a logger in a `context.Context` is not supported by slog. `NewContextWithSlogLogger` and `FromContextAsSlogLogger` can be used to fill this gap. They store and retrieve a `slog.Logger` pointer under the same context key that is also used by `NewContext` and `FromContext` for `logr.Logger` value. When `NewContextWithSlogLogger` is followed by `FromContext`, the latter will automatically convert the `slog.Logger` to a `logr.Logger`. `FromContextAsSlogLogger` does the same for the other direction. With this approach, binaries which use either slog or logr are as efficient as possible with no unnecessary allocations. This is also why the API stores a `slog.Logger` pointer: when storing a `slog.Handler`, creating a `slog.Logger` on retrieval would need to allocate one. The downside is that switching back and forth needs more allocations. Because logr is the API that is already in use by different packages, in particular Kubernetes, the recommendation is to use the `logr.Logger` API in code which uses contextual logging. An alternative to adding values to a logger and storing that logger in the context is to store the values in the context and to configure a logging backend to extract those values when emitting log entries. This only works when log calls are passed the context, which is not supported by the logr API. With the slog API, it is possible, but not required. https://github.com/veqryn/slog-context is a package for slog which provides additional support code for this approach. It also contains wrappers for the context functions in logr, so developers who prefer to not use the logr APIs directly can use those instead and the resulting code will still be interoperable with logr. ## FAQ ### Conceptual #### Why structured logging? - **Structured logs are more easily queryable**: Since you've got key-value pairs, it's much easier to query your structured logs for particular values by filtering on the contents of a particular key -- think searching request logs for error codes, Kubernetes reconcilers for the name and namespace of the reconciled object, etc. - **Structured logging makes it easier to have cross-referenceable logs**: Similarly to searchability, if you maintain conventions around your keys, it becomes easy to gather all log lines related to a particular concept. - **Structured logs allow better dimensions of filtering**: if you have structure to your logs, you've got more precise control over how much information is logged -- you might choose in a particular configuration to log certain keys but not others, only log lines where a certain key matches a certain value, etc., instead of just having v-levels and names to key off of. - **Structured logs better represent structured data**: sometimes, the data that you want to log is inherently structured (think tuple-link objects.) Structured logs allow you to preserve that structure when outputting. #### Why V-levels? **V-levels give operators an easy way to control the chattiness of log operations**. V-levels provide a way for a given package to distinguish the relative importance or verbosity of a given log message. Then, if a particular logger or package is logging too many messages, the user of the package can simply change the v-levels for that library. #### Why not named levels, like Info/Warning/Error? Read [Dave Cheney's post][warning-makes-no-sense]. Then read [Differences from Dave's ideas](#differences-from-daves-ideas). #### Why not allow format strings, too? **Format strings negate many of the benefits of structured logs**: - They're not easily searchable without resorting to fuzzy searching, regular expressions, etc. - They don't store structured data well, since contents are flattened into a string. - They're not cross-referenceable. - They don't compress easily, since the message is not constant. (Unless you turn positional parameters into key-value pairs with numerical keys, at which point you've gotten key-value logging with meaningless keys.) ### Practical #### Why key-value pairs, and not a map? Key-value pairs are *much* easier to optimize, especially around allocations. Zap (a structured logger that inspired logr's interface) has [performance measurements](https://github.com/uber-go/zap#performance) that show this quite nicely. While the interface ends up being a little less obvious, you get potentially better performance, plus avoid making users type `map[string]string{}` every time they want to log. #### What if my V-levels differ between libraries? That's fine. Control your V-levels on a per-logger basis, and use the `WithName` method to pass different loggers to different libraries. Generally, you should take care to ensure that you have relatively consistent V-levels within a given logger, however, as this makes deciding on what verbosity of logs to request easier. #### But I really want to use a format string! That's not actually a question. Assuming your question is "how do I convert my mental model of logging with format strings to logging with constant messages": 1. Figure out what the error actually is, as you'd write in a TL;DR style, and use that as a message. 2. For every place you'd write a format specifier, look to the word before it, and add that as a key value pair. For instance, consider the following examples (all taken from spots in the Kubernetes codebase): - `klog.V(4).Infof("Client is returning errors: code %v, error %v", responseCode, err)` becomes `logger.Error(err, "client returned an error", "code", responseCode)` - `klog.V(4).Infof("Got a Retry-After %ds response for attempt %d to %v", seconds, retries, url)` becomes `logger.V(4).Info("got a retry-after response when requesting url", "attempt", retries, "after seconds", seconds, "url", url)` If you *really* must use a format string, use it in a key's value, and call `fmt.Sprintf` yourself. For instance: `log.Printf("unable to reflect over type %T")` becomes `logger.Info("unable to reflect over type", "type", fmt.Sprintf("%T"))`. In general though, the cases where this is necessary should be few and far between. #### How do I choose my V-levels? This is basically the only hard constraint: increase V-levels to denote more verbose or more debug-y logs. Otherwise, you can start out with `0` as "you always want to see this", `1` as "common logging that you might *possibly* want to turn off", and `10` as "I would like to performance-test your log collection stack." Then gradually choose levels in between as you need them, working your way down from 10 (for debug and trace style logs) and up from 1 (for chattier info-type logs). For reference, slog pre-defines -4 for debug logs (corresponds to 4 in logr), which matches what is [recommended for Kubernetes](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#what-method-to-use). #### How do I choose my keys? Keys are fairly flexible, and can hold more or less any string value. For best compatibility with implementations and consistency with existing code in other projects, there are a few conventions you should consider. - Make your keys human-readable. - Constant keys are generally a good idea. - Be consistent across your codebase. - Keys should naturally match parts of the message string. - Use lower case for simple keys and [lowerCamelCase](https://en.wiktionary.org/wiki/lowerCamelCase) for more complex ones. Kubernetes is one example of a project that has [adopted that convention](https://github.com/kubernetes/community/blob/HEAD/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments). While key names are mostly unrestricted (and spaces are acceptable), it's generally a good idea to stick to printable ascii characters, or at least match the general character set of your log lines. #### Why should keys be constant values? The point of structured logging is to make later log processing easier. Your keys are, effectively, the schema of each log message. If you use different keys across instances of the same log line, you will make your structured logs much harder to use. `Sprintf()` is for values, not for keys! #### Why is this not a pure interface? The Logger type is implemented as a struct in order to allow the Go compiler to optimize things like high-V `Info` logs that are not triggered. Not all of these implementations are implemented yet, but this structure was suggested as a way to ensure they *can* be implemented. All of the real work is behind the `LogSink` interface. [warning-makes-no-sense]: http://dave.cheney.net/2015/11/05/lets-talk-about-logging ================================================ FILE: vendor/github.com/go-logr/logr/SECURITY.md ================================================ # Security Policy If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released. You may submit the report in the following ways: - send an email to go-logr-security@googlegroups.com - send us a [private vulnerability report](https://github.com/go-logr/logr/security/advisories/new) Please provide the following information in your report: - A description of the vulnerability and its impact - How to reproduce the issue We ask that you give us 90 days to work on a fix before public exposure. ================================================ FILE: vendor/github.com/go-logr/logr/context.go ================================================ /* Copyright 2023 The logr Authors. 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. */ package logr // contextKey is how we find Loggers in a context.Context. With Go < 1.21, // the value is always a Logger value. With Go >= 1.21, the value can be a // Logger value or a slog.Logger pointer. type contextKey struct{} // notFoundError exists to carry an IsNotFound method. type notFoundError struct{} func (notFoundError) Error() string { return "no logr.Logger was present" } func (notFoundError) IsNotFound() bool { return true } ================================================ FILE: vendor/github.com/go-logr/logr/context_noslog.go ================================================ //go:build !go1.21 // +build !go1.21 /* Copyright 2019 The logr Authors. 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. */ package logr import ( "context" ) // FromContext returns a Logger from ctx or an error if no Logger is found. func FromContext(ctx context.Context) (Logger, error) { if v, ok := ctx.Value(contextKey{}).(Logger); ok { return v, nil } return Logger{}, notFoundError{} } // FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this // returns a Logger that discards all log messages. func FromContextOrDiscard(ctx context.Context) Logger { if v, ok := ctx.Value(contextKey{}).(Logger); ok { return v } return Discard() } // NewContext returns a new Context, derived from ctx, which carries the // provided Logger. func NewContext(ctx context.Context, logger Logger) context.Context { return context.WithValue(ctx, contextKey{}, logger) } ================================================ FILE: vendor/github.com/go-logr/logr/context_slog.go ================================================ //go:build go1.21 // +build go1.21 /* Copyright 2019 The logr Authors. 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. */ package logr import ( "context" "fmt" "log/slog" ) // FromContext returns a Logger from ctx or an error if no Logger is found. func FromContext(ctx context.Context) (Logger, error) { v := ctx.Value(contextKey{}) if v == nil { return Logger{}, notFoundError{} } switch v := v.(type) { case Logger: return v, nil case *slog.Logger: return FromSlogHandler(v.Handler()), nil default: // Not reached. panic(fmt.Sprintf("unexpected value type for logr context key: %T", v)) } } // FromContextAsSlogLogger returns a slog.Logger from ctx or nil if no such Logger is found. func FromContextAsSlogLogger(ctx context.Context) *slog.Logger { v := ctx.Value(contextKey{}) if v == nil { return nil } switch v := v.(type) { case Logger: return slog.New(ToSlogHandler(v)) case *slog.Logger: return v default: // Not reached. panic(fmt.Sprintf("unexpected value type for logr context key: %T", v)) } } // FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this // returns a Logger that discards all log messages. func FromContextOrDiscard(ctx context.Context) Logger { if logger, err := FromContext(ctx); err == nil { return logger } return Discard() } // NewContext returns a new Context, derived from ctx, which carries the // provided Logger. func NewContext(ctx context.Context, logger Logger) context.Context { return context.WithValue(ctx, contextKey{}, logger) } // NewContextWithSlogLogger returns a new Context, derived from ctx, which carries the // provided slog.Logger. func NewContextWithSlogLogger(ctx context.Context, logger *slog.Logger) context.Context { return context.WithValue(ctx, contextKey{}, logger) } ================================================ FILE: vendor/github.com/go-logr/logr/discard.go ================================================ /* Copyright 2020 The logr Authors. 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. */ package logr // Discard returns a Logger that discards all messages logged to it. It can be // used whenever the caller is not interested in the logs. Logger instances // produced by this function always compare as equal. func Discard() Logger { return New(nil) } ================================================ FILE: vendor/github.com/go-logr/logr/funcr/funcr.go ================================================ /* Copyright 2021 The logr Authors. 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. */ // Package funcr implements formatting of structured log messages and // optionally captures the call site and timestamp. // // The simplest way to use it is via its implementation of a // github.com/go-logr/logr.LogSink with output through an arbitrary // "write" function. See New and NewJSON for details. // // # Custom LogSinks // // For users who need more control, a funcr.Formatter can be embedded inside // your own custom LogSink implementation. This is useful when the LogSink // needs to implement additional methods, for example. // // # Formatting // // This will respect logr.Marshaler, fmt.Stringer, and error interfaces for // values which are being logged. When rendering a struct, funcr will use Go's // standard JSON tags (all except "string"). package funcr import ( "bytes" "encoding" "encoding/json" "fmt" "path/filepath" "reflect" "runtime" "strconv" "strings" "time" "github.com/go-logr/logr" ) // New returns a logr.Logger which is implemented by an arbitrary function. func New(fn func(prefix, args string), opts Options) logr.Logger { return logr.New(newSink(fn, NewFormatter(opts))) } // NewJSON returns a logr.Logger which is implemented by an arbitrary function // and produces JSON output. func NewJSON(fn func(obj string), opts Options) logr.Logger { fnWrapper := func(_, obj string) { fn(obj) } return logr.New(newSink(fnWrapper, NewFormatterJSON(opts))) } // Underlier exposes access to the underlying logging function. Since // callers only have a logr.Logger, they have to know which // implementation is in use, so this interface is less of an // abstraction and more of a way to test type conversion. type Underlier interface { GetUnderlying() func(prefix, args string) } func newSink(fn func(prefix, args string), formatter Formatter) logr.LogSink { l := &fnlogger{ Formatter: formatter, write: fn, } // For skipping fnlogger.Info and fnlogger.Error. l.Formatter.AddCallDepth(1) return l } // Options carries parameters which influence the way logs are generated. type Options struct { // LogCaller tells funcr to add a "caller" key to some or all log lines. // This has some overhead, so some users might not want it. LogCaller MessageClass // LogCallerFunc tells funcr to also log the calling function name. This // has no effect if caller logging is not enabled (see Options.LogCaller). LogCallerFunc bool // LogTimestamp tells funcr to add a "ts" key to log lines. This has some // overhead, so some users might not want it. LogTimestamp bool // TimestampFormat tells funcr how to render timestamps when LogTimestamp // is enabled. If not specified, a default format will be used. For more // details, see docs for Go's time.Layout. TimestampFormat string // LogInfoLevel tells funcr what key to use to log the info level. // If not specified, the info level will be logged as "level". // If this is set to "", the info level will not be logged at all. LogInfoLevel *string // Verbosity tells funcr which V logs to produce. Higher values enable // more logs. Info logs at or below this level will be written, while logs // above this level will be discarded. Verbosity int // RenderBuiltinsHook allows users to mutate the list of key-value pairs // while a log line is being rendered. The kvList argument follows logr // conventions - each pair of slice elements is comprised of a string key // and an arbitrary value (verified and sanitized before calling this // hook). The value returned must follow the same conventions. This hook // can be used to audit or modify logged data. For example, you might want // to prefix all of funcr's built-in keys with some string. This hook is // only called for built-in (provided by funcr itself) key-value pairs. // Equivalent hooks are offered for key-value pairs saved via // logr.Logger.WithValues or Formatter.AddValues (see RenderValuesHook) and // for user-provided pairs (see RenderArgsHook). RenderBuiltinsHook func(kvList []any) []any // RenderValuesHook is the same as RenderBuiltinsHook, except that it is // only called for key-value pairs saved via logr.Logger.WithValues. See // RenderBuiltinsHook for more details. RenderValuesHook func(kvList []any) []any // RenderArgsHook is the same as RenderBuiltinsHook, except that it is only // called for key-value pairs passed directly to Info and Error. See // RenderBuiltinsHook for more details. RenderArgsHook func(kvList []any) []any // MaxLogDepth tells funcr how many levels of nested fields (e.g. a struct // that contains a struct, etc.) it may log. Every time it finds a struct, // slice, array, or map the depth is increased by one. When the maximum is // reached, the value will be converted to a string indicating that the max // depth has been exceeded. If this field is not specified, a default // value will be used. MaxLogDepth int } // MessageClass indicates which category or categories of messages to consider. type MessageClass int const ( // None ignores all message classes. None MessageClass = iota // All considers all message classes. All // Info only considers info messages. Info // Error only considers error messages. Error ) // fnlogger inherits some of its LogSink implementation from Formatter // and just needs to add some glue code. type fnlogger struct { Formatter write func(prefix, args string) } func (l fnlogger) WithName(name string) logr.LogSink { l.Formatter.AddName(name) return &l } func (l fnlogger) WithValues(kvList ...any) logr.LogSink { l.Formatter.AddValues(kvList) return &l } func (l fnlogger) WithCallDepth(depth int) logr.LogSink { l.Formatter.AddCallDepth(depth) return &l } func (l fnlogger) Info(level int, msg string, kvList ...any) { prefix, args := l.FormatInfo(level, msg, kvList) l.write(prefix, args) } func (l fnlogger) Error(err error, msg string, kvList ...any) { prefix, args := l.FormatError(err, msg, kvList) l.write(prefix, args) } func (l fnlogger) GetUnderlying() func(prefix, args string) { return l.write } // Assert conformance to the interfaces. var _ logr.LogSink = &fnlogger{} var _ logr.CallDepthLogSink = &fnlogger{} var _ Underlier = &fnlogger{} // NewFormatter constructs a Formatter which emits a JSON-like key=value format. func NewFormatter(opts Options) Formatter { return newFormatter(opts, outputKeyValue) } // NewFormatterJSON constructs a Formatter which emits strict JSON. func NewFormatterJSON(opts Options) Formatter { return newFormatter(opts, outputJSON) } // Defaults for Options. const defaultTimestampFormat = "2006-01-02 15:04:05.000000" const defaultMaxLogDepth = 16 func newFormatter(opts Options, outfmt outputFormat) Formatter { if opts.TimestampFormat == "" { opts.TimestampFormat = defaultTimestampFormat } if opts.MaxLogDepth == 0 { opts.MaxLogDepth = defaultMaxLogDepth } if opts.LogInfoLevel == nil { opts.LogInfoLevel = new(string) *opts.LogInfoLevel = "level" } f := Formatter{ outputFormat: outfmt, prefix: "", values: nil, depth: 0, opts: &opts, } return f } // Formatter is an opaque struct which can be embedded in a LogSink // implementation. It should be constructed with NewFormatter. Some of // its methods directly implement logr.LogSink. type Formatter struct { outputFormat outputFormat prefix string values []any valuesStr string depth int opts *Options groupName string // for slog groups groups []groupDef } // outputFormat indicates which outputFormat to use. type outputFormat int const ( // outputKeyValue emits a JSON-like key=value format, but not strict JSON. outputKeyValue outputFormat = iota // outputJSON emits strict JSON. outputJSON ) // groupDef represents a saved group. The values may be empty, but we don't // know if we need to render the group until the final record is rendered. type groupDef struct { name string values string } // PseudoStruct is a list of key-value pairs that gets logged as a struct. type PseudoStruct []any // render produces a log line, ready to use. func (f Formatter) render(builtins, args []any) string { // Empirically bytes.Buffer is faster than strings.Builder for this. buf := bytes.NewBuffer(make([]byte, 0, 1024)) if f.outputFormat == outputJSON { buf.WriteByte('{') // for the whole record } // Render builtins vals := builtins if hook := f.opts.RenderBuiltinsHook; hook != nil { vals = hook(f.sanitize(vals)) } f.flatten(buf, vals, false) // keys are ours, no need to escape continuing := len(builtins) > 0 // Turn the inner-most group into a string argsStr := func() string { buf := bytes.NewBuffer(make([]byte, 0, 1024)) vals = args if hook := f.opts.RenderArgsHook; hook != nil { vals = hook(f.sanitize(vals)) } f.flatten(buf, vals, true) // escape user-provided keys return buf.String() }() // Render the stack of groups from the inside out. bodyStr := f.renderGroup(f.groupName, f.valuesStr, argsStr) for i := len(f.groups) - 1; i >= 0; i-- { grp := &f.groups[i] if grp.values == "" && bodyStr == "" { // no contents, so we must elide the whole group continue } bodyStr = f.renderGroup(grp.name, grp.values, bodyStr) } if bodyStr != "" { if continuing { buf.WriteByte(f.comma()) } buf.WriteString(bodyStr) } if f.outputFormat == outputJSON { buf.WriteByte('}') // for the whole record } return buf.String() } // renderGroup returns a string representation of the named group with rendered // values and args. If the name is empty, this will return the values and args, // joined. If the name is not empty, this will return a single key-value pair, // where the value is a grouping of the values and args. If the values and // args are both empty, this will return an empty string, even if the name was // specified. func (f Formatter) renderGroup(name string, values string, args string) string { buf := bytes.NewBuffer(make([]byte, 0, 1024)) needClosingBrace := false if name != "" && (values != "" || args != "") { buf.WriteString(f.quoted(name, true)) // escape user-provided keys buf.WriteByte(f.colon()) buf.WriteByte('{') needClosingBrace = true } continuing := false if values != "" { buf.WriteString(values) continuing = true } if args != "" { if continuing { buf.WriteByte(f.comma()) } buf.WriteString(args) } if needClosingBrace { buf.WriteByte('}') } return buf.String() } // flatten renders a list of key-value pairs into a buffer. If escapeKeys is // true, the keys are assumed to have non-JSON-compatible characters in them // and must be evaluated for escapes. // // This function returns a potentially modified version of kvList, which // ensures that there is a value for every key (adding a value if needed) and // that each key is a string (substituting a key if needed). func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, escapeKeys bool) []any { // This logic overlaps with sanitize() but saves one type-cast per key, // which can be measurable. if len(kvList)%2 != 0 { kvList = append(kvList, noValue) } copied := false for i := 0; i < len(kvList); i += 2 { k, ok := kvList[i].(string) if !ok { if !copied { newList := make([]any, len(kvList)) copy(newList, kvList) kvList = newList copied = true } k = f.nonStringKey(kvList[i]) kvList[i] = k } v := kvList[i+1] if i > 0 { if f.outputFormat == outputJSON { buf.WriteByte(f.comma()) } else { // In theory the format could be something we don't understand. In // practice, we control it, so it won't be. buf.WriteByte(' ') } } buf.WriteString(f.quoted(k, escapeKeys)) buf.WriteByte(f.colon()) buf.WriteString(f.pretty(v)) } return kvList } func (f Formatter) quoted(str string, escape bool) string { if escape { return prettyString(str) } // this is faster return `"` + str + `"` } func (f Formatter) comma() byte { if f.outputFormat == outputJSON { return ',' } return ' ' } func (f Formatter) colon() byte { if f.outputFormat == outputJSON { return ':' } return '=' } func (f Formatter) pretty(value any) string { return f.prettyWithFlags(value, 0, 0) } const ( flagRawStruct = 0x1 // do not print braces on structs ) // TODO: This is not fast. Most of the overhead goes here. func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string { if depth > f.opts.MaxLogDepth { return `""` } // Handle types that take full control of logging. if v, ok := value.(logr.Marshaler); ok { // Replace the value with what the type wants to get logged. // That then gets handled below via reflection. value = invokeMarshaler(v) } // Handle types that want to format themselves. switch v := value.(type) { case fmt.Stringer: value = invokeStringer(v) case error: value = invokeError(v) } // Handling the most common types without reflect is a small perf win. switch v := value.(type) { case bool: return strconv.FormatBool(v) case string: return prettyString(v) case int: return strconv.FormatInt(int64(v), 10) case int8: return strconv.FormatInt(int64(v), 10) case int16: return strconv.FormatInt(int64(v), 10) case int32: return strconv.FormatInt(int64(v), 10) case int64: return strconv.FormatInt(int64(v), 10) case uint: return strconv.FormatUint(uint64(v), 10) case uint8: return strconv.FormatUint(uint64(v), 10) case uint16: return strconv.FormatUint(uint64(v), 10) case uint32: return strconv.FormatUint(uint64(v), 10) case uint64: return strconv.FormatUint(v, 10) case uintptr: return strconv.FormatUint(uint64(v), 10) case float32: return strconv.FormatFloat(float64(v), 'f', -1, 32) case float64: return strconv.FormatFloat(v, 'f', -1, 64) case complex64: return `"` + strconv.FormatComplex(complex128(v), 'f', -1, 64) + `"` case complex128: return `"` + strconv.FormatComplex(v, 'f', -1, 128) + `"` case PseudoStruct: buf := bytes.NewBuffer(make([]byte, 0, 1024)) v = f.sanitize(v) if flags&flagRawStruct == 0 { buf.WriteByte('{') } for i := 0; i < len(v); i += 2 { if i > 0 { buf.WriteByte(f.comma()) } k, _ := v[i].(string) // sanitize() above means no need to check success // arbitrary keys might need escaping buf.WriteString(prettyString(k)) buf.WriteByte(f.colon()) buf.WriteString(f.prettyWithFlags(v[i+1], 0, depth+1)) } if flags&flagRawStruct == 0 { buf.WriteByte('}') } return buf.String() } buf := bytes.NewBuffer(make([]byte, 0, 256)) t := reflect.TypeOf(value) if t == nil { return "null" } v := reflect.ValueOf(value) switch t.Kind() { case reflect.Bool: return strconv.FormatBool(v.Bool()) case reflect.String: return prettyString(v.String()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return strconv.FormatInt(int64(v.Int()), 10) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return strconv.FormatUint(uint64(v.Uint()), 10) case reflect.Float32: return strconv.FormatFloat(float64(v.Float()), 'f', -1, 32) case reflect.Float64: return strconv.FormatFloat(v.Float(), 'f', -1, 64) case reflect.Complex64: return `"` + strconv.FormatComplex(complex128(v.Complex()), 'f', -1, 64) + `"` case reflect.Complex128: return `"` + strconv.FormatComplex(v.Complex(), 'f', -1, 128) + `"` case reflect.Struct: if flags&flagRawStruct == 0 { buf.WriteByte('{') } printComma := false // testing i>0 is not enough because of JSON omitted fields for i := 0; i < t.NumField(); i++ { fld := t.Field(i) if fld.PkgPath != "" { // reflect says this field is only defined for non-exported fields. continue } if !v.Field(i).CanInterface() { // reflect isn't clear exactly what this means, but we can't use it. continue } name := "" omitempty := false if tag, found := fld.Tag.Lookup("json"); found { if tag == "-" { continue } if comma := strings.Index(tag, ","); comma != -1 { if n := tag[:comma]; n != "" { name = n } rest := tag[comma:] if strings.Contains(rest, ",omitempty,") || strings.HasSuffix(rest, ",omitempty") { omitempty = true } } else { name = tag } } if omitempty && isEmpty(v.Field(i)) { continue } if printComma { buf.WriteByte(f.comma()) } printComma = true // if we got here, we are rendering a field if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" { buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), flags|flagRawStruct, depth+1)) continue } if name == "" { name = fld.Name } // field names can't contain characters which need escaping buf.WriteString(f.quoted(name, false)) buf.WriteByte(f.colon()) buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), 0, depth+1)) } if flags&flagRawStruct == 0 { buf.WriteByte('}') } return buf.String() case reflect.Slice, reflect.Array: // If this is outputing as JSON make sure this isn't really a json.RawMessage. // If so just emit "as-is" and don't pretty it as that will just print // it as [X,Y,Z,...] which isn't terribly useful vs the string form you really want. if f.outputFormat == outputJSON { if rm, ok := value.(json.RawMessage); ok { // If it's empty make sure we emit an empty value as the array style would below. if len(rm) > 0 { buf.Write(rm) } else { buf.WriteString("null") } return buf.String() } } buf.WriteByte('[') for i := 0; i < v.Len(); i++ { if i > 0 { buf.WriteByte(f.comma()) } e := v.Index(i) buf.WriteString(f.prettyWithFlags(e.Interface(), 0, depth+1)) } buf.WriteByte(']') return buf.String() case reflect.Map: buf.WriteByte('{') // This does not sort the map keys, for best perf. it := v.MapRange() i := 0 for it.Next() { if i > 0 { buf.WriteByte(f.comma()) } // If a map key supports TextMarshaler, use it. keystr := "" if m, ok := it.Key().Interface().(encoding.TextMarshaler); ok { txt, err := m.MarshalText() if err != nil { keystr = fmt.Sprintf("", err.Error()) } else { keystr = string(txt) } keystr = prettyString(keystr) } else { // prettyWithFlags will produce already-escaped values keystr = f.prettyWithFlags(it.Key().Interface(), 0, depth+1) if t.Key().Kind() != reflect.String { // JSON only does string keys. Unlike Go's standard JSON, we'll // convert just about anything to a string. keystr = prettyString(keystr) } } buf.WriteString(keystr) buf.WriteByte(f.colon()) buf.WriteString(f.prettyWithFlags(it.Value().Interface(), 0, depth+1)) i++ } buf.WriteByte('}') return buf.String() case reflect.Ptr, reflect.Interface: if v.IsNil() { return "null" } return f.prettyWithFlags(v.Elem().Interface(), 0, depth) } return fmt.Sprintf(`""`, t.Kind().String()) } func prettyString(s string) string { // Avoid escaping (which does allocations) if we can. if needsEscape(s) { return strconv.Quote(s) } b := bytes.NewBuffer(make([]byte, 0, 1024)) b.WriteByte('"') b.WriteString(s) b.WriteByte('"') return b.String() } // needsEscape determines whether the input string needs to be escaped or not, // without doing any allocations. func needsEscape(s string) bool { for _, r := range s { if !strconv.IsPrint(r) || r == '\\' || r == '"' { return true } } return false } func isEmpty(v reflect.Value) bool { switch v.Kind() { case reflect.Array, reflect.Map, reflect.Slice, reflect.String: return v.Len() == 0 case reflect.Bool: return !v.Bool() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v.Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return v.Uint() == 0 case reflect.Float32, reflect.Float64: return v.Float() == 0 case reflect.Complex64, reflect.Complex128: return v.Complex() == 0 case reflect.Interface, reflect.Ptr: return v.IsNil() } return false } func invokeMarshaler(m logr.Marshaler) (ret any) { defer func() { if r := recover(); r != nil { ret = fmt.Sprintf("", r) } }() return m.MarshalLog() } func invokeStringer(s fmt.Stringer) (ret string) { defer func() { if r := recover(); r != nil { ret = fmt.Sprintf("", r) } }() return s.String() } func invokeError(e error) (ret string) { defer func() { if r := recover(); r != nil { ret = fmt.Sprintf("", r) } }() return e.Error() } // Caller represents the original call site for a log line, after considering // logr.Logger.WithCallDepth and logr.Logger.WithCallStackHelper. The File and // Line fields will always be provided, while the Func field is optional. // Users can set the render hook fields in Options to examine logged key-value // pairs, one of which will be {"caller", Caller} if the Options.LogCaller // field is enabled for the given MessageClass. type Caller struct { // File is the basename of the file for this call site. File string `json:"file"` // Line is the line number in the file for this call site. Line int `json:"line"` // Func is the function name for this call site, or empty if // Options.LogCallerFunc is not enabled. Func string `json:"function,omitempty"` } func (f Formatter) caller() Caller { // +1 for this frame, +1 for Info/Error. pc, file, line, ok := runtime.Caller(f.depth + 2) if !ok { return Caller{"", 0, ""} } fn := "" if f.opts.LogCallerFunc { if fp := runtime.FuncForPC(pc); fp != nil { fn = fp.Name() } } return Caller{filepath.Base(file), line, fn} } const noValue = "" func (f Formatter) nonStringKey(v any) string { return fmt.Sprintf("", f.snippet(v)) } // snippet produces a short snippet string of an arbitrary value. func (f Formatter) snippet(v any) string { const snipLen = 16 snip := f.pretty(v) if len(snip) > snipLen { snip = snip[:snipLen] } return snip } // sanitize ensures that a list of key-value pairs has a value for every key // (adding a value if needed) and that each key is a string (substituting a key // if needed). func (f Formatter) sanitize(kvList []any) []any { if len(kvList)%2 != 0 { kvList = append(kvList, noValue) } for i := 0; i < len(kvList); i += 2 { _, ok := kvList[i].(string) if !ok { kvList[i] = f.nonStringKey(kvList[i]) } } return kvList } // startGroup opens a new group scope (basically a sub-struct), which locks all // the current saved values and starts them anew. This is needed to satisfy // slog. func (f *Formatter) startGroup(name string) { // Unnamed groups are just inlined. if name == "" { return } n := len(f.groups) f.groups = append(f.groups[:n:n], groupDef{f.groupName, f.valuesStr}) // Start collecting new values. f.groupName = name f.valuesStr = "" f.values = nil } // Init configures this Formatter from runtime info, such as the call depth // imposed by logr itself. // Note that this receiver is a pointer, so depth can be saved. func (f *Formatter) Init(info logr.RuntimeInfo) { f.depth += info.CallDepth } // Enabled checks whether an info message at the given level should be logged. func (f Formatter) Enabled(level int) bool { return level <= f.opts.Verbosity } // GetDepth returns the current depth of this Formatter. This is useful for // implementations which do their own caller attribution. func (f Formatter) GetDepth() int { return f.depth } // FormatInfo renders an Info log message into strings. The prefix will be // empty when no names were set (via AddNames), or when the output is // configured for JSON. func (f Formatter) FormatInfo(level int, msg string, kvList []any) (prefix, argsStr string) { args := make([]any, 0, 64) // using a constant here impacts perf prefix = f.prefix if f.outputFormat == outputJSON { args = append(args, "logger", prefix) prefix = "" } if f.opts.LogTimestamp { args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat)) } if policy := f.opts.LogCaller; policy == All || policy == Info { args = append(args, "caller", f.caller()) } if key := *f.opts.LogInfoLevel; key != "" { args = append(args, key, level) } args = append(args, "msg", msg) return prefix, f.render(args, kvList) } // FormatError renders an Error log message into strings. The prefix will be // empty when no names were set (via AddNames), or when the output is // configured for JSON. func (f Formatter) FormatError(err error, msg string, kvList []any) (prefix, argsStr string) { args := make([]any, 0, 64) // using a constant here impacts perf prefix = f.prefix if f.outputFormat == outputJSON { args = append(args, "logger", prefix) prefix = "" } if f.opts.LogTimestamp { args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat)) } if policy := f.opts.LogCaller; policy == All || policy == Error { args = append(args, "caller", f.caller()) } args = append(args, "msg", msg) var loggableErr any if err != nil { loggableErr = err.Error() } args = append(args, "error", loggableErr) return prefix, f.render(args, kvList) } // AddName appends the specified name. funcr uses '/' characters to separate // name elements. Callers should not pass '/' in the provided name string, but // this library does not actually enforce that. func (f *Formatter) AddName(name string) { if len(f.prefix) > 0 { f.prefix += "/" } f.prefix += name } // AddValues adds key-value pairs to the set of saved values to be logged with // each log line. func (f *Formatter) AddValues(kvList []any) { // Three slice args forces a copy. n := len(f.values) f.values = append(f.values[:n:n], kvList...) vals := f.values if hook := f.opts.RenderValuesHook; hook != nil { vals = hook(f.sanitize(vals)) } // Pre-render values, so we don't have to do it on each Info/Error call. buf := bytes.NewBuffer(make([]byte, 0, 1024)) f.flatten(buf, vals, true) // escape user-provided keys f.valuesStr = buf.String() } // AddCallDepth increases the number of stack-frames to skip when attributing // the log line to a file and line. func (f *Formatter) AddCallDepth(depth int) { f.depth += depth } ================================================ FILE: vendor/github.com/go-logr/logr/funcr/slogsink.go ================================================ //go:build go1.21 // +build go1.21 /* Copyright 2023 The logr Authors. 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. */ package funcr import ( "context" "log/slog" "github.com/go-logr/logr" ) var _ logr.SlogSink = &fnlogger{} const extraSlogSinkDepth = 3 // 2 for slog, 1 for SlogSink func (l fnlogger) Handle(_ context.Context, record slog.Record) error { kvList := make([]any, 0, 2*record.NumAttrs()) record.Attrs(func(attr slog.Attr) bool { kvList = attrToKVs(attr, kvList) return true }) if record.Level >= slog.LevelError { l.WithCallDepth(extraSlogSinkDepth).Error(nil, record.Message, kvList...) } else { level := l.levelFromSlog(record.Level) l.WithCallDepth(extraSlogSinkDepth).Info(level, record.Message, kvList...) } return nil } func (l fnlogger) WithAttrs(attrs []slog.Attr) logr.SlogSink { kvList := make([]any, 0, 2*len(attrs)) for _, attr := range attrs { kvList = attrToKVs(attr, kvList) } l.AddValues(kvList) return &l } func (l fnlogger) WithGroup(name string) logr.SlogSink { l.startGroup(name) return &l } // attrToKVs appends a slog.Attr to a logr-style kvList. It handle slog Groups // and other details of slog. func attrToKVs(attr slog.Attr, kvList []any) []any { attrVal := attr.Value.Resolve() if attrVal.Kind() == slog.KindGroup { groupVal := attrVal.Group() grpKVs := make([]any, 0, 2*len(groupVal)) for _, attr := range groupVal { grpKVs = attrToKVs(attr, grpKVs) } if attr.Key == "" { // slog says we have to inline these kvList = append(kvList, grpKVs...) } else { kvList = append(kvList, attr.Key, PseudoStruct(grpKVs)) } } else if attr.Key != "" { kvList = append(kvList, attr.Key, attrVal.Any()) } return kvList } // levelFromSlog adjusts the level by the logger's verbosity and negates it. // It ensures that the result is >= 0. This is necessary because the result is // passed to a LogSink and that API did not historically document whether // levels could be negative or what that meant. // // Some example usage: // // logrV0 := getMyLogger() // logrV2 := logrV0.V(2) // slogV2 := slog.New(logr.ToSlogHandler(logrV2)) // slogV2.Debug("msg") // =~ logrV2.V(4) =~ logrV0.V(6) // slogV2.Info("msg") // =~ logrV2.V(0) =~ logrV0.V(2) // slogv2.Warn("msg") // =~ logrV2.V(-4) =~ logrV0.V(0) func (l fnlogger) levelFromSlog(level slog.Level) int { result := -level if result < 0 { result = 0 // because LogSink doesn't expect negative V levels } return int(result) } ================================================ FILE: vendor/github.com/go-logr/logr/logr.go ================================================ /* Copyright 2019 The logr Authors. 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. */ // This design derives from Dave Cheney's blog: // http://dave.cheney.net/2015/11/05/lets-talk-about-logging // Package logr defines a general-purpose logging API and abstract interfaces // to back that API. Packages in the Go ecosystem can depend on this package, // while callers can implement logging with whatever backend is appropriate. // // # Usage // // Logging is done using a Logger instance. Logger is a concrete type with // methods, which defers the actual logging to a LogSink interface. The main // methods of Logger are Info() and Error(). Arguments to Info() and Error() // are key/value pairs rather than printf-style formatted strings, emphasizing // "structured logging". // // With Go's standard log package, we might write: // // log.Printf("setting target value %s", targetValue) // // With logr's structured logging, we'd write: // // logger.Info("setting target", "value", targetValue) // // Errors are much the same. Instead of: // // log.Printf("failed to open the pod bay door for user %s: %v", user, err) // // We'd write: // // logger.Error(err, "failed to open the pod bay door", "user", user) // // Info() and Error() are very similar, but they are separate methods so that // LogSink implementations can choose to do things like attach additional // information (such as stack traces) on calls to Error(). Error() messages are // always logged, regardless of the current verbosity. If there is no error // instance available, passing nil is valid. // // # Verbosity // // Often we want to log information only when the application in "verbose // mode". To write log lines that are more verbose, Logger has a V() method. // The higher the V-level of a log line, the less critical it is considered. // Log-lines with V-levels that are not enabled (as per the LogSink) will not // be written. Level V(0) is the default, and logger.V(0).Info() has the same // meaning as logger.Info(). Negative V-levels have the same meaning as V(0). // Error messages do not have a verbosity level and are always logged. // // Where we might have written: // // if flVerbose >= 2 { // log.Printf("an unusual thing happened") // } // // We can write: // // logger.V(2).Info("an unusual thing happened") // // # Logger Names // // Logger instances can have name strings so that all messages logged through // that instance have additional context. For example, you might want to add // a subsystem name: // // logger.WithName("compactor").Info("started", "time", time.Now()) // // The WithName() method returns a new Logger, which can be passed to // constructors or other functions for further use. Repeated use of WithName() // will accumulate name "segments". These name segments will be joined in some // way by the LogSink implementation. It is strongly recommended that name // segments contain simple identifiers (letters, digits, and hyphen), and do // not contain characters that could muddle the log output or confuse the // joining operation (e.g. whitespace, commas, periods, slashes, brackets, // quotes, etc). // // # Saved Values // // Logger instances can store any number of key/value pairs, which will be // logged alongside all messages logged through that instance. For example, // you might want to create a Logger instance per managed object: // // With the standard log package, we might write: // // log.Printf("decided to set field foo to value %q for object %s/%s", // targetValue, object.Namespace, object.Name) // // With logr we'd write: // // // Elsewhere: set up the logger to log the object name. // obj.logger = mainLogger.WithValues( // "name", obj.name, "namespace", obj.namespace) // // // later on... // obj.logger.Info("setting foo", "value", targetValue) // // # Best Practices // // Logger has very few hard rules, with the goal that LogSink implementations // might have a lot of freedom to differentiate. There are, however, some // things to consider. // // The log message consists of a constant message attached to the log line. // This should generally be a simple description of what's occurring, and should // never be a format string. Variable information can then be attached using // named values. // // Keys are arbitrary strings, but should generally be constant values. Values // may be any Go value, but how the value is formatted is determined by the // LogSink implementation. // // Logger instances are meant to be passed around by value. Code that receives // such a value can call its methods without having to check whether the // instance is ready for use. // // The zero logger (= Logger{}) is identical to Discard() and discards all log // entries. Code that receives a Logger by value can simply call it, the methods // will never crash. For cases where passing a logger is optional, a pointer to Logger // should be used. // // # Key Naming Conventions // // Keys are not strictly required to conform to any specification or regex, but // it is recommended that they: // - be human-readable and meaningful (not auto-generated or simple ordinals) // - be constant (not dependent on input data) // - contain only printable characters // - not contain whitespace or punctuation // - use lower case for simple keys and lowerCamelCase for more complex ones // // These guidelines help ensure that log data is processed properly regardless // of the log implementation. For example, log implementations will try to // output JSON data or will store data for later database (e.g. SQL) queries. // // While users are generally free to use key names of their choice, it's // generally best to avoid using the following keys, as they're frequently used // by implementations: // - "caller": the calling information (file/line) of a particular log line // - "error": the underlying error value in the `Error` method // - "level": the log level // - "logger": the name of the associated logger // - "msg": the log message // - "stacktrace": the stack trace associated with a particular log line or // error (often from the `Error` message) // - "ts": the timestamp for a log line // // Implementations are encouraged to make use of these keys to represent the // above concepts, when necessary (for example, in a pure-JSON output form, it // would be necessary to represent at least message and timestamp as ordinary // named values). // // # Break Glass // // Implementations may choose to give callers access to the underlying // logging implementation. The recommended pattern for this is: // // // Underlier exposes access to the underlying logging implementation. // // Since callers only have a logr.Logger, they have to know which // // implementation is in use, so this interface is less of an abstraction // // and more of way to test type conversion. // type Underlier interface { // GetUnderlying() // } // // Logger grants access to the sink to enable type assertions like this: // // func DoSomethingWithImpl(log logr.Logger) { // if underlier, ok := log.GetSink().(impl.Underlier); ok { // implLogger := underlier.GetUnderlying() // ... // } // } // // Custom `With*` functions can be implemented by copying the complete // Logger struct and replacing the sink in the copy: // // // WithFooBar changes the foobar parameter in the log sink and returns a // // new logger with that modified sink. It does nothing for loggers where // // the sink doesn't support that parameter. // func WithFoobar(log logr.Logger, foobar int) logr.Logger { // if foobarLogSink, ok := log.GetSink().(FoobarSink); ok { // log = log.WithSink(foobarLogSink.WithFooBar(foobar)) // } // return log // } // // Don't use New to construct a new Logger with a LogSink retrieved from an // existing Logger. Source code attribution might not work correctly and // unexported fields in Logger get lost. // // Beware that the same LogSink instance may be shared by different logger // instances. Calling functions that modify the LogSink will affect all of // those. package logr // New returns a new Logger instance. This is primarily used by libraries // implementing LogSink, rather than end users. Passing a nil sink will create // a Logger which discards all log lines. func New(sink LogSink) Logger { logger := Logger{} logger.setSink(sink) if sink != nil { sink.Init(runtimeInfo) } return logger } // setSink stores the sink and updates any related fields. It mutates the // logger and thus is only safe to use for loggers that are not currently being // used concurrently. func (l *Logger) setSink(sink LogSink) { l.sink = sink } // GetSink returns the stored sink. func (l Logger) GetSink() LogSink { return l.sink } // WithSink returns a copy of the logger with the new sink. func (l Logger) WithSink(sink LogSink) Logger { l.setSink(sink) return l } // Logger is an interface to an abstract logging implementation. This is a // concrete type for performance reasons, but all the real work is passed on to // a LogSink. Implementations of LogSink should provide their own constructors // that return Logger, not LogSink. // // The underlying sink can be accessed through GetSink and be modified through // WithSink. This enables the implementation of custom extensions (see "Break // Glass" in the package documentation). Normally the sink should be used only // indirectly. type Logger struct { sink LogSink level int } // Enabled tests whether this Logger is enabled. For example, commandline // flags might be used to set the logging verbosity and disable some info logs. func (l Logger) Enabled() bool { // Some implementations of LogSink look at the caller in Enabled (e.g. // different verbosity levels per package or file), but we only pass one // CallDepth in (via Init). This means that all calls from Logger to the // LogSink's Enabled, Info, and Error methods must have the same number of // frames. In other words, Logger methods can't call other Logger methods // which call these LogSink methods unless we do it the same in all paths. return l.sink != nil && l.sink.Enabled(l.level) } // Info logs a non-error message with the given key/value pairs as context. // // The msg argument should be used to add some constant description to the log // line. The key/value pairs can then be used to add additional variable // information. The key/value pairs must alternate string keys and arbitrary // values. func (l Logger) Info(msg string, keysAndValues ...any) { if l.sink == nil { return } if l.sink.Enabled(l.level) { // see comment in Enabled if withHelper, ok := l.sink.(CallStackHelperLogSink); ok { withHelper.GetCallStackHelper()() } l.sink.Info(l.level, msg, keysAndValues...) } } // Error logs an error, with the given message and key/value pairs as context. // It functions similarly to Info, but may have unique behavior, and should be // preferred for logging errors (see the package documentations for more // information). The log message will always be emitted, regardless of // verbosity level. // // The msg argument should be used to add context to any underlying error, // while the err argument should be used to attach the actual error that // triggered this log line, if present. The err parameter is optional // and nil may be passed instead of an error instance. func (l Logger) Error(err error, msg string, keysAndValues ...any) { if l.sink == nil { return } if withHelper, ok := l.sink.(CallStackHelperLogSink); ok { withHelper.GetCallStackHelper()() } l.sink.Error(err, msg, keysAndValues...) } // V returns a new Logger instance for a specific verbosity level, relative to // this Logger. In other words, V-levels are additive. A higher verbosity // level means a log message is less important. Negative V-levels are treated // as 0. func (l Logger) V(level int) Logger { if l.sink == nil { return l } if level < 0 { level = 0 } l.level += level return l } // GetV returns the verbosity level of the logger. If the logger's LogSink is // nil as in the Discard logger, this will always return 0. func (l Logger) GetV() int { // 0 if l.sink nil because of the if check in V above. return l.level } // WithValues returns a new Logger instance with additional key/value pairs. // See Info for documentation on how key/value pairs work. func (l Logger) WithValues(keysAndValues ...any) Logger { if l.sink == nil { return l } l.setSink(l.sink.WithValues(keysAndValues...)) return l } // WithName returns a new Logger instance with the specified name element added // to the Logger's name. Successive calls with WithName append additional // suffixes to the Logger's name. It's strongly recommended that name segments // contain only letters, digits, and hyphens (see the package documentation for // more information). func (l Logger) WithName(name string) Logger { if l.sink == nil { return l } l.setSink(l.sink.WithName(name)) return l } // WithCallDepth returns a Logger instance that offsets the call stack by the // specified number of frames when logging call site information, if possible. // This is useful for users who have helper functions between the "real" call // site and the actual calls to Logger methods. If depth is 0 the attribution // should be to the direct caller of this function. If depth is 1 the // attribution should skip 1 call frame, and so on. Successive calls to this // are additive. // // If the underlying log implementation supports a WithCallDepth(int) method, // it will be called and the result returned. If the implementation does not // support CallDepthLogSink, the original Logger will be returned. // // To skip one level, WithCallStackHelper() should be used instead of // WithCallDepth(1) because it works with implementions that support the // CallDepthLogSink and/or CallStackHelperLogSink interfaces. func (l Logger) WithCallDepth(depth int) Logger { if l.sink == nil { return l } if withCallDepth, ok := l.sink.(CallDepthLogSink); ok { l.setSink(withCallDepth.WithCallDepth(depth)) } return l } // WithCallStackHelper returns a new Logger instance that skips the direct // caller when logging call site information, if possible. This is useful for // users who have helper functions between the "real" call site and the actual // calls to Logger methods and want to support loggers which depend on marking // each individual helper function, like loggers based on testing.T. // // In addition to using that new logger instance, callers also must call the // returned function. // // If the underlying log implementation supports a WithCallDepth(int) method, // WithCallDepth(1) will be called to produce a new logger. If it supports a // WithCallStackHelper() method, that will be also called. If the // implementation does not support either of these, the original Logger will be // returned. func (l Logger) WithCallStackHelper() (func(), Logger) { if l.sink == nil { return func() {}, l } var helper func() if withCallDepth, ok := l.sink.(CallDepthLogSink); ok { l.setSink(withCallDepth.WithCallDepth(1)) } if withHelper, ok := l.sink.(CallStackHelperLogSink); ok { helper = withHelper.GetCallStackHelper() } else { helper = func() {} } return helper, l } // IsZero returns true if this logger is an uninitialized zero value func (l Logger) IsZero() bool { return l.sink == nil } // RuntimeInfo holds information that the logr "core" library knows which // LogSinks might want to know. type RuntimeInfo struct { // CallDepth is the number of call frames the logr library adds between the // end-user and the LogSink. LogSink implementations which choose to print // the original logging site (e.g. file & line) should climb this many // additional frames to find it. CallDepth int } // runtimeInfo is a static global. It must not be changed at run time. var runtimeInfo = RuntimeInfo{ CallDepth: 1, } // LogSink represents a logging implementation. End-users will generally not // interact with this type. type LogSink interface { // Init receives optional information about the logr library for LogSink // implementations that need it. Init(info RuntimeInfo) // Enabled tests whether this LogSink is enabled at the specified V-level. // For example, commandline flags might be used to set the logging // verbosity and disable some info logs. Enabled(level int) bool // Info logs a non-error message with the given key/value pairs as context. // The level argument is provided for optional logging. This method will // only be called when Enabled(level) is true. See Logger.Info for more // details. Info(level int, msg string, keysAndValues ...any) // Error logs an error, with the given message and key/value pairs as // context. See Logger.Error for more details. Error(err error, msg string, keysAndValues ...any) // WithValues returns a new LogSink with additional key/value pairs. See // Logger.WithValues for more details. WithValues(keysAndValues ...any) LogSink // WithName returns a new LogSink with the specified name appended. See // Logger.WithName for more details. WithName(name string) LogSink } // CallDepthLogSink represents a LogSink that knows how to climb the call stack // to identify the original call site and can offset the depth by a specified // number of frames. This is useful for users who have helper functions // between the "real" call site and the actual calls to Logger methods. // Implementations that log information about the call site (such as file, // function, or line) would otherwise log information about the intermediate // helper functions. // // This is an optional interface and implementations are not required to // support it. type CallDepthLogSink interface { // WithCallDepth returns a LogSink that will offset the call // stack by the specified number of frames when logging call // site information. // // If depth is 0, the LogSink should skip exactly the number // of call frames defined in RuntimeInfo.CallDepth when Info // or Error are called, i.e. the attribution should be to the // direct caller of Logger.Info or Logger.Error. // // If depth is 1 the attribution should skip 1 call frame, and so on. // Successive calls to this are additive. WithCallDepth(depth int) LogSink } // CallStackHelperLogSink represents a LogSink that knows how to climb // the call stack to identify the original call site and can skip // intermediate helper functions if they mark themselves as // helper. Go's testing package uses that approach. // // This is useful for users who have helper functions between the // "real" call site and the actual calls to Logger methods. // Implementations that log information about the call site (such as // file, function, or line) would otherwise log information about the // intermediate helper functions. // // This is an optional interface and implementations are not required // to support it. Implementations that choose to support this must not // simply implement it as WithCallDepth(1), because // Logger.WithCallStackHelper will call both methods if they are // present. This should only be implemented for LogSinks that actually // need it, as with testing.T. type CallStackHelperLogSink interface { // GetCallStackHelper returns a function that must be called // to mark the direct caller as helper function when logging // call site information. GetCallStackHelper() func() } // Marshaler is an optional interface that logged values may choose to // implement. Loggers with structured output, such as JSON, should // log the object return by the MarshalLog method instead of the // original value. type Marshaler interface { // MarshalLog can be used to: // - ensure that structs are not logged as strings when the original // value has a String method: return a different type without a // String method // - select which fields of a complex type should get logged: // return a simpler struct with fewer fields // - log unexported fields: return a different struct // with exported fields // // It may return any value of any type. MarshalLog() any } ================================================ FILE: vendor/github.com/go-logr/logr/sloghandler.go ================================================ //go:build go1.21 // +build go1.21 /* Copyright 2023 The logr Authors. 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. */ package logr import ( "context" "log/slog" ) type slogHandler struct { // May be nil, in which case all logs get discarded. sink LogSink // Non-nil if sink is non-nil and implements SlogSink. slogSink SlogSink // groupPrefix collects values from WithGroup calls. It gets added as // prefix to value keys when handling a log record. groupPrefix string // levelBias can be set when constructing the handler to influence the // slog.Level of log records. A positive levelBias reduces the // slog.Level value. slog has no API to influence this value after the // handler got created, so it can only be set indirectly through // Logger.V. levelBias slog.Level } var _ slog.Handler = &slogHandler{} // groupSeparator is used to concatenate WithGroup names and attribute keys. const groupSeparator = "." // GetLevel is used for black box unit testing. func (l *slogHandler) GetLevel() slog.Level { return l.levelBias } func (l *slogHandler) Enabled(_ context.Context, level slog.Level) bool { return l.sink != nil && (level >= slog.LevelError || l.sink.Enabled(l.levelFromSlog(level))) } func (l *slogHandler) Handle(ctx context.Context, record slog.Record) error { if l.slogSink != nil { // Only adjust verbosity level of log entries < slog.LevelError. if record.Level < slog.LevelError { record.Level -= l.levelBias } return l.slogSink.Handle(ctx, record) } // No need to check for nil sink here because Handle will only be called // when Enabled returned true. kvList := make([]any, 0, 2*record.NumAttrs()) record.Attrs(func(attr slog.Attr) bool { kvList = attrToKVs(attr, l.groupPrefix, kvList) return true }) if record.Level >= slog.LevelError { l.sinkWithCallDepth().Error(nil, record.Message, kvList...) } else { level := l.levelFromSlog(record.Level) l.sinkWithCallDepth().Info(level, record.Message, kvList...) } return nil } // sinkWithCallDepth adjusts the stack unwinding so that when Error or Info // are called by Handle, code in slog gets skipped. // // This offset currently (Go 1.21.0) works for calls through // slog.New(ToSlogHandler(...)). There's no guarantee that the call // chain won't change. Wrapping the handler will also break unwinding. It's // still better than not adjusting at all.... // // This cannot be done when constructing the handler because FromSlogHandler needs // access to the original sink without this adjustment. A second copy would // work, but then WithAttrs would have to be called for both of them. func (l *slogHandler) sinkWithCallDepth() LogSink { if sink, ok := l.sink.(CallDepthLogSink); ok { return sink.WithCallDepth(2) } return l.sink } func (l *slogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { if l.sink == nil || len(attrs) == 0 { return l } clone := *l if l.slogSink != nil { clone.slogSink = l.slogSink.WithAttrs(attrs) clone.sink = clone.slogSink } else { kvList := make([]any, 0, 2*len(attrs)) for _, attr := range attrs { kvList = attrToKVs(attr, l.groupPrefix, kvList) } clone.sink = l.sink.WithValues(kvList...) } return &clone } func (l *slogHandler) WithGroup(name string) slog.Handler { if l.sink == nil { return l } if name == "" { // slog says to inline empty groups return l } clone := *l if l.slogSink != nil { clone.slogSink = l.slogSink.WithGroup(name) clone.sink = clone.slogSink } else { clone.groupPrefix = addPrefix(clone.groupPrefix, name) } return &clone } // attrToKVs appends a slog.Attr to a logr-style kvList. It handle slog Groups // and other details of slog. func attrToKVs(attr slog.Attr, groupPrefix string, kvList []any) []any { attrVal := attr.Value.Resolve() if attrVal.Kind() == slog.KindGroup { groupVal := attrVal.Group() grpKVs := make([]any, 0, 2*len(groupVal)) prefix := groupPrefix if attr.Key != "" { prefix = addPrefix(groupPrefix, attr.Key) } for _, attr := range groupVal { grpKVs = attrToKVs(attr, prefix, grpKVs) } kvList = append(kvList, grpKVs...) } else if attr.Key != "" { kvList = append(kvList, addPrefix(groupPrefix, attr.Key), attrVal.Any()) } return kvList } func addPrefix(prefix, name string) string { if prefix == "" { return name } if name == "" { return prefix } return prefix + groupSeparator + name } // levelFromSlog adjusts the level by the logger's verbosity and negates it. // It ensures that the result is >= 0. This is necessary because the result is // passed to a LogSink and that API did not historically document whether // levels could be negative or what that meant. // // Some example usage: // // logrV0 := getMyLogger() // logrV2 := logrV0.V(2) // slogV2 := slog.New(logr.ToSlogHandler(logrV2)) // slogV2.Debug("msg") // =~ logrV2.V(4) =~ logrV0.V(6) // slogV2.Info("msg") // =~ logrV2.V(0) =~ logrV0.V(2) // slogv2.Warn("msg") // =~ logrV2.V(-4) =~ logrV0.V(0) func (l *slogHandler) levelFromSlog(level slog.Level) int { result := -level result += l.levelBias // in case the original Logger had a V level if result < 0 { result = 0 // because LogSink doesn't expect negative V levels } return int(result) } ================================================ FILE: vendor/github.com/go-logr/logr/slogr.go ================================================ //go:build go1.21 // +build go1.21 /* Copyright 2023 The logr Authors. 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. */ package logr import ( "context" "log/slog" ) // FromSlogHandler returns a Logger which writes to the slog.Handler. // // The logr verbosity level is mapped to slog levels such that V(0) becomes // slog.LevelInfo and V(4) becomes slog.LevelDebug. func FromSlogHandler(handler slog.Handler) Logger { if handler, ok := handler.(*slogHandler); ok { if handler.sink == nil { return Discard() } return New(handler.sink).V(int(handler.levelBias)) } return New(&slogSink{handler: handler}) } // ToSlogHandler returns a slog.Handler which writes to the same sink as the Logger. // // The returned logger writes all records with level >= slog.LevelError as // error log entries with LogSink.Error, regardless of the verbosity level of // the Logger: // // logger := // slog.New(ToSlogHandler(logger.V(10))).Error(...) -> logSink.Error(...) // // The level of all other records gets reduced by the verbosity // level of the Logger and the result is negated. If it happens // to be negative, then it gets replaced by zero because a LogSink // is not expected to handled negative levels: // // slog.New(ToSlogHandler(logger)).Debug(...) -> logger.GetSink().Info(level=4, ...) // slog.New(ToSlogHandler(logger)).Warning(...) -> logger.GetSink().Info(level=0, ...) // slog.New(ToSlogHandler(logger)).Info(...) -> logger.GetSink().Info(level=0, ...) // slog.New(ToSlogHandler(logger.V(4))).Info(...) -> logger.GetSink().Info(level=4, ...) func ToSlogHandler(logger Logger) slog.Handler { if sink, ok := logger.GetSink().(*slogSink); ok && logger.GetV() == 0 { return sink.handler } handler := &slogHandler{sink: logger.GetSink(), levelBias: slog.Level(logger.GetV())} if slogSink, ok := handler.sink.(SlogSink); ok { handler.slogSink = slogSink } return handler } // SlogSink is an optional interface that a LogSink can implement to support // logging through the slog.Logger or slog.Handler APIs better. It then should // also support special slog values like slog.Group. When used as a // slog.Handler, the advantages are: // // - stack unwinding gets avoided in favor of logging the pre-recorded PC, // as intended by slog // - proper grouping of key/value pairs via WithGroup // - verbosity levels > slog.LevelInfo can be recorded // - less overhead // // Both APIs (Logger and slog.Logger/Handler) then are supported equally // well. Developers can pick whatever API suits them better and/or mix // packages which use either API in the same binary with a common logging // implementation. // // This interface is necessary because the type implementing the LogSink // interface cannot also implement the slog.Handler interface due to the // different prototype of the common Enabled method. // // An implementation could support both interfaces in two different types, but then // additional interfaces would be needed to convert between those types in FromSlogHandler // and ToSlogHandler. type SlogSink interface { LogSink Handle(ctx context.Context, record slog.Record) error WithAttrs(attrs []slog.Attr) SlogSink WithGroup(name string) SlogSink } ================================================ FILE: vendor/github.com/go-logr/logr/slogsink.go ================================================ //go:build go1.21 // +build go1.21 /* Copyright 2023 The logr Authors. 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. */ package logr import ( "context" "log/slog" "runtime" "time" ) var ( _ LogSink = &slogSink{} _ CallDepthLogSink = &slogSink{} _ Underlier = &slogSink{} ) // Underlier is implemented by the LogSink returned by NewFromLogHandler. type Underlier interface { // GetUnderlying returns the Handler used by the LogSink. GetUnderlying() slog.Handler } const ( // nameKey is used to log the `WithName` values as an additional attribute. nameKey = "logger" // errKey is used to log the error parameter of Error as an additional attribute. errKey = "err" ) type slogSink struct { callDepth int name string handler slog.Handler } func (l *slogSink) Init(info RuntimeInfo) { l.callDepth = info.CallDepth } func (l *slogSink) GetUnderlying() slog.Handler { return l.handler } func (l *slogSink) WithCallDepth(depth int) LogSink { newLogger := *l newLogger.callDepth += depth return &newLogger } func (l *slogSink) Enabled(level int) bool { return l.handler.Enabled(context.Background(), slog.Level(-level)) } func (l *slogSink) Info(level int, msg string, kvList ...interface{}) { l.log(nil, msg, slog.Level(-level), kvList...) } func (l *slogSink) Error(err error, msg string, kvList ...interface{}) { l.log(err, msg, slog.LevelError, kvList...) } func (l *slogSink) log(err error, msg string, level slog.Level, kvList ...interface{}) { var pcs [1]uintptr // skip runtime.Callers, this function, Info/Error, and all helper functions above that. runtime.Callers(3+l.callDepth, pcs[:]) record := slog.NewRecord(time.Now(), level, msg, pcs[0]) if l.name != "" { record.AddAttrs(slog.String(nameKey, l.name)) } if err != nil { record.AddAttrs(slog.Any(errKey, err)) } record.Add(kvList...) _ = l.handler.Handle(context.Background(), record) } func (l slogSink) WithName(name string) LogSink { if l.name != "" { l.name += "/" } l.name += name return &l } func (l slogSink) WithValues(kvList ...interface{}) LogSink { l.handler = l.handler.WithAttrs(kvListToAttrs(kvList...)) return &l } func kvListToAttrs(kvList ...interface{}) []slog.Attr { // We don't need the record itself, only its Add method. record := slog.NewRecord(time.Time{}, 0, "", 0) record.Add(kvList...) attrs := make([]slog.Attr, 0, record.NumAttrs()) record.Attrs(func(attr slog.Attr) bool { attrs = append(attrs, attr) return true }) return attrs } ================================================ FILE: vendor/github.com/go-logr/stdr/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: vendor/github.com/go-logr/stdr/README.md ================================================ # Minimal Go logging using logr and Go's standard library [![Go Reference](https://pkg.go.dev/badge/github.com/go-logr/stdr.svg)](https://pkg.go.dev/github.com/go-logr/stdr) This package implements the [logr interface](https://github.com/go-logr/logr) in terms of Go's standard log package(https://pkg.go.dev/log). ================================================ FILE: vendor/github.com/go-logr/stdr/stdr.go ================================================ /* Copyright 2019 The logr Authors. 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. */ // Package stdr implements github.com/go-logr/logr.Logger in terms of // Go's standard log package. package stdr import ( "log" "os" "github.com/go-logr/logr" "github.com/go-logr/logr/funcr" ) // The global verbosity level. See SetVerbosity(). var globalVerbosity int // SetVerbosity sets the global level against which all info logs will be // compared. If this is greater than or equal to the "V" of the logger, the // message will be logged. A higher value here means more logs will be written. // The previous verbosity value is returned. This is not concurrent-safe - // callers must be sure to call it from only one goroutine. func SetVerbosity(v int) int { old := globalVerbosity globalVerbosity = v return old } // New returns a logr.Logger which is implemented by Go's standard log package, // or something like it. If std is nil, this will use a default logger // instead. // // Example: stdr.New(log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile))) func New(std StdLogger) logr.Logger { return NewWithOptions(std, Options{}) } // NewWithOptions returns a logr.Logger which is implemented by Go's standard // log package, or something like it. See New for details. func NewWithOptions(std StdLogger, opts Options) logr.Logger { if std == nil { // Go's log.Default() is only available in 1.16 and higher. std = log.New(os.Stderr, "", log.LstdFlags) } if opts.Depth < 0 { opts.Depth = 0 } fopts := funcr.Options{ LogCaller: funcr.MessageClass(opts.LogCaller), } sl := &logger{ Formatter: funcr.NewFormatter(fopts), std: std, } // For skipping our own logger.Info/Error. sl.Formatter.AddCallDepth(1 + opts.Depth) return logr.New(sl) } // Options carries parameters which influence the way logs are generated. type Options struct { // Depth biases the assumed number of call frames to the "true" caller. // This is useful when the calling code calls a function which then calls // stdr (e.g. a logging shim to another API). Values less than zero will // be treated as zero. Depth int // LogCaller tells stdr to add a "caller" key to some or all log lines. // Go's log package has options to log this natively, too. LogCaller MessageClass // TODO: add an option to log the date/time } // MessageClass indicates which category or categories of messages to consider. type MessageClass int const ( // None ignores all message classes. None MessageClass = iota // All considers all message classes. All // Info only considers info messages. Info // Error only considers error messages. Error ) // StdLogger is the subset of the Go stdlib log.Logger API that is needed for // this adapter. type StdLogger interface { // Output is the same as log.Output and log.Logger.Output. Output(calldepth int, logline string) error } type logger struct { funcr.Formatter std StdLogger } var _ logr.LogSink = &logger{} var _ logr.CallDepthLogSink = &logger{} func (l logger) Enabled(level int) bool { return globalVerbosity >= level } func (l logger) Info(level int, msg string, kvList ...interface{}) { prefix, args := l.FormatInfo(level, msg, kvList) if prefix != "" { args = prefix + ": " + args } _ = l.std.Output(l.Formatter.GetDepth()+1, args) } func (l logger) Error(err error, msg string, kvList ...interface{}) { prefix, args := l.FormatError(err, msg, kvList) if prefix != "" { args = prefix + ": " + args } _ = l.std.Output(l.Formatter.GetDepth()+1, args) } func (l logger) WithName(name string) logr.LogSink { l.Formatter.AddName(name) return &l } func (l logger) WithValues(kvList ...interface{}) logr.LogSink { l.Formatter.AddValues(kvList) return &l } func (l logger) WithCallDepth(depth int) logr.LogSink { l.Formatter.AddCallDepth(depth) return &l } // Underlier exposes access to the underlying logging implementation. Since // callers only have a logr.Logger, they have to know which implementation is // in use, so this interface is less of an abstraction and more of way to test // type conversion. type Underlier interface { GetUnderlying() StdLogger } // GetUnderlying returns the StdLogger underneath this logger. Since StdLogger // is itself an interface, the result may or may not be a Go log.Logger. func (l logger) GetUnderlying() StdLogger { return l.std } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 siddontang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/client/auth.go ================================================ package client import ( "bytes" "crypto/tls" "encoding/binary" "fmt" . "github.com/go-mysql-org/go-mysql/mysql" "github.com/go-mysql-org/go-mysql/packet" "github.com/pingcap/errors" "github.com/pingcap/tidb/pkg/parser/charset" ) const defaultAuthPluginName = AUTH_NATIVE_PASSWORD // defines the supported auth plugins var supportedAuthPlugins = []string{AUTH_NATIVE_PASSWORD, AUTH_SHA256_PASSWORD, AUTH_CACHING_SHA2_PASSWORD} // helper function to determine what auth methods are allowed by this client func authPluginAllowed(pluginName string) bool { for _, p := range supportedAuthPlugins { if pluginName == p { return true } } return false } // See: // - https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_v10.html // - https://github.com/alibaba/canal/blob/0ec46991499a22870dde4ae736b2586cbcbfea94/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/packets/server/HandshakeInitializationPacket.java#L89 // - https://github.com/vapor/mysql-nio/blob/main/Sources/MySQLNIO/Protocol/MySQLProtocol%2BHandshakeV10.swift // - https://github.com/github/vitess-gh/blob/70ae1a2b3a116ff6411b0f40852d6e71382f6e07/go/mysql/client.go func (c *Conn) readInitialHandshake() error { data, err := c.ReadPacket() if err != nil { return errors.Trace(err) } if data[0] == ERR_HEADER { return errors.Annotate(c.handleErrorPacket(data), "read initial handshake error") } if data[0] != ClassicProtocolVersion { if data[0] == XProtocolVersion { return errors.Errorf( "invalid protocol version %d, expected 10. "+ "This might be X Protocol, make sure to connect to the right port", data[0]) } return errors.Errorf("invalid protocol version %d, expected 10", data[0]) } pos := 1 // skip mysql version // mysql version end with 0x00 version := data[pos : bytes.IndexByte(data[pos:], 0x00)+1] c.serverVersion = string(version) pos += len(version) + 1 /*trailing zero byte*/ // connection id length is 4 c.connectionID = binary.LittleEndian.Uint32(data[pos : pos+4]) pos += 4 // first 8 bytes of the plugin provided data (scramble) c.salt = append(c.salt[:0], data[pos:pos+8]...) pos += 8 if data[pos] != 0 { // 0x00 byte, terminating the first part of a scramble return errors.Errorf("expect 0x00 after scramble, got %q", rune(data[pos])) } pos++ // The lower 2 bytes of the Capabilities Flags c.capability = uint32(binary.LittleEndian.Uint16(data[pos : pos+2])) // check protocol if c.capability&CLIENT_PROTOCOL_41 == 0 { return errors.New("the MySQL server can not support protocol 41 and above required by the client") } if c.capability&CLIENT_SSL == 0 && c.tlsConfig != nil { return errors.New("the MySQL Server does not support TLS required by the client") } pos += 2 if len(data) > pos { // default server a_protocol_character_set, only the lower 8-bits // c.charset = data[pos] pos += 1 c.status = binary.LittleEndian.Uint16(data[pos : pos+2]) pos += 2 // The upper 2 bytes of the Capabilities Flags c.capability = uint32(binary.LittleEndian.Uint16(data[pos:pos+2]))<<16 | c.capability pos += 2 // length of the combined auth_plugin_data (scramble), if auth_plugin_data_len is > 0 authPluginDataLen := data[pos] if (c.capability&CLIENT_PLUGIN_AUTH == 0) && (authPluginDataLen > 0) { return errors.Errorf("invalid auth plugin data filler %d", authPluginDataLen) } pos++ // skip reserved (all [00] ?) pos += 10 if c.capability&CLIENT_SECURE_CONNECTION != 0 { // Rest of the plugin provided data (scramble) // https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_v10.html // $len=MAX(13, length of auth-plugin-data - 8) // // https://github.com/mysql/mysql-server/blob/1bfe02bdad6604d54913c62614bde57a055c8332/sql/auth/sql_authentication.cc#L1641-L1642 // the first packet *must* have at least 20 bytes of a scramble. // if a plugin provided less, we pad it to 20 with zeros rest := int(authPluginDataLen) - 8 if rest < 13 { rest = 13 } authPluginDataPart2 := data[pos : pos+rest-1] pos += rest c.salt = append(c.salt, authPluginDataPart2...) } if c.capability&CLIENT_PLUGIN_AUTH != 0 { c.authPluginName = string(data[pos : pos+bytes.IndexByte(data[pos:], 0x00)]) pos += len(c.authPluginName) if data[pos] != 0 { return errors.Errorf("expect 0x00 after authPluginName, got %q", rune(data[pos])) } // pos++ // ineffectual } } // if server gives no default auth plugin name, use a client default if c.authPluginName == "" { c.authPluginName = defaultAuthPluginName } return nil } // generate auth response data according to auth plugin // // NOTE: the returned boolean value indicates whether to add a \NUL to the end of data. // it is quite tricky because MySQL server expects different formats of responses in different auth situations. // here the \NUL needs to be added when sending back the empty password or cleartext password in 'sha256_password' // authentication. func (c *Conn) genAuthResponse(authData []byte) ([]byte, bool, error) { // password hashing switch c.authPluginName { case AUTH_NATIVE_PASSWORD: return CalcPassword(authData[:20], []byte(c.password)), false, nil case AUTH_CACHING_SHA2_PASSWORD: return CalcCachingSha2Password(authData, c.password), false, nil case AUTH_CLEAR_PASSWORD: return []byte(c.password), true, nil case AUTH_SHA256_PASSWORD: if len(c.password) == 0 { return nil, true, nil } if c.tlsConfig != nil || c.proto == "unix" { // write cleartext auth packet // see: https://dev.mysql.com/doc/refman/8.0/en/sha256-pluggable-authentication.html return []byte(c.password), true, nil } else { // request public key from server // see: https://dev.mysql.com/doc/internals/en/public-key-retrieval.html return []byte{1}, false, nil } default: // not reachable return nil, false, fmt.Errorf("auth plugin '%s' is not supported", c.authPluginName) } } // generate connection attributes data func (c *Conn) genAttributes() []byte { if len(c.attributes) == 0 { return nil } attrData := make([]byte, 0) for k, v := range c.attributes { attrData = append(attrData, PutLengthEncodedString([]byte(k))...) attrData = append(attrData, PutLengthEncodedString([]byte(v))...) } return append(PutLengthEncodedInt(uint64(len(attrData))), attrData...) } // See: http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse func (c *Conn) writeAuthHandshake() error { if !authPluginAllowed(c.authPluginName) { return fmt.Errorf("unknow auth plugin name '%s'", c.authPluginName) } // Set default client capabilities that reflect the abilities of this library capability := CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION | CLIENT_LONG_PASSWORD | CLIENT_TRANSACTIONS | CLIENT_PLUGIN_AUTH // Adjust client capability flags based on server support capability |= c.capability & CLIENT_LONG_FLAG // Adjust client capability flags on specific client requests // Only flags that would make any sense setting and aren't handled elsewhere // in the library are supported here capability |= c.ccaps&CLIENT_FOUND_ROWS | c.ccaps&CLIENT_IGNORE_SPACE | c.ccaps&CLIENT_MULTI_STATEMENTS | c.ccaps&CLIENT_MULTI_RESULTS | c.ccaps&CLIENT_PS_MULTI_RESULTS | c.ccaps&CLIENT_CONNECT_ATTRS | c.ccaps&CLIENT_COMPRESS | c.ccaps&CLIENT_ZSTD_COMPRESSION_ALGORITHM | c.ccaps&CLIENT_LOCAL_FILES // To enable TLS / SSL if c.tlsConfig != nil { capability |= CLIENT_SSL } auth, addNull, err := c.genAuthResponse(c.salt) if err != nil { return err } // encode length of the auth plugin data // here we use the Length-Encoded-Integer(LEI) as the data length may not fit into one byte // see: https://dev.mysql.com/doc/internals/en/integer.html#length-encoded-integer var authRespLEIBuf [9]byte authRespLEI := AppendLengthEncodedInteger(authRespLEIBuf[:0], uint64(len(auth))) if len(authRespLEI) > 1 { // if the length can not be written in 1 byte, it must be written as a // length encoded integer capability |= CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA } // packet length // capability 4 // max-packet size 4 // charset 1 // reserved all[0] 23 // username // auth // mysql_native_password + null-terminated length := 4 + 4 + 1 + 23 + len(c.user) + 1 + len(authRespLEI) + len(auth) + 21 + 1 if addNull { length++ } // db name if len(c.db) > 0 { capability |= CLIENT_CONNECT_WITH_DB length += len(c.db) + 1 } // connection attributes attrData := c.genAttributes() if len(attrData) > 0 { capability |= CLIENT_CONNECT_ATTRS length += len(attrData) } if c.ccaps&CLIENT_ZSTD_COMPRESSION_ALGORITHM > 0 { length++ } data := make([]byte, length+4) // capability [32 bit] data[4] = byte(capability) data[5] = byte(capability >> 8) data[6] = byte(capability >> 16) data[7] = byte(capability >> 24) // MaxPacketSize [32 bit] (none) data[8] = 0x00 data[9] = 0x00 data[10] = 0x00 data[11] = 0x00 // Charset [1 byte] // use default collation id 33 here, is `utf8mb3_general_ci` collationName := c.collation if len(collationName) == 0 { collationName = DEFAULT_COLLATION_NAME } collation, err := charset.GetCollationByName(collationName) if err != nil { return fmt.Errorf("invalid collation name %s", collationName) } // the MySQL protocol calls for the collation id to be sent as 1 byte, where only the // lower 8 bits are used in this field. data[12] = byte(collation.ID & 0xff) // SSL Connection Request Packet // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest if c.tlsConfig != nil { // Send TLS / SSL request packet if err := c.WritePacket(data[:(4+4+1+23)+4]); err != nil { return err } // Switch to TLS tlsConn := tls.Client(c.Conn.Conn, c.tlsConfig) if err := tlsConn.Handshake(); err != nil { return err } currentSequence := c.Sequence c.Conn = packet.NewConnWithTimeout(tlsConn, c.ReadTimeout, c.WriteTimeout, c.BufferSize) c.Sequence = currentSequence } // Filler [23 bytes] (all 0x00) pos := 13 for ; pos < 13+23; pos++ { data[pos] = 0 } // User [null terminated string] if len(c.user) > 0 { pos += copy(data[pos:], c.user) } data[pos] = 0x00 pos++ // auth [length encoded integer] pos += copy(data[pos:], authRespLEI) pos += copy(data[pos:], auth) if addNull { data[pos] = 0x00 pos++ } // db [null terminated string] if len(c.db) > 0 { pos += copy(data[pos:], c.db) data[pos] = 0x00 pos++ } // Assume native client during response pos += copy(data[pos:], c.authPluginName) data[pos] = 0x00 pos++ // connection attributes if len(attrData) > 0 { pos += copy(data[pos:], attrData) } if c.ccaps&CLIENT_ZSTD_COMPRESSION_ALGORITHM > 0 { // zstd_compression_level data[pos] = 0x03 } return c.WritePacket(data) } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/client/conn.go ================================================ package client import ( "bytes" "context" "crypto/tls" "fmt" "net" "runtime" "strings" "time" "github.com/pingcap/errors" "github.com/pingcap/tidb/pkg/parser/charset" . "github.com/go-mysql-org/go-mysql/mysql" "github.com/go-mysql-org/go-mysql/packet" "github.com/go-mysql-org/go-mysql/utils" ) const defaultBufferSize = 65536 // 64kb type Option func(*Conn) error type Conn struct { *packet.Conn user string password string db string tlsConfig *tls.Config proto string // Connection read and write timeouts to set on the connection ReadTimeout time.Duration WriteTimeout time.Duration // The buffer size to use in the packet connection BufferSize int serverVersion string // server capabilities capability uint32 // client-set capabilities only ccaps uint32 attributes map[string]string status uint16 charset string // sets the collation to be set on the auth handshake, this does not issue a 'set names' command collation string salt []byte authPluginName string connectionID uint32 } // This function will be called for every row in resultset from ExecuteSelectStreaming. type SelectPerRowCallback func(row []FieldValue) error // This function will be called once per result from ExecuteSelectStreaming type SelectPerResultCallback func(result *Result) error // This function will be called once per result from ExecuteMultiple type ExecPerResultCallback func(result *Result, err error) func getNetProto(addr string) string { proto := "tcp" if strings.Contains(addr, "/") { proto = "unix" } return proto } // Connect to a MySQL server, addr can be ip:port, or a unix socket domain like /var/sock. // Accepts a series of configuration functions as a variadic argument. func Connect(addr, user, password, dbName string, options ...Option) (*Conn, error) { return ConnectWithTimeout(addr, user, password, dbName, time.Second*10, options...) } // ConnectWithTimeout to a MySQL address using a timeout. func ConnectWithTimeout(addr, user, password, dbName string, timeout time.Duration, options ...Option) (*Conn, error) { return ConnectWithContext(context.Background(), addr, user, password, dbName, time.Second*10, options...) } // ConnectWithContext to a MySQL addr using the provided context. func ConnectWithContext(ctx context.Context, addr, user, password, dbName string, timeout time.Duration, options ...Option) (*Conn, error) { dialer := &net.Dialer{Timeout: timeout} return ConnectWithDialer(ctx, "", addr, user, password, dbName, dialer.DialContext, options...) } // Dialer connects to the address on the named network using the provided context. type Dialer func(ctx context.Context, network, address string) (net.Conn, error) // ConnectWithDialer to a MySQL server using the given Dialer. func ConnectWithDialer(ctx context.Context, network, addr, user, password, dbName string, dialer Dialer, options ...Option) (*Conn, error) { c := new(Conn) c.BufferSize = defaultBufferSize c.attributes = map[string]string{ "_client_name": "go-mysql", // "_client_version": "0.1", "_os": runtime.GOOS, "_platform": runtime.GOARCH, "_runtime_version": runtime.Version(), } if network == "" { network = getNetProto(addr) } var err error conn, err := dialer(ctx, network, addr) if err != nil { return nil, errors.Trace(err) } c.user = user c.password = password c.db = dbName c.proto = network // use default charset here, utf-8 c.charset = DEFAULT_CHARSET // Apply configuration functions. for _, option := range options { if err := option(c); err != nil { // must close the connection in the event the provided configuration is not valid _ = conn.Close() return nil, err } } c.Conn = packet.NewConnWithTimeout(conn, c.ReadTimeout, c.WriteTimeout, c.BufferSize) if c.tlsConfig != nil { seq := c.Conn.Sequence c.Conn = packet.NewTLSConnWithTimeout(conn, c.ReadTimeout, c.WriteTimeout) c.Conn.Sequence = seq } if err = c.handshake(); err != nil { // in the event of an error c.handshake() will close the connection return nil, errors.Trace(err) } if c.ccaps&CLIENT_COMPRESS > 0 { c.Conn.Compression = MYSQL_COMPRESS_ZLIB } else if c.ccaps&CLIENT_ZSTD_COMPRESSION_ALGORITHM > 0 { c.Conn.Compression = MYSQL_COMPRESS_ZSTD } // if a collation was set with a ID of > 255, then we need to call SET NAMES ... // since the auth handshake response only support collations with 1-byte ids if len(c.collation) != 0 { collation, err := charset.GetCollationByName(c.collation) if err != nil { c.Close() return nil, errors.Trace(fmt.Errorf("invalid collation name %s", c.collation)) } if collation.ID > 255 { if _, err := c.exec(fmt.Sprintf("SET NAMES %s COLLATE %s", c.charset, c.collation)); err != nil { c.Close() return nil, errors.Trace(err) } } } return c, nil } func (c *Conn) handshake() error { var err error if err = c.readInitialHandshake(); err != nil { c.Close() return errors.Trace(fmt.Errorf("readInitialHandshake: %w", err)) } if err := c.writeAuthHandshake(); err != nil { c.Close() return errors.Trace(fmt.Errorf("writeAuthHandshake: %w", err)) } if err := c.handleAuthResult(); err != nil { c.Close() return errors.Trace(fmt.Errorf("handleAuthResult: %w", err)) } return nil } func (c *Conn) Close() error { return c.Conn.Close() } func (c *Conn) Quit() error { if err := c.writeCommand(COM_QUIT); err != nil { return err } return c.Close() } func (c *Conn) Ping() error { if err := c.writeCommand(COM_PING); err != nil { return errors.Trace(err) } if _, err := c.readOK(); err != nil { return errors.Trace(err) } return nil } // SetCapability enables the use of a specific capability func (c *Conn) SetCapability(cap uint32) { c.ccaps |= cap } // UnsetCapability disables the use of a specific capability func (c *Conn) UnsetCapability(cap uint32) { c.ccaps &= ^cap } // HasCapability returns true if the connection has the specific capability func (c *Conn) HasCapability(cap uint32) bool { return c.ccaps&cap > 0 } // UseSSL: use default SSL // pass to options when connect func (c *Conn) UseSSL(insecureSkipVerify bool) { c.tlsConfig = &tls.Config{InsecureSkipVerify: insecureSkipVerify} } // SetTLSConfig: use user-specified TLS config // pass to options when connect func (c *Conn) SetTLSConfig(config *tls.Config) { c.tlsConfig = config } func (c *Conn) UseDB(dbName string) error { if c.db == dbName { return nil } if err := c.writeCommandStr(COM_INIT_DB, dbName); err != nil { return errors.Trace(err) } if _, err := c.readOK(); err != nil { return errors.Trace(err) } c.db = dbName return nil } func (c *Conn) GetDB() string { return c.db } // GetServerVersion returns the version of the server as reported by the server // in the initial server greeting. func (c *Conn) GetServerVersion() string { return c.serverVersion } // CompareServerVersion is comparing version v against the version // of the server and returns 0 if they are equal, and 1 if the server version // is higher and -1 if the server version is lower. func (c *Conn) CompareServerVersion(v string) (int, error) { return CompareServerVersions(c.serverVersion, v) } func (c *Conn) Execute(command string, args ...interface{}) (*Result, error) { if len(args) == 0 { return c.exec(command) } else { if s, err := c.Prepare(command); err != nil { return nil, errors.Trace(err) } else { var r *Result r, err = s.Execute(args...) s.Close() return r, err } } } // ExecuteMultiple will call perResultCallback for every result of the multiple queries // that are executed. // // When ExecuteMultiple is used, the connection should have the SERVER_MORE_RESULTS_EXISTS // flag set to signal the server multiple queries are executed. Handling the responses // is up to the implementation of perResultCallback. func (c *Conn) ExecuteMultiple(query string, perResultCallback ExecPerResultCallback) (*Result, error) { if err := c.writeCommandStr(COM_QUERY, query); err != nil { return nil, errors.Trace(err) } var err error var result *Result bs := utils.ByteSliceGet(16) defer utils.ByteSlicePut(bs) for { bs.B, err = c.ReadPacketReuseMem(bs.B[:0]) if err != nil { return nil, errors.Trace(err) } switch bs.B[0] { case OK_HEADER: result, err = c.handleOKPacket(bs.B) case ERR_HEADER: err = c.handleErrorPacket(bytes.Repeat(bs.B, 1)) result = nil case LocalInFile_HEADER: err = ErrMalformPacket result = nil default: result, err = c.readResultset(bs.B, false) } // call user-defined callback perResultCallback(result, err) // if there was an error of this was the last result, stop looping if err != nil || result.Status&SERVER_MORE_RESULTS_EXISTS == 0 { break } } // return an empty result(set) signaling we're done streaming a multiple // streaming session // if this would end up in WriteValue, it would just be ignored as all // responses should have been handled in perResultCallback rs := NewResultset(0) rs.Streaming = StreamingMultiple rs.StreamingDone = true return NewResult(rs), nil } // ExecuteSelectStreaming will call perRowCallback for every row in resultset // WITHOUT saving any row data to Result.{Values/RawPkg/RowDatas} fields. // When given, perResultCallback will be called once per result // // ExecuteSelectStreaming should be used only for SELECT queries with a large response resultset for memory preserving. func (c *Conn) ExecuteSelectStreaming(command string, result *Result, perRowCallback SelectPerRowCallback, perResultCallback SelectPerResultCallback) error { if err := c.writeCommandStr(COM_QUERY, command); err != nil { return errors.Trace(err) } return c.readResultStreaming(false, result, perRowCallback, perResultCallback) } func (c *Conn) Begin() error { _, err := c.exec("BEGIN") return errors.Trace(err) } func (c *Conn) Commit() error { _, err := c.exec("COMMIT") return errors.Trace(err) } func (c *Conn) Rollback() error { _, err := c.exec("ROLLBACK") return errors.Trace(err) } func (c *Conn) SetAttributes(attributes map[string]string) { for k, v := range attributes { c.attributes[k] = v } } func (c *Conn) SetCharset(charset string) error { if c.charset == charset { return nil } if _, err := c.exec(fmt.Sprintf("SET NAMES %s", charset)); err != nil { return errors.Trace(err) } else { c.charset = charset return nil } } func (c *Conn) SetCollation(collation string) error { if len(c.serverVersion) != 0 { return errors.Trace(errors.Errorf("cannot set collation after connection is established")) } c.collation = collation return nil } func (c *Conn) GetCollation() string { return c.collation } func (c *Conn) FieldList(table string, wildcard string) ([]*Field, error) { if err := c.writeCommandStrStr(COM_FIELD_LIST, table, wildcard); err != nil { return nil, errors.Trace(err) } fs := make([]*Field, 0, 4) var f *Field for { data, err := c.ReadPacket() if err != nil { return nil, errors.Trace(err) } // ERR Packet if data[0] == ERR_HEADER { return nil, c.handleErrorPacket(data) } // EOF Packet if c.isEOFPacket(data) { return fs, nil } if f, err = FieldData(data).Parse(); err != nil { return nil, errors.Trace(err) } fs = append(fs, f) } } func (c *Conn) SetAutoCommit() error { if !c.IsAutoCommit() { if _, err := c.exec("SET AUTOCOMMIT = 1"); err != nil { return errors.Trace(err) } } return nil } func (c *Conn) IsAutoCommit() bool { return c.status&SERVER_STATUS_AUTOCOMMIT > 0 } func (c *Conn) IsInTransaction() bool { return c.status&SERVER_STATUS_IN_TRANS > 0 } func (c *Conn) GetCharset() string { return c.charset } func (c *Conn) GetConnectionID() uint32 { return c.connectionID } func (c *Conn) HandleOKPacket(data []byte) *Result { r, _ := c.handleOKPacket(data) return r } func (c *Conn) HandleErrorPacket(data []byte) error { return c.handleErrorPacket(data) } func (c *Conn) ReadOKPacket() (*Result, error) { return c.readOK() } func (c *Conn) exec(query string) (*Result, error) { if err := c.writeCommandStr(COM_QUERY, query); err != nil { return nil, errors.Trace(err) } return c.readResult(false) } // CapabilityString is returning a string with the names of capability flags // separated by "|". Examples of capability names are CLIENT_DEPRECATE_EOF and CLIENT_PROTOCOL_41. func (c *Conn) CapabilityString() string { var caps []string capability := c.capability for i := 0; capability != 0; i++ { field := uint32(1 << i) if capability&field == 0 { continue } capability ^= field switch field { case CLIENT_LONG_PASSWORD: caps = append(caps, "CLIENT_LONG_PASSWORD") case CLIENT_FOUND_ROWS: caps = append(caps, "CLIENT_FOUND_ROWS") case CLIENT_LONG_FLAG: caps = append(caps, "CLIENT_LONG_FLAG") case CLIENT_CONNECT_WITH_DB: caps = append(caps, "CLIENT_CONNECT_WITH_DB") case CLIENT_NO_SCHEMA: caps = append(caps, "CLIENT_NO_SCHEMA") case CLIENT_COMPRESS: caps = append(caps, "CLIENT_COMPRESS") case CLIENT_ODBC: caps = append(caps, "CLIENT_ODBC") case CLIENT_LOCAL_FILES: caps = append(caps, "CLIENT_LOCAL_FILES") case CLIENT_IGNORE_SPACE: caps = append(caps, "CLIENT_IGNORE_SPACE") case CLIENT_PROTOCOL_41: caps = append(caps, "CLIENT_PROTOCOL_41") case CLIENT_INTERACTIVE: caps = append(caps, "CLIENT_INTERACTIVE") case CLIENT_SSL: caps = append(caps, "CLIENT_SSL") case CLIENT_IGNORE_SIGPIPE: caps = append(caps, "CLIENT_IGNORE_SIGPIPE") case CLIENT_TRANSACTIONS: caps = append(caps, "CLIENT_TRANSACTIONS") case CLIENT_RESERVED: caps = append(caps, "CLIENT_RESERVED") case CLIENT_SECURE_CONNECTION: caps = append(caps, "CLIENT_SECURE_CONNECTION") case CLIENT_MULTI_STATEMENTS: caps = append(caps, "CLIENT_MULTI_STATEMENTS") case CLIENT_MULTI_RESULTS: caps = append(caps, "CLIENT_MULTI_RESULTS") case CLIENT_PS_MULTI_RESULTS: caps = append(caps, "CLIENT_PS_MULTI_RESULTS") case CLIENT_PLUGIN_AUTH: caps = append(caps, "CLIENT_PLUGIN_AUTH") case CLIENT_CONNECT_ATTRS: caps = append(caps, "CLIENT_CONNECT_ATTRS") case CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA: caps = append(caps, "CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA") case CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS: caps = append(caps, "CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS") case CLIENT_SESSION_TRACK: caps = append(caps, "CLIENT_SESSION_TRACK") case CLIENT_DEPRECATE_EOF: caps = append(caps, "CLIENT_DEPRECATE_EOF") case CLIENT_OPTIONAL_RESULTSET_METADATA: caps = append(caps, "CLIENT_OPTIONAL_RESULTSET_METADATA") case CLIENT_ZSTD_COMPRESSION_ALGORITHM: caps = append(caps, "CLIENT_ZSTD_COMPRESSION_ALGORITHM") case CLIENT_QUERY_ATTRIBUTES: caps = append(caps, "CLIENT_QUERY_ATTRIBUTES") case MULTI_FACTOR_AUTHENTICATION: caps = append(caps, "MULTI_FACTOR_AUTHENTICATION") case CLIENT_CAPABILITY_EXTENSION: caps = append(caps, "CLIENT_CAPABILITY_EXTENSION") case CLIENT_SSL_VERIFY_SERVER_CERT: caps = append(caps, "CLIENT_SSL_VERIFY_SERVER_CERT") case CLIENT_REMEMBER_OPTIONS: caps = append(caps, "CLIENT_REMEMBER_OPTIONS") default: caps = append(caps, fmt.Sprintf("(%d)", field)) } } return strings.Join(caps, "|") } func (c *Conn) StatusString() string { var stats []string status := c.status for i := 0; status != 0; i++ { field := uint16(1 << i) if status&field == 0 { continue } status ^= field switch field { case SERVER_STATUS_IN_TRANS: stats = append(stats, "SERVER_STATUS_IN_TRANS") case SERVER_STATUS_AUTOCOMMIT: stats = append(stats, "SERVER_STATUS_AUTOCOMMIT") case SERVER_MORE_RESULTS_EXISTS: stats = append(stats, "SERVER_MORE_RESULTS_EXISTS") case SERVER_STATUS_NO_GOOD_INDEX_USED: stats = append(stats, "SERVER_STATUS_NO_GOOD_INDEX_USED") case SERVER_STATUS_NO_INDEX_USED: stats = append(stats, "SERVER_STATUS_NO_INDEX_USED") case SERVER_STATUS_CURSOR_EXISTS: stats = append(stats, "SERVER_STATUS_CURSOR_EXISTS") case SERVER_STATUS_LAST_ROW_SEND: stats = append(stats, "SERVER_STATUS_LAST_ROW_SEND") case SERVER_STATUS_DB_DROPPED: stats = append(stats, "SERVER_STATUS_DB_DROPPED") case SERVER_STATUS_NO_BACKSLASH_ESCAPED: stats = append(stats, "SERVER_STATUS_NO_BACKSLASH_ESCAPED") case SERVER_STATUS_METADATA_CHANGED: stats = append(stats, "SERVER_STATUS_METADATA_CHANGED") case SERVER_QUERY_WAS_SLOW: stats = append(stats, "SERVER_QUERY_WAS_SLOW") case SERVER_PS_OUT_PARAMS: stats = append(stats, "SERVER_PS_OUT_PARAMS") default: stats = append(stats, fmt.Sprintf("(%d)", field)) } } return strings.Join(stats, "|") } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/client/pool.go ================================================ package client import ( "context" "log" "math" "math/rand" "sync" "time" "github.com/go-mysql-org/go-mysql/utils" "github.com/pingcap/errors" ) /* Pool for efficient reuse of connections. Usage: pool := client.NewPool(log.Debugf, 100, 400, 5, `127.0.0.1:3306`, `username`, `userpwd`, `dbname`) ... conn, _ := pool.GetConn(ctx) defer pool.PutConn(conn) conn.Execute/conn.Begin/etc... */ type ( Timestamp int64 LogFunc func(format string, args ...interface{}) Pool struct { logFunc LogFunc minAlive int maxAlive int maxIdle int idleCloseTimeout Timestamp idlePingTimeout Timestamp connect func() (*Conn, error) synchro struct { sync.Mutex idleConnections []Connection stats ConnectionStats } readyConnection chan Connection ctx context.Context cancel context.CancelFunc wg sync.WaitGroup } ConnectionStats struct { // Uses internally TotalCount int // Only for stats IdleCount int CreatedCount int64 } Connection struct { conn *Conn lastUseAt Timestamp } ) var ( // MaxIdleTimeoutWithoutPing - If the connection has been idle for more than this time, // then ping will be performed before use to check if it alive MaxIdleTimeoutWithoutPing = 10 * time.Second // DefaultIdleTimeout - If the connection has been idle for more than this time, // we can close it (but we should remember about Pool.minAlive) DefaultIdleTimeout = 30 * time.Second // MaxNewConnectionAtOnce - If we need to create new connections, // then we will create no more than this number of connections at a time. // This restriction will be ignored on pool initialization. MaxNewConnectionAtOnce = 5 ) // NewPoolWithOptions initializes new connection pool and uses params: addr, user, password, dbName and options. func NewPoolWithOptions( addr string, user string, password string, dbName string, options ...PoolOption, ) (*Pool, error) { po := getDefaultPoolOptions() po.addr = addr po.user = user po.password = password po.dbName = dbName for _, o := range options { o(&po) } if po.minAlive > po.maxAlive { po.minAlive = po.maxAlive } if po.maxIdle > po.maxAlive { po.maxIdle = po.maxAlive } if po.maxIdle <= po.minAlive { po.maxIdle = po.minAlive } pool := &Pool{ logFunc: po.logFunc, minAlive: po.minAlive, maxAlive: po.maxAlive, maxIdle: po.maxIdle, idleCloseTimeout: Timestamp(math.Ceil(DefaultIdleTimeout.Seconds())), idlePingTimeout: Timestamp(math.Ceil(MaxIdleTimeoutWithoutPing.Seconds())), connect: func() (*Conn, error) { return Connect(addr, user, password, dbName, po.connOptions...) }, readyConnection: make(chan Connection), } pool.ctx, pool.cancel = context.WithCancel(context.Background()) pool.synchro.idleConnections = make([]Connection, 0, pool.maxIdle) pool.wg.Add(1) go pool.newConnectionProducer() if pool.minAlive > 0 { go pool.startNewConnections(pool.minAlive) } pool.wg.Add(1) go pool.closeOldIdleConnections() if po.newPoolPingTimeout > 0 { ctx, cancel := context.WithTimeout(pool.ctx, po.newPoolPingTimeout) err := pool.checkConnection(ctx) cancel() if err != nil { pool.Close() return nil, errors.Errorf("checkConnection: %s", err) } } return pool, nil } // NewPool initializes new connection pool and uses params: addr, user, password, dbName and options. // minAlive specifies the minimum number of open connections that the pool will try to maintain. // maxAlive specifies the maximum number of open connections (for internal reasons, // may be greater by 1 inside newConnectionProducer). // maxIdle specifies the maximum number of idle connections (see DefaultIdleTimeout). // // Deprecated: use NewPoolWithOptions func NewPool( logFunc LogFunc, minAlive int, maxAlive int, maxIdle int, addr string, user string, password string, dbName string, options ...Option, ) *Pool { pool, err := NewPoolWithOptions( addr, user, password, dbName, WithLogFunc(logFunc), WithPoolLimits(minAlive, maxAlive, maxIdle), WithConnOptions(options...), ) if err != nil { pool.logFunc(`Pool: NewPool: %s`, err.Error()) } return pool } func (pool *Pool) GetStats(stats *ConnectionStats) { pool.synchro.Lock() *stats = pool.synchro.stats stats.IdleCount = len(pool.synchro.idleConnections) pool.synchro.Unlock() } // GetConn returns connection from the pool or create new func (pool *Pool) GetConn(ctx context.Context) (*Conn, error) { for { if pool.ctx.Err() != nil { return nil, errors.Errorf("failed get conn, pool closed") } connection, err := pool.getConnection(ctx) if err != nil { return nil, err } // For long time idle connections, we do a ping check if delta := pool.nowTs() - connection.lastUseAt; delta > pool.idlePingTimeout { if err := pool.ping(connection.conn); err != nil { pool.closeConn(connection.conn) continue } } return connection.conn, nil } } // PutConn returns working connection back to pool func (pool *Pool) PutConn(conn *Conn) { pool.putConnection(Connection{ conn: conn, lastUseAt: pool.nowTs(), }) } // DropConn closes the connection without any checks func (pool *Pool) DropConn(conn *Conn) { pool.closeConn(conn) } func (pool *Pool) putConnection(connection Connection) { pool.synchro.Lock() defer pool.synchro.Unlock() // If someone is already waiting for a connection, then we return it to him select { case pool.readyConnection <- connection: return default: } // Nobody needs this connection pool.putConnectionUnsafe(connection) } func (pool *Pool) nowTs() Timestamp { return Timestamp(utils.Now().Unix()) } func (pool *Pool) getConnection(ctx context.Context) (Connection, error) { pool.synchro.Lock() connection := pool.getIdleConnectionUnsafe() if connection.conn != nil { pool.synchro.Unlock() return connection, nil } pool.synchro.Unlock() // No idle connections are available select { case connection := <-pool.readyConnection: return connection, nil case <-ctx.Done(): return Connection{}, ctx.Err() } } func (pool *Pool) putConnectionUnsafe(connection Connection) { if len(pool.synchro.idleConnections) == cap(pool.synchro.idleConnections) { pool.synchro.stats.TotalCount-- _ = connection.conn.Close() // Could it be more effective to close older connections? } else { pool.synchro.idleConnections = append(pool.synchro.idleConnections, connection) } } func (pool *Pool) newConnectionProducer() { defer pool.wg.Done() var connection Connection var err error for { connection.conn = nil pool.synchro.Lock() connection = pool.getIdleConnectionUnsafe() if connection.conn == nil { if pool.synchro.stats.TotalCount >= pool.maxAlive { // Can't create more connections pool.synchro.Unlock() time.Sleep(10 * time.Millisecond) continue } pool.synchro.stats.TotalCount++ // "Reserving" new connection } pool.synchro.Unlock() if connection.conn == nil { connection, err = pool.createNewConnection() if err != nil { pool.synchro.Lock() pool.synchro.stats.TotalCount-- // Bad luck, should try again pool.synchro.Unlock() pool.logFunc("Cannot establish new db connection: %s", err.Error()) timer := time.NewTimer( time.Duration(10+rand.Intn(90)) * time.Millisecond, ) select { case <-timer.C: continue case <-pool.ctx.Done(): if !timer.Stop() { <-timer.C } return } } } select { case pool.readyConnection <- connection: case <-pool.ctx.Done(): pool.closeConn(connection.conn) return } } } func (pool *Pool) createNewConnection() (Connection, error) { var connection Connection var err error connection.conn, err = pool.connect() if err != nil { return Connection{}, errors.Errorf(`Could not connect to mysql: %s`, err) } connection.lastUseAt = pool.nowTs() pool.synchro.Lock() pool.synchro.stats.CreatedCount++ pool.synchro.Unlock() return connection, nil } func (pool *Pool) getIdleConnectionUnsafe() Connection { cnt := len(pool.synchro.idleConnections) if cnt == 0 { return Connection{} } last := cnt - 1 connection := pool.synchro.idleConnections[last] pool.synchro.idleConnections[last].conn = nil pool.synchro.idleConnections = pool.synchro.idleConnections[:last] return connection } func (pool *Pool) closeOldIdleConnections() { defer pool.wg.Done() var toPing []Connection ticker := time.NewTicker(5 * time.Second) for { select { case <-pool.ctx.Done(): return case <-ticker.C: toPing = pool.getOldIdleConnections(toPing[:0]) if len(toPing) == 0 { continue } pool.recheckConnections(toPing) if !pool.spawnConnectionsIfNeeded() { pool.closeIdleConnectionsIfCan() } } } } func (pool *Pool) getOldIdleConnections(dst []Connection) []Connection { dst = dst[:0] pool.synchro.Lock() synchro := &pool.synchro idleCnt := len(synchro.idleConnections) checkBefore := pool.nowTs() - pool.idlePingTimeout for i := idleCnt - 1; i >= 0; i-- { if synchro.idleConnections[i].lastUseAt > checkBefore { continue } dst = append(dst, synchro.idleConnections[i]) last := idleCnt - 1 if i < last { // Removing an item from the middle of a slice synchro.idleConnections[i], synchro.idleConnections[last] = synchro.idleConnections[last], synchro.idleConnections[i] } synchro.idleConnections[last].conn = nil synchro.idleConnections = synchro.idleConnections[:last] idleCnt-- } pool.synchro.Unlock() return dst } func (pool *Pool) recheckConnections(connections []Connection) { const workerCnt = 2 // Heuristic :) queue := make(chan Connection, len(connections)) for _, connection := range connections { queue <- connection } close(queue) var wg sync.WaitGroup wg.Add(workerCnt) for worker := 0; worker < workerCnt; worker++ { go func() { defer wg.Done() for connection := range queue { if err := pool.ping(connection.conn); err != nil { pool.closeConn(connection.conn) } else { pool.putConnection(connection) } } }() } wg.Wait() } // spawnConnectionsIfNeeded creates new connections if there are not enough of them and returns true in this case func (pool *Pool) spawnConnectionsIfNeeded() bool { pool.synchro.Lock() totalCount := pool.synchro.stats.TotalCount idleCount := len(pool.synchro.idleConnections) needSpanNew := pool.minAlive - totalCount pool.synchro.Unlock() if needSpanNew <= 0 { return false } // Не хватает соединений, нужно создать еще if needSpanNew > MaxNewConnectionAtOnce { needSpanNew = MaxNewConnectionAtOnce } pool.logFunc(`Pool: Setup %d new connections (total: %d idle: %d)...`, needSpanNew, totalCount, idleCount) pool.startNewConnections(needSpanNew) return true } func (pool *Pool) closeIdleConnectionsIfCan() { pool.synchro.Lock() canCloseCnt := pool.synchro.stats.TotalCount - pool.minAlive canCloseCnt-- // -1 to account for an open but unused connection (pool.readyConnection <- connection in newConnectionProducer) idleCnt := len(pool.synchro.idleConnections) inFly := pool.synchro.stats.TotalCount - idleCnt // We can close no more than 10% connections at a time, but at least 1, if possible idleCanCloseCnt := idleCnt / 10 if idleCanCloseCnt == 0 { idleCanCloseCnt = 1 } if canCloseCnt > idleCanCloseCnt { canCloseCnt = idleCanCloseCnt } if canCloseCnt <= 0 { pool.synchro.Unlock() return } closeFromIdx := idleCnt - canCloseCnt if closeFromIdx < 0 { // If there are enough requests in the "flight" now, then we can close all unnecessary closeFromIdx = 0 } toClose := append([]Connection{}, pool.synchro.idleConnections[closeFromIdx:]...) for i := closeFromIdx; i < idleCnt; i++ { pool.synchro.idleConnections[i].conn = nil } pool.synchro.idleConnections = pool.synchro.idleConnections[:closeFromIdx] pool.synchro.Unlock() pool.logFunc(`Pool: Close %d idle connections (in fly %d)`, len(toClose), inFly) for _, connection := range toClose { pool.closeConn(connection.conn) } } func (pool *Pool) closeConn(conn *Conn) { pool.synchro.Lock() pool.synchro.stats.TotalCount-- pool.synchro.Unlock() _ = conn.Close() // Closing is not an instant action, so do it outside the lock } func (pool *Pool) startNewConnections(count int) { pool.logFunc(`Pool: Setup %d new connections (minimal pool size)...`, count) connections := make([]Connection, 0, count) for i := 0; i < count; i++ { if conn, err := pool.createNewConnection(); err == nil { pool.synchro.Lock() pool.synchro.stats.TotalCount++ pool.synchro.Unlock() connections = append(connections, conn) } else { pool.logFunc(`Pool: createNewConnection: %s`, err) } } pool.synchro.Lock() for _, connection := range connections { pool.putConnectionUnsafe(connection) } pool.synchro.Unlock() } func (pool *Pool) ping(conn *Conn) error { deadline := utils.Now().Add(100 * time.Millisecond) _ = conn.SetDeadline(deadline) err := conn.Ping() if err != nil { pool.logFunc(`Pool: ping query fail: %s`, err.Error()) } else { _ = conn.SetDeadline(time.Time{}) } return err } // Close only shutdown idle connections. we couldn't control the connection which not in the pool. // So before call Close, Call PutConn to put all connections that in use back to connection pool first. func (pool *Pool) Close() { pool.cancel() //wait newConnectionProducer exit. pool.wg.Wait() //close idle connections pool.synchro.Lock() for _, connection := range pool.synchro.idleConnections { pool.synchro.stats.TotalCount-- _ = connection.conn.Close() } pool.synchro.idleConnections = nil pool.synchro.Unlock() } // checkConnection tries to connect and ping DB server func (pool *Pool) checkConnection(ctx context.Context) error { errChan := make(chan error, 1) go func() { conn, err := pool.connect() if err == nil { err = conn.Ping() _ = conn.Close() } errChan <- err }() select { case err := <-errChan: return err case <-ctx.Done(): return ctx.Err() } } // getDefaultPoolOptions returns pool config for low load services func getDefaultPoolOptions() poolOptions { return poolOptions{ logFunc: log.Printf, minAlive: 1, maxAlive: 10, maxIdle: 2, } } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/client/pool_options.go ================================================ package client import ( "time" ) type ( poolOptions struct { logFunc LogFunc minAlive int maxAlive int maxIdle int addr string user string password string dbName string connOptions []Option newPoolPingTimeout time.Duration } ) type ( PoolOption func(o *poolOptions) ) // WithPoolLimits sets pool limits: // - minAlive specifies the minimum number of open connections that the pool will try to maintain. // - maxAlive specifies the maximum number of open connections (for internal reasons, // may be greater by 1 inside newConnectionProducer). // - maxIdle specifies the maximum number of idle connections (see DefaultIdleTimeout). func WithPoolLimits(minAlive, maxAlive, maxIdle int) PoolOption { return func(o *poolOptions) { o.minAlive = minAlive o.maxAlive = maxAlive o.maxIdle = maxIdle } } func WithLogFunc(f LogFunc) PoolOption { return func(o *poolOptions) { o.logFunc = f } } func WithConnOptions(options ...Option) PoolOption { return func(o *poolOptions) { o.connOptions = append(o.connOptions, options...) } } // WithNewPoolPingTimeout enables connect & ping to DB during the pool initialization func WithNewPoolPingTimeout(timeout time.Duration) PoolOption { return func(o *poolOptions) { o.newPoolPingTimeout = timeout } } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/client/req.go ================================================ package client import ( "github.com/go-mysql-org/go-mysql/utils" ) func (c *Conn) writeCommand(command byte) error { c.ResetSequence() return c.WritePacket([]byte{ 0x01, //1 bytes long 0x00, 0x00, 0x00, //sequence command, }) } func (c *Conn) writeCommandBuf(command byte, arg []byte) error { c.ResetSequence() length := len(arg) + 1 data := utils.ByteSliceGet(length + 4) data.B[4] = command copy(data.B[5:], arg) err := c.WritePacket(data.B) utils.ByteSlicePut(data) return err } func (c *Conn) writeCommandStr(command byte, arg string) error { return c.writeCommandBuf(command, utils.StringToByteSlice(arg)) } func (c *Conn) writeCommandUint32(command byte, arg uint32) error { c.ResetSequence() buf := utils.ByteSliceGet(9) buf.B[0] = 0x05 //5 bytes long buf.B[1] = 0x00 buf.B[2] = 0x00 buf.B[3] = 0x00 //sequence buf.B[4] = command buf.B[5] = byte(arg) buf.B[6] = byte(arg >> 8) buf.B[7] = byte(arg >> 16) buf.B[8] = byte(arg >> 24) err := c.WritePacket(buf.B) utils.ByteSlicePut(buf) return err } func (c *Conn) writeCommandStrStr(command byte, arg1 string, arg2 string) error { c.ResetSequence() data := make([]byte, 4, 6+len(arg1)+len(arg2)) data = append(data, command) data = append(data, arg1...) data = append(data, 0) data = append(data, arg2...) return c.WritePacket(data) } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/client/resp.go ================================================ package client import ( "bytes" "crypto/rsa" "crypto/x509" "encoding/binary" "encoding/pem" "fmt" "github.com/pingcap/errors" . "github.com/go-mysql-org/go-mysql/mysql" "github.com/go-mysql-org/go-mysql/utils" ) func (c *Conn) readUntilEOF() (err error) { var data []byte for { data, err = c.ReadPacket() if err != nil { return } // EOF Packet if c.isEOFPacket(data) { return } } } func (c *Conn) isEOFPacket(data []byte) bool { return data[0] == EOF_HEADER && len(data) <= 5 } func (c *Conn) handleOKPacket(data []byte) (*Result, error) { var n int var pos = 1 r := NewResultReserveResultset(0) r.AffectedRows, _, n = LengthEncodedInt(data[pos:]) pos += n r.InsertId, _, n = LengthEncodedInt(data[pos:]) pos += n if c.capability&CLIENT_PROTOCOL_41 > 0 { r.Status = binary.LittleEndian.Uint16(data[pos:]) c.status = r.Status pos += 2 //todo:strict_mode, check warnings as error r.Warnings = binary.LittleEndian.Uint16(data[pos:]) // pos += 2 } else if c.capability&CLIENT_TRANSACTIONS > 0 { r.Status = binary.LittleEndian.Uint16(data[pos:]) c.status = r.Status // pos += 2 } // new ok package will check CLIENT_SESSION_TRACK too, but I don't support it now. // skip info return r, nil } func (c *Conn) handleErrorPacket(data []byte) error { e := new(MyError) var pos = 1 e.Code = binary.LittleEndian.Uint16(data[pos:]) pos += 2 if c.capability&CLIENT_PROTOCOL_41 > 0 { // skip '#' pos++ e.State = utils.ByteSliceToString(data[pos : pos+5]) pos += 5 } e.Message = utils.ByteSliceToString(data[pos:]) return e } func (c *Conn) handleAuthResult() error { data, switchToPlugin, err := c.readAuthResult() if err != nil { return fmt.Errorf("readAuthResult: %w", err) } // handle auth switch, only support 'sha256_password', and 'caching_sha2_password' if switchToPlugin != "" { // fmt.Printf("now switching auth plugin to '%s'\n", switchToPlugin) if data == nil { data = c.salt } else { copy(c.salt, data) } c.authPluginName = switchToPlugin auth, addNull, err := c.genAuthResponse(data) if err != nil { return err } if err = c.WriteAuthSwitchPacket(auth, addNull); err != nil { return err } // Read Result Packet data, switchToPlugin, err = c.readAuthResult() if err != nil { return err } // Do not allow to change the auth plugin more than once if switchToPlugin != "" { return errors.Errorf("can not switch auth plugin more than once") } } // handle caching_sha2_password if c.authPluginName == AUTH_CACHING_SHA2_PASSWORD { if data == nil { return nil // auth already succeeded } if data[0] == CACHE_SHA2_FAST_AUTH { _, err = c.readOK() return err } else if data[0] == CACHE_SHA2_FULL_AUTH { // need full authentication if c.tlsConfig != nil || c.proto == "unix" { if err = c.WriteClearAuthPacket(c.password); err != nil { return err } } else { if err = c.WritePublicKeyAuthPacket(c.password, c.salt); err != nil { return err } } _, err = c.readOK() return err } else { return errors.Errorf("invalid packet %x", data[0]) } } else if c.authPluginName == AUTH_SHA256_PASSWORD { if len(data) == 0 { return nil // auth already succeeded } block, _ := pem.Decode(data) pub, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { return err } // send encrypted password err = c.WriteEncryptedPassword(c.password, c.salt, pub.(*rsa.PublicKey)) if err != nil { return err } _, err = c.readOK() return err } return nil } func (c *Conn) readAuthResult() ([]byte, string, error) { data, err := c.ReadPacket() if err != nil { return nil, "", fmt.Errorf("ReadPacket: %w", err) } // see: https://insidemysql.com/preparing-your-community-connector-for-mysql-8-part-2-sha256/ // packet indicator switch data[0] { case OK_HEADER: _, err := c.handleOKPacket(data) return nil, "", err case MORE_DATE_HEADER: return data[1:], "", err case EOF_HEADER: // server wants to switch auth if len(data) < 1 { // https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest return nil, AUTH_MYSQL_OLD_PASSWORD, nil } pluginEndIndex := bytes.IndexByte(data, 0x00) if pluginEndIndex < 0 { return nil, "", errors.New("invalid packet") } plugin := string(data[1:pluginEndIndex]) authData := data[pluginEndIndex+1:] return authData, plugin, nil default: // Error otherwise return nil, "", c.handleErrorPacket(data) } } func (c *Conn) readOK() (*Result, error) { data, err := c.ReadPacket() if err != nil { return nil, errors.Trace(err) } if data[0] == OK_HEADER { return c.handleOKPacket(data) } else if data[0] == ERR_HEADER { return nil, c.handleErrorPacket(data) } else { return nil, errors.New("invalid ok packet") } } func (c *Conn) readResult(binary bool) (*Result, error) { bs := utils.ByteSliceGet(16) defer utils.ByteSlicePut(bs) var err error bs.B, err = c.ReadPacketReuseMem(bs.B[:0]) if err != nil { return nil, errors.Trace(err) } switch bs.B[0] { case OK_HEADER: return c.handleOKPacket(bs.B) case ERR_HEADER: return nil, c.handleErrorPacket(bytes.Repeat(bs.B, 1)) case LocalInFile_HEADER: return nil, ErrMalformPacket default: return c.readResultset(bs.B, binary) } } func (c *Conn) readResultStreaming(binary bool, result *Result, perRowCb SelectPerRowCallback, perResCb SelectPerResultCallback) error { bs := utils.ByteSliceGet(16) defer utils.ByteSlicePut(bs) var err error bs.B, err = c.ReadPacketReuseMem(bs.B[:0]) if err != nil { return errors.Trace(err) } switch bs.B[0] { case OK_HEADER: // https://dev.mysql.com/doc/internals/en/com-query-response.html // 14.6.4.1 COM_QUERY Response // If the number of columns in the resultset is 0, this is a OK_Packet. okResult, err := c.handleOKPacket(bs.B) if err != nil { return errors.Trace(err) } result.Status = okResult.Status result.AffectedRows = okResult.AffectedRows result.InsertId = okResult.InsertId result.Warnings = okResult.Warnings if result.Resultset == nil { result.Resultset = NewResultset(0) } else { result.Reset(0) } return nil case ERR_HEADER: return c.handleErrorPacket(bytes.Repeat(bs.B, 1)) case LocalInFile_HEADER: return ErrMalformPacket default: return c.readResultsetStreaming(bs.B, binary, result, perRowCb, perResCb) } } func (c *Conn) readResultset(data []byte, binary bool) (*Result, error) { // column count count, _, n := LengthEncodedInt(data) if n-len(data) != 0 { return nil, ErrMalformPacket } result := NewResultReserveResultset(int(count)) if err := c.readResultColumns(result); err != nil { return nil, errors.Trace(err) } if err := c.readResultRows(result, binary); err != nil { return nil, errors.Trace(err) } return result, nil } func (c *Conn) readResultsetStreaming(data []byte, binary bool, result *Result, perRowCb SelectPerRowCallback, perResCb SelectPerResultCallback) error { columnCount, _, n := LengthEncodedInt(data) if n-len(data) != 0 { return ErrMalformPacket } if result.Resultset == nil { result.Resultset = NewResultset(int(columnCount)) } else { // Reuse memory if can result.Reset(int(columnCount)) } // this is a streaming resultset result.Resultset.Streaming = StreamingSelect if err := c.readResultColumns(result); err != nil { return errors.Trace(err) } if perResCb != nil { if err := perResCb(result); err != nil { return err } } if err := c.readResultRowsStreaming(result, binary, perRowCb); err != nil { return errors.Trace(err) } // this resultset is done streaming result.Resultset.StreamingDone = true return nil } func (c *Conn) readResultColumns(result *Result) (err error) { var i = 0 var data []byte for { rawPkgLen := len(result.RawPkg) result.RawPkg, err = c.ReadPacketReuseMem(result.RawPkg) if err != nil { return err } data = result.RawPkg[rawPkgLen:] // EOF Packet if c.isEOFPacket(data) { if c.capability&CLIENT_PROTOCOL_41 > 0 { result.Warnings = binary.LittleEndian.Uint16(data[1:]) // todo add strict_mode, warning will be treat as error result.Status = binary.LittleEndian.Uint16(data[3:]) c.status = result.Status } if i != len(result.Fields) { err = ErrMalformPacket } return err } if result.Fields[i] == nil { result.Fields[i] = &Field{} } err = result.Fields[i].Parse(data) if err != nil { return err } result.FieldNames[utils.ByteSliceToString(result.Fields[i].Name)] = i i++ } } func (c *Conn) readResultRows(result *Result, isBinary bool) (err error) { var data []byte for { rawPkgLen := len(result.RawPkg) result.RawPkg, err = c.ReadPacketReuseMem(result.RawPkg) if err != nil { return err } data = result.RawPkg[rawPkgLen:] // EOF Packet if c.isEOFPacket(data) { if c.capability&CLIENT_PROTOCOL_41 > 0 { result.Warnings = binary.LittleEndian.Uint16(data[1:]) // todo add strict_mode, warning will be treat as error result.Status = binary.LittleEndian.Uint16(data[3:]) c.status = result.Status } break } if data[0] == ERR_HEADER { return c.handleErrorPacket(data) } result.RowDatas = append(result.RowDatas, data) } if cap(result.Values) < len(result.RowDatas) { result.Values = make([][]FieldValue, len(result.RowDatas)) } else { result.Values = result.Values[:len(result.RowDatas)] } for i := range result.Values { result.Values[i], err = result.RowDatas[i].Parse(result.Fields, isBinary, result.Values[i]) if err != nil { return errors.Trace(err) } } return nil } func (c *Conn) readResultRowsStreaming(result *Result, isBinary bool, perRowCb SelectPerRowCallback) (err error) { var ( data []byte row []FieldValue ) for { data, err = c.ReadPacketReuseMem(data[:0]) if err != nil { return err } // EOF Packet if c.isEOFPacket(data) { if c.capability&CLIENT_PROTOCOL_41 > 0 { result.Warnings = binary.LittleEndian.Uint16(data[1:]) // todo add strict_mode, warning will be treat as error result.Status = binary.LittleEndian.Uint16(data[3:]) c.status = result.Status } break } if data[0] == ERR_HEADER { return c.handleErrorPacket(data) } // Parse this row row, err = RowData(data).Parse(result.Fields, isBinary, row) if err != nil { return errors.Trace(err) } // Send the row to "userland" code err = perRowCb(row) if err != nil { return errors.Trace(err) } } return nil } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/client/stmt.go ================================================ package client import ( "encoding/binary" "encoding/json" "fmt" "math" . "github.com/go-mysql-org/go-mysql/mysql" "github.com/go-mysql-org/go-mysql/utils" "github.com/pingcap/errors" ) type Stmt struct { conn *Conn id uint32 params int columns int warnings int } func (s *Stmt) ParamNum() int { return s.params } func (s *Stmt) ColumnNum() int { return s.columns } func (s *Stmt) WarningsNum() int { return s.warnings } func (s *Stmt) Execute(args ...interface{}) (*Result, error) { if err := s.write(args...); err != nil { return nil, errors.Trace(err) } return s.conn.readResult(true) } func (s *Stmt) ExecuteSelectStreaming(result *Result, perRowCb SelectPerRowCallback, perResCb SelectPerResultCallback, args ...interface{}) error { if err := s.write(args...); err != nil { return errors.Trace(err) } return s.conn.readResultStreaming(true, result, perRowCb, perResCb) } func (s *Stmt) Close() error { if err := s.conn.writeCommandUint32(COM_STMT_CLOSE, s.id); err != nil { return errors.Trace(err) } return nil } func (s *Stmt) write(args ...interface{}) error { paramsNum := s.params if len(args) != paramsNum { return fmt.Errorf("argument mismatch, need %d but got %d", s.params, len(args)) } paramTypes := make([]byte, paramsNum<<1) paramValues := make([][]byte, paramsNum) //NULL-bitmap, length: (num-params+7) nullBitmap := make([]byte, (paramsNum+7)>>3) length := 1 + 4 + 1 + 4 + ((paramsNum + 7) >> 3) + 1 + (paramsNum << 1) var newParamBoundFlag byte = 0 for i := range args { if args[i] == nil { nullBitmap[i/8] |= 1 << (uint(i) % 8) paramTypes[i<<1] = MYSQL_TYPE_NULL continue } newParamBoundFlag = 1 switch v := args[i].(type) { case int8: paramTypes[i<<1] = MYSQL_TYPE_TINY paramValues[i] = []byte{byte(v)} case int16: paramTypes[i<<1] = MYSQL_TYPE_SHORT paramValues[i] = Uint16ToBytes(uint16(v)) case int32: paramTypes[i<<1] = MYSQL_TYPE_LONG paramValues[i] = Uint32ToBytes(uint32(v)) case int: paramTypes[i<<1] = MYSQL_TYPE_LONGLONG paramValues[i] = Uint64ToBytes(uint64(v)) case int64: paramTypes[i<<1] = MYSQL_TYPE_LONGLONG paramValues[i] = Uint64ToBytes(uint64(v)) case uint8: paramTypes[i<<1] = MYSQL_TYPE_TINY paramTypes[(i<<1)+1] = 0x80 paramValues[i] = []byte{v} case uint16: paramTypes[i<<1] = MYSQL_TYPE_SHORT paramTypes[(i<<1)+1] = 0x80 paramValues[i] = Uint16ToBytes(v) case uint32: paramTypes[i<<1] = MYSQL_TYPE_LONG paramTypes[(i<<1)+1] = 0x80 paramValues[i] = Uint32ToBytes(v) case uint: paramTypes[i<<1] = MYSQL_TYPE_LONGLONG paramTypes[(i<<1)+1] = 0x80 paramValues[i] = Uint64ToBytes(uint64(v)) case uint64: paramTypes[i<<1] = MYSQL_TYPE_LONGLONG paramTypes[(i<<1)+1] = 0x80 paramValues[i] = Uint64ToBytes(v) case bool: paramTypes[i<<1] = MYSQL_TYPE_TINY if v { paramValues[i] = []byte{1} } else { paramValues[i] = []byte{0} } case float32: paramTypes[i<<1] = MYSQL_TYPE_FLOAT paramValues[i] = Uint32ToBytes(math.Float32bits(v)) case float64: paramTypes[i<<1] = MYSQL_TYPE_DOUBLE paramValues[i] = Uint64ToBytes(math.Float64bits(v)) case string: paramTypes[i<<1] = MYSQL_TYPE_STRING paramValues[i] = append(PutLengthEncodedInt(uint64(len(v))), v...) case []byte: paramTypes[i<<1] = MYSQL_TYPE_STRING paramValues[i] = append(PutLengthEncodedInt(uint64(len(v))), v...) case json.RawMessage: paramTypes[i<<1] = MYSQL_TYPE_STRING paramValues[i] = append(PutLengthEncodedInt(uint64(len(v))), v...) default: return fmt.Errorf("invalid argument type %T", args[i]) } length += len(paramValues[i]) } data := utils.BytesBufferGet() defer func() { utils.BytesBufferPut(data) }() if data.Len() < length+4 { data.Grow(4 + length) } data.Write([]byte{0, 0, 0, 0}) data.WriteByte(COM_STMT_EXECUTE) data.Write([]byte{byte(s.id), byte(s.id >> 8), byte(s.id >> 16), byte(s.id >> 24)}) //flag: CURSOR_TYPE_NO_CURSOR data.WriteByte(0x00) //iteration-count, always 1 data.Write([]byte{1, 0, 0, 0}) if s.params > 0 { data.Write(nullBitmap) //new-params-bound-flag data.WriteByte(newParamBoundFlag) if newParamBoundFlag == 1 { //type of each parameter, length: num-params * 2 data.Write(paramTypes) //value of each parameter for _, v := range paramValues { data.Write(v) } } } s.conn.ResetSequence() return s.conn.WritePacket(data.Bytes()) } func (c *Conn) Prepare(query string) (*Stmt, error) { if err := c.writeCommandStr(COM_STMT_PREPARE, query); err != nil { return nil, errors.Trace(err) } data, err := c.ReadPacket() if err != nil { return nil, errors.Trace(err) } if data[0] == ERR_HEADER { return nil, c.handleErrorPacket(data) } else if data[0] != OK_HEADER { return nil, ErrMalformPacket } s := new(Stmt) s.conn = c pos := 1 //for statement id s.id = binary.LittleEndian.Uint32(data[pos:]) pos += 4 //number columns s.columns = int(binary.LittleEndian.Uint16(data[pos:])) pos += 2 //number params s.params = int(binary.LittleEndian.Uint16(data[pos:])) pos += 2 //warnings s.warnings = int(binary.LittleEndian.Uint16(data[pos:])) // pos += 2 if s.params > 0 { if err := s.conn.readUntilEOF(); err != nil { return nil, errors.Trace(err) } } if s.columns > 0 { if err := s.conn.readUntilEOF(); err != nil { return nil, errors.Trace(err) } } return s, nil } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/client/tls.go ================================================ package client import ( "crypto/tls" "crypto/x509" ) // NewClientTLSConfig: generate TLS config for client side // if insecureSkipVerify is set to true, serverName will not be validated func NewClientTLSConfig(caPem, certPem, keyPem []byte, insecureSkipVerify bool, serverName string) *tls.Config { pool := x509.NewCertPool() if !pool.AppendCertsFromPEM(caPem) { panic("failed to add ca PEM") } var config *tls.Config // Allow cert and key to be optional // Send through `make([]byte, 0)` for "nil" if string(certPem) != "" && string(keyPem) != "" { cert, err := tls.X509KeyPair(certPem, keyPem) if err != nil { panic(err) } config = &tls.Config{ RootCAs: pool, Certificates: []tls.Certificate{cert}, InsecureSkipVerify: insecureSkipVerify, ServerName: serverName, } } else { config = &tls.Config{ RootCAs: pool, InsecureSkipVerify: insecureSkipVerify, ServerName: serverName, } } return config } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/compress/zlib.go ================================================ package compress import ( "bytes" "io" "sync" "github.com/klauspost/compress/zlib" ) const DefaultCompressionLevel = 6 var ( zlibReaderPool *sync.Pool zlibWriterPool sync.Pool ) func init() { zlibReaderPool = &sync.Pool{ New: func() interface{} { return nil }, } zlibWriterPool = sync.Pool{ New: func() interface{} { w, err := zlib.NewWriterLevel(new(bytes.Buffer), DefaultCompressionLevel) if err != nil { panic(err) } return w }, } } var _ io.WriteCloser = zlibWriter{} var _ io.ReadCloser = zlibReader{} type zlibWriter struct { w *zlib.Writer } type zlibReader struct { r io.ReadCloser } func GetPooledZlibWriter(target io.Writer) (io.WriteCloser, error) { w := zlibWriterPool.Get().(*zlib.Writer) w.Reset(target) return zlibWriter{ w: w, }, nil } func GetPooledZlibReader(src io.Reader) (io.ReadCloser, error) { var ( rc io.ReadCloser err error ) if r := zlibReaderPool.Get(); r != nil { rc = r.(io.ReadCloser) if rc.(zlib.Resetter).Reset(src, nil) != nil { return nil, err } } else { if rc, err = zlib.NewReader(src); err != nil { return nil, err } } return zlibReader{ r: rc, }, nil } func (c zlibWriter) Write(data []byte) (n int, err error) { return c.w.Write(data) } func (c zlibWriter) Close() error { err := c.w.Close() zlibWriterPool.Put(c.w) return err } func (d zlibReader) Read(buf []byte) (n int, err error) { return d.r.Read(buf) } func (d zlibReader) Close() error { err := d.r.Close() zlibReaderPool.Put(d.r) return err } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/mysql/const.go ================================================ package mysql const ( ClassicProtocolVersion byte = 10 XProtocolVersion byte = 11 MaxPayloadLen int = 1<<24 - 1 TimeFormat string = "2006-01-02 15:04:05" ) const ( OK_HEADER byte = 0x00 MORE_DATE_HEADER byte = 0x01 ERR_HEADER byte = 0xff EOF_HEADER byte = 0xfe LocalInFile_HEADER byte = 0xfb CACHE_SHA2_FAST_AUTH byte = 0x03 CACHE_SHA2_FULL_AUTH byte = 0x04 ) const ( AUTH_MYSQL_OLD_PASSWORD = "mysql_old_password" AUTH_NATIVE_PASSWORD = "mysql_native_password" AUTH_CLEAR_PASSWORD = "mysql_clear_password" AUTH_CACHING_SHA2_PASSWORD = "caching_sha2_password" AUTH_SHA256_PASSWORD = "sha256_password" ) const ( SERVER_STATUS_IN_TRANS uint16 = 0x0001 SERVER_STATUS_AUTOCOMMIT uint16 = 0x0002 SERVER_MORE_RESULTS_EXISTS uint16 = 0x0008 SERVER_STATUS_NO_GOOD_INDEX_USED uint16 = 0x0010 SERVER_STATUS_NO_INDEX_USED uint16 = 0x0020 SERVER_STATUS_CURSOR_EXISTS uint16 = 0x0040 SERVER_STATUS_LAST_ROW_SEND uint16 = 0x0080 SERVER_STATUS_DB_DROPPED uint16 = 0x0100 SERVER_STATUS_NO_BACKSLASH_ESCAPED uint16 = 0x0200 SERVER_STATUS_METADATA_CHANGED uint16 = 0x0400 SERVER_QUERY_WAS_SLOW uint16 = 0x0800 SERVER_PS_OUT_PARAMS uint16 = 0x1000 ) const ( COM_SLEEP byte = iota COM_QUIT COM_INIT_DB COM_QUERY COM_FIELD_LIST COM_CREATE_DB COM_DROP_DB COM_REFRESH COM_SHUTDOWN COM_STATISTICS COM_PROCESS_INFO COM_CONNECT COM_PROCESS_KILL COM_DEBUG COM_PING COM_TIME COM_DELAYED_INSERT COM_CHANGE_USER COM_BINLOG_DUMP COM_TABLE_DUMP COM_CONNECT_OUT COM_REGISTER_SLAVE COM_STMT_PREPARE COM_STMT_EXECUTE COM_STMT_SEND_LONG_DATA COM_STMT_CLOSE COM_STMT_RESET COM_SET_OPTION COM_STMT_FETCH COM_DAEMON COM_BINLOG_DUMP_GTID COM_RESET_CONNECTION ) const ( // https://dev.mysql.com/doc/dev/mysql-server/latest/group__group__cs__capabilities__flags.html CLIENT_LONG_PASSWORD uint32 = 1 << iota CLIENT_FOUND_ROWS CLIENT_LONG_FLAG CLIENT_CONNECT_WITH_DB CLIENT_NO_SCHEMA CLIENT_COMPRESS CLIENT_ODBC CLIENT_LOCAL_FILES CLIENT_IGNORE_SPACE CLIENT_PROTOCOL_41 CLIENT_INTERACTIVE CLIENT_SSL CLIENT_IGNORE_SIGPIPE CLIENT_TRANSACTIONS CLIENT_RESERVED CLIENT_SECURE_CONNECTION CLIENT_MULTI_STATEMENTS CLIENT_MULTI_RESULTS CLIENT_PS_MULTI_RESULTS CLIENT_PLUGIN_AUTH CLIENT_CONNECT_ATTRS CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS CLIENT_SESSION_TRACK CLIENT_DEPRECATE_EOF CLIENT_OPTIONAL_RESULTSET_METADATA CLIENT_ZSTD_COMPRESSION_ALGORITHM CLIENT_QUERY_ATTRIBUTES MULTI_FACTOR_AUTHENTICATION CLIENT_CAPABILITY_EXTENSION CLIENT_SSL_VERIFY_SERVER_CERT CLIENT_REMEMBER_OPTIONS ) const ( MYSQL_TYPE_DECIMAL byte = iota MYSQL_TYPE_TINY MYSQL_TYPE_SHORT MYSQL_TYPE_LONG MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24 MYSQL_TYPE_DATE MYSQL_TYPE_TIME MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR MYSQL_TYPE_NEWDATE MYSQL_TYPE_VARCHAR MYSQL_TYPE_BIT // mysql 5.6 MYSQL_TYPE_TIMESTAMP2 MYSQL_TYPE_DATETIME2 MYSQL_TYPE_TIME2 ) const ( MYSQL_TYPE_JSON byte = iota + 0xf5 MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_ENUM MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB MYSQL_TYPE_BLOB MYSQL_TYPE_VAR_STRING MYSQL_TYPE_STRING MYSQL_TYPE_GEOMETRY ) const ( NOT_NULL_FLAG = 1 PRI_KEY_FLAG = 2 UNIQUE_KEY_FLAG = 4 BLOB_FLAG = 16 UNSIGNED_FLAG = 32 ZEROFILL_FLAG = 64 BINARY_FLAG = 128 ENUM_FLAG = 256 AUTO_INCREMENT_FLAG = 512 TIMESTAMP_FLAG = 1024 SET_FLAG = 2048 NUM_FLAG = 32768 PART_KEY_FLAG = 16384 GROUP_FLAG = 32768 UNIQUE_FLAG = 65536 ) const ( DEFAULT_ADDR = "127.0.0.1:3306" DEFAULT_IPV6_ADDR = "[::1]:3306" DEFAULT_USER = "root" DEFAULT_PASSWORD = "" DEFAULT_FLAVOR = "mysql" DEFAULT_CHARSET = "utf8" DEFAULT_COLLATION_ID uint8 = 33 DEFAULT_COLLATION_NAME string = "utf8_general_ci" ) const ( DEFAULT_DUMP_EXECUTION_PATH = "mysqldump" ) // Like vitess, use flavor for different MySQL versions, const ( MySQLFlavor = "mysql" MariaDBFlavor = "mariadb" ) const ( MYSQL_OPTION_MULTI_STATEMENTS_ON = iota MYSQL_OPTION_MULTI_STATEMENTS_OFF ) const ( MYSQL_COMPRESS_NONE = iota MYSQL_COMPRESS_ZLIB MYSQL_COMPRESS_ZSTD ) ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/mysql/errcode.go ================================================ package mysql const ( ER_ERROR_FIRST = 1000 ER_HASHCHK = 1000 ER_NISAMCHK = 1001 ER_NO = 1002 ER_YES = 1003 ER_CANT_CREATE_FILE = 1004 ER_CANT_CREATE_TABLE = 1005 ER_CANT_CREATE_DB = 1006 ER_DB_CREATE_EXISTS = 1007 ER_DB_DROP_EXISTS = 1008 ER_DB_DROP_DELETE = 1009 ER_DB_DROP_RMDIR = 1010 ER_CANT_DELETE_FILE = 1011 ER_CANT_FIND_SYSTEM_REC = 1012 ER_CANT_GET_STAT = 1013 ER_CANT_GET_WD = 1014 ER_CANT_LOCK = 1015 ER_CANT_OPEN_FILE = 1016 ER_FILE_NOT_FOUND = 1017 ER_CANT_READ_DIR = 1018 ER_CANT_SET_WD = 1019 ER_CHECKREAD = 1020 ER_DISK_FULL = 1021 ER_DUP_KEY = 1022 ER_ERROR_ON_CLOSE = 1023 ER_ERROR_ON_READ = 1024 ER_ERROR_ON_RENAME = 1025 ER_ERROR_ON_WRITE = 1026 ER_FILE_USED = 1027 ER_FILSORT_ABORT = 1028 ER_FORM_NOT_FOUND = 1029 ER_GET_ERRNO = 1030 ER_ILLEGAL_HA = 1031 ER_KEY_NOT_FOUND = 1032 ER_NOT_FORM_FILE = 1033 ER_NOT_KEYFILE = 1034 ER_OLD_KEYFILE = 1035 ER_OPEN_AS_READONLY = 1036 ER_OUTOFMEMORY = 1037 ER_OUT_OF_SORTMEMORY = 1038 ER_UNEXPECTED_EOF = 1039 ER_CON_COUNT_ERROR = 1040 ER_OUT_OF_RESOURCES = 1041 ER_BAD_HOST_ERROR = 1042 ER_HANDSHAKE_ERROR = 1043 ER_DBACCESS_DENIED_ERROR = 1044 ER_ACCESS_DENIED_ERROR = 1045 ER_NO_DB_ERROR = 1046 ER_UNKNOWN_COM_ERROR = 1047 ER_BAD_NULL_ERROR = 1048 ER_BAD_DB_ERROR = 1049 ER_TABLE_EXISTS_ERROR = 1050 ER_BAD_TABLE_ERROR = 1051 ER_NON_UNIQ_ERROR = 1052 ER_SERVER_SHUTDOWN = 1053 ER_BAD_FIELD_ERROR = 1054 ER_WRONG_FIELD_WITH_GROUP = 1055 ER_WRONG_GROUP_FIELD = 1056 ER_WRONG_SUM_SELECT = 1057 ER_WRONG_VALUE_COUNT = 1058 ER_TOO_LONG_IDENT = 1059 ER_DUP_FIELDNAME = 1060 ER_DUP_KEYNAME = 1061 ER_DUP_ENTRY = 1062 ER_WRONG_FIELD_SPEC = 1063 ER_PARSE_ERROR = 1064 ER_EMPTY_QUERY = 1065 ER_NONUNIQ_TABLE = 1066 ER_INVALID_DEFAULT = 1067 ER_MULTIPLE_PRI_KEY = 1068 ER_TOO_MANY_KEYS = 1069 ER_TOO_MANY_KEY_PARTS = 1070 ER_TOO_LONG_KEY = 1071 ER_KEY_COLUMN_DOES_NOT_EXITS = 1072 ER_BLOB_USED_AS_KEY = 1073 ER_TOO_BIG_FIELDLENGTH = 1074 ER_WRONG_AUTO_KEY = 1075 ER_READY = 1076 ER_NORMAL_SHUTDOWN = 1077 ER_GOT_SIGNAL = 1078 ER_SHUTDOWN_COMPLETE = 1079 ER_FORCING_CLOSE = 1080 ER_IPSOCK_ERROR = 1081 ER_NO_SUCH_INDEX = 1082 ER_WRONG_FIELD_TERMINATORS = 1083 ER_BLOBS_AND_NO_TERMINATED = 1084 ER_TEXTFILE_NOT_READABLE = 1085 ER_FILE_EXISTS_ERROR = 1086 ER_LOAD_INFO = 1087 ER_ALTER_INFO = 1088 ER_WRONG_SUB_KEY = 1089 ER_CANT_REMOVE_ALL_FIELDS = 1090 ER_CANT_DROP_FIELD_OR_KEY = 1091 ER_INSERT_INFO = 1092 ER_UPDATE_TABLE_USED = 1093 ER_NO_SUCH_THREAD = 1094 ER_KILL_DENIED_ERROR = 1095 ER_NO_TABLES_USED = 1096 ER_TOO_BIG_SET = 1097 ER_NO_UNIQUE_LOGFILE = 1098 ER_TABLE_NOT_LOCKED_FOR_WRITE = 1099 ER_TABLE_NOT_LOCKED = 1100 ER_BLOB_CANT_HAVE_DEFAULT = 1101 ER_WRONG_DB_NAME = 1102 ER_WRONG_TABLE_NAME = 1103 ER_TOO_BIG_SELECT = 1104 ER_UNKNOWN_ERROR = 1105 ER_UNKNOWN_PROCEDURE = 1106 ER_WRONG_PARAMCOUNT_TO_PROCEDURE = 1107 ER_WRONG_PARAMETERS_TO_PROCEDURE = 1108 ER_UNKNOWN_TABLE = 1109 ER_FIELD_SPECIFIED_TWICE = 1110 ER_INVALID_GROUP_FUNC_USE = 1111 ER_UNSUPPORTED_EXTENSION = 1112 ER_TABLE_MUST_HAVE_COLUMNS = 1113 ER_RECORD_FILE_FULL = 1114 ER_UNKNOWN_CHARACTER_SET = 1115 ER_TOO_MANY_TABLES = 1116 ER_TOO_MANY_FIELDS = 1117 ER_TOO_BIG_ROWSIZE = 1118 ER_STACK_OVERRUN = 1119 ER_WRONG_OUTER_JOIN = 1120 ER_NULL_COLUMN_IN_INDEX = 1121 ER_CANT_FIND_UDF = 1122 ER_CANT_INITIALIZE_UDF = 1123 ER_UDF_NO_PATHS = 1124 ER_UDF_EXISTS = 1125 ER_CANT_OPEN_LIBRARY = 1126 ER_CANT_FIND_DL_ENTRY = 1127 ER_FUNCTION_NOT_DEFINED = 1128 ER_HOST_IS_BLOCKED = 1129 ER_HOST_NOT_PRIVILEGED = 1130 ER_PASSWORD_ANONYMOUS_USER = 1131 ER_PASSWORD_NOT_ALLOWED = 1132 ER_PASSWORD_NO_MATCH = 1133 ER_UPDATE_INFO = 1134 ER_CANT_CREATE_THREAD = 1135 ER_WRONG_VALUE_COUNT_ON_ROW = 1136 ER_CANT_REOPEN_TABLE = 1137 ER_INVALID_USE_OF_NULL = 1138 ER_REGEXP_ERROR = 1139 ER_MIX_OF_GROUP_FUNC_AND_FIELDS = 1140 ER_NONEXISTING_GRANT = 1141 ER_TABLEACCESS_DENIED_ERROR = 1142 ER_COLUMNACCESS_DENIED_ERROR = 1143 ER_ILLEGAL_GRANT_FOR_TABLE = 1144 ER_GRANT_WRONG_HOST_OR_USER = 1145 ER_NO_SUCH_TABLE = 1146 ER_NONEXISTING_TABLE_GRANT = 1147 ER_NOT_ALLOWED_COMMAND = 1148 ER_SYNTAX_ERROR = 1149 ER_DELAYED_CANT_CHANGE_LOCK = 1150 ER_TOO_MANY_DELAYED_THREADS = 1151 ER_ABORTING_CONNECTION = 1152 ER_NET_PACKET_TOO_LARGE = 1153 ER_NET_READ_ERROR_FROM_PIPE = 1154 ER_NET_FCNTL_ERROR = 1155 ER_NET_PACKETS_OUT_OF_ORDER = 1156 ER_NET_UNCOMPRESS_ERROR = 1157 ER_NET_READ_ERROR = 1158 ER_NET_READ_INTERRUPTED = 1159 ER_NET_ERROR_ON_WRITE = 1160 ER_NET_WRITE_INTERRUPTED = 1161 ER_TOO_LONG_STRING = 1162 ER_TABLE_CANT_HANDLE_BLOB = 1163 ER_TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164 ER_DELAYED_INSERT_TABLE_LOCKED = 1165 ER_WRONG_COLUMN_NAME = 1166 ER_WRONG_KEY_COLUMN = 1167 ER_WRONG_MRG_TABLE = 1168 ER_DUP_UNIQUE = 1169 ER_BLOB_KEY_WITHOUT_LENGTH = 1170 ER_PRIMARY_CANT_HAVE_NULL = 1171 ER_TOO_MANY_ROWS = 1172 ER_REQUIRES_PRIMARY_KEY = 1173 ER_NO_RAID_COMPILED = 1174 ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175 ER_KEY_DOES_NOT_EXITS = 1176 ER_CHECK_NO_SUCH_TABLE = 1177 ER_CHECK_NOT_IMPLEMENTED = 1178 ER_CANT_DO_THIS_DURING_AN_TRANSACTION = 1179 ER_ERROR_DURING_COMMIT = 1180 ER_ERROR_DURING_ROLLBACK = 1181 ER_ERROR_DURING_FLUSH_LOGS = 1182 ER_ERROR_DURING_CHECKPOINT = 1183 ER_NEW_ABORTING_CONNECTION = 1184 ER_DUMP_NOT_IMPLEMENTED = 1185 ER_FLUSH_MASTER_BINLOG_CLOSED = 1186 ER_INDEX_REBUILD = 1187 ER_MASTER = 1188 ER_MASTER_NET_READ = 1189 ER_MASTER_NET_WRITE = 1190 ER_FT_MATCHING_KEY_NOT_FOUND = 1191 ER_LOCK_OR_ACTIVE_TRANSACTION = 1192 ER_UNKNOWN_SYSTEM_VARIABLE = 1193 ER_CRASHED_ON_USAGE = 1194 ER_CRASHED_ON_REPAIR = 1195 ER_WARNING_NOT_COMPLETE_ROLLBACK = 1196 ER_TRANS_CACHE_FULL = 1197 ER_SLAVE_MUST_STOP = 1198 ER_SLAVE_NOT_RUNNING = 1199 ER_BAD_SLAVE = 1200 ER_MASTER_INFO = 1201 ER_SLAVE_THREAD = 1202 ER_TOO_MANY_USER_CONNECTIONS = 1203 ER_SET_CONSTANTS_ONLY = 1204 ER_LOCK_WAIT_TIMEOUT = 1205 ER_LOCK_TABLE_FULL = 1206 ER_READ_ONLY_TRANSACTION = 1207 ER_DROP_DB_WITH_READ_LOCK = 1208 ER_CREATE_DB_WITH_READ_LOCK = 1209 ER_WRONG_ARGUMENTS = 1210 ER_NO_PERMISSION_TO_CREATE_USER = 1211 ER_UNION_TABLES_IN_DIFFERENT_DIR = 1212 ER_LOCK_DEADLOCK = 1213 ER_TABLE_CANT_HANDLE_FT = 1214 ER_CANNOT_ADD_FOREIGN = 1215 ER_NO_REFERENCED_ROW = 1216 ER_ROW_IS_REFERENCED = 1217 ER_CONNECT_TO_MASTER = 1218 ER_QUERY_ON_MASTER = 1219 ER_ERROR_WHEN_EXECUTING_COMMAND = 1220 ER_WRONG_USAGE = 1221 ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222 ER_CANT_UPDATE_WITH_READLOCK = 1223 ER_MIXING_NOT_ALLOWED = 1224 ER_DUP_ARGUMENT = 1225 ER_USER_LIMIT_REACHED = 1226 ER_SPECIFIC_ACCESS_DENIED_ERROR = 1227 ER_LOCAL_VARIABLE = 1228 ER_GLOBAL_VARIABLE = 1229 ER_NO_DEFAULT = 1230 ER_WRONG_VALUE_FOR_VAR = 1231 ER_WRONG_TYPE_FOR_VAR = 1232 ER_VAR_CANT_BE_READ = 1233 ER_CANT_USE_OPTION_HERE = 1234 ER_NOT_SUPPORTED_YET = 1235 ER_MASTER_FATAL_ERROR_READING_BINLOG = 1236 ER_SLAVE_IGNORED_TABLE = 1237 ER_INCORRECT_GLOBAL_LOCAL_VAR = 1238 ER_WRONG_FK_DEF = 1239 ER_KEY_REF_DO_NOT_MATCH_TABLE_REF = 1240 ER_OPERAND_COLUMNS = 1241 ER_SUBQUERY_NO_1_ROW = 1242 ER_UNKNOWN_STMT_HANDLER = 1243 ER_CORRUPT_HELP_DB = 1244 ER_CYCLIC_REFERENCE = 1245 ER_AUTO_CONVERT = 1246 ER_ILLEGAL_REFERENCE = 1247 ER_DERIVED_MUST_HAVE_ALIAS = 1248 ER_SELECT_REDUCED = 1249 ER_TABLENAME_NOT_ALLOWED_HERE = 1250 ER_NOT_SUPPORTED_AUTH_MODE = 1251 ER_SPATIAL_CANT_HAVE_NULL = 1252 ER_COLLATION_CHARSET_MISMATCH = 1253 ER_SLAVE_WAS_RUNNING = 1254 ER_SLAVE_WAS_NOT_RUNNING = 1255 ER_TOO_BIG_FOR_UNCOMPRESS = 1256 ER_ZLIB_Z_MEM_ERROR = 1257 ER_ZLIB_Z_BUF_ERROR = 1258 ER_ZLIB_Z_DATA_ERROR = 1259 ER_CUT_VALUE_GROUP_CONCAT = 1260 ER_WARN_TOO_FEW_RECORDS = 1261 ER_WARN_TOO_MANY_RECORDS = 1262 ER_WARN_NULL_TO_NOTNULL = 1263 ER_WARN_DATA_OUT_OF_RANGE = 1264 WARN_DATA_TRUNCATED = 1265 ER_WARN_USING_OTHER_HANDLER = 1266 ER_CANT_AGGREGATE_2COLLATIONS = 1267 ER_DROP_USER = 1268 ER_REVOKE_GRANTS = 1269 ER_CANT_AGGREGATE_3COLLATIONS = 1270 ER_CANT_AGGREGATE_NCOLLATIONS = 1271 ER_VARIABLE_IS_NOT_STRUCT = 1272 ER_UNKNOWN_COLLATION = 1273 ER_SLAVE_IGNORED_SSL_PARAMS = 1274 ER_SERVER_IS_IN_SECURE_AUTH_MODE = 1275 ER_WARN_FIELD_RESOLVED = 1276 ER_BAD_SLAVE_UNTIL_COND = 1277 ER_MISSING_SKIP_SLAVE = 1278 ER_UNTIL_COND_IGNORED = 1279 ER_WRONG_NAME_FOR_INDEX = 1280 ER_WRONG_NAME_FOR_CATALOG = 1281 ER_WARN_QC_RESIZE = 1282 ER_BAD_FT_COLUMN = 1283 ER_UNKNOWN_KEY_CACHE = 1284 ER_WARN_HOSTNAME_WONT_WORK = 1285 ER_UNKNOWN_STORAGE_ENGINE = 1286 ER_WARN_DEPRECATED_SYNTAX = 1287 ER_NON_UPDATABLE_TABLE = 1288 ER_FEATURE_DISABLED = 1289 ER_OPTION_PREVENTS_STATEMENT = 1290 ER_DUPLICATED_VALUE_IN_TYPE = 1291 ER_TRUNCATED_WRONG_VALUE = 1292 ER_TOO_MUCH_AUTO_TIMESTAMP_COLS = 1293 ER_INVALID_ON_UPDATE = 1294 ER_UNSUPPORTED_PS = 1295 ER_GET_ERRMSG = 1296 ER_GET_TEMPORARY_ERRMSG = 1297 ER_UNKNOWN_TIME_ZONE = 1298 ER_WARN_INVALID_TIMESTAMP = 1299 ER_INVALID_CHARACTER_STRING = 1300 ER_WARN_ALLOWED_PACKET_OVERFLOWED = 1301 ER_CONFLICTING_DECLARATIONS = 1302 ER_SP_NO_RECURSIVE_CREATE = 1303 ER_SP_ALREADY_EXISTS = 1304 ER_SP_DOES_NOT_EXIST = 1305 ER_SP_DROP_FAILED = 1306 ER_SP_STORE_FAILED = 1307 ER_SP_LILABEL_MISMATCH = 1308 ER_SP_LABEL_REDEFINE = 1309 ER_SP_LABEL_MISMATCH = 1310 ER_SP_UNINIT_VAR = 1311 ER_SP_BADSELECT = 1312 ER_SP_BADRETURN = 1313 ER_SP_BADSTATEMENT = 1314 ER_UPDATE_LOG_DEPRECATED_IGNORED = 1315 ER_UPDATE_LOG_DEPRECATED_TRANSLATED = 1316 ER_QUERY_INTERRUPTED = 1317 ER_SP_WRONG_NO_OF_ARGS = 1318 ER_SP_COND_MISMATCH = 1319 ER_SP_NORETURN = 1320 ER_SP_NORETURNEND = 1321 ER_SP_BAD_CURSOR_QUERY = 1322 ER_SP_BAD_CURSOR_SELECT = 1323 ER_SP_CURSOR_MISMATCH = 1324 ER_SP_CURSOR_ALREADY_OPEN = 1325 ER_SP_CURSOR_NOT_OPEN = 1326 ER_SP_UNDECLARED_VAR = 1327 ER_SP_WRONG_NO_OF_FETCH_ARGS = 1328 ER_SP_FETCH_NO_DATA = 1329 ER_SP_DUP_PARAM = 1330 ER_SP_DUP_VAR = 1331 ER_SP_DUP_COND = 1332 ER_SP_DUP_CURS = 1333 ER_SP_CANT_ALTER = 1334 ER_SP_SUBSELECT_NYI = 1335 ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG = 1336 ER_SP_VARCOND_AFTER_CURSHNDLR = 1337 ER_SP_CURSOR_AFTER_HANDLER = 1338 ER_SP_CASE_NOT_FOUND = 1339 ER_FPARSER_TOO_BIG_FILE = 1340 ER_FPARSER_BAD_HEADER = 1341 ER_FPARSER_EOF_IN_COMMENT = 1342 ER_FPARSER_ERROR_IN_PARAMETER = 1343 ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER = 1344 ER_VIEW_NO_EXPLAIN = 1345 ER_FRM_UNKNOWN_TYPE = 1346 ER_WRONG_OBJECT = 1347 ER_NONUPDATEABLE_COLUMN = 1348 ER_VIEW_SELECT_DERIVED = 1349 ER_VIEW_SELECT_CLAUSE = 1350 ER_VIEW_SELECT_VARIABLE = 1351 ER_VIEW_SELECT_TMPTABLE = 1352 ER_VIEW_WRONG_LIST = 1353 ER_WARN_VIEW_MERGE = 1354 ER_WARN_VIEW_WITHOUT_KEY = 1355 ER_VIEW_INVALID = 1356 ER_SP_NO_DROP_SP = 1357 ER_SP_GOTO_IN_HNDLR = 1358 ER_TRG_ALREADY_EXISTS = 1359 ER_TRG_DOES_NOT_EXIST = 1360 ER_TRG_ON_VIEW_OR_TEMP_TABLE = 1361 ER_TRG_CANT_CHANGE_ROW = 1362 ER_TRG_NO_SUCH_ROW_IN_TRG = 1363 ER_NO_DEFAULT_FOR_FIELD = 1364 ER_DIVISION_BY_ZERO = 1365 ER_TRUNCATED_WRONG_VALUE_FOR_FIELD = 1366 ER_ILLEGAL_VALUE_FOR_TYPE = 1367 ER_VIEW_NONUPD_CHECK = 1368 ER_VIEW_CHECK_FAILED = 1369 ER_PROCACCESS_DENIED_ERROR = 1370 ER_RELAY_LOG_FAIL = 1371 ER_PASSWD_LENGTH = 1372 ER_UNKNOWN_TARGET_BINLOG = 1373 ER_IO_ERR_LOG_INDEX_READ = 1374 ER_BINLOG_PURGE_PROHIBITED = 1375 ER_FSEEK_FAIL = 1376 ER_BINLOG_PURGE_FATAL_ERR = 1377 ER_LOG_IN_USE = 1378 ER_LOG_PURGE_UNKNOWN_ERR = 1379 ER_RELAY_LOG_INIT = 1380 ER_NO_BINARY_LOGGING = 1381 ER_RESERVED_SYNTAX = 1382 ER_WSAS_FAILED = 1383 ER_DIFF_GROUPS_PROC = 1384 ER_NO_GROUP_FOR_PROC = 1385 ER_ORDER_WITH_PROC = 1386 ER_LOGGING_PROHIBIT_CHANGING_OF = 1387 ER_NO_FILE_MAPPING = 1388 ER_WRONG_MAGIC = 1389 ER_PS_MANY_PARAM = 1390 ER_KEY_PART_0 = 1391 ER_VIEW_CHECKSUM = 1392 ER_VIEW_MULTIUPDATE = 1393 ER_VIEW_NO_INSERT_FIELD_LIST = 1394 ER_VIEW_DELETE_MERGE_VIEW = 1395 ER_CANNOT_USER = 1396 ER_XAER_NOTA = 1397 ER_XAER_INVAL = 1398 ER_XAER_RMFAIL = 1399 ER_XAER_OUTSIDE = 1400 ER_XAER_RMERR = 1401 ER_XA_RBROLLBACK = 1402 ER_NONEXISTING_PROC_GRANT = 1403 ER_PROC_AUTO_GRANT_FAIL = 1404 ER_PROC_AUTO_REVOKE_FAIL = 1405 ER_DATA_TOO_LONG = 1406 ER_SP_BAD_SQLSTATE = 1407 ER_STARTUP = 1408 ER_LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR = 1409 ER_CANT_CREATE_USER_WITH_GRANT = 1410 ER_WRONG_VALUE_FOR_TYPE = 1411 ER_TABLE_DEF_CHANGED = 1412 ER_SP_DUP_HANDLER = 1413 ER_SP_NOT_VAR_ARG = 1414 ER_SP_NO_RETSET = 1415 ER_CANT_CREATE_GEOMETRY_OBJECT = 1416 ER_FAILED_ROUTINE_BREAK_BINLOG = 1417 ER_BINLOG_UNSAFE_ROUTINE = 1418 ER_BINLOG_CREATE_ROUTINE_NEED_SUPER = 1419 ER_EXEC_STMT_WITH_OPEN_CURSOR = 1420 ER_STMT_HAS_NO_OPEN_CURSOR = 1421 ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG = 1422 ER_NO_DEFAULT_FOR_VIEW_FIELD = 1423 ER_SP_NO_RECURSION = 1424 ER_TOO_BIG_SCALE = 1425 ER_TOO_BIG_PRECISION = 1426 ER_M_BIGGER_THAN_D = 1427 ER_WRONG_LOCK_OF_SYSTEM_TABLE = 1428 ER_CONNECT_TO_FOREIGN_DATA_SOURCE = 1429 ER_QUERY_ON_FOREIGN_DATA_SOURCE = 1430 ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST = 1431 ER_FOREIGN_DATA_STRING_INVALID_CANT_CREATE = 1432 ER_FOREIGN_DATA_STRING_INVALID = 1433 ER_CANT_CREATE_FEDERATED_TABLE = 1434 ER_TRG_IN_WRONG_SCHEMA = 1435 ER_STACK_OVERRUN_NEED_MORE = 1436 ER_TOO_LONG_BODY = 1437 ER_WARN_CANT_DROP_DEFAULT_KEYCACHE = 1438 ER_TOO_BIG_DISPLAYWIDTH = 1439 ER_XAER_DUPID = 1440 ER_DATETIME_FUNCTION_OVERFLOW = 1441 ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG = 1442 ER_VIEW_PREVENT_UPDATE = 1443 ER_PS_NO_RECURSION = 1444 ER_SP_CANT_SET_AUTOCOMMIT = 1445 ER_MALFORMED_DEFINER = 1446 ER_VIEW_FRM_NO_USER = 1447 ER_VIEW_OTHER_USER = 1448 ER_NO_SUCH_USER = 1449 ER_FORBID_SCHEMA_CHANGE = 1450 ER_ROW_IS_REFERENCED_2 = 1451 ER_NO_REFERENCED_ROW_2 = 1452 ER_SP_BAD_VAR_SHADOW = 1453 ER_TRG_NO_DEFINER = 1454 ER_OLD_FILE_FORMAT = 1455 ER_SP_RECURSION_LIMIT = 1456 ER_SP_PROC_TABLE_CORRUPT = 1457 ER_SP_WRONG_NAME = 1458 ER_TABLE_NEEDS_UPGRADE = 1459 ER_SP_NO_AGGREGATE = 1460 ER_MAX_PREPARED_STMT_COUNT_REACHED = 1461 ER_VIEW_RECURSIVE = 1462 ER_NON_GROUPING_FIELD_USED = 1463 ER_TABLE_CANT_HANDLE_SPKEYS = 1464 ER_NO_TRIGGERS_ON_SYSTEM_SCHEMA = 1465 ER_REMOVED_SPACES = 1466 ER_AUTOINC_READ_FAILED = 1467 ER_USERNAME = 1468 ER_HOSTNAME = 1469 ER_WRONG_STRING_LENGTH = 1470 ER_NON_INSERTABLE_TABLE = 1471 ER_ADMIN_WRONG_MRG_TABLE = 1472 ER_TOO_HIGH_LEVEL_OF_NESTING_FOR_SELECT = 1473 ER_NAME_BECOMES_EMPTY = 1474 ER_AMBIGUOUS_FIELD_TERM = 1475 ER_FOREIGN_SERVER_EXISTS = 1476 ER_FOREIGN_SERVER_DOESNT_EXIST = 1477 ER_ILLEGAL_HA_CREATE_OPTION = 1478 ER_PARTITION_REQUIRES_VALUES_ERROR = 1479 ER_PARTITION_WRONG_VALUES_ERROR = 1480 ER_PARTITION_MAXVALUE_ERROR = 1481 ER_PARTITION_SUBPARTITION_ERROR = 1482 ER_PARTITION_SUBPART_MIX_ERROR = 1483 ER_PARTITION_WRONG_NO_PART_ERROR = 1484 ER_PARTITION_WRONG_NO_SUBPART_ERROR = 1485 ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR = 1486 ER_NO_CONST_EXPR_IN_RANGE_OR_LIST_ERROR = 1487 ER_FIELD_NOT_FOUND_PART_ERROR = 1488 ER_LIST_OF_FIELDS_ONLY_IN_HASH_ERROR = 1489 ER_INCONSISTENT_PARTITION_INFO_ERROR = 1490 ER_PARTITION_FUNC_NOT_ALLOWED_ERROR = 1491 ER_PARTITIONS_MUST_BE_DEFINED_ERROR = 1492 ER_RANGE_NOT_INCREASING_ERROR = 1493 ER_INCONSISTENT_TYPE_OF_FUNCTIONS_ERROR = 1494 ER_MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR = 1495 ER_PARTITION_ENTRY_ERROR = 1496 ER_MIX_HANDLER_ERROR = 1497 ER_PARTITION_NOT_DEFINED_ERROR = 1498 ER_TOO_MANY_PARTITIONS_ERROR = 1499 ER_SUBPARTITION_ERROR = 1500 ER_CANT_CREATE_HANDLER_FILE = 1501 ER_BLOB_FIELD_IN_PART_FUNC_ERROR = 1502 ER_UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF = 1503 ER_NO_PARTS_ERROR = 1504 ER_PARTITION_MGMT_ON_NONPARTITIONED = 1505 ER_FOREIGN_KEY_ON_PARTITIONED = 1506 ER_DROP_PARTITION_NON_EXISTENT = 1507 ER_DROP_LAST_PARTITION = 1508 ER_COALESCE_ONLY_ON_HASH_PARTITION = 1509 ER_REORG_HASH_ONLY_ON_SAME_NO = 1510 ER_REORG_NO_PARAM_ERROR = 1511 ER_ONLY_ON_RANGE_LIST_PARTITION = 1512 ER_ADD_PARTITION_SUBPART_ERROR = 1513 ER_ADD_PARTITION_NO_NEW_PARTITION = 1514 ER_COALESCE_PARTITION_NO_PARTITION = 1515 ER_REORG_PARTITION_NOT_EXIST = 1516 ER_SAME_NAME_PARTITION = 1517 ER_NO_BINLOG_ERROR = 1518 ER_CONSECUTIVE_REORG_PARTITIONS = 1519 ER_REORG_OUTSIDE_RANGE = 1520 ER_PARTITION_FUNCTION_FAILURE = 1521 ER_PART_STATE_ERROR = 1522 ER_LIMITED_PART_RANGE = 1523 ER_PLUGIN_IS_NOT_LOADED = 1524 ER_WRONG_VALUE = 1525 ER_NO_PARTITION_FOR_GIVEN_VALUE = 1526 ER_FILEGROUP_OPTION_ONLY_ONCE = 1527 ER_CREATE_FILEGROUP_FAILED = 1528 ER_DROP_FILEGROUP_FAILED = 1529 ER_TABLESPACE_AUTO_EXTEND_ERROR = 1530 ER_WRONG_SIZE_NUMBER = 1531 ER_SIZE_OVERFLOW_ERROR = 1532 ER_ALTER_FILEGROUP_FAILED = 1533 ER_BINLOG_ROW_LOGGING_FAILED = 1534 ER_BINLOG_ROW_WRONG_TABLE_DEF = 1535 ER_BINLOG_ROW_RBR_TO_SBR = 1536 ER_EVENT_ALREADY_EXISTS = 1537 ER_EVENT_STORE_FAILED = 1538 ER_EVENT_DOES_NOT_EXIST = 1539 ER_EVENT_CANT_ALTER = 1540 ER_EVENT_DROP_FAILED = 1541 ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG = 1542 ER_EVENT_ENDS_BEFORE_STARTS = 1543 ER_EVENT_EXEC_TIME_IN_THE_PAST = 1544 ER_EVENT_OPEN_TABLE_FAILED = 1545 ER_EVENT_NEITHER_M_EXPR_NOR_M_AT = 1546 ER_OBSOLETE_COL_COUNT_DOESNT_MATCH_CORRUPTED = 1547 ER_OBSOLETE_CANNOT_LOAD_FROM_TABLE = 1548 ER_EVENT_CANNOT_DELETE = 1549 ER_EVENT_COMPILE_ERROR = 1550 ER_EVENT_SAME_NAME = 1551 ER_EVENT_DATA_TOO_LONG = 1552 ER_DROP_INDEX_FK = 1553 ER_WARN_DEPRECATED_SYNTAX_WITH_VER = 1554 ER_CANT_WRITE_LOCK_LOG_TABLE = 1555 ER_CANT_LOCK_LOG_TABLE = 1556 ER_FOREIGN_DUPLICATE_KEY_OLD_UNUSED = 1557 ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE = 1558 ER_TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR = 1559 ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1560 ER_NDB_CANT_SWITCH_BINLOG_FORMAT = 1561 ER_PARTITION_NO_TEMPORARY = 1562 ER_PARTITION_CONST_DOMAIN_ERROR = 1563 ER_PARTITION_FUNCTION_IS_NOT_ALLOWED = 1564 ER_DDL_LOG_ERROR = 1565 ER_NULL_IN_VALUES_LESS_THAN = 1566 ER_WRONG_PARTITION_NAME = 1567 ER_CANT_CHANGE_TX_CHARACTERISTICS = 1568 ER_DUP_ENTRY_AUTOINCREMENT_CASE = 1569 ER_EVENT_MODIFY_QUEUE_ERROR = 1570 ER_EVENT_SET_VAR_ERROR = 1571 ER_PARTITION_MERGE_ERROR = 1572 ER_CANT_ACTIVATE_LOG = 1573 ER_RBR_NOT_AVAILABLE = 1574 ER_BASE64_DECODE_ERROR = 1575 ER_EVENT_RECURSION_FORBIDDEN = 1576 ER_EVENTS_DB_ERROR = 1577 ER_ONLY_INTEGERS_ALLOWED = 1578 ER_UNSUPORTED_LOG_ENGINE = 1579 ER_BAD_LOG_STATEMENT = 1580 ER_CANT_RENAME_LOG_TABLE = 1581 ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT = 1582 ER_WRONG_PARAMETERS_TO_NATIVE_FCT = 1583 ER_WRONG_PARAMETERS_TO_STORED_FCT = 1584 ER_NATIVE_FCT_NAME_COLLISION = 1585 ER_DUP_ENTRY_WITH_KEY_NAME = 1586 ER_BINLOG_PURGE_EMFILE = 1587 ER_EVENT_CANNOT_CREATE_IN_THE_PAST = 1588 ER_EVENT_CANNOT_ALTER_IN_THE_PAST = 1589 ER_SLAVE_INCIDENT = 1590 ER_NO_PARTITION_FOR_GIVEN_VALUE_SILENT = 1591 ER_BINLOG_UNSAFE_STATEMENT = 1592 ER_SLAVE_FATAL_ERROR = 1593 ER_SLAVE_RELAY_LOG_READ_FAILURE = 1594 ER_SLAVE_RELAY_LOG_WRITE_FAILURE = 1595 ER_SLAVE_CREATE_EVENT_FAILURE = 1596 ER_SLAVE_MASTER_COM_FAILURE = 1597 ER_BINLOG_LOGGING_IMPOSSIBLE = 1598 ER_VIEW_NO_CREATION_CTX = 1599 ER_VIEW_INVALID_CREATION_CTX = 1600 ER_SR_INVALID_CREATION_CTX = 1601 ER_TRG_CORRUPTED_FILE = 1602 ER_TRG_NO_CREATION_CTX = 1603 ER_TRG_INVALID_CREATION_CTX = 1604 ER_EVENT_INVALID_CREATION_CTX = 1605 ER_TRG_CANT_OPEN_TABLE = 1606 ER_CANT_CREATE_SROUTINE = 1607 ER_NEVER_USED = 1608 ER_NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT = 1609 ER_SLAVE_CORRUPT_EVENT = 1610 ER_LOAD_DATA_INVALID_COLUMN = 1611 ER_LOG_PURGE_NO_FILE = 1612 ER_XA_RBTIMEOUT = 1613 ER_XA_RBDEADLOCK = 1614 ER_NEED_REPREPARE = 1615 ER_DELAYED_NOT_SUPPORTED = 1616 WARN_NO_MASTER_INFO = 1617 WARN_OPTION_IGNORED = 1618 WARN_PLUGIN_DELETE_BUILTIN = 1619 WARN_PLUGIN_BUSY = 1620 ER_VARIABLE_IS_READONLY = 1621 ER_WARN_ENGINE_TRANSACTION_ROLLBACK = 1622 ER_SLAVE_HEARTBEAT_FAILURE = 1623 ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE = 1624 ER_NDB_REPLICATION_SCHEMA_ERROR = 1625 ER_CONFLICT_FN_PARSE_ERROR = 1626 ER_EXCEPTIONS_WRITE_ERROR = 1627 ER_TOO_LONG_TABLE_COMMENT = 1628 ER_TOO_LONG_FIELD_COMMENT = 1629 ER_FUNC_INEXISTENT_NAME_COLLISION = 1630 ER_DATABASE_NAME = 1631 ER_TABLE_NAME = 1632 ER_PARTITION_NAME = 1633 ER_SUBPARTITION_NAME = 1634 ER_TEMPORARY_NAME = 1635 ER_RENAMED_NAME = 1636 ER_TOO_MANY_CONCURRENT_TRXS = 1637 WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED = 1638 ER_DEBUG_SYNC_TIMEOUT = 1639 ER_DEBUG_SYNC_HIT_LIMIT = 1640 ER_DUP_SIGNAL_SET = 1641 ER_SIGNAL_WARN = 1642 ER_SIGNAL_NOT_FOUND = 1643 ER_SIGNAL_EXCEPTION = 1644 ER_RESIGNAL_WITHOUT_ACTIVE_HANDLER = 1645 ER_SIGNAL_BAD_CONDITION_TYPE = 1646 WARN_COND_ITEM_TRUNCATED = 1647 ER_COND_ITEM_TOO_LONG = 1648 ER_UNKNOWN_LOCALE = 1649 ER_SLAVE_IGNORE_SERVER_IDS = 1650 ER_QUERY_CACHE_DISABLED = 1651 ER_SAME_NAME_PARTITION_FIELD = 1652 ER_PARTITION_COLUMN_LIST_ERROR = 1653 ER_WRONG_TYPE_COLUMN_VALUE_ERROR = 1654 ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR = 1655 ER_MAXVALUE_IN_VALUES_IN = 1656 ER_TOO_MANY_VALUES_ERROR = 1657 ER_ROW_SINGLE_PARTITION_FIELD_ERROR = 1658 ER_FIELD_TYPE_NOT_ALLOWED_AS_PARTITION_FIELD = 1659 ER_PARTITION_FIELDS_TOO_LONG = 1660 ER_BINLOG_ROW_ENGINE_AND_STMT_ENGINE = 1661 ER_BINLOG_ROW_MODE_AND_STMT_ENGINE = 1662 ER_BINLOG_UNSAFE_AND_STMT_ENGINE = 1663 ER_BINLOG_ROW_INJECTION_AND_STMT_ENGINE = 1664 ER_BINLOG_STMT_MODE_AND_ROW_ENGINE = 1665 ER_BINLOG_ROW_INJECTION_AND_STMT_MODE = 1666 ER_BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1667 ER_BINLOG_UNSAFE_LIMIT = 1668 ER_BINLOG_UNSAFE_INSERT_DELAYED = 1669 ER_BINLOG_UNSAFE_SYSTEM_TABLE = 1670 ER_BINLOG_UNSAFE_AUTOINC_COLUMNS = 1671 ER_BINLOG_UNSAFE_UDF = 1672 ER_BINLOG_UNSAFE_SYSTEM_VARIABLE = 1673 ER_BINLOG_UNSAFE_SYSTEM_FUNCTION = 1674 ER_BINLOG_UNSAFE_NONTRANS_AFTER_TRANS = 1675 ER_MESSAGE_AND_STATEMENT = 1676 ER_SLAVE_CONVERSION_FAILED = 1677 ER_SLAVE_CANT_CREATE_CONVERSION = 1678 ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1679 ER_PATH_LENGTH = 1680 ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT = 1681 ER_WRONG_NATIVE_TABLE_STRUCTURE = 1682 ER_WRONG_PERFSCHEMA_USAGE = 1683 ER_WARN_I_S_SKIPPED_TABLE = 1684 ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1685 ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1686 ER_SPATIAL_MUST_HAVE_GEOM_COL = 1687 ER_TOO_LONG_INDEX_COMMENT = 1688 ER_LOCK_ABORTED = 1689 ER_DATA_OUT_OF_RANGE = 1690 ER_WRONG_SPVAR_TYPE_IN_LIMIT = 1691 ER_BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1692 ER_BINLOG_UNSAFE_MIXED_STATEMENT = 1693 ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1694 ER_STORED_FUNCTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1695 ER_FAILED_READ_FROM_PAR_FILE = 1696 ER_VALUES_IS_NOT_INT_TYPE_ERROR = 1697 ER_ACCESS_DENIED_NO_PASSWORD_ERROR = 1698 ER_SET_PASSWORD_AUTH_PLUGIN = 1699 ER_GRANT_PLUGIN_USER_EXISTS = 1700 ER_TRUNCATE_ILLEGAL_FK = 1701 ER_PLUGIN_IS_PERMANENT = 1702 ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN = 1703 ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX = 1704 ER_STMT_CACHE_FULL = 1705 ER_MULTI_UPDATE_KEY_CONFLICT = 1706 ER_TABLE_NEEDS_REBUILD = 1707 WARN_OPTION_BELOW_LIMIT = 1708 ER_INDEX_COLUMN_TOO_LONG = 1709 ER_ERROR_IN_TRIGGER_BODY = 1710 ER_ERROR_IN_UNKNOWN_TRIGGER_BODY = 1711 ER_INDEX_CORRUPT = 1712 ER_UNDO_RECORD_TOO_BIG = 1713 ER_BINLOG_UNSAFE_INSERT_IGNORE_SELECT = 1714 ER_BINLOG_UNSAFE_INSERT_SELECT_UPDATE = 1715 ER_BINLOG_UNSAFE_REPLACE_SELECT = 1716 ER_BINLOG_UNSAFE_CREATE_IGNORE_SELECT = 1717 ER_BINLOG_UNSAFE_CREATE_REPLACE_SELECT = 1718 ER_BINLOG_UNSAFE_UPDATE_IGNORE = 1719 ER_PLUGIN_NO_UNINSTALL = 1720 ER_PLUGIN_NO_INSTALL = 1721 ER_BINLOG_UNSAFE_WRITE_AUTOINC_SELECT = 1722 ER_BINLOG_UNSAFE_CREATE_SELECT_AUTOINC = 1723 ER_BINLOG_UNSAFE_INSERT_TWO_KEYS = 1724 ER_TABLE_IN_FK_CHECK = 1725 ER_UNSUPPORTED_ENGINE = 1726 ER_BINLOG_UNSAFE_AUTOINC_NOT_FIRST = 1727 ER_CANNOT_LOAD_FROM_TABLE_V2 = 1728 ER_MASTER_DELAY_VALUE_OUT_OF_RANGE = 1729 ER_ONLY_FD_AND_RBR_EVENTS_ALLOWED_IN_BINLOG_STATEMENT = 1730 ER_PARTITION_EXCHANGE_DIFFERENT_OPTION = 1731 ER_PARTITION_EXCHANGE_PART_TABLE = 1732 ER_PARTITION_EXCHANGE_TEMP_TABLE = 1733 ER_PARTITION_INSTEAD_OF_SUBPARTITION = 1734 ER_UNKNOWN_PARTITION = 1735 ER_TABLES_DIFFERENT_METADATA = 1736 ER_ROW_DOES_NOT_MATCH_PARTITION = 1737 ER_BINLOG_CACHE_SIZE_GREATER_THAN_MAX = 1738 ER_WARN_INDEX_NOT_APPLICABLE = 1739 ER_PARTITION_EXCHANGE_FOREIGN_KEY = 1740 ER_NO_SUCH_KEY_VALUE = 1741 ER_RPL_INFO_DATA_TOO_LONG = 1742 ER_NETWORK_READ_EVENT_CHECKSUM_FAILURE = 1743 ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE = 1744 ER_BINLOG_STMT_CACHE_SIZE_GREATER_THAN_MAX = 1745 ER_CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT = 1746 ER_PARTITION_CLAUSE_ON_NONPARTITIONED = 1747 ER_ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET = 1748 ER_NO_SUCH_PARTITION__UNUSED = 1749 ER_CHANGE_RPL_INFO_REPOSITORY_FAILURE = 1750 ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_CREATED_TEMP_TABLE = 1751 ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_DROPPED_TEMP_TABLE = 1752 ER_MTS_FEATURE_IS_NOT_SUPPORTED = 1753 ER_MTS_UPDATED_DBS_GREATER_MAX = 1754 ER_MTS_CANT_PARALLEL = 1755 ER_MTS_INCONSISTENT_DATA = 1756 ER_FULLTEXT_NOT_SUPPORTED_WITH_PARTITIONING = 1757 ER_DA_INVALID_CONDITION_NUMBER = 1758 ER_INSECURE_PLAIN_TEXT = 1759 ER_INSECURE_CHANGE_MASTER = 1760 ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO = 1761 ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO = 1762 ER_SQLTHREAD_WITH_SECURE_SLAVE = 1763 ER_TABLE_HAS_NO_FT = 1764 ER_VARIABLE_NOT_SETTABLE_IN_SF_OR_TRIGGER = 1765 ER_VARIABLE_NOT_SETTABLE_IN_TRANSACTION = 1766 ER_GTID_NEXT_IS_NOT_IN_GTID_NEXT_LIST = 1767 ER_CANT_CHANGE_GTID_NEXT_IN_TRANSACTION_WHEN_GTID_NEXT_LIST_IS_NULL = 1768 ER_SET_STATEMENT_CANNOT_INVOKE_FUNCTION = 1769 ER_GTID_NEXT_CANT_BE_AUTOMATIC_IF_GTID_NEXT_LIST_IS_NON_NULL = 1770 ER_SKIPPING_LOGGED_TRANSACTION = 1771 ER_MALFORMED_GTID_SET_SPECIFICATION = 1772 ER_MALFORMED_GTID_SET_ENCODING = 1773 ER_MALFORMED_GTID_SPECIFICATION = 1774 ER_GNO_EXHAUSTED = 1775 ER_BAD_SLAVE_AUTO_POSITION = 1776 ER_AUTO_POSITION_REQUIRES_GTID_MODE_ON = 1777 ER_CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET = 1778 ER_GTID_MODE_2_OR_3_REQUIRES_ENFORCE_GTID_CONSISTENCY_ON = 1779 ER_GTID_MODE_REQUIRES_BINLOG = 1780 ER_CANT_SET_GTID_NEXT_TO_GTID_WHEN_GTID_MODE_IS_OFF = 1781 ER_CANT_SET_GTID_NEXT_TO_ANONYMOUS_WHEN_GTID_MODE_IS_ON = 1782 ER_CANT_SET_GTID_NEXT_LIST_TO_NON_NULL_WHEN_GTID_MODE_IS_OFF = 1783 ER_FOUND_GTID_EVENT_WHEN_GTID_MODE_IS_OFF = 1784 ER_GTID_UNSAFE_NON_TRANSACTIONAL_TABLE = 1785 ER_GTID_UNSAFE_CREATE_SELECT = 1786 ER_GTID_UNSAFE_CREATE_DROP_TEMPORARY_TABLE_IN_TRANSACTION = 1787 ER_GTID_MODE_CAN_ONLY_CHANGE_ONE_STEP_AT_A_TIME = 1788 ER_MASTER_HAS_PURGED_REQUIRED_GTIDS = 1789 ER_CANT_SET_GTID_NEXT_WHEN_OWNING_GTID = 1790 ER_UNKNOWN_EXPLAIN_FORMAT = 1791 ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION = 1792 ER_TOO_LONG_TABLE_PARTITION_COMMENT = 1793 ER_SLAVE_CONFIGURATION = 1794 ER_INNODB_FT_LIMIT = 1795 ER_INNODB_NO_FT_TEMP_TABLE = 1796 ER_INNODB_FT_WRONG_DOCID_COLUMN = 1797 ER_INNODB_FT_WRONG_DOCID_INDEX = 1798 ER_INNODB_ONLINE_LOG_TOO_BIG = 1799 ER_UNKNOWN_ALTER_ALGORITHM = 1800 ER_UNKNOWN_ALTER_LOCK = 1801 ER_MTS_CHANGE_MASTER_CANT_RUN_WITH_GAPS = 1802 ER_MTS_RECOVERY_FAILURE = 1803 ER_MTS_RESET_WORKERS = 1804 ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2 = 1805 ER_SLAVE_SILENT_RETRY_TRANSACTION = 1806 ER_DISCARD_FK_CHECKS_RUNNING = 1807 ER_TABLE_SCHEMA_MISMATCH = 1808 ER_TABLE_IN_SYSTEM_TABLESPACE = 1809 ER_IO_READ_ERROR = 1810 ER_IO_WRITE_ERROR = 1811 ER_TABLESPACE_MISSING = 1812 ER_TABLESPACE_EXISTS = 1813 ER_TABLESPACE_DISCARDED = 1814 ER_INTERNAL_ERROR = 1815 ER_INNODB_IMPORT_ERROR = 1816 ER_INNODB_INDEX_CORRUPT = 1817 ER_INVALID_YEAR_COLUMN_LENGTH = 1818 ER_NOT_VALID_PASSWORD = 1819 ER_MUST_CHANGE_PASSWORD = 1820 ER_FK_NO_INDEX_CHILD = 1821 ER_FK_NO_INDEX_PARENT = 1822 ER_FK_FAIL_ADD_SYSTEM = 1823 ER_FK_CANNOT_OPEN_PARENT = 1824 ER_FK_INCORRECT_OPTION = 1825 ER_FK_DUP_NAME = 1826 ER_PASSWORD_FORMAT = 1827 ER_FK_COLUMN_CANNOT_DROP = 1828 ER_FK_COLUMN_CANNOT_DROP_CHILD = 1829 ER_FK_COLUMN_NOT_NULL = 1830 ER_DUP_INDEX = 1831 ER_FK_COLUMN_CANNOT_CHANGE = 1832 ER_FK_COLUMN_CANNOT_CHANGE_CHILD = 1833 ER_FK_CANNOT_DELETE_PARENT = 1834 ER_MALFORMED_PACKET = 1835 ER_READ_ONLY_MODE = 1836 ER_GTID_NEXT_TYPE_UNDEFINED_GROUP = 1837 ER_VARIABLE_NOT_SETTABLE_IN_SP = 1838 ER_CANT_SET_GTID_PURGED_WHEN_GTID_MODE_IS_OFF = 1839 ER_CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY = 1840 ER_CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY = 1841 ER_GTID_PURGED_WAS_CHANGED = 1842 ER_GTID_EXECUTED_WAS_CHANGED = 1843 ER_BINLOG_STMT_MODE_AND_NO_REPL_TABLES = 1844 ER_ALTER_OPERATION_NOT_SUPPORTED = 1845 ER_ALTER_OPERATION_NOT_SUPPORTED_REASON = 1846 ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY = 1847 ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION = 1848 ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME = 1849 ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE = 1850 ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK = 1851 ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_IGNORE = 1852 ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK = 1853 ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC = 1854 ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS = 1855 ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS = 1856 ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS = 1857 ER_SQL_SLAVE_SKIP_COUNTER_NOT_SETTABLE_IN_GTID_MODE = 1858 ER_DUP_UNKNOWN_IN_INDEX = 1859 ER_IDENT_CAUSES_TOO_LONG_PATH = 1860 ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL = 1861 ER_MUST_CHANGE_PASSWORD_LOGIN = 1862 ER_ROW_IN_WRONG_PARTITION = 1863 ER_ERROR_LAST = 1863 ) ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/mysql/errname.go ================================================ package mysql var MySQLErrName = map[uint16]string{ ER_HASHCHK: "hashchk", ER_NISAMCHK: "isamchk", ER_NO: "NO", ER_YES: "YES", ER_CANT_CREATE_FILE: "Can't create file '%-.200s' (errno: %d - %s)", ER_CANT_CREATE_TABLE: "Can't create table '%-.200s' (errno: %d)", ER_CANT_CREATE_DB: "Can't create database '%-.192s' (errno: %d)", ER_DB_CREATE_EXISTS: "Can't create database '%-.192s'; database exists", ER_DB_DROP_EXISTS: "Can't drop database '%-.192s'; database doesn't exist", ER_DB_DROP_DELETE: "Error dropping database (can't delete '%-.192s', errno: %d)", ER_DB_DROP_RMDIR: "Error dropping database (can't rmdir '%-.192s', errno: %d)", ER_CANT_DELETE_FILE: "Error on delete of '%-.192s' (errno: %d - %s)", ER_CANT_FIND_SYSTEM_REC: "Can't read record in system table", ER_CANT_GET_STAT: "Can't get status of '%-.200s' (errno: %d - %s)", ER_CANT_GET_WD: "Can't get working directory (errno: %d - %s)", ER_CANT_LOCK: "Can't lock file (errno: %d - %s)", ER_CANT_OPEN_FILE: "Can't open file: '%-.200s' (errno: %d - %s)", ER_FILE_NOT_FOUND: "Can't find file: '%-.200s' (errno: %d - %s)", ER_CANT_READ_DIR: "Can't read dir of '%-.192s' (errno: %d - %s)", ER_CANT_SET_WD: "Can't change dir to '%-.192s' (errno: %d - %s)", ER_CHECKREAD: "Record has changed since last read in table '%-.192s'", ER_DISK_FULL: "Disk full (%s); waiting for someone to free some space... (errno: %d - %s)", ER_DUP_KEY: "Can't write; duplicate key in table '%-.192s'", ER_ERROR_ON_CLOSE: "Error on close of '%-.192s' (errno: %d - %s)", ER_ERROR_ON_READ: "Error reading file '%-.200s' (errno: %d - %s)", ER_ERROR_ON_RENAME: "Error on rename of '%-.210s' to '%-.210s' (errno: %d - %s)", ER_ERROR_ON_WRITE: "Error writing file '%-.200s' (errno: %d - %s)", ER_FILE_USED: "'%-.192s' is locked against change", ER_FILSORT_ABORT: "Sort aborted", ER_FORM_NOT_FOUND: "View '%-.192s' doesn't exist for '%-.192s'", ER_GET_ERRNO: "Got error %d from storage engine", ER_ILLEGAL_HA: "Table storage engine for '%-.192s' doesn't have this option", ER_KEY_NOT_FOUND: "Can't find record in '%-.192s'", ER_NOT_FORM_FILE: "Incorrect information in file: '%-.200s'", ER_NOT_KEYFILE: "Incorrect key file for table '%-.200s'; try to repair it", ER_OLD_KEYFILE: "Old key file for table '%-.192s'; repair it!", ER_OPEN_AS_READONLY: "Table '%-.192s' is read only", ER_OUTOFMEMORY: "Out of memory; restart server and try again (needed %d bytes)", ER_OUT_OF_SORTMEMORY: "Out of sort memory, consider increasing server sort buffer size", ER_UNEXPECTED_EOF: "Unexpected EOF found when reading file '%-.192s' (errno: %d - %s)", ER_CON_COUNT_ERROR: "Too many connections", ER_OUT_OF_RESOURCES: "Out of memory; check if mysqld or some other process uses all available memory; if not, you may have to use 'ulimit' to allow mysqld to use more memory or you can add more swap space", ER_BAD_HOST_ERROR: "Can't get hostname for your address", ER_HANDSHAKE_ERROR: "Bad handshake", ER_DBACCESS_DENIED_ERROR: "Access denied for user '%-.48s'@'%-.64s' to database '%-.192s'", ER_ACCESS_DENIED_ERROR: "Access denied for user '%-.48s'@'%-.64s' (using password: %s)", ER_NO_DB_ERROR: "No database selected", ER_UNKNOWN_COM_ERROR: "Unknown command", ER_BAD_NULL_ERROR: "Column '%-.192s' cannot be null", ER_BAD_DB_ERROR: "Unknown database '%-.192s'", ER_TABLE_EXISTS_ERROR: "Table '%-.192s' already exists", ER_BAD_TABLE_ERROR: "Unknown table '%-.100s'", ER_NON_UNIQ_ERROR: "Column '%-.192s' in %-.192s is ambiguous", ER_SERVER_SHUTDOWN: "Server shutdown in progress", ER_BAD_FIELD_ERROR: "Unknown column '%-.192s' in '%-.192s'", ER_WRONG_FIELD_WITH_GROUP: "'%-.192s' isn't in GROUP BY", ER_WRONG_GROUP_FIELD: "Can't group on '%-.192s'", ER_WRONG_SUM_SELECT: "Statement has sum functions and columns in same statement", ER_WRONG_VALUE_COUNT: "Column count doesn't match value count", ER_TOO_LONG_IDENT: "Identifier name '%-.100s' is too long", ER_DUP_FIELDNAME: "Duplicate column name '%-.192s'", ER_DUP_KEYNAME: "Duplicate key name '%-.192s'", ER_DUP_ENTRY: "Duplicate entry '%-.192s' for key %d", ER_WRONG_FIELD_SPEC: "Incorrect column specifier for column '%-.192s'", ER_PARSE_ERROR: "%s near '%-.80s' at line %d", ER_EMPTY_QUERY: "Query was empty", ER_NONUNIQ_TABLE: "Not unique table/alias: '%-.192s'", ER_INVALID_DEFAULT: "Invalid default value for '%-.192s'", ER_MULTIPLE_PRI_KEY: "Multiple primary key defined", ER_TOO_MANY_KEYS: "Too many keys specified; max %d keys allowed", ER_TOO_MANY_KEY_PARTS: "Too many key parts specified; max %d parts allowed", ER_TOO_LONG_KEY: "Specified key was too long; max key length is %d bytes", ER_KEY_COLUMN_DOES_NOT_EXITS: "Key column '%-.192s' doesn't exist in table", ER_BLOB_USED_AS_KEY: "BLOB column '%-.192s' can't be used in key specification with the used table type", ER_TOO_BIG_FIELDLENGTH: "Column length too big for column '%-.192s' (max = %d); use BLOB or TEXT instead", ER_WRONG_AUTO_KEY: "Incorrect table definition; there can be only one auto column and it must be defined as a key", ER_READY: "%s: ready for connections.\nVersion: '%s' socket: '%s' port: %d", ER_NORMAL_SHUTDOWN: "%s: Normal shutdown\n", ER_GOT_SIGNAL: "%s: Got signal %d. Aborting!\n", ER_SHUTDOWN_COMPLETE: "%s: Shutdown complete\n", ER_FORCING_CLOSE: "%s: Forcing close of thread %d user: '%-.48s'\n", ER_IPSOCK_ERROR: "Can't create IP socket", ER_NO_SUCH_INDEX: "Table '%-.192s' has no index like the one used in CREATE INDEX; recreate the table", ER_WRONG_FIELD_TERMINATORS: "Field separator argument is not what is expected; check the manual", ER_BLOBS_AND_NO_TERMINATED: "You can't use fixed rowlength with BLOBs; please use 'fields terminated by'", ER_TEXTFILE_NOT_READABLE: "The file '%-.128s' must be in the database directory or be readable by all", ER_FILE_EXISTS_ERROR: "File '%-.200s' already exists", ER_LOAD_INFO: "Records: %d Deleted: %d Skipped: %d Warnings: %d", ER_ALTER_INFO: "Records: %d Duplicates: %d", ER_WRONG_SUB_KEY: "Incorrect prefix key; the used key part isn't a string, the used length is longer than the key part, or the storage engine doesn't support unique prefix keys", ER_CANT_REMOVE_ALL_FIELDS: "You can't delete all columns with ALTER TABLE; use DROP TABLE instead", ER_CANT_DROP_FIELD_OR_KEY: "Can't DROP '%-.192s'; check that column/key exists", ER_INSERT_INFO: "Records: %d Duplicates: %d Warnings: %d", ER_UPDATE_TABLE_USED: "You can't specify target table '%-.192s' for update in FROM clause", ER_NO_SUCH_THREAD: "Unknown thread id: %d", ER_KILL_DENIED_ERROR: "You are not owner of thread %d", ER_NO_TABLES_USED: "No tables used", ER_TOO_BIG_SET: "Too many strings for column %-.192s and SET", ER_NO_UNIQUE_LOGFILE: "Can't generate a unique log-filename %-.200s.(1-999)\n", ER_TABLE_NOT_LOCKED_FOR_WRITE: "Table '%-.192s' was locked with a READ lock and can't be updated", ER_TABLE_NOT_LOCKED: "Table '%-.192s' was not locked with LOCK TABLES", ER_BLOB_CANT_HAVE_DEFAULT: "BLOB/TEXT column '%-.192s' can't have a default value", ER_WRONG_DB_NAME: "Incorrect database name '%-.100s'", ER_WRONG_TABLE_NAME: "Incorrect table name '%-.100s'", ER_TOO_BIG_SELECT: "The SELECT would examine more than MAX_JOIN_SIZE rows; check your WHERE and use SET SQL_BIG_SELECTS=1 or SET MAX_JOIN_SIZE=# if the SELECT is okay", ER_UNKNOWN_ERROR: "Unknown error", ER_UNKNOWN_PROCEDURE: "Unknown procedure '%-.192s'", ER_WRONG_PARAMCOUNT_TO_PROCEDURE: "Incorrect parameter count to procedure '%-.192s'", ER_WRONG_PARAMETERS_TO_PROCEDURE: "Incorrect parameters to procedure '%-.192s'", ER_UNKNOWN_TABLE: "Unknown table '%-.192s' in %-.32s", ER_FIELD_SPECIFIED_TWICE: "Column '%-.192s' specified twice", ER_INVALID_GROUP_FUNC_USE: "Invalid use of group function", ER_UNSUPPORTED_EXTENSION: "Table '%-.192s' uses an extension that doesn't exist in this MySQL version", ER_TABLE_MUST_HAVE_COLUMNS: "A table must have at least 1 column", ER_RECORD_FILE_FULL: "The table '%-.192s' is full", ER_UNKNOWN_CHARACTER_SET: "Unknown character set: '%-.64s'", ER_TOO_MANY_TABLES: "Too many tables; MySQL can only use %d tables in a join", ER_TOO_MANY_FIELDS: "Too many columns", ER_TOO_BIG_ROWSIZE: "Row size too large. The maximum row size for the used table type, not counting BLOBs, is %d. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs", ER_STACK_OVERRUN: "Thread stack overrun: Used: %d of a %d stack. Use 'mysqld --thread_stack=#' to specify a bigger stack if needed", ER_WRONG_OUTER_JOIN: "Cross dependency found in OUTER JOIN; examine your ON conditions", ER_NULL_COLUMN_IN_INDEX: "Table handler doesn't support NULL in given index. Please change column '%-.192s' to be NOT NULL or use another handler", ER_CANT_FIND_UDF: "Can't load function '%-.192s'", ER_CANT_INITIALIZE_UDF: "Can't initialize function '%-.192s'; %-.80s", ER_UDF_NO_PATHS: "No paths allowed for shared library", ER_UDF_EXISTS: "Function '%-.192s' already exists", ER_CANT_OPEN_LIBRARY: "Can't open shared library '%-.192s' (errno: %d %-.128s)", ER_CANT_FIND_DL_ENTRY: "Can't find symbol '%-.128s' in library", ER_FUNCTION_NOT_DEFINED: "Function '%-.192s' is not defined", ER_HOST_IS_BLOCKED: "Host '%-.64s' is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts'", ER_HOST_NOT_PRIVILEGED: "Host '%-.64s' is not allowed to connect to this MySQL server", ER_PASSWORD_ANONYMOUS_USER: "You are using MySQL as an anonymous user and anonymous users are not allowed to change passwords", ER_PASSWORD_NOT_ALLOWED: "You must have privileges to update tables in the mysql database to be able to change passwords for others", ER_PASSWORD_NO_MATCH: "Can't find any matching row in the user table", ER_UPDATE_INFO: "Rows matched: %d Changed: %d Warnings: %d", ER_CANT_CREATE_THREAD: "Can't create a new thread (errno %d); if you are not out of available memory, you can consult the manual for a possible OS-dependent bug", ER_WRONG_VALUE_COUNT_ON_ROW: "Column count doesn't match value count at row %d", ER_CANT_REOPEN_TABLE: "Can't reopen table: '%-.192s'", ER_INVALID_USE_OF_NULL: "Invalid use of NULL value", ER_REGEXP_ERROR: "Got error '%-.64s' from regexp", ER_MIX_OF_GROUP_FUNC_AND_FIELDS: "Mixing of GROUP columns (MIN(),MAX(),COUNT(),...) with no GROUP columns is illegal if there is no GROUP BY clause", ER_NONEXISTING_GRANT: "There is no such grant defined for user '%-.48s' on host '%-.64s'", ER_TABLEACCESS_DENIED_ERROR: "%-.128s command denied to user '%-.48s'@'%-.64s' for table '%-.64s'", ER_COLUMNACCESS_DENIED_ERROR: "%-.16s command denied to user '%-.48s'@'%-.64s' for column '%-.192s' in table '%-.192s'", ER_ILLEGAL_GRANT_FOR_TABLE: "Illegal GRANT/REVOKE command; please consult the manual to see which privileges can be used", ER_GRANT_WRONG_HOST_OR_USER: "The host or user argument to GRANT is too long", ER_NO_SUCH_TABLE: "Table '%-.192s.%-.192s' doesn't exist", ER_NONEXISTING_TABLE_GRANT: "There is no such grant defined for user '%-.48s' on host '%-.64s' on table '%-.192s'", ER_NOT_ALLOWED_COMMAND: "The used command is not allowed with this MySQL version", ER_SYNTAX_ERROR: "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use", ER_DELAYED_CANT_CHANGE_LOCK: "Delayed insert thread couldn't get requested lock for table %-.192s", ER_TOO_MANY_DELAYED_THREADS: "Too many delayed threads in use", ER_ABORTING_CONNECTION: "Aborted connection %d to db: '%-.192s' user: '%-.48s' (%-.64s)", ER_NET_PACKET_TOO_LARGE: "Got a packet bigger than 'max_allowed_packet' bytes", ER_NET_READ_ERROR_FROM_PIPE: "Got a read error from the connection pipe", ER_NET_FCNTL_ERROR: "Got an error from fcntl()", ER_NET_PACKETS_OUT_OF_ORDER: "Got packets out of order", ER_NET_UNCOMPRESS_ERROR: "Couldn't uncompress communication packet", ER_NET_READ_ERROR: "Got an error reading communication packets", ER_NET_READ_INTERRUPTED: "Got timeout reading communication packets", ER_NET_ERROR_ON_WRITE: "Got an error writing communication packets", ER_NET_WRITE_INTERRUPTED: "Got timeout writing communication packets", ER_TOO_LONG_STRING: "Result string is longer than 'max_allowed_packet' bytes", ER_TABLE_CANT_HANDLE_BLOB: "The used table type doesn't support BLOB/TEXT columns", ER_TABLE_CANT_HANDLE_AUTO_INCREMENT: "The used table type doesn't support AUTO_INCREMENT columns", ER_DELAYED_INSERT_TABLE_LOCKED: "INSERT DELAYED can't be used with table '%-.192s' because it is locked with LOCK TABLES", ER_WRONG_COLUMN_NAME: "Incorrect column name '%-.100s'", ER_WRONG_KEY_COLUMN: "The used storage engine can't index column '%-.192s'", ER_WRONG_MRG_TABLE: "Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist", ER_DUP_UNIQUE: "Can't write, because of unique constraint, to table '%-.192s'", ER_BLOB_KEY_WITHOUT_LENGTH: "BLOB/TEXT column '%-.192s' used in key specification without a key length", ER_PRIMARY_CANT_HAVE_NULL: "All parts of a PRIMARY KEY must be NOT NULL; if you need NULL in a key, use UNIQUE instead", ER_TOO_MANY_ROWS: "Result consisted of more than one row", ER_REQUIRES_PRIMARY_KEY: "This table type requires a primary key", ER_NO_RAID_COMPILED: "This version of MySQL is not compiled with RAID support", ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE: "You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column", ER_KEY_DOES_NOT_EXITS: "Key '%-.192s' doesn't exist in table '%-.192s'", ER_CHECK_NO_SUCH_TABLE: "Can't open table", ER_CHECK_NOT_IMPLEMENTED: "The storage engine for the table doesn't support %s", ER_CANT_DO_THIS_DURING_AN_TRANSACTION: "You are not allowed to execute this command in a transaction", ER_ERROR_DURING_COMMIT: "Got error %d during COMMIT", ER_ERROR_DURING_ROLLBACK: "Got error %d during ROLLBACK", ER_ERROR_DURING_FLUSH_LOGS: "Got error %d during FLUSH_LOGS", ER_ERROR_DURING_CHECKPOINT: "Got error %d during CHECKPOINT", ER_NEW_ABORTING_CONNECTION: "Aborted connection %d to db: '%-.192s' user: '%-.48s' host: '%-.64s' (%-.64s)", ER_DUMP_NOT_IMPLEMENTED: "The storage engine for the table does not support binary table dump", ER_FLUSH_MASTER_BINLOG_CLOSED: "Binlog closed, cannot RESET MASTER", ER_INDEX_REBUILD: "Failed rebuilding the index of dumped table '%-.192s'", ER_MASTER: "Error from master: '%-.64s'", ER_MASTER_NET_READ: "Net error reading from master", ER_MASTER_NET_WRITE: "Net error writing to master", ER_FT_MATCHING_KEY_NOT_FOUND: "Can't find FULLTEXT index matching the column list", ER_LOCK_OR_ACTIVE_TRANSACTION: "Can't execute the given command because you have active locked tables or an active transaction", ER_UNKNOWN_SYSTEM_VARIABLE: "Unknown system variable '%-.64s'", ER_CRASHED_ON_USAGE: "Table '%-.192s' is marked as crashed and should be repaired", ER_CRASHED_ON_REPAIR: "Table '%-.192s' is marked as crashed and last (automatic?) repair failed", ER_WARNING_NOT_COMPLETE_ROLLBACK: "Some non-transactional changed tables couldn't be rolled back", ER_TRANS_CACHE_FULL: "Multi-statement transaction required more than 'max_binlog_cache_size' bytes of storage; increase this mysqld variable and try again", ER_SLAVE_MUST_STOP: "This operation cannot be performed with a running slave; run STOP SLAVE first", ER_SLAVE_NOT_RUNNING: "This operation requires a running slave; configure slave and do START SLAVE", ER_BAD_SLAVE: "The server is not configured as slave; fix in config file or with CHANGE MASTER TO", ER_MASTER_INFO: "Could not initialize master info structure; more error messages can be found in the MySQL error log", ER_SLAVE_THREAD: "Could not create slave thread; check system resources", ER_TOO_MANY_USER_CONNECTIONS: "User %-.64s already has more than 'max_user_connections' active connections", ER_SET_CONSTANTS_ONLY: "You may only use constant expressions with SET", ER_LOCK_WAIT_TIMEOUT: "Lock wait timeout exceeded; try restarting transaction", ER_LOCK_TABLE_FULL: "The total number of locks exceeds the lock table size", ER_READ_ONLY_TRANSACTION: "Update locks cannot be acquired during a READ UNCOMMITTED transaction", ER_DROP_DB_WITH_READ_LOCK: "DROP DATABASE not allowed while thread is holding global read lock", ER_CREATE_DB_WITH_READ_LOCK: "CREATE DATABASE not allowed while thread is holding global read lock", ER_WRONG_ARGUMENTS: "Incorrect arguments to %s", ER_NO_PERMISSION_TO_CREATE_USER: "'%-.48s'@'%-.64s' is not allowed to create new users", ER_UNION_TABLES_IN_DIFFERENT_DIR: "Incorrect table definition; all MERGE tables must be in the same database", ER_LOCK_DEADLOCK: "Deadlock found when trying to get lock; try restarting transaction", ER_TABLE_CANT_HANDLE_FT: "The used table type doesn't support FULLTEXT indexes", ER_CANNOT_ADD_FOREIGN: "Cannot add foreign key constraint", ER_NO_REFERENCED_ROW: "Cannot add or update a child row: a foreign key constraint fails", ER_ROW_IS_REFERENCED: "Cannot delete or update a parent row: a foreign key constraint fails", ER_CONNECT_TO_MASTER: "Error connecting to master: %-.128s", ER_QUERY_ON_MASTER: "Error running query on master: %-.128s", ER_ERROR_WHEN_EXECUTING_COMMAND: "Error when executing command %s: %-.128s", ER_WRONG_USAGE: "Incorrect usage of %s and %s", ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT: "The used SELECT statements have a different number of columns", ER_CANT_UPDATE_WITH_READLOCK: "Can't execute the query because you have a conflicting read lock", ER_MIXING_NOT_ALLOWED: "Mixing of transactional and non-transactional tables is disabled", ER_DUP_ARGUMENT: "Option '%s' used twice in statement", ER_USER_LIMIT_REACHED: "User '%-.64s' has exceeded the '%s' resource (current value: %d)", ER_SPECIFIC_ACCESS_DENIED_ERROR: "Access denied; you need (at least one of) the %-.128s privilege(s) for this operation", ER_LOCAL_VARIABLE: "Variable '%-.64s' is a SESSION variable and can't be used with SET GLOBAL", ER_GLOBAL_VARIABLE: "Variable '%-.64s' is a GLOBAL variable and should be set with SET GLOBAL", ER_NO_DEFAULT: "Variable '%-.64s' doesn't have a default value", ER_WRONG_VALUE_FOR_VAR: "Variable '%-.64s' can't be set to the value of '%-.200s'", ER_WRONG_TYPE_FOR_VAR: "Incorrect argument type to variable '%-.64s'", ER_VAR_CANT_BE_READ: "Variable '%-.64s' can only be set, not read", ER_CANT_USE_OPTION_HERE: "Incorrect usage/placement of '%s'", ER_NOT_SUPPORTED_YET: "This version of MySQL doesn't yet support '%s'", ER_MASTER_FATAL_ERROR_READING_BINLOG: "Got fatal error %d from master when reading data from binary log: '%-.320s'", ER_SLAVE_IGNORED_TABLE: "Slave SQL thread ignored the query because of replicate-*-table rules", ER_INCORRECT_GLOBAL_LOCAL_VAR: "Variable '%-.192s' is a %s variable", ER_WRONG_FK_DEF: "Incorrect foreign key definition for '%-.192s': %s", ER_KEY_REF_DO_NOT_MATCH_TABLE_REF: "Key reference and table reference don't match", ER_OPERAND_COLUMNS: "Operand should contain %d column(s)", ER_SUBQUERY_NO_1_ROW: "Subquery returns more than 1 row", ER_UNKNOWN_STMT_HANDLER: "Unknown prepared statement handler (%.*s) given to %s", ER_CORRUPT_HELP_DB: "Help database is corrupt or does not exist", ER_CYCLIC_REFERENCE: "Cyclic reference on subqueries", ER_AUTO_CONVERT: "Converting column '%s' from %s to %s", ER_ILLEGAL_REFERENCE: "Reference '%-.64s' not supported (%s)", ER_DERIVED_MUST_HAVE_ALIAS: "Every derived table must have its own alias", ER_SELECT_REDUCED: "Select %d was reduced during optimization", ER_TABLENAME_NOT_ALLOWED_HERE: "Table '%-.192s' from one of the SELECTs cannot be used in %-.32s", ER_NOT_SUPPORTED_AUTH_MODE: "Client does not support authentication protocol requested by server; consider upgrading MySQL client", ER_SPATIAL_CANT_HAVE_NULL: "All parts of a SPATIAL index must be NOT NULL", ER_COLLATION_CHARSET_MISMATCH: "COLLATION '%s' is not valid for CHARACTER SET '%s'", ER_SLAVE_WAS_RUNNING: "Slave is already running", ER_SLAVE_WAS_NOT_RUNNING: "Slave already has been stopped", ER_TOO_BIG_FOR_UNCOMPRESS: "Uncompressed data size too large; the maximum size is %d (probably, length of uncompressed data was corrupted)", ER_ZLIB_Z_MEM_ERROR: "ZLIB: Not enough memory", ER_ZLIB_Z_BUF_ERROR: "ZLIB: Not enough room in the output buffer (probably, length of uncompressed data was corrupted)", ER_ZLIB_Z_DATA_ERROR: "ZLIB: Input data corrupted", ER_CUT_VALUE_GROUP_CONCAT: "Row %d was cut by GROUP_CONCAT()", ER_WARN_TOO_FEW_RECORDS: "Row %d doesn't contain data for all columns", ER_WARN_TOO_MANY_RECORDS: "Row %d was truncated; it contained more data than there were input columns", ER_WARN_NULL_TO_NOTNULL: "Column set to default value; NULL supplied to NOT NULL column '%s' at row %d", ER_WARN_DATA_OUT_OF_RANGE: "Out of range value for column '%s' at row %d", WARN_DATA_TRUNCATED: "Data truncated for column '%s' at row %d", ER_WARN_USING_OTHER_HANDLER: "Using storage engine %s for table '%s'", ER_CANT_AGGREGATE_2COLLATIONS: "Illegal mix of collations (%s,%s) and (%s,%s) for operation '%s'", ER_DROP_USER: "Cannot drop one or more of the requested users", ER_REVOKE_GRANTS: "Can't revoke all privileges for one or more of the requested users", ER_CANT_AGGREGATE_3COLLATIONS: "Illegal mix of collations (%s,%s), (%s,%s), (%s,%s) for operation '%s'", ER_CANT_AGGREGATE_NCOLLATIONS: "Illegal mix of collations for operation '%s'", ER_VARIABLE_IS_NOT_STRUCT: "Variable '%-.64s' is not a variable component (can't be used as XXXX.variable_name)", ER_UNKNOWN_COLLATION: "Unknown collation: '%-.64s'", ER_SLAVE_IGNORED_SSL_PARAMS: "SSL parameters in CHANGE MASTER are ignored because this MySQL slave was compiled without SSL support; they can be used later if MySQL slave with SSL is started", ER_SERVER_IS_IN_SECURE_AUTH_MODE: "Server is running in --secure-auth mode, but '%s'@'%s' has a password in the old format; please change the password to the new format", ER_WARN_FIELD_RESOLVED: "Field or reference '%-.192s%s%-.192s%s%-.192s' of SELECT #%d was resolved in SELECT #%d", ER_BAD_SLAVE_UNTIL_COND: "Incorrect parameter or combination of parameters for START SLAVE UNTIL", ER_MISSING_SKIP_SLAVE: "It is recommended to use --skip-slave-start when doing step-by-step replication with START SLAVE UNTIL; otherwise, you will get problems if you get an unexpected slave's mysqld restart", ER_UNTIL_COND_IGNORED: "SQL thread is not to be started so UNTIL options are ignored", ER_WRONG_NAME_FOR_INDEX: "Incorrect index name '%-.100s'", ER_WRONG_NAME_FOR_CATALOG: "Incorrect catalog name '%-.100s'", ER_WARN_QC_RESIZE: "Query cache failed to set size %d; new query cache size is %d", ER_BAD_FT_COLUMN: "Column '%-.192s' cannot be part of FULLTEXT index", ER_UNKNOWN_KEY_CACHE: "Unknown key cache '%-.100s'", ER_WARN_HOSTNAME_WONT_WORK: "MySQL is started in --skip-name-resolve mode; you must restart it without this switch for this grant to work", ER_UNKNOWN_STORAGE_ENGINE: "Unknown storage engine '%s'", ER_WARN_DEPRECATED_SYNTAX: "'%s' is deprecated and will be removed in a future release. Please use %s instead", ER_NON_UPDATABLE_TABLE: "The target table %-.100s of the %s is not updatable", ER_FEATURE_DISABLED: "The '%s' feature is disabled; you need MySQL built with '%s' to have it working", ER_OPTION_PREVENTS_STATEMENT: "The MySQL server is running with the %s option so it cannot execute this statement", ER_DUPLICATED_VALUE_IN_TYPE: "Column '%-.100s' has duplicated value '%-.64s' in %s", ER_TRUNCATED_WRONG_VALUE: "Truncated incorrect %-.32s value: '%-.128s'", ER_TOO_MUCH_AUTO_TIMESTAMP_COLS: "Incorrect table definition; there can be only one TIMESTAMP column with CURRENT_TIMESTAMP in DEFAULT or ON UPDATE clause", ER_INVALID_ON_UPDATE: "Invalid ON UPDATE clause for '%-.192s' column", ER_UNSUPPORTED_PS: "This command is not supported in the prepared statement protocol yet", ER_GET_ERRMSG: "Got error %d '%-.100s' from %s", ER_GET_TEMPORARY_ERRMSG: "Got temporary error %d '%-.100s' from %s", ER_UNKNOWN_TIME_ZONE: "Unknown or incorrect time zone: '%-.64s'", ER_WARN_INVALID_TIMESTAMP: "Invalid TIMESTAMP value in column '%s' at row %d", ER_INVALID_CHARACTER_STRING: "Invalid %s character string: '%.64s'", ER_WARN_ALLOWED_PACKET_OVERFLOWED: "Result of %s() was larger than max_allowed_packet (%d) - truncated", ER_CONFLICTING_DECLARATIONS: "Conflicting declarations: '%s%s' and '%s%s'", ER_SP_NO_RECURSIVE_CREATE: "Can't create a %s from within another stored routine", ER_SP_ALREADY_EXISTS: "%s %s already exists", ER_SP_DOES_NOT_EXIST: "%s %s does not exist", ER_SP_DROP_FAILED: "Failed to DROP %s %s", ER_SP_STORE_FAILED: "Failed to CREATE %s %s", ER_SP_LILABEL_MISMATCH: "%s with no matching label: %s", ER_SP_LABEL_REDEFINE: "Redefining label %s", ER_SP_LABEL_MISMATCH: "End-label %s without match", ER_SP_UNINIT_VAR: "Referring to uninitialized variable %s", ER_SP_BADSELECT: "PROCEDURE %s can't return a result set in the given context", ER_SP_BADRETURN: "RETURN is only allowed in a FUNCTION", ER_SP_BADSTATEMENT: "%s is not allowed in stored procedures", ER_UPDATE_LOG_DEPRECATED_IGNORED: "The update log is deprecated and replaced by the binary log; SET SQL_LOG_UPDATE has been ignored.", ER_UPDATE_LOG_DEPRECATED_TRANSLATED: "The update log is deprecated and replaced by the binary log; SET SQL_LOG_UPDATE has been translated to SET SQL_LOG_BIN.", ER_QUERY_INTERRUPTED: "Query execution was interrupted", ER_SP_WRONG_NO_OF_ARGS: "Incorrect number of arguments for %s %s; expected %d, got %d", ER_SP_COND_MISMATCH: "Undefined CONDITION: %s", ER_SP_NORETURN: "No RETURN found in FUNCTION %s", ER_SP_NORETURNEND: "FUNCTION %s ended without RETURN", ER_SP_BAD_CURSOR_QUERY: "Cursor statement must be a SELECT", ER_SP_BAD_CURSOR_SELECT: "Cursor SELECT must not have INTO", ER_SP_CURSOR_MISMATCH: "Undefined CURSOR: %s", ER_SP_CURSOR_ALREADY_OPEN: "Cursor is already open", ER_SP_CURSOR_NOT_OPEN: "Cursor is not open", ER_SP_UNDECLARED_VAR: "Undeclared variable: %s", ER_SP_WRONG_NO_OF_FETCH_ARGS: "Incorrect number of FETCH variables", ER_SP_FETCH_NO_DATA: "No data - zero rows fetched, selected, or processed", ER_SP_DUP_PARAM: "Duplicate parameter: %s", ER_SP_DUP_VAR: "Duplicate variable: %s", ER_SP_DUP_COND: "Duplicate condition: %s", ER_SP_DUP_CURS: "Duplicate cursor: %s", ER_SP_CANT_ALTER: "Failed to ALTER %s %s", ER_SP_SUBSELECT_NYI: "Subquery value not supported", ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG: "%s is not allowed in stored function or trigger", ER_SP_VARCOND_AFTER_CURSHNDLR: "Variable or condition declaration after cursor or handler declaration", ER_SP_CURSOR_AFTER_HANDLER: "Cursor declaration after handler declaration", ER_SP_CASE_NOT_FOUND: "Case not found for CASE statement", ER_FPARSER_TOO_BIG_FILE: "Configuration file '%-.192s' is too big", ER_FPARSER_BAD_HEADER: "Malformed file type header in file '%-.192s'", ER_FPARSER_EOF_IN_COMMENT: "Unexpected end of file while parsing comment '%-.200s'", ER_FPARSER_ERROR_IN_PARAMETER: "Error while parsing parameter '%-.192s' (line: '%-.192s')", ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER: "Unexpected end of file while skipping unknown parameter '%-.192s'", ER_VIEW_NO_EXPLAIN: "EXPLAIN/SHOW can not be issued; lacking privileges for underlying table", ER_FRM_UNKNOWN_TYPE: "File '%-.192s' has unknown type '%-.64s' in its header", ER_WRONG_OBJECT: "'%-.192s.%-.192s' is not %s", ER_NONUPDATEABLE_COLUMN: "Column '%-.192s' is not updatable", ER_VIEW_SELECT_DERIVED: "View's SELECT contains a subquery in the FROM clause", ER_VIEW_SELECT_CLAUSE: "View's SELECT contains a '%s' clause", ER_VIEW_SELECT_VARIABLE: "View's SELECT contains a variable or parameter", ER_VIEW_SELECT_TMPTABLE: "View's SELECT refers to a temporary table '%-.192s'", ER_VIEW_WRONG_LIST: "View's SELECT and view's field list have different column counts", ER_WARN_VIEW_MERGE: "View merge algorithm can't be used here for now (assumed undefined algorithm)", ER_WARN_VIEW_WITHOUT_KEY: "View being updated does not have complete key of underlying table in it", ER_VIEW_INVALID: "View '%-.192s.%-.192s' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them", ER_SP_NO_DROP_SP: "Can't drop or alter a %s from within another stored routine", ER_SP_GOTO_IN_HNDLR: "GOTO is not allowed in a stored procedure handler", ER_TRG_ALREADY_EXISTS: "Trigger already exists", ER_TRG_DOES_NOT_EXIST: "Trigger does not exist", ER_TRG_ON_VIEW_OR_TEMP_TABLE: "Trigger's '%-.192s' is view or temporary table", ER_TRG_CANT_CHANGE_ROW: "Updating of %s row is not allowed in %strigger", ER_TRG_NO_SUCH_ROW_IN_TRG: "There is no %s row in %s trigger", ER_NO_DEFAULT_FOR_FIELD: "Field '%-.192s' doesn't have a default value", ER_DIVISION_BY_ZERO: "Division by 0", ER_TRUNCATED_WRONG_VALUE_FOR_FIELD: "Incorrect %-.32s value: '%-.128s' for column '%.192s' at row %d", ER_ILLEGAL_VALUE_FOR_TYPE: "Illegal %s '%-.192s' value found during parsing", ER_VIEW_NONUPD_CHECK: "CHECK OPTION on non-updatable view '%-.192s.%-.192s'", ER_VIEW_CHECK_FAILED: "CHECK OPTION failed '%-.192s.%-.192s'", ER_PROCACCESS_DENIED_ERROR: "%-.16s command denied to user '%-.48s'@'%-.64s' for routine '%-.192s'", ER_RELAY_LOG_FAIL: "Failed purging old relay logs: %s", ER_PASSWD_LENGTH: "Password hash should be a %d-digit hexadecimal number", ER_UNKNOWN_TARGET_BINLOG: "Target log not found in binlog index", ER_IO_ERR_LOG_INDEX_READ: "I/O error reading log index file", ER_BINLOG_PURGE_PROHIBITED: "Server configuration does not permit binlog purge", ER_FSEEK_FAIL: "Failed on fseek()", ER_BINLOG_PURGE_FATAL_ERR: "Fatal error during log purge", ER_LOG_IN_USE: "A purgeable log is in use, will not purge", ER_LOG_PURGE_UNKNOWN_ERR: "Unknown error during log purge", ER_RELAY_LOG_INIT: "Failed initializing relay log position: %s", ER_NO_BINARY_LOGGING: "You are not using binary logging", ER_RESERVED_SYNTAX: "The '%-.64s' syntax is reserved for purposes internal to the MySQL server", ER_WSAS_FAILED: "WSAStartup Failed", ER_DIFF_GROUPS_PROC: "Can't handle procedures with different groups yet", ER_NO_GROUP_FOR_PROC: "Select must have a group with this procedure", ER_ORDER_WITH_PROC: "Can't use ORDER clause with this procedure", ER_LOGGING_PROHIBIT_CHANGING_OF: "Binary logging and replication forbid changing the global server %s", ER_NO_FILE_MAPPING: "Can't map file: %-.200s, errno: %d", ER_WRONG_MAGIC: "Wrong magic in %-.64s", ER_PS_MANY_PARAM: "Prepared statement contains too many placeholders", ER_KEY_PART_0: "Key part '%-.192s' length cannot be 0", ER_VIEW_CHECKSUM: "View text checksum failed", ER_VIEW_MULTIUPDATE: "Can not modify more than one base table through a join view '%-.192s.%-.192s'", ER_VIEW_NO_INSERT_FIELD_LIST: "Can not insert into join view '%-.192s.%-.192s' without fields list", ER_VIEW_DELETE_MERGE_VIEW: "Can not delete from join view '%-.192s.%-.192s'", ER_CANNOT_USER: "Operation %s failed for %.256s", ER_XAER_NOTA: "XAER_NOTA: Unknown XID", ER_XAER_INVAL: "XAER_INVAL: Invalid arguments (or unsupported command)", ER_XAER_RMFAIL: "XAER_RMFAIL: The command cannot be executed when global transaction is in the %.64s state", ER_XAER_OUTSIDE: "XAER_OUTSIDE: Some work is done outside global transaction", ER_XAER_RMERR: "XAER_RMERR: Fatal error occurred in the transaction branch - check your data for consistency", ER_XA_RBROLLBACK: "XA_RBROLLBACK: Transaction branch was rolled back", ER_NONEXISTING_PROC_GRANT: "There is no such grant defined for user '%-.48s' on host '%-.64s' on routine '%-.192s'", ER_PROC_AUTO_GRANT_FAIL: "Failed to grant EXECUTE and ALTER ROUTINE privileges", ER_PROC_AUTO_REVOKE_FAIL: "Failed to revoke all privileges to dropped routine", ER_DATA_TOO_LONG: "Data too long for column '%s' at row %d", ER_SP_BAD_SQLSTATE: "Bad SQLSTATE: '%s'", ER_STARTUP: "%s: ready for connections.\nVersion: '%s' socket: '%s' port: %d %s", ER_LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR: "Can't load value from file with fixed size rows to variable", ER_CANT_CREATE_USER_WITH_GRANT: "You are not allowed to create a user with GRANT", ER_WRONG_VALUE_FOR_TYPE: "Incorrect %-.32s value: '%-.128s' for function %-.32s", ER_TABLE_DEF_CHANGED: "Table definition has changed, please retry transaction", ER_SP_DUP_HANDLER: "Duplicate handler declared in the same block", ER_SP_NOT_VAR_ARG: "OUT or INOUT argument %d for routine %s is not a variable or NEW pseudo-variable in BEFORE trigger", ER_SP_NO_RETSET: "Not allowed to return a result set from a %s", ER_CANT_CREATE_GEOMETRY_OBJECT: "Cannot get geometry object from data you send to the GEOMETRY field", ER_FAILED_ROUTINE_BREAK_BINLOG: "A routine failed and has neither NO SQL nor READS SQL DATA in its declaration and binary logging is enabled; if non-transactional tables were updated, the binary log will miss their changes", ER_BINLOG_UNSAFE_ROUTINE: "This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)", ER_BINLOG_CREATE_ROUTINE_NEED_SUPER: "You do not have the SUPER privilege and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)", ER_EXEC_STMT_WITH_OPEN_CURSOR: "You can't execute a prepared statement which has an open cursor associated with it. Reset the statement to re-execute it.", ER_STMT_HAS_NO_OPEN_CURSOR: "The statement (%d) has no open cursor.", ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG: "Explicit or implicit commit is not allowed in stored function or trigger.", ER_NO_DEFAULT_FOR_VIEW_FIELD: "Field of view '%-.192s.%-.192s' underlying table doesn't have a default value", ER_SP_NO_RECURSION: "Recursive stored functions and triggers are not allowed.", ER_TOO_BIG_SCALE: "Too big scale %d specified for column '%-.192s'. Maximum is %d.", ER_TOO_BIG_PRECISION: "Too big precision %d specified for column '%-.192s'. Maximum is %d.", ER_M_BIGGER_THAN_D: "For float(M,D), double(M,D) or decimal(M,D), M must be >= D (column '%-.192s').", ER_WRONG_LOCK_OF_SYSTEM_TABLE: "You can't combine write-locking of system tables with other tables or lock types", ER_CONNECT_TO_FOREIGN_DATA_SOURCE: "Unable to connect to foreign data source: %.64s", ER_QUERY_ON_FOREIGN_DATA_SOURCE: "There was a problem processing the query on the foreign data source. Data source error: %-.64s", ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST: "The foreign data source you are trying to reference does not exist. Data source error: %-.64s", ER_FOREIGN_DATA_STRING_INVALID_CANT_CREATE: "Can't create federated table. The data source connection string '%-.64s' is not in the correct format", ER_FOREIGN_DATA_STRING_INVALID: "The data source connection string '%-.64s' is not in the correct format", ER_CANT_CREATE_FEDERATED_TABLE: "Can't create federated table. Foreign data src error: %-.64s", ER_TRG_IN_WRONG_SCHEMA: "Trigger in wrong schema", ER_STACK_OVERRUN_NEED_MORE: "Thread stack overrun: %d bytes used of a %d byte stack, and %d bytes needed. Use 'mysqld --thread_stack=#' to specify a bigger stack.", ER_TOO_LONG_BODY: "Routine body for '%-.100s' is too long", ER_WARN_CANT_DROP_DEFAULT_KEYCACHE: "Cannot drop default keycache", ER_TOO_BIG_DISPLAYWIDTH: "Display width out of range for column '%-.192s' (max = %d)", ER_XAER_DUPID: "XAER_DUPID: The XID already exists", ER_DATETIME_FUNCTION_OVERFLOW: "Datetime function: %-.32s field overflow", ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG: "Can't update table '%-.192s' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.", ER_VIEW_PREVENT_UPDATE: "The definition of table '%-.192s' prevents operation %.192s on table '%-.192s'.", ER_PS_NO_RECURSION: "The prepared statement contains a stored routine call that refers to that same statement. It's not allowed to execute a prepared statement in such a recursive manner", ER_SP_CANT_SET_AUTOCOMMIT: "Not allowed to set autocommit from a stored function or trigger", ER_MALFORMED_DEFINER: "Definer is not fully qualified", ER_VIEW_FRM_NO_USER: "View '%-.192s'.'%-.192s' has no definer information (old table format). Current user is used as definer. Please recreate the view!", ER_VIEW_OTHER_USER: "You need the SUPER privilege for creation view with '%-.192s'@'%-.192s' definer", ER_NO_SUCH_USER: "The user specified as a definer ('%-.64s'@'%-.64s') does not exist", ER_FORBID_SCHEMA_CHANGE: "Changing schema from '%-.192s' to '%-.192s' is not allowed.", ER_ROW_IS_REFERENCED_2: "Cannot delete or update a parent row: a foreign key constraint fails (%.192s)", ER_NO_REFERENCED_ROW_2: "Cannot add or update a child row: a foreign key constraint fails (%.192s)", ER_SP_BAD_VAR_SHADOW: "Variable '%-.64s' must be quoted with `...`, or renamed", ER_TRG_NO_DEFINER: "No definer attribute for trigger '%-.192s'.'%-.192s'. The trigger will be activated under the authorization of the invoker, which may have insufficient privileges. Please recreate the trigger.", ER_OLD_FILE_FORMAT: "'%-.192s' has an old format, you should re-create the '%s' object(s)", ER_SP_RECURSION_LIMIT: "Recursive limit %d (as set by the max_sp_recursion_depth variable) was exceeded for routine %.192s", ER_SP_PROC_TABLE_CORRUPT: "Failed to load routine %-.192s. The table mysql.proc is missing, corrupt, or contains bad data (internal code %d)", ER_SP_WRONG_NAME: "Incorrect routine name '%-.192s'", ER_TABLE_NEEDS_UPGRADE: "Table upgrade required. Please do \"REPAIR TABLE `%-.32s`\" or dump/reload to fix it!", ER_SP_NO_AGGREGATE: "AGGREGATE is not supported for stored functions", ER_MAX_PREPARED_STMT_COUNT_REACHED: "Can't create more than max_prepared_stmt_count statements (current value: %d)", ER_VIEW_RECURSIVE: "`%-.192s`.`%-.192s` contains view recursion", ER_NON_GROUPING_FIELD_USED: "Non-grouping field '%-.192s' is used in %-.64s clause", ER_TABLE_CANT_HANDLE_SPKEYS: "The used table type doesn't support SPATIAL indexes", ER_NO_TRIGGERS_ON_SYSTEM_SCHEMA: "Triggers can not be created on system tables", ER_REMOVED_SPACES: "Leading spaces are removed from name '%s'", ER_AUTOINC_READ_FAILED: "Failed to read auto-increment value from storage engine", ER_USERNAME: "user name", ER_HOSTNAME: "host name", ER_WRONG_STRING_LENGTH: "String '%-.70s' is too long for %s (should be no longer than %d)", ER_NON_INSERTABLE_TABLE: "The target table %-.100s of the %s is not insertable-into", ER_ADMIN_WRONG_MRG_TABLE: "Table '%-.64s' is differently defined or of non-MyISAM type or doesn't exist", ER_TOO_HIGH_LEVEL_OF_NESTING_FOR_SELECT: "Too high level of nesting for select", ER_NAME_BECOMES_EMPTY: "Name '%-.64s' has become ''", ER_AMBIGUOUS_FIELD_TERM: "First character of the FIELDS TERMINATED string is ambiguous; please use non-optional and non-empty FIELDS ENCLOSED BY", ER_FOREIGN_SERVER_EXISTS: "The foreign server, %s, you are trying to create already exists.", ER_FOREIGN_SERVER_DOESNT_EXIST: "The foreign server name you are trying to reference does not exist. Data source error: %-.64s", ER_ILLEGAL_HA_CREATE_OPTION: "Table storage engine '%-.64s' does not support the create option '%.64s'", ER_PARTITION_REQUIRES_VALUES_ERROR: "Syntax error: %-.64s PARTITIONING requires definition of VALUES %-.64s for each partition", ER_PARTITION_WRONG_VALUES_ERROR: "Only %-.64s PARTITIONING can use VALUES %-.64s in partition definition", ER_PARTITION_MAXVALUE_ERROR: "MAXVALUE can only be used in last partition definition", ER_PARTITION_SUBPARTITION_ERROR: "Subpartitions can only be hash partitions and by key", ER_PARTITION_SUBPART_MIX_ERROR: "Must define subpartitions on all partitions if on one partition", ER_PARTITION_WRONG_NO_PART_ERROR: "Wrong number of partitions defined, mismatch with previous setting", ER_PARTITION_WRONG_NO_SUBPART_ERROR: "Wrong number of subpartitions defined, mismatch with previous setting", ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR: "Constant, random or timezone-dependent expressions in (sub)partitioning function are not allowed", ER_NO_CONST_EXPR_IN_RANGE_OR_LIST_ERROR: "Expression in RANGE/LIST VALUES must be constant", ER_FIELD_NOT_FOUND_PART_ERROR: "Field in list of fields for partition function not found in table", ER_LIST_OF_FIELDS_ONLY_IN_HASH_ERROR: "List of fields is only allowed in KEY partitions", ER_INCONSISTENT_PARTITION_INFO_ERROR: "The partition info in the frm file is not consistent with what can be written into the frm file", ER_PARTITION_FUNC_NOT_ALLOWED_ERROR: "The %-.192s function returns the wrong type", ER_PARTITIONS_MUST_BE_DEFINED_ERROR: "For %-.64s partitions each partition must be defined", ER_RANGE_NOT_INCREASING_ERROR: "VALUES LESS THAN value must be strictly increasing for each partition", ER_INCONSISTENT_TYPE_OF_FUNCTIONS_ERROR: "VALUES value must be of same type as partition function", ER_MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR: "Multiple definition of same constant in list partitioning", ER_PARTITION_ENTRY_ERROR: "Partitioning can not be used stand-alone in query", ER_MIX_HANDLER_ERROR: "The mix of handlers in the partitions is not allowed in this version of MySQL", ER_PARTITION_NOT_DEFINED_ERROR: "For the partitioned engine it is necessary to define all %-.64s", ER_TOO_MANY_PARTITIONS_ERROR: "Too many partitions (including subpartitions) were defined", ER_SUBPARTITION_ERROR: "It is only possible to mix RANGE/LIST partitioning with HASH/KEY partitioning for subpartitioning", ER_CANT_CREATE_HANDLER_FILE: "Failed to create specific handler file", ER_BLOB_FIELD_IN_PART_FUNC_ERROR: "A BLOB field is not allowed in partition function", ER_UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF: "A %-.192s must include all columns in the table's partitioning function", ER_NO_PARTS_ERROR: "Number of %-.64s = 0 is not an allowed value", ER_PARTITION_MGMT_ON_NONPARTITIONED: "Partition management on a not partitioned table is not possible", ER_FOREIGN_KEY_ON_PARTITIONED: "Foreign key clause is not yet supported in conjunction with partitioning", ER_DROP_PARTITION_NON_EXISTENT: "Error in list of partitions to %-.64s", ER_DROP_LAST_PARTITION: "Cannot remove all partitions, use DROP TABLE instead", ER_COALESCE_ONLY_ON_HASH_PARTITION: "COALESCE PARTITION can only be used on HASH/KEY partitions", ER_REORG_HASH_ONLY_ON_SAME_NO: "REORGANIZE PARTITION can only be used to reorganize partitions not to change their numbers", ER_REORG_NO_PARAM_ERROR: "REORGANIZE PARTITION without parameters can only be used on auto-partitioned tables using HASH PARTITIONs", ER_ONLY_ON_RANGE_LIST_PARTITION: "%-.64s PARTITION can only be used on RANGE/LIST partitions", ER_ADD_PARTITION_SUBPART_ERROR: "Trying to Add partition(s) with wrong number of subpartitions", ER_ADD_PARTITION_NO_NEW_PARTITION: "At least one partition must be added", ER_COALESCE_PARTITION_NO_PARTITION: "At least one partition must be coalesced", ER_REORG_PARTITION_NOT_EXIST: "More partitions to reorganize than there are partitions", ER_SAME_NAME_PARTITION: "Duplicate partition name %-.192s", ER_NO_BINLOG_ERROR: "It is not allowed to shut off binlog on this command", ER_CONSECUTIVE_REORG_PARTITIONS: "When reorganizing a set of partitions they must be in consecutive order", ER_REORG_OUTSIDE_RANGE: "Reorganize of range partitions cannot change total ranges except for last partition where it can extend the range", ER_PARTITION_FUNCTION_FAILURE: "Partition function not supported in this version for this handler", ER_PART_STATE_ERROR: "Partition state cannot be defined from CREATE/ALTER TABLE", ER_LIMITED_PART_RANGE: "The %-.64s handler only supports 32 bit integers in VALUES", ER_PLUGIN_IS_NOT_LOADED: "Plugin '%-.192s' is not loaded", ER_WRONG_VALUE: "Incorrect %-.32s value: '%-.128s'", ER_NO_PARTITION_FOR_GIVEN_VALUE: "Table has no partition for value %-.64s", ER_FILEGROUP_OPTION_ONLY_ONCE: "It is not allowed to specify %s more than once", ER_CREATE_FILEGROUP_FAILED: "Failed to create %s", ER_DROP_FILEGROUP_FAILED: "Failed to drop %s", ER_TABLESPACE_AUTO_EXTEND_ERROR: "The handler doesn't support autoextend of tablespaces", ER_WRONG_SIZE_NUMBER: "A size parameter was incorrectly specified, either number or on the form 10M", ER_SIZE_OVERFLOW_ERROR: "The size number was correct but we don't allow the digit part to be more than 2 billion", ER_ALTER_FILEGROUP_FAILED: "Failed to alter: %s", ER_BINLOG_ROW_LOGGING_FAILED: "Writing one row to the row-based binary log failed", ER_BINLOG_ROW_WRONG_TABLE_DEF: "Table definition on master and slave does not match: %s", ER_BINLOG_ROW_RBR_TO_SBR: "Slave running with --log-slave-updates must use row-based binary logging to be able to replicate row-based binary log events", ER_EVENT_ALREADY_EXISTS: "Event '%-.192s' already exists", ER_EVENT_STORE_FAILED: "Failed to store event %s. Error code %d from storage engine.", ER_EVENT_DOES_NOT_EXIST: "Unknown event '%-.192s'", ER_EVENT_CANT_ALTER: "Failed to alter event '%-.192s'", ER_EVENT_DROP_FAILED: "Failed to drop %s", ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG: "INTERVAL is either not positive or too big", ER_EVENT_ENDS_BEFORE_STARTS: "ENDS is either invalid or before STARTS", ER_EVENT_EXEC_TIME_IN_THE_PAST: "Event execution time is in the past. Event has been disabled", ER_EVENT_OPEN_TABLE_FAILED: "Failed to open mysql.event", ER_EVENT_NEITHER_M_EXPR_NOR_M_AT: "No datetime expression provided", ER_OBSOLETE_COL_COUNT_DOESNT_MATCH_CORRUPTED: "Column count of mysql.%s is wrong. Expected %d, found %d. The table is probably corrupted", ER_OBSOLETE_CANNOT_LOAD_FROM_TABLE: "Cannot load from mysql.%s. The table is probably corrupted", ER_EVENT_CANNOT_DELETE: "Failed to delete the event from mysql.event", ER_EVENT_COMPILE_ERROR: "Error during compilation of event's body", ER_EVENT_SAME_NAME: "Same old and new event name", ER_EVENT_DATA_TOO_LONG: "Data for column '%s' too long", ER_DROP_INDEX_FK: "Cannot drop index '%-.192s': needed in a foreign key constraint", ER_WARN_DEPRECATED_SYNTAX_WITH_VER: "The syntax '%s' is deprecated and will be removed in MySQL %s. Please use %s instead", ER_CANT_WRITE_LOCK_LOG_TABLE: "You can't write-lock a log table. Only read access is possible", ER_CANT_LOCK_LOG_TABLE: "You can't use locks with log tables.", ER_FOREIGN_DUPLICATE_KEY_OLD_UNUSED: "Upholding foreign key constraints for table '%.192s', entry '%-.192s', key %d would lead to a duplicate entry", ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE: "Column count of mysql.%s is wrong. Expected %d, found %d. Created with MySQL %d, now running %d. Please use mysql_upgrade to fix this error.", ER_TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR: "Cannot switch out of the row-based binary log format when the session has open temporary tables", ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_FORMAT: "Cannot change the binary logging format inside a stored function or trigger", ER_NDB_CANT_SWITCH_BINLOG_FORMAT: "The NDB cluster engine does not support changing the binlog format on the fly yet", ER_PARTITION_NO_TEMPORARY: "Cannot create temporary table with partitions", ER_PARTITION_CONST_DOMAIN_ERROR: "Partition constant is out of partition function domain", ER_PARTITION_FUNCTION_IS_NOT_ALLOWED: "This partition function is not allowed", ER_DDL_LOG_ERROR: "Error in DDL log", ER_NULL_IN_VALUES_LESS_THAN: "Not allowed to use NULL value in VALUES LESS THAN", ER_WRONG_PARTITION_NAME: "Incorrect partition name", ER_CANT_CHANGE_TX_CHARACTERISTICS: "Transaction characteristics can't be changed while a transaction is in progress", ER_DUP_ENTRY_AUTOINCREMENT_CASE: "ALTER TABLE causes auto_increment resequencing, resulting in duplicate entry '%-.192s' for key '%-.192s'", ER_EVENT_MODIFY_QUEUE_ERROR: "Internal scheduler error %d", ER_EVENT_SET_VAR_ERROR: "Error during starting/stopping of the scheduler. Error code %d", ER_PARTITION_MERGE_ERROR: "Engine cannot be used in partitioned tables", ER_CANT_ACTIVATE_LOG: "Cannot activate '%-.64s' log", ER_RBR_NOT_AVAILABLE: "The server was not built with row-based replication", ER_BASE64_DECODE_ERROR: "Decoding of base64 string failed", ER_EVENT_RECURSION_FORBIDDEN: "Recursion of EVENT DDL statements is forbidden when body is present", ER_EVENTS_DB_ERROR: "Cannot proceed because system tables used by Event Scheduler were found damaged at server start", ER_ONLY_INTEGERS_ALLOWED: "Only integers allowed as number here", ER_UNSUPORTED_LOG_ENGINE: "This storage engine cannot be used for log tables\"", ER_BAD_LOG_STATEMENT: "You cannot '%s' a log table if logging is enabled", ER_CANT_RENAME_LOG_TABLE: "Cannot rename '%s'. When logging enabled, rename to/from log table must rename two tables: the log table to an archive table and another table back to '%s'", ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT: "Incorrect parameter count in the call to native function '%-.192s'", ER_WRONG_PARAMETERS_TO_NATIVE_FCT: "Incorrect parameters in the call to native function '%-.192s'", ER_WRONG_PARAMETERS_TO_STORED_FCT: "Incorrect parameters in the call to stored function '%-.192s'", ER_NATIVE_FCT_NAME_COLLISION: "This function '%-.192s' has the same name as a native function", ER_DUP_ENTRY_WITH_KEY_NAME: "Duplicate entry '%-.64s' for key '%-.192s'", ER_BINLOG_PURGE_EMFILE: "Too many files opened, please execute the command again", ER_EVENT_CANNOT_CREATE_IN_THE_PAST: "Event execution time is in the past and ON COMPLETION NOT PRESERVE is set. The event was dropped immediately after creation.", ER_EVENT_CANNOT_ALTER_IN_THE_PAST: "Event execution time is in the past and ON COMPLETION NOT PRESERVE is set. The event was not changed. Specify a time in the future.", ER_SLAVE_INCIDENT: "The incident %s occurred on the master. Message: %-.64s", ER_NO_PARTITION_FOR_GIVEN_VALUE_SILENT: "Table has no partition for some existing values", ER_BINLOG_UNSAFE_STATEMENT: "Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. %s", ER_SLAVE_FATAL_ERROR: "Fatal error: %s", ER_SLAVE_RELAY_LOG_READ_FAILURE: "Relay log read failure: %s", ER_SLAVE_RELAY_LOG_WRITE_FAILURE: "Relay log write failure: %s", ER_SLAVE_CREATE_EVENT_FAILURE: "Failed to create %s", ER_SLAVE_MASTER_COM_FAILURE: "Master command %s failed: %s", ER_BINLOG_LOGGING_IMPOSSIBLE: "Binary logging not possible. Message: %s", ER_VIEW_NO_CREATION_CTX: "View `%-.64s`.`%-.64s` has no creation context", ER_VIEW_INVALID_CREATION_CTX: "Creation context of view `%-.64s`.`%-.64s' is invalid", ER_SR_INVALID_CREATION_CTX: "Creation context of stored routine `%-.64s`.`%-.64s` is invalid", ER_TRG_CORRUPTED_FILE: "Corrupted TRG file for table `%-.64s`.`%-.64s`", ER_TRG_NO_CREATION_CTX: "Triggers for table `%-.64s`.`%-.64s` have no creation context", ER_TRG_INVALID_CREATION_CTX: "Trigger creation context of table `%-.64s`.`%-.64s` is invalid", ER_EVENT_INVALID_CREATION_CTX: "Creation context of event `%-.64s`.`%-.64s` is invalid", ER_TRG_CANT_OPEN_TABLE: "Cannot open table for trigger `%-.64s`.`%-.64s`", ER_CANT_CREATE_SROUTINE: "Cannot create stored routine `%-.64s`. Check warnings", ER_NEVER_USED: "Ambiguous slave modes combination. %s", ER_NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT: "The BINLOG statement of type `%s` was not preceded by a format description BINLOG statement.", ER_SLAVE_CORRUPT_EVENT: "Corrupted replication event was detected", ER_LOAD_DATA_INVALID_COLUMN: "Invalid column reference (%-.64s) in LOAD DATA", ER_LOG_PURGE_NO_FILE: "Being purged log %s was not found", ER_XA_RBTIMEOUT: "XA_RBTIMEOUT: Transaction branch was rolled back: took too long", ER_XA_RBDEADLOCK: "XA_RBDEADLOCK: Transaction branch was rolled back: deadlock was detected", ER_NEED_REPREPARE: "Prepared statement needs to be re-prepared", ER_DELAYED_NOT_SUPPORTED: "DELAYED option not supported for table '%-.192s'", WARN_NO_MASTER_INFO: "The master info structure does not exist", WARN_OPTION_IGNORED: "<%-.64s> option ignored", WARN_PLUGIN_DELETE_BUILTIN: "Built-in plugins cannot be deleted", WARN_PLUGIN_BUSY: "Plugin is busy and will be uninstalled on shutdown", ER_VARIABLE_IS_READONLY: "%s variable '%s' is read-only. Use SET %s to assign the value", ER_WARN_ENGINE_TRANSACTION_ROLLBACK: "Storage engine %s does not support rollback for this statement. Transaction rolled back and must be restarted", ER_SLAVE_HEARTBEAT_FAILURE: "Unexpected master's heartbeat data: %s", ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE: "The requested value for the heartbeat period is either negative or exceeds the maximum allowed (%s seconds).", ER_NDB_REPLICATION_SCHEMA_ERROR: "Bad schema for mysql.ndb_replication table. Message: %-.64s", ER_CONFLICT_FN_PARSE_ERROR: "Error in parsing conflict function. Message: %-.64s", ER_EXCEPTIONS_WRITE_ERROR: "Write to exceptions table failed. Message: %-.128s\"", ER_TOO_LONG_TABLE_COMMENT: "Comment for table '%-.64s' is too long (max = %d)", ER_TOO_LONG_FIELD_COMMENT: "Comment for field '%-.64s' is too long (max = %d)", ER_FUNC_INEXISTENT_NAME_COLLISION: "FUNCTION %s does not exist. Check the 'Function Name Parsing and Resolution' section in the Reference Manual", ER_DATABASE_NAME: "Database", ER_TABLE_NAME: "Table", ER_PARTITION_NAME: "Partition", ER_SUBPARTITION_NAME: "Subpartition", ER_TEMPORARY_NAME: "Temporary", ER_RENAMED_NAME: "Renamed", ER_TOO_MANY_CONCURRENT_TRXS: "Too many active concurrent transactions", WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED: "Non-ASCII separator arguments are not fully supported", ER_DEBUG_SYNC_TIMEOUT: "debug sync point wait timed out", ER_DEBUG_SYNC_HIT_LIMIT: "debug sync point hit limit reached", ER_DUP_SIGNAL_SET: "Duplicate condition information item '%s'", ER_SIGNAL_WARN: "Unhandled user-defined warning condition", ER_SIGNAL_NOT_FOUND: "Unhandled user-defined not found condition", ER_SIGNAL_EXCEPTION: "Unhandled user-defined exception condition", ER_RESIGNAL_WITHOUT_ACTIVE_HANDLER: "RESIGNAL when handler not active", ER_SIGNAL_BAD_CONDITION_TYPE: "SIGNAL/RESIGNAL can only use a CONDITION defined with SQLSTATE", WARN_COND_ITEM_TRUNCATED: "Data truncated for condition item '%s'", ER_COND_ITEM_TOO_LONG: "Data too long for condition item '%s'", ER_UNKNOWN_LOCALE: "Unknown locale: '%-.64s'", ER_SLAVE_IGNORE_SERVER_IDS: "The requested server id %d clashes with the slave startup option --replicate-same-server-id", ER_QUERY_CACHE_DISABLED: "Query cache is disabled; restart the server with query_cache_type=1 to enable it", ER_SAME_NAME_PARTITION_FIELD: "Duplicate partition field name '%-.192s'", ER_PARTITION_COLUMN_LIST_ERROR: "Inconsistency in usage of column lists for partitioning", ER_WRONG_TYPE_COLUMN_VALUE_ERROR: "Partition column values of incorrect type", ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR: "Too many fields in '%-.192s'", ER_MAXVALUE_IN_VALUES_IN: "Cannot use MAXVALUE as value in VALUES IN", ER_TOO_MANY_VALUES_ERROR: "Cannot have more than one value for this type of %-.64s partitioning", ER_ROW_SINGLE_PARTITION_FIELD_ERROR: "Row expressions in VALUES IN only allowed for multi-field column partitioning", ER_FIELD_TYPE_NOT_ALLOWED_AS_PARTITION_FIELD: "Field '%-.192s' is of a not allowed type for this type of partitioning", ER_PARTITION_FIELDS_TOO_LONG: "The total length of the partitioning fields is too large", ER_BINLOG_ROW_ENGINE_AND_STMT_ENGINE: "Cannot execute statement: impossible to write to binary log since both row-incapable engines and statement-incapable engines are involved.", ER_BINLOG_ROW_MODE_AND_STMT_ENGINE: "Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = ROW and at least one table uses a storage engine limited to statement-based logging.", ER_BINLOG_UNSAFE_AND_STMT_ENGINE: "Cannot execute statement: impossible to write to binary log since statement is unsafe, storage engine is limited to statement-based logging, and BINLOG_FORMAT = MIXED. %s", ER_BINLOG_ROW_INJECTION_AND_STMT_ENGINE: "Cannot execute statement: impossible to write to binary log since statement is in row format and at least one table uses a storage engine limited to statement-based logging.", ER_BINLOG_STMT_MODE_AND_ROW_ENGINE: "Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT and at least one table uses a storage engine limited to row-based logging.%s", ER_BINLOG_ROW_INJECTION_AND_STMT_MODE: "Cannot execute statement: impossible to write to binary log since statement is in row format and BINLOG_FORMAT = STATEMENT.", ER_BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE: "Cannot execute statement: impossible to write to binary log since more than one engine is involved and at least one engine is self-logging.", ER_BINLOG_UNSAFE_LIMIT: "The statement is unsafe because it uses a LIMIT clause. This is unsafe because the set of rows included cannot be predicted.", ER_BINLOG_UNSAFE_INSERT_DELAYED: "The statement is unsafe because it uses INSERT DELAYED. This is unsafe because the times when rows are inserted cannot be predicted.", ER_BINLOG_UNSAFE_SYSTEM_TABLE: "The statement is unsafe because it uses the general log, slow query log, or performance_schema table(s). This is unsafe because system tables may differ on slaves.", ER_BINLOG_UNSAFE_AUTOINC_COLUMNS: "Statement is unsafe because it invokes a trigger or a stored function that inserts into an AUTO_INCREMENT column. Inserted values cannot be logged correctly.", ER_BINLOG_UNSAFE_UDF: "Statement is unsafe because it uses a UDF which may not return the same value on the slave.", ER_BINLOG_UNSAFE_SYSTEM_VARIABLE: "Statement is unsafe because it uses a system variable that may have a different value on the slave.", ER_BINLOG_UNSAFE_SYSTEM_FUNCTION: "Statement is unsafe because it uses a system function that may return a different value on the slave.", ER_BINLOG_UNSAFE_NONTRANS_AFTER_TRANS: "Statement is unsafe because it accesses a non-transactional table after accessing a transactional table within the same transaction.", ER_MESSAGE_AND_STATEMENT: "%s Statement: %s", ER_SLAVE_CONVERSION_FAILED: "Column %d of table '%-.192s.%-.192s' cannot be converted from type '%-.32s' to type '%-.32s'", ER_SLAVE_CANT_CREATE_CONVERSION: "Can't create conversion table for table '%-.192s.%-.192s'", ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_FORMAT: "Cannot modify @@session.binlog_format inside a transaction", ER_PATH_LENGTH: "The path specified for %.64s is too long.", ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT: "'%s' is deprecated and will be removed in a future release.", ER_WRONG_NATIVE_TABLE_STRUCTURE: "Native table '%-.64s'.'%-.64s' has the wrong structure", ER_WRONG_PERFSCHEMA_USAGE: "Invalid performance_schema usage.", ER_WARN_I_S_SKIPPED_TABLE: "Table '%s'.'%s' was skipped since its definition is being modified by concurrent DDL statement", ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_DIRECT: "Cannot modify @@session.binlog_direct_non_transactional_updates inside a transaction", ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_DIRECT: "Cannot change the binlog direct flag inside a stored function or trigger", ER_SPATIAL_MUST_HAVE_GEOM_COL: "A SPATIAL index may only contain a geometrical type column", ER_TOO_LONG_INDEX_COMMENT: "Comment for index '%-.64s' is too long (max = %d)", ER_LOCK_ABORTED: "Wait on a lock was aborted due to a pending exclusive lock", ER_DATA_OUT_OF_RANGE: "%s value is out of range in '%s'", ER_WRONG_SPVAR_TYPE_IN_LIMIT: "A variable of a non-integer based type in LIMIT clause", ER_BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE: "Mixing self-logging and non-self-logging engines in a statement is unsafe.", ER_BINLOG_UNSAFE_MIXED_STATEMENT: "Statement accesses nontransactional table as well as transactional or temporary table, and writes to any of them.", ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_SQL_LOG_BIN: "Cannot modify @@session.sql_log_bin inside a transaction", ER_STORED_FUNCTION_PREVENTS_SWITCH_SQL_LOG_BIN: "Cannot change the sql_log_bin inside a stored function or trigger", ER_FAILED_READ_FROM_PAR_FILE: "Failed to read from the .par file", ER_VALUES_IS_NOT_INT_TYPE_ERROR: "VALUES value for partition '%-.64s' must have type INT", ER_ACCESS_DENIED_NO_PASSWORD_ERROR: "Access denied for user '%-.48s'@'%-.64s'", ER_SET_PASSWORD_AUTH_PLUGIN: "SET PASSWORD has no significance for users authenticating via plugins", ER_GRANT_PLUGIN_USER_EXISTS: "GRANT with IDENTIFIED WITH is illegal because the user %-.*s already exists", ER_TRUNCATE_ILLEGAL_FK: "Cannot truncate a table referenced in a foreign key constraint (%.192s)", ER_PLUGIN_IS_PERMANENT: "Plugin '%s' is force_plus_permanent and can not be unloaded", ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN: "The requested value for the heartbeat period is less than 1 millisecond. The value is reset to 0, meaning that heartbeating will effectively be disabled.", ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX: "The requested value for the heartbeat period exceeds the value of `slave_net_timeout' seconds. A sensible value for the period should be less than the timeout.", ER_STMT_CACHE_FULL: "Multi-row statements required more than 'max_binlog_stmt_cache_size' bytes of storage; increase this mysqld variable and try again", ER_MULTI_UPDATE_KEY_CONFLICT: "Primary key/partition key update is not allowed since the table is updated both as '%-.192s' and '%-.192s'.", ER_TABLE_NEEDS_REBUILD: "Table rebuild required. Please do \"ALTER TABLE `%-.32s` FORCE\" or dump/reload to fix it!", WARN_OPTION_BELOW_LIMIT: "The value of '%s' should be no less than the value of '%s'", ER_INDEX_COLUMN_TOO_LONG: "Index column size too large. The maximum column size is %d bytes.", ER_ERROR_IN_TRIGGER_BODY: "Trigger '%-.64s' has an error in its body: '%-.256s'", ER_ERROR_IN_UNKNOWN_TRIGGER_BODY: "Unknown trigger has an error in its body: '%-.256s'", ER_INDEX_CORRUPT: "Index %s is corrupted", ER_UNDO_RECORD_TOO_BIG: "Undo log record is too big.", ER_BINLOG_UNSAFE_INSERT_IGNORE_SELECT: "INSERT IGNORE... SELECT is unsafe because the order in which rows are retrieved by the SELECT determines which (if any) rows are ignored. This order cannot be predicted and may differ on master and the slave.", ER_BINLOG_UNSAFE_INSERT_SELECT_UPDATE: "INSERT... SELECT... ON DUPLICATE KEY UPDATE is unsafe because the order in which rows are retrieved by the SELECT determines which (if any) rows are updated. This order cannot be predicted and may differ on master and the slave.", ER_BINLOG_UNSAFE_REPLACE_SELECT: "REPLACE... SELECT is unsafe because the order in which rows are retrieved by the SELECT determines which (if any) rows are replaced. This order cannot be predicted and may differ on master and the slave.", ER_BINLOG_UNSAFE_CREATE_IGNORE_SELECT: "CREATE... IGNORE SELECT is unsafe because the order in which rows are retrieved by the SELECT determines which (if any) rows are ignored. This order cannot be predicted and may differ on master and the slave.", ER_BINLOG_UNSAFE_CREATE_REPLACE_SELECT: "CREATE... REPLACE SELECT is unsafe because the order in which rows are retrieved by the SELECT determines which (if any) rows are replaced. This order cannot be predicted and may differ on master and the slave.", ER_BINLOG_UNSAFE_UPDATE_IGNORE: "UPDATE IGNORE is unsafe because the order in which rows are updated determines which (if any) rows are ignored. This order cannot be predicted and may differ on master and the slave.", ER_PLUGIN_NO_UNINSTALL: "Plugin '%s' is marked as not dynamically uninstallable. You have to stop the server to uninstall it.", ER_PLUGIN_NO_INSTALL: "Plugin '%s' is marked as not dynamically installable. You have to stop the server to install it.", ER_BINLOG_UNSAFE_WRITE_AUTOINC_SELECT: "Statements writing to a table with an auto-increment column after selecting from another table are unsafe because the order in which rows are retrieved determines what (if any) rows will be written. This order cannot be predicted and may differ on master and the slave.", ER_BINLOG_UNSAFE_CREATE_SELECT_AUTOINC: "CREATE TABLE... SELECT... on a table with an auto-increment column is unsafe because the order in which rows are retrieved by the SELECT determines which (if any) rows are inserted. This order cannot be predicted and may differ on master and the slave.", ER_BINLOG_UNSAFE_INSERT_TWO_KEYS: "INSERT... ON DUPLICATE KEY UPDATE on a table with more than one UNIQUE KEY is unsafe", ER_TABLE_IN_FK_CHECK: "Table is being used in foreign key check.", ER_UNSUPPORTED_ENGINE: "Storage engine '%s' does not support system tables. [%s.%s]", ER_BINLOG_UNSAFE_AUTOINC_NOT_FIRST: "INSERT into autoincrement field which is not the first part in the composed primary key is unsafe.", ER_CANNOT_LOAD_FROM_TABLE_V2: "Cannot load from %s.%s. The table is probably corrupted", ER_MASTER_DELAY_VALUE_OUT_OF_RANGE: "The requested value %d for the master delay exceeds the maximum %d", ER_ONLY_FD_AND_RBR_EVENTS_ALLOWED_IN_BINLOG_STATEMENT: "Only Format_description_log_event and row events are allowed in BINLOG statements (but %s was provided)", ER_PARTITION_EXCHANGE_DIFFERENT_OPTION: "Non matching attribute '%-.64s' between partition and table", ER_PARTITION_EXCHANGE_PART_TABLE: "Table to exchange with partition is partitioned: '%-.64s'", ER_PARTITION_EXCHANGE_TEMP_TABLE: "Table to exchange with partition is temporary: '%-.64s'", ER_PARTITION_INSTEAD_OF_SUBPARTITION: "Subpartitioned table, use subpartition instead of partition", ER_UNKNOWN_PARTITION: "Unknown partition '%-.64s' in table '%-.64s'", ER_TABLES_DIFFERENT_METADATA: "Tables have different definitions", ER_ROW_DOES_NOT_MATCH_PARTITION: "Found a row that does not match the partition", ER_BINLOG_CACHE_SIZE_GREATER_THAN_MAX: "Option binlog_cache_size (%d) is greater than max_binlog_cache_size (%d); setting binlog_cache_size equal to max_binlog_cache_size.", ER_WARN_INDEX_NOT_APPLICABLE: "Cannot use %-.64s access on index '%-.64s' due to type or collation conversion on field '%-.64s'", ER_PARTITION_EXCHANGE_FOREIGN_KEY: "Table to exchange with partition has foreign key references: '%-.64s'", ER_NO_SUCH_KEY_VALUE: "Key value '%-.192s' was not found in table '%-.192s.%-.192s'", ER_RPL_INFO_DATA_TOO_LONG: "Data for column '%s' too long", ER_NETWORK_READ_EVENT_CHECKSUM_FAILURE: "Replication event checksum verification failed while reading from network.", ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE: "Replication event checksum verification failed while reading from a log file.", ER_BINLOG_STMT_CACHE_SIZE_GREATER_THAN_MAX: "Option binlog_stmt_cache_size (%d) is greater than max_binlog_stmt_cache_size (%d); setting binlog_stmt_cache_size equal to max_binlog_stmt_cache_size.", ER_CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT: "Can't update table '%-.192s' while '%-.192s' is being created.", ER_PARTITION_CLAUSE_ON_NONPARTITIONED: "PARTITION () clause on non partitioned table", ER_ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET: "Found a row not matching the given partition set", ER_NO_SUCH_PARTITION__UNUSED: "partition '%-.64s' doesn't exist", ER_CHANGE_RPL_INFO_REPOSITORY_FAILURE: "Failure while changing the type of replication repository: %s.", ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_CREATED_TEMP_TABLE: "The creation of some temporary tables could not be rolled back.", ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_DROPPED_TEMP_TABLE: "Some temporary tables were dropped, but these operations could not be rolled back.", ER_MTS_FEATURE_IS_NOT_SUPPORTED: "%s is not supported in multi-threaded slave mode. %s", ER_MTS_UPDATED_DBS_GREATER_MAX: "The number of modified databases exceeds the maximum %d; the database names will not be included in the replication event metadata.", ER_MTS_CANT_PARALLEL: "Cannot execute the current event group in the parallel mode. Encountered event %s, relay-log name %s, position %s which prevents execution of this event group in parallel mode. Reason: %s.", ER_MTS_INCONSISTENT_DATA: "%s", ER_FULLTEXT_NOT_SUPPORTED_WITH_PARTITIONING: "FULLTEXT index is not supported for partitioned tables.", ER_DA_INVALID_CONDITION_NUMBER: "Invalid condition number", ER_INSECURE_PLAIN_TEXT: "Sending passwords in plain text without SSL/TLS is extremely insecure.", ER_INSECURE_CHANGE_MASTER: "Storing MySQL user name or password information in the master.info repository is not secure and is therefore not recommended. Please see the MySQL Manual for more about this issue and possible alternatives.", ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO: "Foreign key constraint for table '%.192s', record '%-.192s' would lead to a duplicate entry in table '%.192s', key '%.192s'", ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO: "Foreign key constraint for table '%.192s', record '%-.192s' would lead to a duplicate entry in a child table", ER_SQLTHREAD_WITH_SECURE_SLAVE: "Setting authentication options is not possible when only the Slave SQL Thread is being started.", ER_TABLE_HAS_NO_FT: "The table does not have FULLTEXT index to support this query", ER_VARIABLE_NOT_SETTABLE_IN_SF_OR_TRIGGER: "The system variable %.200s cannot be set in stored functions or triggers.", ER_VARIABLE_NOT_SETTABLE_IN_TRANSACTION: "The system variable %.200s cannot be set when there is an ongoing transaction.", ER_GTID_NEXT_IS_NOT_IN_GTID_NEXT_LIST: "The system variable @@SESSION.GTID_NEXT has the value %.200s, which is not listed in @@SESSION.GTID_NEXT_LIST.", ER_CANT_CHANGE_GTID_NEXT_IN_TRANSACTION_WHEN_GTID_NEXT_LIST_IS_NULL: "When @@SESSION.GTID_NEXT_LIST == NULL, the system variable @@SESSION.GTID_NEXT cannot change inside a transaction.", ER_SET_STATEMENT_CANNOT_INVOKE_FUNCTION: "The statement 'SET %.200s' cannot invoke a stored function.", ER_GTID_NEXT_CANT_BE_AUTOMATIC_IF_GTID_NEXT_LIST_IS_NON_NULL: "The system variable @@SESSION.GTID_NEXT cannot be 'AUTOMATIC' when @@SESSION.GTID_NEXT_LIST is non-NULL.", ER_SKIPPING_LOGGED_TRANSACTION: "Skipping transaction %.200s because it has already been executed and logged.", ER_MALFORMED_GTID_SET_SPECIFICATION: "Malformed GTID set specification '%.200s'.", ER_MALFORMED_GTID_SET_ENCODING: "Malformed GTID set encoding.", ER_MALFORMED_GTID_SPECIFICATION: "Malformed GTID specification '%.200s'.", ER_GNO_EXHAUSTED: "Impossible to generate Global Transaction Identifier: the integer component reached the maximal value. Restart the server with a new server_uuid.", ER_BAD_SLAVE_AUTO_POSITION: "Parameters MASTER_LOG_FILE, MASTER_LOG_POS, RELAY_LOG_FILE and RELAY_LOG_POS cannot be set when MASTER_AUTO_POSITION is active.", ER_AUTO_POSITION_REQUIRES_GTID_MODE_ON: "CHANGE MASTER TO MASTER_AUTO_POSITION = 1 can only be executed when @@GLOBAL.GTID_MODE = ON.", ER_CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET: "Cannot execute statements with implicit commit inside a transaction when @@SESSION.GTID_NEXT != AUTOMATIC or @@SESSION.GTID_NEXT_LIST != NULL.", ER_GTID_MODE_2_OR_3_REQUIRES_ENFORCE_GTID_CONSISTENCY_ON: "@@GLOBAL.GTID_MODE = ON or UPGRADE_STEP_2 requires @@GLOBAL.ENFORCE_GTID_CONSISTENCY = 1.", ER_GTID_MODE_REQUIRES_BINLOG: "@@GLOBAL.GTID_MODE = ON or UPGRADE_STEP_1 or UPGRADE_STEP_2 requires --log-bin and --log-slave-updates.", ER_CANT_SET_GTID_NEXT_TO_GTID_WHEN_GTID_MODE_IS_OFF: "@@SESSION.GTID_NEXT cannot be set to UUID:NUMBER when @@GLOBAL.GTID_MODE = OFF.", ER_CANT_SET_GTID_NEXT_TO_ANONYMOUS_WHEN_GTID_MODE_IS_ON: "@@SESSION.GTID_NEXT cannot be set to ANONYMOUS when @@GLOBAL.GTID_MODE = ON.", ER_CANT_SET_GTID_NEXT_LIST_TO_NON_NULL_WHEN_GTID_MODE_IS_OFF: "@@SESSION.GTID_NEXT_LIST cannot be set to a non-NULL value when @@GLOBAL.GTID_MODE = OFF.", ER_FOUND_GTID_EVENT_WHEN_GTID_MODE_IS_OFF: "Found a Gtid_log_event or Previous_gtids_log_event when @@GLOBAL.GTID_MODE = OFF.", ER_GTID_UNSAFE_NON_TRANSACTIONAL_TABLE: "When @@GLOBAL.ENFORCE_GTID_CONSISTENCY = 1, updates to non-transactional tables can only be done in either autocommitted statements or single-statement transactions, and never in the same statement as updates to transactional tables.", ER_GTID_UNSAFE_CREATE_SELECT: "CREATE TABLE ... SELECT is forbidden when @@GLOBAL.ENFORCE_GTID_CONSISTENCY = 1.", ER_GTID_UNSAFE_CREATE_DROP_TEMPORARY_TABLE_IN_TRANSACTION: "When @@GLOBAL.ENFORCE_GTID_CONSISTENCY = 1, the statements CREATE TEMPORARY TABLE and DROP TEMPORARY TABLE can be executed in a non-transactional context only, and require that AUTOCOMMIT = 1.", ER_GTID_MODE_CAN_ONLY_CHANGE_ONE_STEP_AT_A_TIME: "The value of @@GLOBAL.GTID_MODE can only change one step at a time: OFF <-> UPGRADE_STEP_1 <-> UPGRADE_STEP_2 <-> ON. Also note that this value must be stepped up or down simultaneously on all servers; see the Manual for instructions.", ER_MASTER_HAS_PURGED_REQUIRED_GTIDS: "The slave is connecting using CHANGE MASTER TO MASTER_AUTO_POSITION = 1, but the master has purged binary logs containing GTIDs that the slave requires.", ER_CANT_SET_GTID_NEXT_WHEN_OWNING_GTID: "@@SESSION.GTID_NEXT cannot be changed by a client that owns a GTID. The client owns %s. Ownership is released on COMMIT or ROLLBACK.", ER_UNKNOWN_EXPLAIN_FORMAT: "Unknown EXPLAIN format name: '%s'", ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION: "Cannot execute statement in a READ ONLY transaction.", ER_TOO_LONG_TABLE_PARTITION_COMMENT: "Comment for table partition '%-.64s' is too long (max = %d)", ER_SLAVE_CONFIGURATION: "Slave is not configured or failed to initialize properly. You must at least set --server-id to enable either a master or a slave. Additional error messages can be found in the MySQL error log.", ER_INNODB_FT_LIMIT: "InnoDB presently supports one FULLTEXT index creation at a time", ER_INNODB_NO_FT_TEMP_TABLE: "Cannot create FULLTEXT index on temporary InnoDB table", ER_INNODB_FT_WRONG_DOCID_COLUMN: "Column '%-.192s' is of wrong type for an InnoDB FULLTEXT index", ER_INNODB_FT_WRONG_DOCID_INDEX: "Index '%-.192s' is of wrong type for an InnoDB FULLTEXT index", ER_INNODB_ONLINE_LOG_TOO_BIG: "Creating index '%-.192s' required more than 'innodb_online_alter_log_max_size' bytes of modification log. Please try again.", ER_UNKNOWN_ALTER_ALGORITHM: "Unknown ALGORITHM '%s'", ER_UNKNOWN_ALTER_LOCK: "Unknown LOCK type '%s'", ER_MTS_CHANGE_MASTER_CANT_RUN_WITH_GAPS: "CHANGE MASTER cannot be executed when the slave was stopped with an error or killed in MTS mode. Consider using RESET SLAVE or START SLAVE UNTIL.", ER_MTS_RECOVERY_FAILURE: "Cannot recover after SLAVE errored out in parallel execution mode. Additional error messages can be found in the MySQL error log.", ER_MTS_RESET_WORKERS: "Cannot clean up worker info tables. Additional error messages can be found in the MySQL error log.", ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2: "Column count of %s.%s is wrong. Expected %d, found %d. The table is probably corrupted", ER_SLAVE_SILENT_RETRY_TRANSACTION: "Slave must silently retry current transaction", ER_DISCARD_FK_CHECKS_RUNNING: "There is a foreign key check running on table '%-.192s'. Cannot discard the table.", ER_TABLE_SCHEMA_MISMATCH: "Schema mismatch (%s)", ER_TABLE_IN_SYSTEM_TABLESPACE: "Table '%-.192s' in system tablespace", ER_IO_READ_ERROR: "IO Read error: (%d, %s) %s", ER_IO_WRITE_ERROR: "IO Write error: (%d, %s) %s", ER_TABLESPACE_MISSING: "Tablespace is missing for table '%-.192s'", ER_TABLESPACE_EXISTS: "Tablespace for table '%-.192s' exists. Please DISCARD the tablespace before IMPORT.", ER_TABLESPACE_DISCARDED: "Tablespace has been discarded for table '%-.192s'", ER_INTERNAL_ERROR: "Internal error: %s", ER_INNODB_IMPORT_ERROR: "ALTER TABLE '%-.192s' IMPORT TABLESPACE failed with error %d : '%s'", ER_INNODB_INDEX_CORRUPT: "Index corrupt: %s", ER_INVALID_YEAR_COLUMN_LENGTH: "YEAR(%d) column type is deprecated. Creating YEAR(4) column instead.", ER_NOT_VALID_PASSWORD: "Your password does not satisfy the current policy requirements", ER_MUST_CHANGE_PASSWORD: "You must SET PASSWORD before executing this statement", ER_FK_NO_INDEX_CHILD: "Failed to add the foreign key constaint. Missing index for constraint '%s' in the foreign table '%s'", ER_FK_NO_INDEX_PARENT: "Failed to add the foreign key constaint. Missing index for constraint '%s' in the referenced table '%s'", ER_FK_FAIL_ADD_SYSTEM: "Failed to add the foreign key constraint '%s' to system tables", ER_FK_CANNOT_OPEN_PARENT: "Failed to open the referenced table '%s'", ER_FK_INCORRECT_OPTION: "Failed to add the foreign key constraint on table '%s'. Incorrect options in FOREIGN KEY constraint '%s'", ER_FK_DUP_NAME: "Duplicate foreign key constraint name '%s'", ER_PASSWORD_FORMAT: "The password hash doesn't have the expected format. Check if the correct password algorithm is being used with the PASSWORD() function.", ER_FK_COLUMN_CANNOT_DROP: "Cannot drop column '%-.192s': needed in a foreign key constraint '%-.192s'", ER_FK_COLUMN_CANNOT_DROP_CHILD: "Cannot drop column '%-.192s': needed in a foreign key constraint '%-.192s' of table '%-.192s'", ER_FK_COLUMN_NOT_NULL: "Column '%-.192s' cannot be NOT NULL: needed in a foreign key constraint '%-.192s' SET NULL", ER_DUP_INDEX: "Duplicate index '%-.64s' defined on the table '%-.64s.%-.64s'. This is deprecated and will be disallowed in a future release.", ER_FK_COLUMN_CANNOT_CHANGE: "Cannot change column '%-.192s': used in a foreign key constraint '%-.192s'", ER_FK_COLUMN_CANNOT_CHANGE_CHILD: "Cannot change column '%-.192s': used in a foreign key constraint '%-.192s' of table '%-.192s'", ER_FK_CANNOT_DELETE_PARENT: "Cannot delete rows from table which is parent in a foreign key constraint '%-.192s' of table '%-.192s'", ER_MALFORMED_PACKET: "Malformed communication packet.", ER_READ_ONLY_MODE: "Running in read-only mode", ER_GTID_NEXT_TYPE_UNDEFINED_GROUP: "When @@SESSION.GTID_NEXT is set to a GTID, you must explicitly set it again after a COMMIT or ROLLBACK. If you see this error message in the slave SQL thread, it means that a table in the current transaction is transactional on the master and non-transactional on the slave. In a client connection, it means that you executed SET @@SESSION.GTID_NEXT before a transaction and forgot to set @@SESSION.GTID_NEXT to a different identifier or to 'AUTOMATIC' after COMMIT or ROLLBACK. Current @@SESSION.GTID_NEXT is '%s'.", ER_VARIABLE_NOT_SETTABLE_IN_SP: "The system variable %.200s cannot be set in stored procedures.", ER_CANT_SET_GTID_PURGED_WHEN_GTID_MODE_IS_OFF: "@@GLOBAL.GTID_PURGED can only be set when @@GLOBAL.GTID_MODE = ON.", ER_CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY: "@@GLOBAL.GTID_PURGED can only be set when @@GLOBAL.GTID_EXECUTED is empty.", ER_CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY: "@@GLOBAL.GTID_PURGED can only be set when there are no ongoing transactions (not even in other clients).", ER_GTID_PURGED_WAS_CHANGED: "@@GLOBAL.GTID_PURGED was changed from '%s' to '%s'.", ER_GTID_EXECUTED_WAS_CHANGED: "@@GLOBAL.GTID_EXECUTED was changed from '%s' to '%s'.", ER_BINLOG_STMT_MODE_AND_NO_REPL_TABLES: "Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT, and both replicated and non replicated tables are written to.", ER_ALTER_OPERATION_NOT_SUPPORTED: "%s is not supported for this operation. Try %s.", ER_ALTER_OPERATION_NOT_SUPPORTED_REASON: "%s is not supported. Reason: %s. Try %s.", ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY: "COPY algorithm requires a lock", ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION: "Partition specific operations do not yet support LOCK/ALGORITHM", ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME: "Columns participating in a foreign key are renamed", ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE: "Cannot change column type INPLACE", ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK: "Adding foreign keys needs foreign_key_checks=OFF", ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_IGNORE: "Creating unique indexes with IGNORE requires COPY algorithm to remove duplicate rows", ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK: "Dropping a primary key is not allowed without also adding a new primary key", ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC: "Adding an auto-increment column requires a lock", ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS: "Cannot replace hidden FTS_DOC_ID with a user-visible one", ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS: "Cannot drop or rename FTS_DOC_ID", ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS: "Fulltext index creation requires a lock", ER_SQL_SLAVE_SKIP_COUNTER_NOT_SETTABLE_IN_GTID_MODE: "sql_slave_skip_counter can not be set when the server is running with @@GLOBAL.GTID_MODE = ON. Instead, for each transaction that you want to skip, generate an empty transaction with the same GTID as the transaction", ER_DUP_UNKNOWN_IN_INDEX: "Duplicate entry for key '%-.192s'", ER_IDENT_CAUSES_TOO_LONG_PATH: "Long database name and identifier for object resulted in path length exceeding %d characters. Path: '%s'.", ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL: "cannot silently convert NULL values, as required in this SQL_MODE", ER_MUST_CHANGE_PASSWORD_LOGIN: "Your password has expired. To log in you must change it using a client that supports expired passwords.", ER_ROW_IN_WRONG_PARTITION: "Found a row in wrong partition %s", } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/mysql/error.go ================================================ package mysql import ( "fmt" "github.com/pingcap/errors" ) var ( ErrBadConn = errors.New("connection was bad") ErrMalformPacket = errors.New("Malform packet error") ErrTxDone = errors.New("sql: Transaction has already been committed or rolled back") ) type MyError struct { Code uint16 Message string State string } func (e *MyError) Error() string { return fmt.Sprintf("ERROR %d (%s): %s", e.Code, e.State, e.Message) } // NewDefaultError: default mysql error, must adapt errname message format func NewDefaultError(errCode uint16, args ...interface{}) *MyError { e := new(MyError) e.Code = errCode if s, ok := MySQLState[errCode]; ok { e.State = s } else { e.State = DEFAULT_MYSQL_STATE } if format, ok := MySQLErrName[errCode]; ok { e.Message = fmt.Sprintf(format, args...) } else { e.Message = fmt.Sprint(args...) } return e } func NewError(errCode uint16, message string) *MyError { e := new(MyError) e.Code = errCode if s, ok := MySQLState[errCode]; ok { e.State = s } else { e.State = DEFAULT_MYSQL_STATE } e.Message = message return e } func ErrorCode(errMsg string) (code int) { var tmpStr string // golang scanf doesn't support %*,so I used a temporary variable _, _ = fmt.Sscanf(errMsg, "%s%d", &tmpStr, &code) return } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/mysql/field.go ================================================ package mysql import ( "encoding/binary" "fmt" "strconv" "strings" "github.com/go-mysql-org/go-mysql/utils" ) type FieldData []byte type Field struct { Data FieldData Schema []byte Table []byte OrgTable []byte Name []byte OrgName []byte Charset uint16 ColumnLength uint32 Type uint8 Flag uint16 Decimal uint8 DefaultValueLength uint64 DefaultValue []byte } type FieldValueType uint8 type FieldValue struct { Type FieldValueType value uint64 // Also for int64 and float64 str []byte } const ( FieldValueTypeNull = iota FieldValueTypeUnsigned FieldValueTypeSigned FieldValueTypeFloat FieldValueTypeString ) func NewFieldValue(t FieldValueType, v uint64, str []byte) FieldValue { return FieldValue{ Type: t, value: v, str: str, } } func (f *Field) Parse(p FieldData) (err error) { f.Data = p var n int pos := 0 //skip catelog, always def n, err = SkipLengthEncodedString(p) if err != nil { return err } pos += n //schema f.Schema, _, n, err = LengthEncodedString(p[pos:]) if err != nil { return err } pos += n //table f.Table, _, n, err = LengthEncodedString(p[pos:]) if err != nil { return err } pos += n //org_table f.OrgTable, _, n, err = LengthEncodedString(p[pos:]) if err != nil { return err } pos += n //name f.Name, _, n, err = LengthEncodedString(p[pos:]) if err != nil { return err } pos += n //org_name f.OrgName, _, n, err = LengthEncodedString(p[pos:]) if err != nil { return err } pos += n //skip oc pos += 1 //charset f.Charset = binary.LittleEndian.Uint16(p[pos:]) pos += 2 //column length f.ColumnLength = binary.LittleEndian.Uint32(p[pos:]) pos += 4 //type f.Type = p[pos] pos++ //flag f.Flag = binary.LittleEndian.Uint16(p[pos:]) pos += 2 //decimals 1 f.Decimal = p[pos] pos++ //filter [0x00][0x00] pos += 2 f.DefaultValue = nil //if more data, command was field list if len(p) > pos { //length of default value lenenc-int f.DefaultValueLength, _, n = LengthEncodedInt(p[pos:]) pos += n if pos+int(f.DefaultValueLength) > len(p) { err = ErrMalformPacket return err } //default value string[$len] f.DefaultValue = p[pos:(pos + int(f.DefaultValueLength))] } return nil } func (p FieldData) Parse() (f *Field, err error) { f = new(Field) if err = f.Parse(p); err != nil { return nil, err } return f, nil } func (f *Field) Dump() []byte { if f == nil { f = &Field{} } if f.Data != nil { return f.Data } l := len(f.Schema) + len(f.Table) + len(f.OrgTable) + len(f.Name) + len(f.OrgName) + len(f.DefaultValue) + 48 data := make([]byte, 0, l) data = append(data, PutLengthEncodedString([]byte("def"))...) data = append(data, PutLengthEncodedString(f.Schema)...) data = append(data, PutLengthEncodedString(f.Table)...) data = append(data, PutLengthEncodedString(f.OrgTable)...) data = append(data, PutLengthEncodedString(f.Name)...) data = append(data, PutLengthEncodedString(f.OrgName)...) data = append(data, 0x0c) data = append(data, Uint16ToBytes(f.Charset)...) data = append(data, Uint32ToBytes(f.ColumnLength)...) data = append(data, f.Type) data = append(data, Uint16ToBytes(f.Flag)...) data = append(data, f.Decimal) data = append(data, 0, 0) if f.DefaultValue != nil { data = append(data, Uint64ToBytes(f.DefaultValueLength)...) data = append(data, f.DefaultValue...) } return data } func (fv *FieldValue) AsUint64() uint64 { return fv.value } func (fv *FieldValue) AsInt64() int64 { return utils.Uint64ToInt64(fv.value) } func (fv *FieldValue) AsFloat64() float64 { return utils.Uint64ToFloat64(fv.value) } func (fv *FieldValue) AsString() []byte { return fv.str } func (fv *FieldValue) Value() interface{} { switch fv.Type { case FieldValueTypeUnsigned: return fv.AsUint64() case FieldValueTypeSigned: return fv.AsInt64() case FieldValueTypeFloat: return fv.AsFloat64() case FieldValueTypeString: return fv.AsString() default: // FieldValueTypeNull return nil } } // String returns a MySQL literal string that equals the value. func (fv *FieldValue) String() string { switch fv.Type { case FieldValueTypeNull: return "NULL" case FieldValueTypeUnsigned: return strconv.FormatUint(fv.AsUint64(), 10) case FieldValueTypeSigned: return strconv.FormatInt(fv.AsInt64(), 10) case FieldValueTypeFloat: return strconv.FormatFloat(fv.AsFloat64(), 'f', -1, 64) case FieldValueTypeString: b := strings.Builder{} b.Grow(len(fv.str) + 2) b.WriteByte('\'') for i := range fv.str { if fv.str[i] == '\'' { b.WriteByte('\\') } b.WriteByte(fv.str[i]) } b.WriteByte('\'') return b.String() default: return fmt.Sprintf("unknown type %d of FieldValue", fv.Type) } } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/mysql/gtid.go ================================================ package mysql import ( "github.com/pingcap/errors" ) type GTIDSet interface { String() string // Encode GTID set into binary format used in binlog dump commands Encode() []byte Equal(o GTIDSet) bool Contain(o GTIDSet) bool Update(GTIDStr string) error Clone() GTIDSet } func ParseGTIDSet(flavor string, s string) (GTIDSet, error) { switch flavor { case MySQLFlavor: return ParseMysqlGTIDSet(s) case MariaDBFlavor: return ParseMariadbGTIDSet(s) default: return nil, errors.Errorf("invalid flavor %s", flavor) } } type BinlogGTIDEvent interface { GTIDNext() (GTIDSet, error) } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/mysql/mariadb_gtid.go ================================================ package mysql import ( "bytes" "fmt" "sort" "strconv" "strings" "github.com/pingcap/errors" "github.com/siddontang/go-log/log" ) // MariadbGTID represent mariadb gtid, [domain ID]-[server-id]-[sequence] type MariadbGTID struct { DomainID uint32 ServerID uint32 SequenceNumber uint64 } // ParseMariadbGTID parses mariadb gtid, [domain ID]-[server-id]-[sequence] func ParseMariadbGTID(str string) (*MariadbGTID, error) { if len(str) == 0 { return &MariadbGTID{0, 0, 0}, nil } seps := strings.Split(str, "-") gtid := new(MariadbGTID) if len(seps) != 3 { return gtid, errors.Errorf("invalid Mariadb GTID %v, must domain-server-sequence", str) } domainID, err := strconv.ParseUint(seps[0], 10, 32) if err != nil { return gtid, errors.Errorf("invalid MariaDB GTID Domain ID (%v): %v", seps[0], err) } serverID, err := strconv.ParseUint(seps[1], 10, 32) if err != nil { return gtid, errors.Errorf("invalid MariaDB GTID Server ID (%v): %v", seps[1], err) } sequenceID, err := strconv.ParseUint(seps[2], 10, 64) if err != nil { return gtid, errors.Errorf("invalid MariaDB GTID Sequence number (%v): %v", seps[2], err) } return &MariadbGTID{ DomainID: uint32(domainID), ServerID: uint32(serverID), SequenceNumber: sequenceID}, nil } func (gtid *MariadbGTID) String() string { if gtid.DomainID == 0 && gtid.ServerID == 0 && gtid.SequenceNumber == 0 { return "" } return fmt.Sprintf("%d-%d-%d", gtid.DomainID, gtid.ServerID, gtid.SequenceNumber) } // Contain return whether one mariadb gtid covers another mariadb gtid func (gtid *MariadbGTID) Contain(other *MariadbGTID) bool { return gtid.DomainID == other.DomainID && gtid.SequenceNumber >= other.SequenceNumber } // Clone clones a mariadb gtid func (gtid *MariadbGTID) Clone() *MariadbGTID { o := new(MariadbGTID) *o = *gtid return o } func (gtid *MariadbGTID) forward(newer *MariadbGTID) error { if newer.DomainID != gtid.DomainID { return errors.Errorf("%s is not same with doamin of %s", newer, gtid) } /* Here's a simplified example of binlog events. Although I think one domain should have only one update at same time, we can't limit the user's usage. we just output a warn log and let it go on | mysqld-bin.000001 | 1453 | Gtid | 112 | 1495 | BEGIN GTID 0-112-6 | | mysqld-bin.000001 | 1624 | Xid | 112 | 1655 | COMMIT xid=74 | | mysqld-bin.000001 | 1655 | Gtid | 112 | 1697 | BEGIN GTID 0-112-7 | | mysqld-bin.000001 | 1826 | Xid | 112 | 1857 | COMMIT xid=75 | | mysqld-bin.000001 | 1857 | Gtid | 111 | 1899 | BEGIN GTID 0-111-5 | | mysqld-bin.000001 | 1981 | Xid | 111 | 2012 | COMMIT xid=77 | | mysqld-bin.000001 | 2012 | Gtid | 112 | 2054 | BEGIN GTID 0-112-8 | | mysqld-bin.000001 | 2184 | Xid | 112 | 2215 | COMMIT xid=116 | | mysqld-bin.000001 | 2215 | Gtid | 111 | 2257 | BEGIN GTID 0-111-6 | */ if newer.SequenceNumber <= gtid.SequenceNumber { log.Warnf("out of order binlog appears with gtid %s vs current position gtid %s", newer, gtid) } gtid.ServerID = newer.ServerID gtid.SequenceNumber = newer.SequenceNumber return nil } // MariadbGTIDSet is a set of mariadb gtid type MariadbGTIDSet struct { Sets map[uint32]map[uint32]*MariadbGTID } // ParseMariadbGTIDSet parses str into mariadb gtid sets func ParseMariadbGTIDSet(str string) (GTIDSet, error) { s := new(MariadbGTIDSet) s.Sets = make(map[uint32]map[uint32]*MariadbGTID) if str == "" { return s, nil } err := s.Update(str) if err != nil { return nil, err } return s, nil } // AddSet adds mariadb gtid into mariadb gtid set func (s *MariadbGTIDSet) AddSet(gtid *MariadbGTID) error { if gtid == nil { return nil } if serverSets, ok := s.Sets[gtid.DomainID]; !ok { s.Sets[gtid.DomainID] = map[uint32]*MariadbGTID{ gtid.ServerID: gtid, } } else if o, ok := serverSets[gtid.ServerID]; !ok { serverSets[gtid.ServerID] = gtid } else { err := o.forward(gtid) if err != nil { return errors.Trace(err) } } return nil } // Update updates mariadb gtid set func (s *MariadbGTIDSet) Update(GTIDStr string) error { sp := strings.Split(GTIDStr, ",") //todo, handle redundant same uuid for i := 0; i < len(sp); i++ { gtid, err := ParseMariadbGTID(sp[i]) if err != nil { return errors.Trace(err) } err = s.AddSet(gtid) if err != nil { return errors.Trace(err) } } return nil } func (s *MariadbGTIDSet) String() string { sets := make([]string, 0, len(s.Sets)) for _, set := range s.Sets { for _, gtid := range set { sets = append(sets, gtid.String()) } } sort.Strings(sets) return strings.Join(sets, ",") } // Encode encodes mariadb gtid set func (s *MariadbGTIDSet) Encode() []byte { var buf bytes.Buffer sep := "" for _, set := range s.Sets { for _, gtid := range set { buf.WriteString(sep) buf.WriteString(gtid.String()) sep = "," } } return buf.Bytes() } // Clone clones a mariadb gtid set func (s *MariadbGTIDSet) Clone() GTIDSet { clone := &MariadbGTIDSet{ Sets: make(map[uint32]map[uint32]*MariadbGTID), } for domainID, set := range s.Sets { clone.Sets[domainID] = make(map[uint32]*MariadbGTID) for serverID, gtid := range set { clone.Sets[domainID][serverID] = gtid.Clone() } } return clone } // Equal returns true if two mariadb gtid set is same, otherwise return false func (s *MariadbGTIDSet) Equal(o GTIDSet) bool { other, ok := o.(*MariadbGTIDSet) if !ok { return false } if len(other.Sets) != len(s.Sets) { return false } for domainID, set := range other.Sets { serverSet, ok := s.Sets[domainID] if !ok { return false } if len(serverSet) != len(set) { return false } for serverID, gtid := range set { if o, ok := serverSet[serverID]; !ok { return false } else if *gtid != *o { return false } } } return true } // Contain return whether one mariadb gtid set covers another mariadb gtid set func (s *MariadbGTIDSet) Contain(o GTIDSet) bool { other, ok := o.(*MariadbGTIDSet) if !ok { return false } for doaminID, set := range other.Sets { serverSet, ok := s.Sets[doaminID] if !ok { return false } for serverID, gtid := range set { if o, ok := serverSet[serverID]; !ok { return false } else if !o.Contain(gtid) { return false } } } return true } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/mysql/mysql_gtid.go ================================================ package mysql import ( "bytes" "encoding/binary" "fmt" "io" "math" "sort" "strconv" "strings" "github.com/go-mysql-org/go-mysql/utils" "github.com/google/uuid" "github.com/pingcap/errors" ) // Like MySQL GTID Interval struct, [start, stop), left closed and right open // See MySQL rpl_gtid.h type Interval struct { // The first GID of this interval. Start int64 // The first GID after this interval. Stop int64 } // Interval is [start, stop), but the GTID string's format is [n] or [n1-n2], closed interval func parseInterval(str string) (i Interval, err error) { p := strings.Split(str, "-") switch len(p) { case 1: i.Start, err = strconv.ParseInt(p[0], 10, 64) i.Stop = i.Start + 1 case 2: i.Start, err = strconv.ParseInt(p[0], 10, 64) if err == nil { i.Stop, err = strconv.ParseInt(p[1], 10, 64) i.Stop++ } default: err = errors.Errorf("invalid interval format, must n[-n]") } if err != nil { return } if i.Stop <= i.Start { err = errors.Errorf("invalid interval format, must n[-n] and the end must >= start") } return } func (i Interval) String() string { if i.Stop == i.Start+1 { return fmt.Sprintf("%d", i.Start) } else { return fmt.Sprintf("%d-%d", i.Start, i.Stop-1) } } type IntervalSlice []Interval func (s IntervalSlice) Len() int { return len(s) } func (s IntervalSlice) Less(i, j int) bool { if s[i].Start < s[j].Start { return true } else if s[i].Start > s[j].Start { return false } else { return s[i].Stop < s[j].Stop } } func (s IntervalSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s IntervalSlice) Sort() { sort.Sort(s) } func (s IntervalSlice) Normalize() IntervalSlice { var n IntervalSlice if len(s) == 0 { return n } s.Sort() n = append(n, s[0]) for i := 1; i < len(s); i++ { last := n[len(n)-1] if s[i].Start > last.Stop { n = append(n, s[i]) continue } else { stop := s[i].Stop if last.Stop > stop { stop = last.Stop } n[len(n)-1] = Interval{last.Start, stop} } } return n } func min(a, b int64) int64 { if a < b { return a } return b } func max(a, b int64) int64 { if a > b { return a } return b } func (s *IntervalSlice) InsertInterval(interval Interval) { var ( count int i int ) *s = append(*s, interval) total := len(*s) for i = total - 1; i > 0; i-- { if (*s)[i].Stop < (*s)[i-1].Start { (*s)[i], (*s)[i-1] = (*s)[i-1], (*s)[i] } else if (*s)[i].Start > (*s)[i-1].Stop { break } else { (*s)[i-1].Start = min((*s)[i-1].Start, (*s)[i].Start) (*s)[i-1].Stop = max((*s)[i-1].Stop, (*s)[i].Stop) count++ } } if count > 0 { i++ if i+count < total { copy((*s)[i:], (*s)[i+count:]) } *s = (*s)[:total-count] } } // Contain returns true if sub in s func (s IntervalSlice) Contain(sub IntervalSlice) bool { j := 0 for i := 0; i < len(sub); i++ { for ; j < len(s); j++ { if sub[i].Start > s[j].Stop { continue } else { break } } if j == len(s) { return false } if sub[i].Start < s[j].Start || sub[i].Stop > s[j].Stop { return false } } return true } func (s IntervalSlice) Equal(o IntervalSlice) bool { if len(s) != len(o) { return false } for i := 0; i < len(s); i++ { if s[i].Start != o[i].Start || s[i].Stop != o[i].Stop { return false } } return true } func (s IntervalSlice) Compare(o IntervalSlice) int { if s.Equal(o) { return 0 } else if s.Contain(o) { return 1 } else { return -1 } } // Refer http://dev.mysql.com/doc/refman/5.6/en/replication-gtids-concepts.html type UUIDSet struct { SID uuid.UUID Intervals IntervalSlice } func ParseUUIDSet(str string) (*UUIDSet, error) { str = strings.TrimSpace(str) sep := strings.Split(str, ":") if len(sep) < 2 { return nil, errors.Errorf("invalid GTID format, must UUID:interval[:interval]") } var err error s := new(UUIDSet) if s.SID, err = uuid.Parse(sep[0]); err != nil { return nil, errors.Trace(err) } // Handle interval for i := 1; i < len(sep); i++ { if in, err := parseInterval(sep[i]); err != nil { return nil, errors.Trace(err) } else { s.Intervals = append(s.Intervals, in) } } s.Intervals = s.Intervals.Normalize() return s, nil } func NewUUIDSet(sid uuid.UUID, in ...Interval) *UUIDSet { s := new(UUIDSet) s.SID = sid s.Intervals = in s.Intervals = s.Intervals.Normalize() return s } func (s *UUIDSet) Contain(sub *UUIDSet) bool { if s.SID != sub.SID { return false } return s.Intervals.Contain(sub.Intervals) } func (s *UUIDSet) Bytes() []byte { var buf bytes.Buffer buf.WriteString(s.SID.String()) for _, i := range s.Intervals { buf.WriteString(":") buf.WriteString(i.String()) } return buf.Bytes() } func (s *UUIDSet) AddInterval(in IntervalSlice) { s.Intervals = append(s.Intervals, in...) s.Intervals = s.Intervals.Normalize() } func (s *UUIDSet) MinusInterval(in IntervalSlice) { var n IntervalSlice in = in.Normalize() i, j := 0, 0 var minuend Interval var subtrahend Interval for i < len(s.Intervals) { if minuend.Stop != s.Intervals[i].Stop { // `i` changed? minuend = s.Intervals[i] } if j < len(in) { subtrahend = in[j] } else { subtrahend = Interval{math.MaxInt64, math.MaxInt64} } if minuend.Stop <= subtrahend.Start { // no overlapping n = append(n, minuend) i++ } else if minuend.Start >= subtrahend.Stop { // no overlapping j++ } else { if minuend.Start < subtrahend.Start && minuend.Stop <= subtrahend.Stop { n = append(n, Interval{minuend.Start, subtrahend.Start}) i++ } else if minuend.Start >= subtrahend.Start && minuend.Stop > subtrahend.Stop { minuend = Interval{subtrahend.Stop, minuend.Stop} j++ } else if minuend.Start >= subtrahend.Start && minuend.Stop <= subtrahend.Stop { // minuend is completely removed i++ } else if minuend.Start < subtrahend.Start && minuend.Stop > subtrahend.Stop { n = append(n, Interval{minuend.Start, subtrahend.Start}) minuend = Interval{subtrahend.Stop, minuend.Stop} j++ } else { panic("should never be here") } } } s.Intervals = n.Normalize() } func (s *UUIDSet) String() string { return utils.ByteSliceToString(s.Bytes()) } func (s *UUIDSet) encode(w io.Writer) { b, _ := s.SID.MarshalBinary() _, _ = w.Write(b) n := int64(len(s.Intervals)) _ = binary.Write(w, binary.LittleEndian, n) for _, i := range s.Intervals { _ = binary.Write(w, binary.LittleEndian, i.Start) _ = binary.Write(w, binary.LittleEndian, i.Stop) } } func (s *UUIDSet) Encode() []byte { var buf bytes.Buffer s.encode(&buf) return buf.Bytes() } func (s *UUIDSet) decode(data []byte) (int, error) { if len(data) < 24 { return 0, errors.Errorf("invalid uuid set buffer, less 24") } pos := 0 var err error if s.SID, err = uuid.FromBytes(data[0:16]); err != nil { return 0, err } pos += 16 n := int64(binary.LittleEndian.Uint64(data[pos : pos+8])) pos += 8 if len(data) < int(16*n)+pos { return 0, errors.Errorf("invalid uuid set buffer, must %d, but %d", pos+int(16*n), len(data)) } s.Intervals = make([]Interval, 0, n) var in Interval for i := int64(0); i < n; i++ { in.Start = int64(binary.LittleEndian.Uint64(data[pos : pos+8])) pos += 8 in.Stop = int64(binary.LittleEndian.Uint64(data[pos : pos+8])) pos += 8 s.Intervals = append(s.Intervals, in) } return pos, nil } func (s *UUIDSet) Decode(data []byte) error { n, err := s.decode(data) if n != len(data) { return errors.Errorf("invalid uuid set buffer, must %d, but %d", n, len(data)) } return err } func (s *UUIDSet) Clone() *UUIDSet { clone := new(UUIDSet) clone.SID = s.SID clone.Intervals = make([]Interval, len(s.Intervals)) copy(clone.Intervals, s.Intervals) return clone } type MysqlGTIDSet struct { Sets map[string]*UUIDSet } var _ GTIDSet = &MysqlGTIDSet{} func ParseMysqlGTIDSet(str string) (GTIDSet, error) { s := new(MysqlGTIDSet) s.Sets = make(map[string]*UUIDSet) if str == "" { return s, nil } sp := strings.Split(str, ",") //todo, handle redundant same uuid for i := 0; i < len(sp); i++ { if set, err := ParseUUIDSet(sp[i]); err != nil { return nil, errors.Trace(err) } else { s.AddSet(set) } } return s, nil } func DecodeMysqlGTIDSet(data []byte) (*MysqlGTIDSet, error) { s := new(MysqlGTIDSet) if len(data) < 8 { return nil, errors.Errorf("invalid gtid set buffer, less 4") } n := int(binary.LittleEndian.Uint64(data)) s.Sets = make(map[string]*UUIDSet, n) pos := 8 for i := 0; i < n; i++ { set := new(UUIDSet) if n, err := set.decode(data[pos:]); err != nil { return nil, errors.Trace(err) } else { pos += n s.AddSet(set) } } return s, nil } func (s *MysqlGTIDSet) AddSet(set *UUIDSet) { if set == nil { return } sid := set.SID.String() o, ok := s.Sets[sid] if ok { o.AddInterval(set.Intervals) } else { s.Sets[sid] = set } } func (s *MysqlGTIDSet) MinusSet(set *UUIDSet) { if set == nil { return } sid := set.SID.String() uuidSet, ok := s.Sets[sid] if ok { uuidSet.MinusInterval(set.Intervals) if uuidSet.Intervals == nil { delete(s.Sets, sid) } } } func (s *MysqlGTIDSet) Update(GTIDStr string) error { gtidSet, err := ParseMysqlGTIDSet(GTIDStr) if err != nil { return err } for _, uuidSet := range gtidSet.(*MysqlGTIDSet).Sets { s.AddSet(uuidSet) } return nil } func (s *MysqlGTIDSet) AddGTID(uuid uuid.UUID, gno int64) { sid := uuid.String() o, ok := s.Sets[sid] if ok { o.Intervals.InsertInterval(Interval{gno, gno + 1}) } else { s.Sets[sid] = &UUIDSet{uuid, IntervalSlice{Interval{gno, gno + 1}}} } } func (s *MysqlGTIDSet) Add(addend MysqlGTIDSet) error { for _, uuidSet := range addend.Sets { s.AddSet(uuidSet) } return nil } func (s *MysqlGTIDSet) Minus(subtrahend MysqlGTIDSet) error { for _, uuidSet := range subtrahend.Sets { s.MinusSet(uuidSet) } return nil } func (s *MysqlGTIDSet) Contain(o GTIDSet) bool { sub, ok := o.(*MysqlGTIDSet) if !ok { return false } for key, set := range sub.Sets { o, ok := s.Sets[key] if !ok { return false } if !o.Contain(set) { return false } } return true } func (s *MysqlGTIDSet) Equal(o GTIDSet) bool { sub, ok := o.(*MysqlGTIDSet) if !ok { return false } if len(sub.Sets) != len(s.Sets) { return false } for key, set := range sub.Sets { o, ok := s.Sets[key] if !ok { return false } if !o.Intervals.Equal(set.Intervals) { return false } } return true } func (s *MysqlGTIDSet) String() string { // there is only one element in gtid set if len(s.Sets) == 1 { for _, set := range s.Sets { return set.String() } } // sort multi set var buf bytes.Buffer sets := make([]string, 0, len(s.Sets)) for _, set := range s.Sets { sets = append(sets, set.String()) } sort.Strings(sets) sep := "" for _, set := range sets { buf.WriteString(sep) buf.WriteString(set) sep = "," } return utils.ByteSliceToString(buf.Bytes()) } func (s *MysqlGTIDSet) Encode() []byte { var buf bytes.Buffer _ = binary.Write(&buf, binary.LittleEndian, uint64(len(s.Sets))) for i := range s.Sets { s.Sets[i].encode(&buf) } return buf.Bytes() } func (gtid *MysqlGTIDSet) Clone() GTIDSet { clone := &MysqlGTIDSet{ Sets: make(map[string]*UUIDSet), } for sid, uuidSet := range gtid.Sets { clone.Sets[sid] = uuidSet.Clone() } return clone } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/mysql/parse_binary.go ================================================ package mysql import ( "encoding/binary" "math" ) func ParseBinaryInt8(data []byte) int8 { return int8(data[0]) } func ParseBinaryUint8(data []byte) uint8 { return data[0] } func ParseBinaryInt16(data []byte) int16 { return int16(binary.LittleEndian.Uint16(data)) } func ParseBinaryUint16(data []byte) uint16 { return binary.LittleEndian.Uint16(data) } func ParseBinaryInt24(data []byte) int32 { u32 := ParseBinaryUint24(data) if u32&0x00800000 != 0 { u32 |= 0xFF000000 } return int32(u32) } func ParseBinaryUint24(data []byte) uint32 { return uint32(data[0]) | uint32(data[1])<<8 | uint32(data[2])<<16 } func ParseBinaryInt32(data []byte) int32 { return int32(binary.LittleEndian.Uint32(data)) } func ParseBinaryUint32(data []byte) uint32 { return binary.LittleEndian.Uint32(data) } func ParseBinaryInt64(data []byte) int64 { return int64(binary.LittleEndian.Uint64(data)) } func ParseBinaryUint64(data []byte) uint64 { return binary.LittleEndian.Uint64(data) } func ParseBinaryFloat32(data []byte) float32 { return math.Float32frombits(binary.LittleEndian.Uint32(data)) } func ParseBinaryFloat64(data []byte) float64 { return math.Float64frombits(binary.LittleEndian.Uint64(data)) } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/mysql/position.go ================================================ package mysql import ( "fmt" "strconv" "strings" ) // Position for binlog filename + position based replication type Position struct { Name string Pos uint32 } // Compare the position information between the p and o, // if p > o return 1 means the position of p is further back than o. func (p Position) Compare(o Position) int { // First compare binlog name nameCmp := CompareBinlogFileName(p.Name, o.Name) if nameCmp != 0 { return nameCmp } // Same binlog file, compare position if p.Pos > o.Pos { return 1 } else if p.Pos < o.Pos { return -1 } else { return 0 } } func (p Position) String() string { return fmt.Sprintf("(%s, %d)", p.Name, p.Pos) } // CompareBinlogFileName compares the binlog filename of a and b. // if a>b will return 1. // if b>a will return -1. func CompareBinlogFileName(a, b string) int { // sometimes it's convenient to construct a `Position` literal with no `Name` if a == "" && b == "" { return 0 } else if a == "" { return -1 } else if b == "" { return 1 } splitBinlogName := func(n string) (string, int) { // mysqld appends a numeric extension to the binary log base name to generate binary log file names // ... // If you supply an extension in the log name (for example, --log-bin=base_name.extension), // the extension is silently removed and ignored. // ref: https://dev.mysql.com/doc/refman/8.0/en/binary-log.html i := strings.LastIndexByte(n, '.') if i == -1 { // try keeping backward compatibility return n, 0 } seq, err := strconv.Atoi(n[i+1:]) if err != nil { panic(fmt.Sprintf("binlog file %s doesn't contain numeric extension", err)) } return n[:i], seq } // get the basename(aBase) and the serial number(aSeq) aBase, aSeq := splitBinlogName(a) bBase, bSeq := splitBinlogName(b) // aBase and bBase generally will be equal if they are both from the same database configuration. if aBase > bBase { return 1 } else if aBase < bBase { return -1 } if aSeq > bSeq { return 1 } else if aSeq < bSeq { return -1 } else { return 0 } } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/mysql/result.go ================================================ package mysql // Result should be created by NewResultWithoutRows or NewResult. The zero value // of Result is invalid. type Result struct { Status uint16 Warnings uint16 InsertId uint64 AffectedRows uint64 *Resultset } func NewResult(resultset *Resultset) *Result { return &Result{ Resultset: resultset, } } func NewResultReserveResultset(fieldCount int) *Result { return &Result{ Resultset: NewResultset(fieldCount), } } type Executer interface { Execute(query string, args ...interface{}) (*Result, error) } func (r *Result) Close() { if r.Resultset != nil { r.Resultset.returnToPool() r.Resultset = nil } } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/mysql/resultset.go ================================================ package mysql import ( "fmt" "strconv" "sync" "github.com/go-mysql-org/go-mysql/utils" "github.com/pingcap/errors" ) type StreamingType int const ( // StreamingNone means there is no streaming StreamingNone StreamingType = iota // StreamingSelect is used with select queries for which each result is // directly returned to the client StreamingSelect // StreamingMultiple is used when multiple queries are given at once // usually in combination with SERVER_MORE_RESULTS_EXISTS flag set StreamingMultiple ) // Resultset should be created with NewResultset to avoid nil pointer and reduce // GC pressure. type Resultset struct { Fields []*Field FieldNames map[string]int Values [][]FieldValue RawPkg []byte RowDatas []RowData Streaming StreamingType StreamingDone bool } var ( resultsetPool = sync.Pool{ New: func() interface{} { return &Resultset{} }, } ) func NewResultset(fieldsCount int) *Resultset { r := resultsetPool.Get().(*Resultset) r.Reset(fieldsCount) return r } func (r *Resultset) returnToPool() { resultsetPool.Put(r) } func (r *Resultset) Reset(fieldsCount int) { r.RawPkg = r.RawPkg[:0] r.Fields = r.Fields[:0] r.Values = r.Values[:0] r.RowDatas = r.RowDatas[:0] if r.FieldNames != nil { clear(r.FieldNames) } else { r.FieldNames = make(map[string]int) } if fieldsCount == 0 { return } if cap(r.Fields) < fieldsCount { r.Fields = make([]*Field, fieldsCount) } else { r.Fields = r.Fields[:fieldsCount] } } // RowNumber is returning the number of rows in the [Resultset]. func (r *Resultset) RowNumber() int { return len(r.Values) } // ColumnNumber is returning the number of fields in the [Resultset]. func (r *Resultset) ColumnNumber() int { return len(r.Fields) } func (r *Resultset) GetValue(row, column int) (interface{}, error) { if row >= len(r.Values) || row < 0 { return nil, errors.Errorf("invalid row index %d", row) } if column >= len(r.Fields) || column < 0 { return nil, errors.Errorf("invalid column index %d", column) } return r.Values[row][column].Value(), nil } func (r *Resultset) NameIndex(name string) (int, error) { if column, ok := r.FieldNames[name]; ok { return column, nil } else { return 0, errors.Errorf("invalid field name %s", name) } } func (r *Resultset) GetValueByName(row int, name string) (interface{}, error) { if column, err := r.NameIndex(name); err != nil { return nil, errors.Trace(err) } else { return r.GetValue(row, column) } } func (r *Resultset) IsNull(row, column int) (bool, error) { d, err := r.GetValue(row, column) if err != nil { return false, err } return d == nil, nil } func (r *Resultset) IsNullByName(row int, name string) (bool, error) { if column, err := r.NameIndex(name); err != nil { return false, err } else { return r.IsNull(row, column) } } func (r *Resultset) GetUint(row, column int) (uint64, error) { d, err := r.GetValue(row, column) if err != nil { return 0, err } switch v := d.(type) { case int: return uint64(v), nil case int8: return uint64(v), nil case int16: return uint64(v), nil case int32: return uint64(v), nil case int64: return uint64(v), nil case uint: return uint64(v), nil case uint8: return uint64(v), nil case uint16: return uint64(v), nil case uint32: return uint64(v), nil case uint64: return v, nil case float32: return uint64(v), nil case float64: return uint64(v), nil case string: return strconv.ParseUint(v, 10, 64) case []byte: return strconv.ParseUint(string(v), 10, 64) case nil: return 0, nil default: return 0, errors.Errorf("data type is %T", v) } } func (r *Resultset) GetUintByName(row int, name string) (uint64, error) { if column, err := r.NameIndex(name); err != nil { return 0, err } else { return r.GetUint(row, column) } } func (r *Resultset) GetInt(row, column int) (int64, error) { d, err := r.GetValue(row, column) if err != nil { return 0, err } switch v := d.(type) { case int: return int64(v), nil case int8: return int64(v), nil case int16: return int64(v), nil case int32: return int64(v), nil case int64: return v, nil case uint: return int64(v), nil case uint8: return int64(v), nil case uint16: return int64(v), nil case uint32: return int64(v), nil case uint64: return int64(v), nil case float32: return int64(v), nil case float64: return int64(v), nil case string: return strconv.ParseInt(v, 10, 64) case []byte: return strconv.ParseInt(string(v), 10, 64) case nil: return 0, nil default: return 0, errors.Errorf("data type is %T", v) } } func (r *Resultset) GetIntByName(row int, name string) (int64, error) { v, err := r.GetUintByName(row, name) if err != nil { return 0, err } return int64(v), nil } func (r *Resultset) GetFloat(row, column int) (float64, error) { d, err := r.GetValue(row, column) if err != nil { return 0, err } switch v := d.(type) { case int: return float64(v), nil case int8: return float64(v), nil case int16: return float64(v), nil case int32: return float64(v), nil case int64: return float64(v), nil case uint: return float64(v), nil case uint8: return float64(v), nil case uint16: return float64(v), nil case uint32: return float64(v), nil case uint64: return float64(v), nil case float32: return float64(v), nil case float64: return v, nil case string: return strconv.ParseFloat(v, 64) case []byte: return strconv.ParseFloat(string(v), 64) case nil: return 0, nil default: return 0, errors.Errorf("data type is %T", v) } } func (r *Resultset) GetFloatByName(row int, name string) (float64, error) { if column, err := r.NameIndex(name); err != nil { return 0, err } else { return r.GetFloat(row, column) } } func (r *Resultset) GetString(row, column int) (string, error) { d, err := r.GetValue(row, column) if err != nil { return "", err } switch v := d.(type) { case string: return v, nil case []byte: return utils.ByteSliceToString(v), nil case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: return fmt.Sprintf("%d", v), nil case float32: return strconv.FormatFloat(float64(v), 'f', -1, 64), nil case float64: return strconv.FormatFloat(v, 'f', -1, 64), nil case nil: return "", nil default: return "", errors.Errorf("data type is %T", v) } } func (r *Resultset) GetStringByName(row int, name string) (string, error) { if column, err := r.NameIndex(name); err != nil { return "", err } else { return r.GetString(row, column) } } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/mysql/resultset_helper.go ================================================ package mysql import ( "bytes" "encoding/binary" "math" "strconv" "time" "github.com/pingcap/errors" "github.com/go-mysql-org/go-mysql/utils" ) func FormatTextValue(value interface{}) ([]byte, error) { switch v := value.(type) { case int8: return strconv.AppendInt(nil, int64(v), 10), nil case int16: return strconv.AppendInt(nil, int64(v), 10), nil case int32: return strconv.AppendInt(nil, int64(v), 10), nil case int64: return strconv.AppendInt(nil, v, 10), nil case int: return strconv.AppendInt(nil, int64(v), 10), nil case uint8: return strconv.AppendUint(nil, uint64(v), 10), nil case uint16: return strconv.AppendUint(nil, uint64(v), 10), nil case uint32: return strconv.AppendUint(nil, uint64(v), 10), nil case uint64: return strconv.AppendUint(nil, v, 10), nil case uint: return strconv.AppendUint(nil, uint64(v), 10), nil case float32: return strconv.AppendFloat(nil, float64(v), 'f', -1, 64), nil case float64: return strconv.AppendFloat(nil, v, 'f', -1, 64), nil case []byte: return v, nil case string: return utils.StringToByteSlice(v), nil case time.Time: return utils.StringToByteSlice(v.Format(time.DateTime)), nil case nil: return nil, nil default: return nil, errors.Errorf("invalid type %T", value) } } func toBinaryDateTime(t time.Time) ([]byte, error) { var buf bytes.Buffer if t.IsZero() { return nil, nil } year, month, day := t.Year(), t.Month(), t.Day() hour, min, sec := t.Hour(), t.Minute(), t.Second() nanosec := t.Nanosecond() if nanosec > 0 { buf.WriteByte(byte(11)) _ = binary.Write(&buf, binary.LittleEndian, uint16(year)) buf.WriteByte(byte(month)) buf.WriteByte(byte(day)) buf.WriteByte(byte(hour)) buf.WriteByte(byte(min)) buf.WriteByte(byte(sec)) _ = binary.Write(&buf, binary.LittleEndian, uint32(nanosec/1000)) } else if hour > 0 || min > 0 || sec > 0 { buf.WriteByte(byte(7)) _ = binary.Write(&buf, binary.LittleEndian, uint16(year)) buf.WriteByte(byte(month)) buf.WriteByte(byte(day)) buf.WriteByte(byte(hour)) buf.WriteByte(byte(min)) buf.WriteByte(byte(sec)) } else { buf.WriteByte(byte(4)) _ = binary.Write(&buf, binary.LittleEndian, uint16(year)) buf.WriteByte(byte(month)) buf.WriteByte(byte(day)) } return buf.Bytes(), nil } func formatBinaryValue(value interface{}) ([]byte, error) { switch v := value.(type) { case int8: return Uint64ToBytes(uint64(v)), nil case int16: return Uint64ToBytes(uint64(v)), nil case int32: return Uint64ToBytes(uint64(v)), nil case int64: return Uint64ToBytes(uint64(v)), nil case int: return Uint64ToBytes(uint64(v)), nil case uint8: return Uint64ToBytes(uint64(v)), nil case uint16: return Uint64ToBytes(uint64(v)), nil case uint32: return Uint64ToBytes(uint64(v)), nil case uint64: return Uint64ToBytes(v), nil case uint: return Uint64ToBytes(uint64(v)), nil case float32: return Uint64ToBytes(math.Float64bits(float64(v))), nil case float64: return Uint64ToBytes(math.Float64bits(v)), nil case []byte: return v, nil case string: return utils.StringToByteSlice(v), nil case time.Time: return toBinaryDateTime(v) default: return nil, errors.Errorf("invalid type %T", value) } } func fieldType(value interface{}) (typ uint8, err error) { switch value.(type) { case int8, int16, int32, int64, int: typ = MYSQL_TYPE_LONGLONG case uint8, uint16, uint32, uint64, uint: typ = MYSQL_TYPE_LONGLONG case float32, float64: typ = MYSQL_TYPE_DOUBLE case string, []byte: typ = MYSQL_TYPE_VAR_STRING case time.Time: typ = MYSQL_TYPE_DATETIME case nil: typ = MYSQL_TYPE_NULL default: err = errors.Errorf("unsupport type %T for resultset", value) } return } func formatField(field *Field, value interface{}) error { switch value.(type) { case int8, int16, int32, int64, int: field.Charset = 63 field.Flag = BINARY_FLAG | NOT_NULL_FLAG case uint8, uint16, uint32, uint64, uint: field.Charset = 63 field.Flag = BINARY_FLAG | NOT_NULL_FLAG | UNSIGNED_FLAG case float32, float64: field.Charset = 63 field.Flag = BINARY_FLAG | NOT_NULL_FLAG case string, []byte, time.Time: field.Charset = 33 case nil: field.Charset = 33 default: return errors.Errorf("unsupport type %T for resultset", value) } return nil } func BuildSimpleTextResultset(names []string, values [][]interface{}) (*Resultset, error) { r := NewResultset(len(names)) var b []byte if len(values) == 0 { for i, name := range names { r.Fields[i] = &Field{Name: utils.StringToByteSlice(name), Charset: 33, Type: MYSQL_TYPE_NULL} } return r, nil } for i, vs := range values { if len(vs) != len(r.Fields) { return nil, errors.Errorf("row %d has %d column not equal %d", i, len(vs), len(r.Fields)) } var row []byte for j, value := range vs { typ, err := fieldType(value) if err != nil { return nil, errors.Trace(err) } if r.Fields[j] == nil { r.Fields[j] = &Field{Name: utils.StringToByteSlice(names[j]), Type: typ} err = formatField(r.Fields[j], value) if err != nil { return nil, errors.Trace(err) } } else if typ != r.Fields[j].Type { // we got another type in the same column. in general, we treat it as an error, except // the case, when old value was null, and the new one isn't null, so we can update // type info for fields. oldIsNull, newIsNull := r.Fields[j].Type == MYSQL_TYPE_NULL, typ == MYSQL_TYPE_NULL if oldIsNull && !newIsNull { // old is null, new isn't, update type info. r.Fields[j].Type = typ err = formatField(r.Fields[j], value) if err != nil { return nil, errors.Trace(err) } } else if !oldIsNull && !newIsNull { // different non-null types, that's an error. return nil, errors.Errorf("row types aren't consistent") } } b, err = FormatTextValue(value) if err != nil { return nil, errors.Trace(err) } if b == nil { // NULL value is encoded as 0xfb here (without additional info about length) row = append(row, 0xfb) } else { row = append(row, PutLengthEncodedString(b)...) } } r.RowDatas = append(r.RowDatas, row) } return r, nil } func BuildSimpleBinaryResultset(names []string, values [][]interface{}) (*Resultset, error) { r := NewResultset(len(names)) var b []byte bitmapLen := (len(names) + 7 + 2) >> 3 for i, vs := range values { if len(vs) != len(r.Fields) { return nil, errors.Errorf("row %d has %d column not equal %d", i, len(vs), len(r.Fields)) } var row []byte nullBitmap := make([]byte, bitmapLen) row = append(row, 0) row = append(row, nullBitmap...) for j, value := range vs { typ, err := fieldType(value) if err != nil { return nil, errors.Trace(err) } if i == 0 { field := &Field{Type: typ} r.Fields[j] = field field.Name = utils.StringToByteSlice(names[j]) if err = formatField(field, value); err != nil { return nil, errors.Trace(err) } } if value == nil { nullBitmap[(j+2)/8] |= 1 << (uint(j+2) % 8) continue } b, err = formatBinaryValue(value) if err != nil { return nil, errors.Trace(err) } if r.Fields[j].Type == MYSQL_TYPE_VAR_STRING { row = append(row, PutLengthEncodedString(b)...) } else { row = append(row, b...) } } copy(row[1:], nullBitmap) r.RowDatas = append(r.RowDatas, row) } return r, nil } func BuildSimpleResultset(names []string, values [][]interface{}, binary bool) (*Resultset, error) { if binary { return BuildSimpleBinaryResultset(names, values) } else { return BuildSimpleTextResultset(names, values) } } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/mysql/rowdata.go ================================================ package mysql import ( "strconv" "github.com/go-mysql-org/go-mysql/utils" "github.com/pingcap/errors" ) type RowData []byte func (p RowData) Parse(f []*Field, binary bool, dst []FieldValue) ([]FieldValue, error) { if binary { return p.ParseBinary(f, dst) } else { return p.ParseText(f, dst) } } func (p RowData) ParseText(f []*Field, dst []FieldValue) ([]FieldValue, error) { for len(dst) < len(f) { dst = append(dst, FieldValue{}) } data := dst[:len(f)] var err error var v []byte var isNull bool var pos, n int for i := range f { v, isNull, n, err = LengthEncodedString(p[pos:]) if err != nil { return nil, errors.Trace(err) } pos += n if isNull { data[i].Type = FieldValueTypeNull } else { isUnsigned := f[i].Flag&UNSIGNED_FLAG != 0 switch f[i].Type { case MYSQL_TYPE_TINY, MYSQL_TYPE_SHORT, MYSQL_TYPE_INT24, MYSQL_TYPE_LONGLONG, MYSQL_TYPE_LONG, MYSQL_TYPE_YEAR: if isUnsigned { var val uint64 data[i].Type = FieldValueTypeUnsigned val, err = strconv.ParseUint(utils.ByteSliceToString(v), 10, 64) data[i].value = val } else { var val int64 data[i].Type = FieldValueTypeSigned val, err = strconv.ParseInt(utils.ByteSliceToString(v), 10, 64) data[i].value = utils.Int64ToUint64(val) } case MYSQL_TYPE_FLOAT, MYSQL_TYPE_DOUBLE: var val float64 data[i].Type = FieldValueTypeFloat val, err = strconv.ParseFloat(utils.ByteSliceToString(v), 64) data[i].value = utils.Float64ToUint64(val) default: data[i].Type = FieldValueTypeString data[i].str = append(data[i].str[:0], v...) } if err != nil { return nil, errors.Trace(err) } } } return data, nil } // ParseBinary parses the binary format of data // see https://dev.mysql.com/doc/internals/en/binary-protocol-value.html func (p RowData) ParseBinary(f []*Field, dst []FieldValue) ([]FieldValue, error) { for len(dst) < len(f) { dst = append(dst, FieldValue{}) } data := dst[:len(f)] if p[0] != OK_HEADER { return nil, ErrMalformPacket } pos := 1 + ((len(f) + 7 + 2) >> 3) nullBitmap := p[1:pos] var isNull bool var n int var err error var v []byte for i := range data { if nullBitmap[(i+2)/8]&(1<<(uint(i+2)%8)) > 0 { data[i].Type = FieldValueTypeNull continue } isUnsigned := f[i].Flag&UNSIGNED_FLAG != 0 switch f[i].Type { case MYSQL_TYPE_NULL: data[i].Type = FieldValueTypeNull continue case MYSQL_TYPE_TINY: if isUnsigned { v := ParseBinaryUint8(p[pos : pos+1]) data[i].Type = FieldValueTypeUnsigned data[i].value = uint64(v) } else { v := ParseBinaryInt8(p[pos : pos+1]) data[i].Type = FieldValueTypeSigned data[i].value = utils.Int64ToUint64(int64(v)) } pos++ continue case MYSQL_TYPE_SHORT, MYSQL_TYPE_YEAR: if isUnsigned { v := ParseBinaryUint16(p[pos : pos+2]) data[i].Type = FieldValueTypeUnsigned data[i].value = uint64(v) } else { v := ParseBinaryInt16(p[pos : pos+2]) data[i].Type = FieldValueTypeSigned data[i].value = utils.Int64ToUint64(int64(v)) } pos += 2 continue case MYSQL_TYPE_INT24, MYSQL_TYPE_LONG: if isUnsigned { v := ParseBinaryUint32(p[pos : pos+4]) data[i].Type = FieldValueTypeUnsigned data[i].value = uint64(v) } else { v := ParseBinaryInt32(p[pos : pos+4]) data[i].Type = FieldValueTypeSigned data[i].value = utils.Int64ToUint64(int64(v)) } pos += 4 continue case MYSQL_TYPE_LONGLONG: if isUnsigned { v := ParseBinaryUint64(p[pos : pos+8]) data[i].Type = FieldValueTypeUnsigned data[i].value = v } else { v := ParseBinaryInt64(p[pos : pos+8]) data[i].Type = FieldValueTypeSigned data[i].value = utils.Int64ToUint64(v) } pos += 8 continue case MYSQL_TYPE_FLOAT: v := ParseBinaryFloat32(p[pos : pos+4]) data[i].Type = FieldValueTypeFloat data[i].value = utils.Float64ToUint64(float64(v)) pos += 4 continue case MYSQL_TYPE_DOUBLE: v := ParseBinaryFloat64(p[pos : pos+8]) data[i].Type = FieldValueTypeFloat data[i].value = utils.Float64ToUint64(v) pos += 8 continue case MYSQL_TYPE_DECIMAL, MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_VARCHAR, MYSQL_TYPE_BIT, MYSQL_TYPE_ENUM, MYSQL_TYPE_SET, MYSQL_TYPE_TINY_BLOB, MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_BLOB, MYSQL_TYPE_VAR_STRING, MYSQL_TYPE_STRING, MYSQL_TYPE_GEOMETRY, MYSQL_TYPE_JSON: v, isNull, n, err = LengthEncodedString(p[pos:]) pos += n if err != nil { return nil, errors.Trace(err) } if !isNull { data[i].Type = FieldValueTypeString data[i].str = append(data[i].str[:0], v...) continue } else { data[i].Type = FieldValueTypeNull continue } case MYSQL_TYPE_DATE, MYSQL_TYPE_NEWDATE: var num uint64 num, isNull, n = LengthEncodedInt(p[pos:]) pos += n if isNull { data[i].Type = FieldValueTypeNull continue } data[i].Type = FieldValueTypeString data[i].str, err = FormatBinaryDate(int(num), p[pos:]) pos += int(num) if err != nil { return nil, errors.Trace(err) } case MYSQL_TYPE_TIMESTAMP, MYSQL_TYPE_DATETIME: var num uint64 num, isNull, n = LengthEncodedInt(p[pos:]) pos += n if isNull { data[i].Type = FieldValueTypeNull continue } data[i].Type = FieldValueTypeString data[i].str, err = FormatBinaryDateTime(int(num), p[pos:]) pos += int(num) if err != nil { return nil, errors.Trace(err) } case MYSQL_TYPE_TIME: var num uint64 num, isNull, n = LengthEncodedInt(p[pos:]) pos += n if isNull { data[i].Type = FieldValueTypeNull continue } data[i].Type = FieldValueTypeString data[i].str, err = FormatBinaryTime(int(num), p[pos:]) pos += int(num) if err != nil { return nil, errors.Trace(err) } default: return nil, errors.Errorf("Stmt Unknown FieldType %d %s", f[i].Type, f[i].Name) } } return data, nil } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/mysql/state.go ================================================ package mysql const ( DEFAULT_MYSQL_STATE = "HY000" ) var MySQLState = map[uint16]string{ ER_DUP_KEY: "23000", ER_OUTOFMEMORY: "HY001", ER_OUT_OF_SORTMEMORY: "HY001", ER_CON_COUNT_ERROR: "08004", ER_BAD_HOST_ERROR: "08S01", ER_HANDSHAKE_ERROR: "08S01", ER_DBACCESS_DENIED_ERROR: "42000", ER_ACCESS_DENIED_ERROR: "28000", ER_NO_DB_ERROR: "3D000", ER_UNKNOWN_COM_ERROR: "08S01", ER_BAD_NULL_ERROR: "23000", ER_BAD_DB_ERROR: "42000", ER_TABLE_EXISTS_ERROR: "42S01", ER_BAD_TABLE_ERROR: "42S02", ER_NON_UNIQ_ERROR: "23000", ER_SERVER_SHUTDOWN: "08S01", ER_BAD_FIELD_ERROR: "42S22", ER_WRONG_FIELD_WITH_GROUP: "42000", ER_WRONG_SUM_SELECT: "42000", ER_WRONG_GROUP_FIELD: "42000", ER_WRONG_VALUE_COUNT: "21S01", ER_TOO_LONG_IDENT: "42000", ER_DUP_FIELDNAME: "42S21", ER_DUP_KEYNAME: "42000", ER_DUP_ENTRY: "23000", ER_WRONG_FIELD_SPEC: "42000", ER_PARSE_ERROR: "42000", ER_EMPTY_QUERY: "42000", ER_NONUNIQ_TABLE: "42000", ER_INVALID_DEFAULT: "42000", ER_MULTIPLE_PRI_KEY: "42000", ER_TOO_MANY_KEYS: "42000", ER_TOO_MANY_KEY_PARTS: "42000", ER_TOO_LONG_KEY: "42000", ER_KEY_COLUMN_DOES_NOT_EXITS: "42000", ER_BLOB_USED_AS_KEY: "42000", ER_TOO_BIG_FIELDLENGTH: "42000", ER_WRONG_AUTO_KEY: "42000", ER_FORCING_CLOSE: "08S01", ER_IPSOCK_ERROR: "08S01", ER_NO_SUCH_INDEX: "42S12", ER_WRONG_FIELD_TERMINATORS: "42000", ER_BLOBS_AND_NO_TERMINATED: "42000", ER_CANT_REMOVE_ALL_FIELDS: "42000", ER_CANT_DROP_FIELD_OR_KEY: "42000", ER_BLOB_CANT_HAVE_DEFAULT: "42000", ER_WRONG_DB_NAME: "42000", ER_WRONG_TABLE_NAME: "42000", ER_TOO_BIG_SELECT: "42000", ER_UNKNOWN_PROCEDURE: "42000", ER_WRONG_PARAMCOUNT_TO_PROCEDURE: "42000", ER_UNKNOWN_TABLE: "42S02", ER_FIELD_SPECIFIED_TWICE: "42000", ER_UNSUPPORTED_EXTENSION: "42000", ER_TABLE_MUST_HAVE_COLUMNS: "42000", ER_UNKNOWN_CHARACTER_SET: "42000", ER_TOO_BIG_ROWSIZE: "42000", ER_WRONG_OUTER_JOIN: "42000", ER_NULL_COLUMN_IN_INDEX: "42000", ER_PASSWORD_ANONYMOUS_USER: "42000", ER_PASSWORD_NOT_ALLOWED: "42000", ER_PASSWORD_NO_MATCH: "42000", ER_WRONG_VALUE_COUNT_ON_ROW: "21S01", ER_INVALID_USE_OF_NULL: "22004", ER_REGEXP_ERROR: "42000", ER_MIX_OF_GROUP_FUNC_AND_FIELDS: "42000", ER_NONEXISTING_GRANT: "42000", ER_TABLEACCESS_DENIED_ERROR: "42000", ER_COLUMNACCESS_DENIED_ERROR: "42000", ER_ILLEGAL_GRANT_FOR_TABLE: "42000", ER_GRANT_WRONG_HOST_OR_USER: "42000", ER_NO_SUCH_TABLE: "42S02", ER_NONEXISTING_TABLE_GRANT: "42000", ER_NOT_ALLOWED_COMMAND: "42000", ER_SYNTAX_ERROR: "42000", ER_ABORTING_CONNECTION: "08S01", ER_NET_PACKET_TOO_LARGE: "08S01", ER_NET_READ_ERROR_FROM_PIPE: "08S01", ER_NET_FCNTL_ERROR: "08S01", ER_NET_PACKETS_OUT_OF_ORDER: "08S01", ER_NET_UNCOMPRESS_ERROR: "08S01", ER_NET_READ_ERROR: "08S01", ER_NET_READ_INTERRUPTED: "08S01", ER_NET_ERROR_ON_WRITE: "08S01", ER_NET_WRITE_INTERRUPTED: "08S01", ER_TOO_LONG_STRING: "42000", ER_TABLE_CANT_HANDLE_BLOB: "42000", ER_TABLE_CANT_HANDLE_AUTO_INCREMENT: "42000", ER_WRONG_COLUMN_NAME: "42000", ER_WRONG_KEY_COLUMN: "42000", ER_DUP_UNIQUE: "23000", ER_BLOB_KEY_WITHOUT_LENGTH: "42000", ER_PRIMARY_CANT_HAVE_NULL: "42000", ER_TOO_MANY_ROWS: "42000", ER_REQUIRES_PRIMARY_KEY: "42000", ER_KEY_DOES_NOT_EXITS: "42000", ER_CHECK_NO_SUCH_TABLE: "42000", ER_CHECK_NOT_IMPLEMENTED: "42000", ER_CANT_DO_THIS_DURING_AN_TRANSACTION: "25000", ER_NEW_ABORTING_CONNECTION: "08S01", ER_MASTER_NET_READ: "08S01", ER_MASTER_NET_WRITE: "08S01", ER_TOO_MANY_USER_CONNECTIONS: "42000", ER_READ_ONLY_TRANSACTION: "25000", ER_NO_PERMISSION_TO_CREATE_USER: "42000", ER_LOCK_DEADLOCK: "40001", ER_NO_REFERENCED_ROW: "23000", ER_ROW_IS_REFERENCED: "23000", ER_CONNECT_TO_MASTER: "08S01", ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT: "21000", ER_USER_LIMIT_REACHED: "42000", ER_SPECIFIC_ACCESS_DENIED_ERROR: "42000", ER_NO_DEFAULT: "42000", ER_WRONG_VALUE_FOR_VAR: "42000", ER_WRONG_TYPE_FOR_VAR: "42000", ER_CANT_USE_OPTION_HERE: "42000", ER_NOT_SUPPORTED_YET: "42000", ER_WRONG_FK_DEF: "42000", ER_OPERAND_COLUMNS: "21000", ER_SUBQUERY_NO_1_ROW: "21000", ER_ILLEGAL_REFERENCE: "42S22", ER_DERIVED_MUST_HAVE_ALIAS: "42000", ER_SELECT_REDUCED: "01000", ER_TABLENAME_NOT_ALLOWED_HERE: "42000", ER_NOT_SUPPORTED_AUTH_MODE: "08004", ER_SPATIAL_CANT_HAVE_NULL: "42000", ER_COLLATION_CHARSET_MISMATCH: "42000", ER_WARN_TOO_FEW_RECORDS: "01000", ER_WARN_TOO_MANY_RECORDS: "01000", ER_WARN_NULL_TO_NOTNULL: "22004", ER_WARN_DATA_OUT_OF_RANGE: "22003", WARN_DATA_TRUNCATED: "01000", ER_WRONG_NAME_FOR_INDEX: "42000", ER_WRONG_NAME_FOR_CATALOG: "42000", ER_UNKNOWN_STORAGE_ENGINE: "42000", ER_TRUNCATED_WRONG_VALUE: "22007", ER_SP_NO_RECURSIVE_CREATE: "2F003", ER_SP_ALREADY_EXISTS: "42000", ER_SP_DOES_NOT_EXIST: "42000", ER_SP_LILABEL_MISMATCH: "42000", ER_SP_LABEL_REDEFINE: "42000", ER_SP_LABEL_MISMATCH: "42000", ER_SP_UNINIT_VAR: "01000", ER_SP_BADSELECT: "0A000", ER_SP_BADRETURN: "42000", ER_SP_BADSTATEMENT: "0A000", ER_UPDATE_LOG_DEPRECATED_IGNORED: "42000", ER_UPDATE_LOG_DEPRECATED_TRANSLATED: "42000", ER_QUERY_INTERRUPTED: "70100", ER_SP_WRONG_NO_OF_ARGS: "42000", ER_SP_COND_MISMATCH: "42000", ER_SP_NORETURN: "42000", ER_SP_NORETURNEND: "2F005", ER_SP_BAD_CURSOR_QUERY: "42000", ER_SP_BAD_CURSOR_SELECT: "42000", ER_SP_CURSOR_MISMATCH: "42000", ER_SP_CURSOR_ALREADY_OPEN: "24000", ER_SP_CURSOR_NOT_OPEN: "24000", ER_SP_UNDECLARED_VAR: "42000", ER_SP_FETCH_NO_DATA: "02000", ER_SP_DUP_PARAM: "42000", ER_SP_DUP_VAR: "42000", ER_SP_DUP_COND: "42000", ER_SP_DUP_CURS: "42000", ER_SP_SUBSELECT_NYI: "0A000", ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG: "0A000", ER_SP_VARCOND_AFTER_CURSHNDLR: "42000", ER_SP_CURSOR_AFTER_HANDLER: "42000", ER_SP_CASE_NOT_FOUND: "20000", ER_DIVISION_BY_ZERO: "22012", ER_ILLEGAL_VALUE_FOR_TYPE: "22007", ER_PROCACCESS_DENIED_ERROR: "42000", ER_XAER_NOTA: "XAE04", ER_XAER_INVAL: "XAE05", ER_XAER_RMFAIL: "XAE07", ER_XAER_OUTSIDE: "XAE09", ER_XAER_RMERR: "XAE03", ER_XA_RBROLLBACK: "XA100", ER_NONEXISTING_PROC_GRANT: "42000", ER_DATA_TOO_LONG: "22001", ER_SP_BAD_SQLSTATE: "42000", ER_CANT_CREATE_USER_WITH_GRANT: "42000", ER_SP_DUP_HANDLER: "42000", ER_SP_NOT_VAR_ARG: "42000", ER_SP_NO_RETSET: "0A000", ER_CANT_CREATE_GEOMETRY_OBJECT: "22003", ER_TOO_BIG_SCALE: "42000", ER_TOO_BIG_PRECISION: "42000", ER_M_BIGGER_THAN_D: "42000", ER_TOO_LONG_BODY: "42000", ER_TOO_BIG_DISPLAYWIDTH: "42000", ER_XAER_DUPID: "XAE08", ER_DATETIME_FUNCTION_OVERFLOW: "22008", ER_ROW_IS_REFERENCED_2: "23000", ER_NO_REFERENCED_ROW_2: "23000", ER_SP_BAD_VAR_SHADOW: "42000", ER_SP_WRONG_NAME: "42000", ER_SP_NO_AGGREGATE: "42000", ER_MAX_PREPARED_STMT_COUNT_REACHED: "42000", ER_NON_GROUPING_FIELD_USED: "42000", ER_FOREIGN_DUPLICATE_KEY_OLD_UNUSED: "23000", ER_CANT_CHANGE_TX_CHARACTERISTICS: "25001", ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT: "42000", ER_WRONG_PARAMETERS_TO_NATIVE_FCT: "42000", ER_WRONG_PARAMETERS_TO_STORED_FCT: "42000", ER_DUP_ENTRY_WITH_KEY_NAME: "23000", ER_XA_RBTIMEOUT: "XA106", ER_XA_RBDEADLOCK: "XA102", ER_FUNC_INEXISTENT_NAME_COLLISION: "42000", ER_DUP_SIGNAL_SET: "42000", ER_SIGNAL_WARN: "01000", ER_SIGNAL_NOT_FOUND: "02000", ER_SIGNAL_EXCEPTION: "HY000", ER_RESIGNAL_WITHOUT_ACTIVE_HANDLER: "0K000", ER_SPATIAL_MUST_HAVE_GEOM_COL: "42000", ER_DATA_OUT_OF_RANGE: "22003", ER_ACCESS_DENIED_NO_PASSWORD_ERROR: "28000", ER_TRUNCATE_ILLEGAL_FK: "42000", ER_DA_INVALID_CONDITION_NUMBER: "35000", ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO: "23000", ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO: "23000", ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION: "25006", ER_ALTER_OPERATION_NOT_SUPPORTED: "0A000", ER_ALTER_OPERATION_NOT_SUPPORTED_REASON: "0A000", ER_DUP_UNKNOWN_IN_INDEX: "23000", } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/mysql/util.go ================================================ package mysql import ( "bytes" "compress/zlib" "crypto/rand" "crypto/rsa" "crypto/sha1" "crypto/sha256" "encoding/binary" "fmt" "io" mrand "math/rand" "runtime" "strings" "time" "github.com/Masterminds/semver" "github.com/go-mysql-org/go-mysql/utils" "github.com/pingcap/errors" ) func Pstack() string { buf := make([]byte, 1024) n := runtime.Stack(buf, false) return string(buf[0:n]) } func CalcPassword(scramble, password []byte) []byte { if len(password) == 0 { return nil } // stage1Hash = SHA1(password) crypt := sha1.New() crypt.Write(password) stage1 := crypt.Sum(nil) // scrambleHash = SHA1(scramble + SHA1(stage1Hash)) // inner Hash crypt.Reset() crypt.Write(stage1) hash := crypt.Sum(nil) // outer Hash crypt.Reset() crypt.Write(scramble) crypt.Write(hash) scramble = crypt.Sum(nil) // token = scrambleHash XOR stage1Hash for i := range scramble { scramble[i] ^= stage1[i] } return scramble } // CalcCachingSha2Password: Hash password using MySQL 8+ method (SHA256) func CalcCachingSha2Password(scramble []byte, password string) []byte { if len(password) == 0 { return nil } // XOR(SHA256(password), SHA256(SHA256(SHA256(password)), scramble)) crypt := sha256.New() crypt.Write([]byte(password)) message1 := crypt.Sum(nil) crypt.Reset() crypt.Write(message1) message1Hash := crypt.Sum(nil) crypt.Reset() crypt.Write(message1Hash) crypt.Write(scramble) message2 := crypt.Sum(nil) for i := range message1 { message1[i] ^= message2[i] } return message1 } func EncryptPassword(password string, seed []byte, pub *rsa.PublicKey) ([]byte, error) { plain := make([]byte, len(password)+1) copy(plain, password) for i := range plain { j := i % len(seed) plain[i] ^= seed[j] } sha1v := sha1.New() return rsa.EncryptOAEP(sha1v, rand.Reader, pub, plain, nil) } func DecompressMariadbData(data []byte) ([]byte, error) { // algorithm always 0=zlib // algorithm := (data[pos] & 0x07) >> 4 headerSize := int(data[0] & 0x07) uncompressedDataSize := BFixedLengthInt(data[1 : 1+headerSize]) uncompressedData := make([]byte, uncompressedDataSize) r, err := zlib.NewReader(bytes.NewReader(data[1+headerSize:])) if err != nil { return nil, err } defer r.Close() _, err = io.ReadFull(r, uncompressedData) if err != nil { return nil, err } return uncompressedData, nil } // AppendLengthEncodedInteger: encodes a uint64 value and appends it to the given bytes slice func AppendLengthEncodedInteger(b []byte, n uint64) []byte { switch { case n <= 250: return append(b, byte(n)) case n <= 0xffff: return append(b, 0xfc, byte(n), byte(n>>8)) case n <= 0xffffff: return append(b, 0xfd, byte(n), byte(n>>8), byte(n>>16)) } return append(b, 0xfe, byte(n), byte(n>>8), byte(n>>16), byte(n>>24), byte(n>>32), byte(n>>40), byte(n>>48), byte(n>>56)) } func RandomBuf(size int) []byte { buf := make([]byte, size) // When this project supports golang 1.20 as a minimum, then this mrand.New(...) // line can be eliminated and the random number can be generated by simply // calling mrand.Intn() random := mrand.New(mrand.NewSource(time.Now().UTC().UnixNano())) min, max := 30, 127 for i := 0; i < size; i++ { buf[i] = byte(min + random.Intn(max-min)) } return buf } // FixedLengthInt: little endian func FixedLengthInt(buf []byte) uint64 { var num uint64 = 0 for i, b := range buf { num |= uint64(b) << (uint(i) * 8) } return num } // BFixedLengthInt: big endian func BFixedLengthInt(buf []byte) uint64 { var num uint64 = 0 for i, b := range buf { num |= uint64(b) << (uint(len(buf)-i-1) * 8) } return num } func LengthEncodedInt(b []byte) (num uint64, isNull bool, n int) { if len(b) == 0 { return 0, true, 0 } switch b[0] { // 251: NULL case 0xfb: return 0, true, 1 // 252: value of following 2 case 0xfc: return uint64(b[1]) | uint64(b[2])<<8, false, 3 // 253: value of following 3 case 0xfd: return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16, false, 4 // 254: value of following 8 case 0xfe: return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 | uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 | uint64(b[7])<<48 | uint64(b[8])<<56, false, 9 } // 0-250: value of first byte return uint64(b[0]), false, 1 } func PutLengthEncodedInt(n uint64) []byte { switch { case n <= 250: return []byte{byte(n)} case n <= 0xffff: return []byte{0xfc, byte(n), byte(n >> 8)} case n <= 0xffffff: return []byte{0xfd, byte(n), byte(n >> 8), byte(n >> 16)} default: // handles case n <= 0xffffffffffffffff // using 'default' instead of 'case' to avoid static analysis error // SA4003: every value of type uint64 is <= math.MaxUint64 return []byte{0xfe, byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24), byte(n >> 32), byte(n >> 40), byte(n >> 48), byte(n >> 56)} } } // LengthEncodedString returns the string read as a bytes slice, whether the value is NULL, // the number of bytes read and an error, in case the string is longer than // the input slice func LengthEncodedString(b []byte) ([]byte, bool, int, error) { // Get length num, isNull, n := LengthEncodedInt(b) if num < 1 { return b[n:n], isNull, n, nil } n += int(num) // Check data length if len(b) >= n { return b[n-int(num) : n : n], false, n, nil } return nil, false, n, io.EOF } func SkipLengthEncodedString(b []byte) (int, error) { // Get length num, _, n := LengthEncodedInt(b) if num < 1 { return n, nil } n += int(num) // Check data length if len(b) >= n { return n, nil } return n, io.EOF } func PutLengthEncodedString(b []byte) []byte { data := make([]byte, 0, len(b)+9) data = append(data, PutLengthEncodedInt(uint64(len(b)))...) data = append(data, b...) return data } func Uint16ToBytes(n uint16) []byte { return []byte{ byte(n), byte(n >> 8), } } func Uint32ToBytes(n uint32) []byte { return []byte{ byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24), } } func Uint64ToBytes(n uint64) []byte { return []byte{ byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24), byte(n >> 32), byte(n >> 40), byte(n >> 48), byte(n >> 56), } } func FormatBinaryDate(n int, data []byte) ([]byte, error) { switch n { case 0: return []byte("0000-00-00"), nil case 4: return []byte(fmt.Sprintf("%04d-%02d-%02d", binary.LittleEndian.Uint16(data[:2]), data[2], data[3])), nil default: return nil, errors.Errorf("invalid date packet length %d", n) } } func FormatBinaryDateTime(n int, data []byte) ([]byte, error) { switch n { case 0: return []byte("0000-00-00 00:00:00"), nil case 4: return []byte(fmt.Sprintf("%04d-%02d-%02d 00:00:00", binary.LittleEndian.Uint16(data[:2]), data[2], data[3])), nil case 7: return []byte(fmt.Sprintf( "%04d-%02d-%02d %02d:%02d:%02d", binary.LittleEndian.Uint16(data[:2]), data[2], data[3], data[4], data[5], data[6])), nil case 11: return []byte(fmt.Sprintf( "%04d-%02d-%02d %02d:%02d:%02d.%06d", binary.LittleEndian.Uint16(data[:2]), data[2], data[3], data[4], data[5], data[6], binary.LittleEndian.Uint32(data[7:11]))), nil default: return nil, errors.Errorf("invalid datetime packet length %d", n) } } func FormatBinaryTime(n int, data []byte) ([]byte, error) { if n == 0 { return []byte("00:00:00"), nil } var sign byte if data[0] == 1 { sign = byte('-') } var bytes []byte switch n { case 8: bytes = []byte(fmt.Sprintf( "%c%02d:%02d:%02d", sign, uint16(data[1])*24+uint16(data[5]), data[6], data[7], )) case 12: bytes = []byte(fmt.Sprintf( "%c%02d:%02d:%02d.%06d", sign, uint16(data[1])*24+uint16(data[5]), data[6], data[7], binary.LittleEndian.Uint32(data[8:12]), )) default: return nil, errors.Errorf("invalid time packet length %d", n) } if bytes[0] == 0 { return bytes[1:], nil } return bytes, nil } var ( DONTESCAPE = byte(255) EncodeMap [256]byte ) // Escape: only support utf-8 func Escape(sql string) string { dest := make([]byte, 0, 2*len(sql)) for _, w := range utils.StringToByteSlice(sql) { if c := EncodeMap[w]; c == DONTESCAPE { dest = append(dest, w) } else { dest = append(dest, '\\', c) } } return string(dest) } func GetNetProto(addr string) string { if strings.Contains(addr, "/") { return "unix" } else { return "tcp" } } // ErrorEqual returns a boolean indicating whether err1 is equal to err2. func ErrorEqual(err1, err2 error) bool { e1 := errors.Cause(err1) e2 := errors.Cause(err2) if e1 == e2 { return true } if e1 == nil || e2 == nil { return e1 == e2 } return e1.Error() == e2.Error() } func CompareServerVersions(a, b string) (int, error) { var ( aVer, bVer *semver.Version err error ) if aVer, err = semver.NewVersion(a); err != nil { return 0, fmt.Errorf("cannot parse %q as semver: %w", a, err) } if bVer, err = semver.NewVersion(b); err != nil { return 0, fmt.Errorf("cannot parse %q as semver: %w", b, err) } return aVer.Compare(bVer), nil } var encodeRef = map[byte]byte{ '\x00': '0', '\'': '\'', '"': '"', '\b': 'b', '\n': 'n', '\r': 'r', '\t': 't', 26: 'Z', // ctl-Z '\\': '\\', } func init() { for i := range EncodeMap { EncodeMap[i] = DONTESCAPE } for i := range EncodeMap { if to, ok := encodeRef[byte(i)]; ok { EncodeMap[byte(i)] = to } } } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/mysql/validate.go ================================================ package mysql import ( "fmt" "strings" ) func ValidateFlavor(flavor string) error { switch strings.ToLower(flavor) { case MySQLFlavor: return nil case MariaDBFlavor: return nil default: return fmt.Errorf("%s is not a valid flavor", flavor) } } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/packet/conn.go ================================================ package packet import ( "bufio" "bytes" "crypto/rand" "crypto/rsa" "crypto/sha1" "crypto/x509" "encoding/pem" goErrors "errors" "io" "net" "time" "github.com/go-mysql-org/go-mysql/compress" . "github.com/go-mysql-org/go-mysql/mysql" "github.com/go-mysql-org/go-mysql/utils" "github.com/klauspost/compress/zstd" "github.com/pingcap/errors" ) const MinCompressionLength = 50 const DefaultBufferSize = 16 * 1024 // Conn is the base class to handle MySQL protocol. type Conn struct { net.Conn readTimeout time.Duration writeTimeout time.Duration // Buffered reader for net.Conn in Non-TLS connection only to address replication performance issue. // See https://github.com/go-mysql-org/go-mysql/pull/422 for more details. br *bufio.Reader reader io.Reader copyNBuf []byte header [4]byte Sequence uint8 Compression uint8 CompressedSequence uint8 compressedHeader [7]byte compressedReader io.Reader compressedReaderActive bool } func NewConn(conn net.Conn) *Conn { return NewBufferedConn(conn, 65536) // 64kb } func NewBufferedConn(conn net.Conn, bufferSize int) *Conn { c := new(Conn) c.Conn = conn c.br = bufio.NewReaderSize(c, bufferSize) c.reader = c.br c.copyNBuf = make([]byte, DefaultBufferSize) return c } func NewConnWithTimeout(conn net.Conn, readTimeout, writeTimeout time.Duration, bufferSize int) *Conn { c := NewBufferedConn(conn, bufferSize) c.readTimeout = readTimeout c.writeTimeout = writeTimeout return c } func NewTLSConn(conn net.Conn) *Conn { c := new(Conn) c.Conn = conn c.reader = c c.copyNBuf = make([]byte, DefaultBufferSize) return c } func NewTLSConnWithTimeout(conn net.Conn, readTimeout, writeTimeout time.Duration) *Conn { c := NewTLSConn(conn) c.readTimeout = readTimeout c.writeTimeout = writeTimeout return c } func (c *Conn) ReadPacket() ([]byte, error) { return c.ReadPacketReuseMem(nil) } func (c *Conn) ReadPacketReuseMem(dst []byte) ([]byte, error) { // Here we use `sync.Pool` to avoid allocate/destroy buffers frequently. buf := utils.BytesBufferGet() defer func() { utils.BytesBufferPut(buf) }() if c.Compression != MYSQL_COMPRESS_NONE { // it's possible that we're using compression but the server response with a compressed // packet with uncompressed length of 0. In this case we leave compressedReader nil. The // compressedReaderActive flag is important to track the state of the reader, allowing // for the compressedReader to be reset after a packet write. Without this flag, when a // compressed packet with uncompressed length of 0 is read, the compressedReader would // be nil, and we'd incorrectly attempt to read the next packet as compressed. if !c.compressedReaderActive { var err error c.compressedReader, err = c.newCompressedPacketReader() if err != nil { return nil, err } c.compressedReaderActive = true } } if err := c.ReadPacketTo(buf); err != nil { return nil, errors.Trace(err) } readBytes := buf.Bytes() readSize := len(readBytes) var result []byte if len(dst) > 0 { result = append(dst, readBytes...) // if read block is big, do not cache buf anymore if readSize > utils.TooBigBlockSize { buf = nil } } else { if readSize > utils.TooBigBlockSize { // if read block is big, use read block as result and do not cache buf anymore result = readBytes buf = nil } else { result = append(dst, readBytes...) } } return result, nil } // newCompressedPacketReader creates a new compressed packet reader. func (c *Conn) newCompressedPacketReader() (io.Reader, error) { if c.readTimeout != 0 { if err := c.SetReadDeadline(utils.Now().Add(c.readTimeout)); err != nil { return nil, err } } if _, err := io.ReadFull(c.reader, c.compressedHeader[:7]); err != nil { return nil, errors.Wrapf(ErrBadConn, "io.ReadFull(compressedHeader) failed. err %v", err) } compressedSequence := c.compressedHeader[3] if compressedSequence != c.CompressedSequence { return nil, errors.Errorf("invalid compressed sequence %d != %d", compressedSequence, c.CompressedSequence) } compressedLength := int(uint32(c.compressedHeader[0]) | uint32(c.compressedHeader[1])<<8 | uint32(c.compressedHeader[2])<<16) uncompressedLength := int(uint32(c.compressedHeader[4]) | uint32(c.compressedHeader[5])<<8 | uint32(c.compressedHeader[6])<<16) if uncompressedLength > 0 { limitedReader := io.LimitReader(c.reader, int64(compressedLength)) switch c.Compression { case MYSQL_COMPRESS_ZLIB: return compress.GetPooledZlibReader(limitedReader) case MYSQL_COMPRESS_ZSTD: return zstd.NewReader(limitedReader) } } return nil, nil } func (c *Conn) currentPacketReader() io.Reader { if c.Compression == MYSQL_COMPRESS_NONE || c.compressedReader == nil { return c.reader } else { return c.compressedReader } } func (c *Conn) copyN(dst io.Writer, n int64) (int64, error) { var written int64 for n > 0 { bcap := cap(c.copyNBuf) if int64(bcap) > n { bcap = int(n) } buf := c.copyNBuf[:bcap] // Call ReadAtLeast with the currentPacketReader as it may change on every iteration // of this loop. if c.readTimeout != 0 { if err := c.SetReadDeadline(utils.Now().Add(c.readTimeout)); err != nil { return written, err } } rd, err := io.ReadAtLeast(c.currentPacketReader(), buf, bcap) n -= int64(rd) // ReadAtLeast will return EOF or ErrUnexpectedEOF when fewer than the min // bytes are read. In this case, and when we have compression then advance // the sequence number and reset the compressed reader to continue reading // the remaining bytes in the next compressed packet. if c.Compression != MYSQL_COMPRESS_NONE && (goErrors.Is(err, io.ErrUnexpectedEOF) || goErrors.Is(err, io.EOF)) { // we have read to EOF and read an incomplete uncompressed packet // so advance the compressed sequence number and reset the compressed reader // to get the remaining unread uncompressed bytes from the next compressed packet. c.CompressedSequence++ if c.compressedReader, err = c.newCompressedPacketReader(); err != nil { return written, errors.Trace(err) } } if err != nil { return written, errors.Trace(err) } // careful to only write from the buffer the number of bytes read wr, err := dst.Write(buf[:rd]) written += int64(wr) if err != nil { return written, errors.Trace(err) } } return written, nil } func (c *Conn) ReadPacketTo(w io.Writer) error { b := utils.BytesBufferGet() defer func() { utils.BytesBufferPut(b) }() // packets that come in a compressed packet may be partial // so use the copyN function to read the packet header into a // buffer, since copyN is capable of getting the next compressed // packet and updating the Conn state with a new compressedReader. if _, err := c.copyN(b, 4); err != nil { return errors.Wrapf(ErrBadConn, "io.ReadFull(header) failed. err %v", err) } else { // copy was successful so copy the 4 bytes from the buffer to the header copy(c.header[:4], b.Bytes()[:4]) } length := int(uint32(c.header[0]) | uint32(c.header[1])<<8 | uint32(c.header[2])<<16) sequence := c.header[3] if sequence != c.Sequence { return errors.Errorf("invalid sequence %d != %d", sequence, c.Sequence) } c.Sequence++ if buf, ok := w.(*bytes.Buffer); ok { // Allocate the buffer with expected length directly instead of call `grow` and migrate data many times. buf.Grow(length) } if n, err := c.copyN(w, int64(length)); err != nil { return errors.Wrapf(ErrBadConn, "io.CopyN failed. err %v, copied %v, expected %v", err, n, length) } else if n != int64(length) { return errors.Wrapf(ErrBadConn, "io.CopyN failed(n != int64(length)). %v bytes copied, while %v expected", n, length) } else { if length < MaxPayloadLen { return nil } if err = c.ReadPacketTo(w); err != nil { return errors.Wrap(err, "ReadPacketTo failed") } } return nil } // WritePacket data already has 4 bytes header will modify data in-place func (c *Conn) WritePacket(data []byte) error { length := len(data) - 4 for length >= MaxPayloadLen { data[0] = 0xff data[1] = 0xff data[2] = 0xff data[3] = c.Sequence if n, err := c.writeWithTimeout(data[:4+MaxPayloadLen]); err != nil { return errors.Wrapf(ErrBadConn, "Write(payload portion) failed. err %v", err) } else if n != (4 + MaxPayloadLen) { return errors.Wrapf(ErrBadConn, "Write(payload portion) failed. only %v bytes written, while %v expected", n, 4+MaxPayloadLen) } else { c.Sequence++ length -= MaxPayloadLen data = data[MaxPayloadLen:] } } data[0] = byte(length) data[1] = byte(length >> 8) data[2] = byte(length >> 16) data[3] = c.Sequence switch c.Compression { case MYSQL_COMPRESS_NONE: if n, err := c.writeWithTimeout(data); err != nil { return errors.Wrapf(ErrBadConn, "Write failed. err %v", err) } else if n != len(data) { return errors.Wrapf(ErrBadConn, "Write failed. only %v bytes written, while %v expected", n, len(data)) } case MYSQL_COMPRESS_ZLIB, MYSQL_COMPRESS_ZSTD: if n, err := c.writeCompressed(data); err != nil { return errors.Wrapf(ErrBadConn, "Write failed. err %v", err) } else if n != len(data) { return errors.Wrapf(ErrBadConn, "Write failed. only %v bytes written, while %v expected", n, len(data)) } c.compressedReaderActive = false if c.compressedReader != nil { if _, ok := c.compressedReader.(io.ReadCloser); ok { _ = c.compressedReader.(io.ReadCloser).Close() } c.compressedReader = nil } default: return errors.Wrapf(ErrBadConn, "Write failed. Unsuppored compression algorithm set") } c.Sequence++ return nil } func (c *Conn) writeWithTimeout(b []byte) (n int, err error) { if c.writeTimeout != 0 { if err := c.SetWriteDeadline(utils.Now().Add(c.writeTimeout)); err != nil { return n, err } } return c.Write(b) } func (c *Conn) writeCompressed(data []byte) (n int, err error) { var ( compressedLength, uncompressedLength int payload *bytes.Buffer compressedHeader = make([]byte, 7) ) if len(data) > MinCompressionLength { var w io.WriteCloser payload = utils.BytesBufferGet() defer utils.BytesBufferPut(payload) switch c.Compression { case MYSQL_COMPRESS_ZLIB: w, err = compress.GetPooledZlibWriter(payload) case MYSQL_COMPRESS_ZSTD: w, err = zstd.NewWriter(payload) default: return 0, errors.Wrapf(ErrBadConn, "Write failed. Unsuppored compression algorithm set") } if err != nil { return 0, err } uncompressedLength = len(data) if n, err = w.Write(data); err != nil { _ = w.Close() return 0, err } if err = w.Close(); err != nil { return 0, err } compressedLength = payload.Len() } else { compressedLength = len(data) } c.CompressedSequence = 0 // write the compressed packet header compressedPacket := utils.BytesBufferGet() defer utils.BytesBufferPut(compressedPacket) compressedHeader[0] = byte(compressedLength) compressedHeader[1] = byte(compressedLength >> 8) compressedHeader[2] = byte(compressedLength >> 16) compressedHeader[3] = c.CompressedSequence compressedHeader[4] = byte(uncompressedLength) compressedHeader[5] = byte(uncompressedLength >> 8) compressedHeader[6] = byte(uncompressedLength >> 16) if _, err = compressedPacket.Write(compressedHeader); err != nil { return 0, err } c.CompressedSequence++ if payload != nil { _, err = compressedPacket.Write(payload.Bytes()) } else { n, err = compressedPacket.Write(data) } if err != nil { return 0, err } if _, err = c.writeWithTimeout(compressedPacket.Bytes()); err != nil { return 0, err } return n, nil } // WriteClearAuthPacket Client clear text authentication packet // https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_switch_response.html func (c *Conn) WriteClearAuthPacket(password string) error { // Calculate the packet length and add a tailing 0 pktLen := len(password) + 1 data := make([]byte, 4+pktLen) // Add the clear password [null terminated string] copy(data[4:], password) data[4+pktLen-1] = 0x00 return errors.Wrap(c.WritePacket(data), "WritePacket failed") } // WritePublicKeyAuthPacket Caching sha2 authentication. Public key request and send encrypted password // https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_switch_response.html func (c *Conn) WritePublicKeyAuthPacket(password string, cipher []byte) error { // request public key data := make([]byte, 4+1) data[4] = 2 // cachingSha2PasswordRequestPublicKey if err := c.WritePacket(data); err != nil { return errors.Wrap(err, "WritePacket(single byte) failed") } data, err := c.ReadPacket() if err != nil { return errors.Wrap(err, "ReadPacket failed") } block, _ := pem.Decode(data[1:]) pub, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { return errors.Wrap(err, "x509.ParsePKIXPublicKey failed") } plain := make([]byte, len(password)+1) copy(plain, password) for i := range plain { j := i % len(cipher) plain[i] ^= cipher[j] } sha1v := sha1.New() enc, _ := rsa.EncryptOAEP(sha1v, rand.Reader, pub.(*rsa.PublicKey), plain, nil) data = make([]byte, 4+len(enc)) copy(data[4:], enc) return errors.Wrap(c.WritePacket(data), "WritePacket failed") } func (c *Conn) WriteEncryptedPassword(password string, seed []byte, pub *rsa.PublicKey) error { enc, err := EncryptPassword(password, seed, pub) if err != nil { return errors.Wrap(err, "EncryptPassword failed") } return errors.Wrap(c.WriteAuthSwitchPacket(enc, false), "WriteAuthSwitchPacket failed") } // WriteAuthSwitchPacket see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_switch_response.html func (c *Conn) WriteAuthSwitchPacket(authData []byte, addNUL bool) error { pktLen := 4 + len(authData) if addNUL { pktLen++ } data := make([]byte, pktLen) // Add the auth data [EOF] copy(data[4:], authData) if addNUL { data[pktLen-1] = 0x00 } return errors.Wrap(c.WritePacket(data), "WritePacket failed") } func (c *Conn) ResetSequence() { c.Sequence = 0 } func (c *Conn) Close() error { c.Sequence = 0 if c.Conn != nil { return errors.Wrap(c.Conn.Close(), "Conn.Close failed") } return nil } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/replication/backup.go ================================================ package replication import ( "context" "io" "os" "path" "sync" "time" . "github.com/go-mysql-org/go-mysql/mysql" "github.com/pingcap/errors" ) // StartBackup starts the backup process for the binary log and writes to the backup directory. func (b *BinlogSyncer) StartBackup(backupDir string, p Position, timeout time.Duration) error { err := os.MkdirAll(backupDir, 0755) if err != nil { return errors.Trace(err) } if b.cfg.SynchronousEventHandler == nil { return b.StartBackupWithHandler(p, timeout, func(filename string) (io.WriteCloser, error) { return os.OpenFile(path.Join(backupDir, filename), os.O_CREATE|os.O_WRONLY, 0644) }) } else { return b.StartSynchronousBackup(p, timeout) } } // StartBackupWithHandler starts the backup process for the binary log using the specified position and handler. // The process will continue until the timeout is reached or an error occurs. // This method should not be used together with SynchronousEventHandler. // // Parameters: // - p: The starting position in the binlog from which to begin the backup. // - timeout: The maximum duration to wait for new binlog events before stopping the backup process. // If set to 0, a default very long timeout (30 days) is used instead. // - handler: A function that takes a binlog filename and returns an WriteCloser for writing raw events to. func (b *BinlogSyncer) StartBackupWithHandler(p Position, timeout time.Duration, handler func(binlogFilename string) (io.WriteCloser, error)) (retErr error) { if timeout == 0 { // a very long timeout here timeout = 30 * 3600 * 24 * time.Second } if b.cfg.SynchronousEventHandler != nil { return errors.New("StartBackupWithHandler cannot be used when SynchronousEventHandler is set. Use StartSynchronousBackup instead.") } // Force use raw mode b.parser.SetRawMode(true) // Set up the backup event handler backupHandler := &BackupEventHandler{ handler: handler, } s, err := b.StartSync(p) if err != nil { return errors.Trace(err) } defer func() { if backupHandler.w != nil { closeErr := backupHandler.w.Close() if retErr == nil { retErr = closeErr } } }() for { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() select { case <-ctx.Done(): return nil case <-b.ctx.Done(): return nil case err := <-s.ech: return errors.Trace(err) case e := <-s.ch: err = backupHandler.HandleEvent(e) if err != nil { return errors.Trace(err) } } } } // StartSynchronousBackup starts the backup process using the SynchronousEventHandler in the BinlogSyncerConfig. func (b *BinlogSyncer) StartSynchronousBackup(p Position, timeout time.Duration) error { if b.cfg.SynchronousEventHandler == nil { return errors.New("SynchronousEventHandler must be set in BinlogSyncerConfig to use StartSynchronousBackup") } s, err := b.StartSync(p) if err != nil { return errors.Trace(err) } var ctx context.Context var cancel context.CancelFunc if timeout > 0 { ctx, cancel = context.WithTimeout(context.Background(), timeout) defer cancel() } else { ctx = context.Background() } select { case <-ctx.Done(): // The timeout has been reached return nil case <-b.ctx.Done(): // The BinlogSyncer has been closed return nil case err := <-s.ech: // An error occurred during streaming return errors.Trace(err) } } // BackupEventHandler handles writing events for backup type BackupEventHandler struct { handler func(binlogFilename string) (io.WriteCloser, error) w io.WriteCloser mutex sync.Mutex filename string } func NewBackupEventHandler(handlerFunction func(filename string) (io.WriteCloser, error)) *BackupEventHandler { return &BackupEventHandler{ handler: handlerFunction, } } // HandleEvent processes a single event for the backup. func (h *BackupEventHandler) HandleEvent(e *BinlogEvent) error { h.mutex.Lock() defer h.mutex.Unlock() var err error offset := e.Header.LogPos if e.Header.EventType == ROTATE_EVENT { rotateEvent := e.Event.(*RotateEvent) h.filename = string(rotateEvent.NextLogName) if e.Header.Timestamp == 0 || offset == 0 { // fake rotate event return nil } } else if e.Header.EventType == FORMAT_DESCRIPTION_EVENT { if h.w != nil { if err = h.w.Close(); err != nil { h.w = nil return errors.Trace(err) } } if len(h.filename) == 0 { return errors.Errorf("empty binlog filename for FormatDescriptionEvent") } h.w, err = h.handler(h.filename) if err != nil { return errors.Trace(err) } // Write binlog header 0xfebin _, err = h.w.Write(BinLogFileHeader) if err != nil { return errors.Trace(err) } } if h.w != nil { n, err := h.w.Write(e.RawData) if err != nil { return errors.Trace(err) } if n != len(e.RawData) { return errors.Trace(io.ErrShortWrite) } } else { return errors.New("writer is not initialized") } return nil } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/replication/binlogstreamer.go ================================================ package replication import ( "context" "time" "github.com/pingcap/errors" "github.com/siddontang/go-log/log" ) var ( ErrNeedSyncAgain = errors.New("Last sync error or closed, try sync and get event again") ErrSyncClosed = errors.New("Sync was closed") ) // BinlogStreamer gets the streaming event. type BinlogStreamer struct { ch chan *BinlogEvent ech chan error err error } // GetEvent gets the binlog event one by one, it will block until Syncer receives any events from MySQL // or meets a sync error. You can pass a context (like Cancel or Timeout) to break the block. func (s *BinlogStreamer) GetEvent(ctx context.Context) (*BinlogEvent, error) { if s.err != nil { return nil, ErrNeedSyncAgain } select { case c := <-s.ch: return c, nil case s.err = <-s.ech: return nil, s.err case <-ctx.Done(): return nil, ctx.Err() } } // GetEventWithStartTime gets the binlog event with starttime, if current binlog event timestamp smaller than specify starttime // return nil event func (s *BinlogStreamer) GetEventWithStartTime(ctx context.Context, startTime time.Time) (*BinlogEvent, error) { if s.err != nil { return nil, ErrNeedSyncAgain } startUnix := startTime.Unix() select { case c := <-s.ch: if int64(c.Header.Timestamp) >= startUnix { return c, nil } return nil, nil case s.err = <-s.ech: return nil, s.err case <-ctx.Done(): return nil, ctx.Err() } } // DumpEvents dumps all left events func (s *BinlogStreamer) DumpEvents() []*BinlogEvent { count := len(s.ch) events := make([]*BinlogEvent, count) for i := range events { events[i] = <-s.ch } return events } func (s *BinlogStreamer) close() { s.closeWithError(nil) } func (s *BinlogStreamer) closeWithError(err error) { if err == nil { err = ErrSyncClosed } else { log.Errorf("close sync with err: %v", err) } select { case s.ech <- err: default: } } func NewBinlogStreamer() *BinlogStreamer { return NewBinlogStreamerWithChanSize(10240) } func NewBinlogStreamerWithChanSize(chanSize int) *BinlogStreamer { s := new(BinlogStreamer) if chanSize <= 0 { chanSize = 10240 } s.ch = make(chan *BinlogEvent, chanSize) s.ech = make(chan error, 4) return s } // AddEventToStreamer adds a binlog event to the streamer. You can use it when you want to add an event to the streamer manually. // can be used in replication handlers func (s *BinlogStreamer) AddEventToStreamer(ev *BinlogEvent) error { select { case s.ch <- ev: return nil case err := <-s.ech: return err } } // AddErrorToStreamer adds an error to the streamer. func (s *BinlogStreamer) AddErrorToStreamer(err error) bool { select { case s.ech <- err: return true default: return false } } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/replication/binlogsyncer.go ================================================ package replication import ( "context" "crypto/tls" "encoding/binary" "fmt" "net" "os" "strconv" "strings" "sync" "time" "github.com/google/uuid" "github.com/pingcap/errors" "github.com/siddontang/go-log/log" "github.com/siddontang/go-log/loggers" "github.com/go-mysql-org/go-mysql/client" . "github.com/go-mysql-org/go-mysql/mysql" "github.com/go-mysql-org/go-mysql/utils" ) var ( errSyncRunning = errors.New("Sync is running, must Close first") ) // BinlogSyncerConfig is the configuration for BinlogSyncer. type BinlogSyncerConfig struct { // ServerID is the unique ID in cluster. ServerID uint32 // Flavor is "mysql" or "mariadb", if not set, use "mysql" default. Flavor string // Host is for MySQL server host. Host string // Port is for MySQL server port. Port uint16 // User is for MySQL user. User string // Password is for MySQL password. Password string // Localhost is local hostname if register salve. // If not set, use os.Hostname() instead. Localhost string // Charset is for MySQL client character set Charset string // SemiSyncEnabled enables semi-sync or not. SemiSyncEnabled bool // RawModeEnabled is for not parsing binlog event. RawModeEnabled bool // If not nil, use the provided tls.Config to connect to the database using TLS/SSL. TLSConfig *tls.Config // Use replication.Time structure for timestamp and datetime. // We will use Local location for timestamp and UTC location for datetime. ParseTime bool // If ParseTime is false, convert TIMESTAMP into this specified timezone. If // ParseTime is true, this option will have no effect and TIMESTAMP data will // be parsed into the local timezone and a full time.Time struct will be // returned. // // Note that MySQL TIMESTAMP columns are offset from the machine local // timezone while DATETIME columns are offset from UTC. This is consistent // with documented MySQL behaviour as it return TIMESTAMP in local timezone // and DATETIME in UTC. // // Setting this to UTC effectively equalizes the TIMESTAMP and DATETIME time // strings obtained from MySQL. TimestampStringLocation *time.Location // Use decimal.Decimal structure for decimals. UseDecimal bool // RecvBufferSize sets the size in bytes of the operating system's receive buffer associated with the connection. RecvBufferSize int // master heartbeat period HeartbeatPeriod time.Duration // read timeout ReadTimeout time.Duration // maximum number of attempts to re-establish a broken connection, zero or negative number means infinite retry. // this configuration will not work if DisableRetrySync is true MaxReconnectAttempts int // whether disable re-sync for broken connection DisableRetrySync bool // Only works when MySQL/MariaDB variable binlog_checksum=CRC32. // For MySQL, binlog_checksum was introduced since 5.6.2, but CRC32 was set as default value since 5.6.6 . // https://dev.mysql.com/doc/refman/5.6/en/replication-options-binary-log.html#option_mysqld_binlog-checksum // For MariaDB, binlog_checksum was introduced since MariaDB 5.3, but CRC32 was set as default value since MariaDB 10.2.1 . // https://mariadb.com/kb/en/library/replication-and-binary-log-server-system-variables/#binlog_checksum VerifyChecksum bool // DumpCommandFlag is used to send binglog dump command. Default 0, aka BINLOG_DUMP_NEVER_STOP. // For MySQL, BINLOG_DUMP_NEVER_STOP and BINLOG_DUMP_NON_BLOCK are available. // https://dev.mysql.com/doc/internals/en/com-binlog-dump.html#binlog-dump-non-block // For MariaDB, BINLOG_DUMP_NEVER_STOP, BINLOG_DUMP_NON_BLOCK and BINLOG_SEND_ANNOTATE_ROWS_EVENT are available. // https://mariadb.com/kb/en/library/com_binlog_dump/ // https://mariadb.com/kb/en/library/annotate_rows_event/ DumpCommandFlag uint16 //Option function is used to set outside of BinlogSyncerConfig, between mysql connection and COM_REGISTER_SLAVE //For MariaDB: slave_gtid_ignore_duplicates、skip_replication、slave_until_gtid Option func(*client.Conn) error // Set Logger Logger loggers.Advanced // Set Dialer Dialer client.Dialer RowsEventDecodeFunc func(*RowsEvent, []byte) error TableMapOptionalMetaDecodeFunc func([]byte) error DiscardGTIDSet bool EventCacheCount int // SynchronousEventHandler is used for synchronous event handling. // This should not be used together with StartBackupWithHandler. // If this is not nil, GetEvent does not need to be called. SynchronousEventHandler EventHandler } // EventHandler defines the interface for processing binlog events. type EventHandler interface { HandleEvent(e *BinlogEvent) error } // BinlogSyncer syncs binlog events from the server. type BinlogSyncer struct { m sync.RWMutex cfg BinlogSyncerConfig c *client.Conn wg sync.WaitGroup parser *BinlogParser nextPos Position prevGset, currGset GTIDSet // instead of GTIDSet.Clone, use this to speed up calculate prevGset prevMySQLGTIDEvent *GTIDEvent running bool ctx context.Context cancel context.CancelFunc lastConnectionID uint32 retryCount int } // NewBinlogSyncer creates the BinlogSyncer with the given configuration. func NewBinlogSyncer(cfg BinlogSyncerConfig) *BinlogSyncer { if cfg.Logger == nil { streamHandler, _ := log.NewStreamHandler(os.Stdout) cfg.Logger = log.NewDefault(streamHandler) } if cfg.ServerID == 0 { cfg.Logger.Fatal("can't use 0 as the server ID") } if cfg.Dialer == nil { dialer := &net.Dialer{} cfg.Dialer = dialer.DialContext } if cfg.EventCacheCount == 0 { cfg.EventCacheCount = 10240 } // Clear the Password to avoid outputting it in logs. pass := cfg.Password cfg.Password = "" cfg.Logger.Infof("create BinlogSyncer with config %+v", cfg) cfg.Password = pass b := new(BinlogSyncer) b.cfg = cfg b.parser = NewBinlogParser() b.parser.SetFlavor(cfg.Flavor) b.parser.SetRawMode(b.cfg.RawModeEnabled) b.parser.SetParseTime(b.cfg.ParseTime) b.parser.SetTimestampStringLocation(b.cfg.TimestampStringLocation) b.parser.SetUseDecimal(b.cfg.UseDecimal) b.parser.SetVerifyChecksum(b.cfg.VerifyChecksum) b.parser.SetRowsEventDecodeFunc(b.cfg.RowsEventDecodeFunc) b.parser.SetTableMapOptionalMetaDecodeFunc(b.cfg.TableMapOptionalMetaDecodeFunc) b.running = false b.ctx, b.cancel = context.WithCancel(context.Background()) return b } // Close closes the BinlogSyncer. func (b *BinlogSyncer) Close() { b.m.Lock() defer b.m.Unlock() b.close() } func (b *BinlogSyncer) close() { if b.isClosed() { return } b.cfg.Logger.Info("syncer is closing...") b.running = false b.cancel() if b.c != nil { err := b.c.SetReadDeadline(utils.Now().Add(100 * time.Millisecond)) if err != nil { b.cfg.Logger.Warnf(`could not set read deadline: %s`, err) } } // kill last connection id if b.lastConnectionID > 0 { // Use a new connection to kill the binlog syncer // because calling KILL from the same connection // doesn't actually disconnect it. c, err := b.newConnection(context.Background()) if err == nil { b.killConnection(c, b.lastConnectionID) c.Close() } } b.wg.Wait() if b.c != nil { b.c.Close() } b.cfg.Logger.Info("syncer is closed") } func (b *BinlogSyncer) isClosed() bool { select { case <-b.ctx.Done(): return true default: return false } } func (b *BinlogSyncer) registerSlave() error { if b.c != nil { b.c.Close() } var err error b.c, err = b.newConnection(b.ctx) if err != nil { return errors.Trace(err) } if b.cfg.Option != nil { if err = b.cfg.Option(b.c); err != nil { return errors.Trace(err) } } if len(b.cfg.Charset) != 0 { if err = b.c.SetCharset(b.cfg.Charset); err != nil { return errors.Trace(err) } } //set read timeout if b.cfg.ReadTimeout > 0 { _ = b.c.SetReadDeadline(utils.Now().Add(b.cfg.ReadTimeout)) } if b.cfg.RecvBufferSize > 0 { if tcp, ok := b.c.Conn.Conn.(*net.TCPConn); ok { _ = tcp.SetReadBuffer(b.cfg.RecvBufferSize) } } // kill last connection id if b.lastConnectionID > 0 { b.killConnection(b.c, b.lastConnectionID) } // save last last connection id for kill b.lastConnectionID = b.c.GetConnectionID() //for mysql 5.6+, binlog has a crc32 checksum //before mysql 5.6, this will not work, don't matter.:-) if r, err := b.c.Execute("SHOW GLOBAL VARIABLES LIKE 'BINLOG_CHECKSUM'"); err != nil { return errors.Trace(err) } else { s, _ := r.GetString(0, 1) if s != "" { // maybe CRC32 or NONE // mysqlbinlog.cc use NONE, see its below comments: // Make a notice to the server that this client // is checksum-aware. It does not need the first fake Rotate // necessary checksummed. // That preference is specified below. if _, err = b.c.Execute(`SET @master_binlog_checksum='NONE'`); err != nil { return errors.Trace(err) } // if _, err = b.c.Execute(`SET @master_binlog_checksum=@@global.binlog_checksum`); err != nil { // return errors.Trace(err) // } } } if b.cfg.Flavor == MariaDBFlavor { // Refer https://github.com/alibaba/canal/wiki/BinlogChange(MariaDB5&10) // Tell the server that we understand GTIDs by setting our slave capability // to MARIA_SLAVE_CAPABILITY_GTID = 4 (MariaDB >= 10.0.1). if _, err := b.c.Execute("SET @mariadb_slave_capability=4"); err != nil { return errors.Errorf("failed to set @mariadb_slave_capability=4: %v", err) } } if b.cfg.HeartbeatPeriod > 0 { _, err = b.c.Execute(fmt.Sprintf("SET @master_heartbeat_period=%d;", b.cfg.HeartbeatPeriod)) if err != nil { b.cfg.Logger.Errorf("failed to set @master_heartbeat_period=%d, err: %v", b.cfg.HeartbeatPeriod, err) return errors.Trace(err) } } serverUUID, err := uuid.NewUUID() if err != nil { b.cfg.Logger.Errorf("failed to get new uuid %v", err) return errors.Trace(err) } if _, err = b.c.Execute(fmt.Sprintf("SET @slave_uuid = '%s', @replica_uuid = '%s'", serverUUID, serverUUID)); err != nil { b.cfg.Logger.Errorf("failed to set @slave_uuid = '%s', err: %v", serverUUID, err) return errors.Trace(err) } if err = b.writeRegisterSlaveCommand(); err != nil { return errors.Trace(err) } if _, err = b.c.ReadOKPacket(); err != nil { return errors.Trace(err) } return nil } func (b *BinlogSyncer) enableSemiSync() error { if !b.cfg.SemiSyncEnabled { return nil } if r, err := b.c.Execute("SHOW VARIABLES LIKE 'rpl_semi_sync_master_enabled';"); err != nil { return errors.Trace(err) } else { s, _ := r.GetString(0, 1) if s != "ON" { b.cfg.Logger.Errorf("master does not support semi synchronous replication, use no semi-sync") b.cfg.SemiSyncEnabled = false return nil } } _, err := b.c.Execute(`SET @rpl_semi_sync_slave = 1;`) if err != nil { return errors.Trace(err) } return nil } func (b *BinlogSyncer) prepare() error { if b.isClosed() { return errors.Trace(ErrSyncClosed) } if err := b.registerSlave(); err != nil { return errors.Trace(err) } if err := b.enableSemiSync(); err != nil { return errors.Trace(err) } b.cfg.Logger.Infof("Connected to %s %s server", b.cfg.Flavor, b.c.GetServerVersion()) return nil } func (b *BinlogSyncer) startDumpStream() *BinlogStreamer { b.running = true s := NewBinlogStreamerWithChanSize(b.cfg.EventCacheCount) b.wg.Add(1) go b.onStream(s) return s } // GetNextPosition returns the next position of the syncer func (b *BinlogSyncer) GetNextPosition() Position { return b.nextPos } func (b *BinlogSyncer) checkFlavor() { serverVersion := b.c.GetServerVersion() if b.cfg.Flavor != MariaDBFlavor && strings.Contains(b.c.GetServerVersion(), "MariaDB") { // Setting the flavor to `mysql` causes MariaDB to try and behave // in a MySQL compatible way. In this mode MariaDB won't use // MariaDB specific binlog event types, but may used dummy events instead. b.cfg.Logger.Errorf("misconfigured flavor (%s) for server %s", b.cfg.Flavor, serverVersion) } } // StartSync starts syncing from the `pos` position. func (b *BinlogSyncer) StartSync(pos Position) (*BinlogStreamer, error) { b.cfg.Logger.Infof("begin to sync binlog from position %s", pos) b.m.Lock() defer b.m.Unlock() if b.running { return nil, errors.Trace(errSyncRunning) } if err := b.prepareSyncPos(pos); err != nil { return nil, errors.Trace(err) } b.checkFlavor() return b.startDumpStream(), nil } // StartSyncGTID starts syncing from the `gset` GTIDSet. func (b *BinlogSyncer) StartSyncGTID(gset GTIDSet) (*BinlogStreamer, error) { b.cfg.Logger.Infof("begin to sync binlog from GTID set %s", gset) b.prevMySQLGTIDEvent = nil b.prevGset = gset b.m.Lock() defer b.m.Unlock() if b.running { return nil, errors.Trace(errSyncRunning) } // establishing network connection here and will start getting binlog events from "gset + 1", thus until first // MariadbGTIDEvent/GTIDEvent event is received - we effectively do not have a "current GTID" b.currGset = nil if err := b.prepare(); err != nil { return nil, errors.Trace(err) } var err error switch b.cfg.Flavor { case MariaDBFlavor: err = b.writeBinlogDumpMariadbGTIDCommand(gset) default: // default use MySQL err = b.writeBinlogDumpMysqlGTIDCommand(gset) } if err != nil { return nil, err } b.checkFlavor() return b.startDumpStream(), nil } func (b *BinlogSyncer) writeBinlogDumpCommand(p Position) error { b.c.ResetSequence() data := make([]byte, 4+1+4+2+4+len(p.Name)) pos := 4 data[pos] = COM_BINLOG_DUMP pos++ binary.LittleEndian.PutUint32(data[pos:], p.Pos) pos += 4 binary.LittleEndian.PutUint16(data[pos:], b.cfg.DumpCommandFlag) pos += 2 binary.LittleEndian.PutUint32(data[pos:], b.cfg.ServerID) pos += 4 copy(data[pos:], p.Name) return b.c.WritePacket(data) } func (b *BinlogSyncer) writeBinlogDumpMysqlGTIDCommand(gset GTIDSet) error { p := Position{Name: "", Pos: 4} gtidData := gset.Encode() b.c.ResetSequence() data := make([]byte, 4+1+2+4+4+len(p.Name)+8+4+len(gtidData)) pos := 4 data[pos] = COM_BINLOG_DUMP_GTID pos++ binary.LittleEndian.PutUint16(data[pos:], 0) pos += 2 binary.LittleEndian.PutUint32(data[pos:], b.cfg.ServerID) pos += 4 binary.LittleEndian.PutUint32(data[pos:], uint32(len(p.Name))) pos += 4 n := copy(data[pos:], p.Name) pos += n binary.LittleEndian.PutUint64(data[pos:], uint64(p.Pos)) pos += 8 binary.LittleEndian.PutUint32(data[pos:], uint32(len(gtidData))) pos += 4 n = copy(data[pos:], gtidData) pos += n data = data[0:pos] return b.c.WritePacket(data) } func (b *BinlogSyncer) writeBinlogDumpMariadbGTIDCommand(gset GTIDSet) error { // Copy from vitess startPos := gset.String() // Set the slave_connect_state variable before issuing COM_BINLOG_DUMP to // provide the start position in GTID form. query := fmt.Sprintf("SET @slave_connect_state='%s'", startPos) if _, err := b.c.Execute(query); err != nil { return errors.Errorf("failed to set @slave_connect_state='%s': %v", startPos, err) } // Real slaves set this upon connecting if their gtid_strict_mode option was // enabled. We always use gtid_strict_mode because we need it to make our // internal GTID comparisons safe. if _, err := b.c.Execute("SET @slave_gtid_strict_mode=1"); err != nil { return errors.Errorf("failed to set @slave_gtid_strict_mode=1: %v", err) } // Since we use @slave_connect_state, the file and position here are ignored. return b.writeBinlogDumpCommand(Position{Name: "", Pos: 0}) } // localHostname returns the hostname that register slave would register as. func (b *BinlogSyncer) localHostname() string { if len(b.cfg.Localhost) == 0 { h, _ := os.Hostname() return h } return b.cfg.Localhost } func (b *BinlogSyncer) writeRegisterSlaveCommand() error { b.c.ResetSequence() hostname := b.localHostname() // This should be the name of slave host not the host we are connecting to. data := make([]byte, 4+1+4+1+len(hostname)+1+len(b.cfg.User)+1+2+4+4) pos := 4 data[pos] = COM_REGISTER_SLAVE pos++ binary.LittleEndian.PutUint32(data[pos:], b.cfg.ServerID) pos += 4 // This should be the name of slave hostname not the host we are connecting to. data[pos] = uint8(len(hostname)) pos++ n := copy(data[pos:], hostname) pos += n data[pos] = uint8(len(b.cfg.User)) pos++ n = copy(data[pos:], b.cfg.User) pos += n data[pos] = uint8(0) pos++ binary.LittleEndian.PutUint16(data[pos:], b.cfg.Port) pos += 2 //replication rank, not used binary.LittleEndian.PutUint32(data[pos:], 0) pos += 4 // master ID, 0 is OK binary.LittleEndian.PutUint32(data[pos:], 0) return b.c.WritePacket(data) } func (b *BinlogSyncer) replySemiSyncACK(p Position) error { b.c.ResetSequence() data := make([]byte, 4+1+8+len(p.Name)) pos := 4 // semi sync indicator data[pos] = SemiSyncIndicator pos++ binary.LittleEndian.PutUint64(data[pos:], uint64(p.Pos)) pos += 8 copy(data[pos:], p.Name) err := b.c.WritePacket(data) if err != nil { return errors.Trace(err) } return nil } func (b *BinlogSyncer) retrySync() error { b.m.Lock() defer b.m.Unlock() b.parser.Reset() b.prevMySQLGTIDEvent = nil if b.prevGset != nil { msg := fmt.Sprintf("begin to re-sync from %s", b.prevGset.String()) if b.currGset != nil { msg = fmt.Sprintf("%v (last read GTID=%v)", msg, b.currGset) } b.cfg.Logger.Infof(msg) if err := b.prepareSyncGTID(b.prevGset); err != nil { return errors.Trace(err) } } else { b.cfg.Logger.Infof("begin to re-sync from %s", b.nextPos) if err := b.prepareSyncPos(b.nextPos); err != nil { return errors.Trace(err) } } return nil } func (b *BinlogSyncer) prepareSyncPos(pos Position) error { // always start from position 4 if pos.Pos < 4 { pos.Pos = 4 } if err := b.prepare(); err != nil { return errors.Trace(err) } if err := b.writeBinlogDumpCommand(pos); err != nil { return errors.Trace(err) } return nil } func (b *BinlogSyncer) prepareSyncGTID(gset GTIDSet) error { var err error // re establishing network connection here and will start getting binlog events from "gset + 1", thus until first // MariadbGTIDEvent/GTIDEvent event is received - we effectively do not have a "current GTID" b.currGset = nil if err = b.prepare(); err != nil { return errors.Trace(err) } switch b.cfg.Flavor { case MariaDBFlavor: err = b.writeBinlogDumpMariadbGTIDCommand(gset) default: // default use MySQL err = b.writeBinlogDumpMysqlGTIDCommand(gset) } if err != nil { return err } return nil } func (b *BinlogSyncer) onStream(s *BinlogStreamer) { defer func() { if e := recover(); e != nil { s.closeWithError(fmt.Errorf("Err: %v\n Stack: %s", e, Pstack())) } b.wg.Done() }() for { data, err := b.c.ReadPacket() select { case <-b.ctx.Done(): s.close() return default: } if err != nil { b.cfg.Logger.Error(err) // we meet connection error, should re-connect again with // last nextPos or nextGTID we got. if len(b.nextPos.Name) == 0 && b.prevGset == nil { // we can't get the correct position, close. s.closeWithError(err) return } if b.cfg.DisableRetrySync { b.cfg.Logger.Warn("retry sync is disabled") s.closeWithError(err) return } for { select { case <-b.ctx.Done(): s.close() return case <-time.After(time.Second): b.retryCount++ if err = b.retrySync(); err != nil { if b.cfg.MaxReconnectAttempts > 0 && b.retryCount >= b.cfg.MaxReconnectAttempts { b.cfg.Logger.Errorf( "retry sync err: %v, exceeded max retries (%d)", err, b.cfg.MaxReconnectAttempts, ) s.closeWithError(err) return } b.cfg.Logger.Errorf( "retry sync err: %v, wait 1s and retry again (retries: %d/%d)", err, b.retryCount, b.cfg.MaxReconnectAttempts, ) continue } } break } // we connect the server and begin to re-sync again. continue } //set read timeout if b.cfg.ReadTimeout > 0 { _ = b.c.SetReadDeadline(utils.Now().Add(b.cfg.ReadTimeout)) } // Reset retry count on successful packet receieve b.retryCount = 0 switch data[0] { case OK_HEADER: // Parse the event e, needACK, err := b.parseEvent(data) if err != nil { s.closeWithError(err) return } // Handle the event and send ACK if necessary err = b.handleEventAndACK(s, e, needACK) if err != nil { s.closeWithError(err) return } case ERR_HEADER: err = b.c.HandleErrorPacket(data) s.closeWithError(err) return case EOF_HEADER: // refer to https://dev.mysql.com/doc/internals/en/com-binlog-dump.html#binlog-dump-non-block // when COM_BINLOG_DUMP command use BINLOG_DUMP_NON_BLOCK flag, // if there is no more event to send an EOF_Packet instead of blocking the connection b.cfg.Logger.Info("receive EOF packet, no more binlog event now.") continue default: b.cfg.Logger.Errorf("invalid stream header %c", data[0]) continue } } } // parseEvent parses the raw data into a BinlogEvent. // It only handles parsing and does not perform any side effects. // Returns the parsed BinlogEvent, a boolean indicating if an ACK is needed, and an error if the // parsing fails func (b *BinlogSyncer) parseEvent(data []byte) (event *BinlogEvent, needACK bool, err error) { // Skip OK byte (0x00) data = data[1:] needACK = false if b.cfg.SemiSyncEnabled && data[0] == SemiSyncIndicator { needACK = data[1] == 0x01 // Skip semi-sync header data = data[2:] } // Parse the event using the BinlogParser event, err = b.parser.Parse(data) if err != nil { return nil, false, errors.Trace(err) } return event, needACK, nil } // handleEventAndACK processes an event and sends an ACK if necessary. func (b *BinlogSyncer) handleEventAndACK(s *BinlogStreamer, e *BinlogEvent, needACK bool) error { // Update the next position based on the event's LogPos if e.Header.LogPos > 0 { // Some events like FormatDescriptionEvent return 0, ignore. b.nextPos.Pos = e.Header.LogPos } // Handle event types to update positions and GTID sets switch event := e.Event.(type) { case *RotateEvent: b.nextPos.Name = string(event.NextLogName) b.nextPos.Pos = uint32(event.Position) b.cfg.Logger.Infof("rotate to %s", b.nextPos) case *GTIDEvent: if b.prevGset == nil { break } if b.currGset == nil { b.currGset = b.prevGset.Clone() } u, err := uuid.FromBytes(event.SID) if err != nil { return errors.Trace(err) } b.currGset.(*MysqlGTIDSet).AddGTID(u, event.GNO) if b.prevMySQLGTIDEvent != nil { u, err = uuid.FromBytes(b.prevMySQLGTIDEvent.SID) if err != nil { return errors.Trace(err) } b.prevGset.(*MysqlGTIDSet).AddGTID(u, b.prevMySQLGTIDEvent.GNO) } b.prevMySQLGTIDEvent = event case *MariadbGTIDEvent: if b.prevGset == nil { break } if b.currGset == nil { b.currGset = b.prevGset.Clone() } prev := b.currGset.Clone() err := b.currGset.(*MariadbGTIDSet).AddSet(&event.GTID) if err != nil { return errors.Trace(err) } // Right after reconnect we may see the same GTID as before; update prevGset if currGset changed if !b.currGset.Equal(prev) { b.prevGset = prev } case *XIDEvent: if !b.cfg.DiscardGTIDSet { event.GSet = b.getCurrentGtidSet() } case *QueryEvent: if !b.cfg.DiscardGTIDSet { event.GSet = b.getCurrentGtidSet() } } // Use SynchronousEventHandler if it's set if b.cfg.SynchronousEventHandler != nil { err := b.cfg.SynchronousEventHandler.HandleEvent(e) if err != nil { return errors.Trace(err) } } else { // Asynchronous mode: send the event to the streamer channel select { case s.ch <- e: case <-b.ctx.Done(): return errors.New("sync is being closed...") } } if needACK { err := b.replySemiSyncACK(b.nextPos) if err != nil { return errors.Trace(err) } } return nil } // getCurrentGtidSet returns a clone of the current GTID set. func (b *BinlogSyncer) getCurrentGtidSet() GTIDSet { if b.currGset != nil { return b.currGset.Clone() } return nil } // LastConnectionID returns last connectionID. func (b *BinlogSyncer) LastConnectionID() uint32 { return b.lastConnectionID } func (b *BinlogSyncer) newConnection(ctx context.Context) (*client.Conn, error) { var addr string if b.cfg.Port != 0 { addr = net.JoinHostPort(b.cfg.Host, strconv.Itoa(int(b.cfg.Port))) } else { addr = b.cfg.Host } timeoutCtx, cancel := context.WithTimeout(ctx, time.Second*10) defer cancel() return client.ConnectWithDialer(timeoutCtx, "", addr, b.cfg.User, b.cfg.Password, "", b.cfg.Dialer, func(c *client.Conn) error { c.SetTLSConfig(b.cfg.TLSConfig) c.SetAttributes(map[string]string{"_client_role": "binary_log_listener"}) if b.cfg.ReadTimeout > 0 { c.ReadTimeout = b.cfg.ReadTimeout } return nil }) } func (b *BinlogSyncer) killConnection(conn *client.Conn, id uint32) { cmd := fmt.Sprintf("KILL %d", id) if _, err := conn.Execute(cmd); err != nil { b.cfg.Logger.Errorf("kill connection %d error %v", id, err) // Unknown thread id if code := ErrorCode(err.Error()); code != ER_NO_SUCH_THREAD { b.cfg.Logger.Error(errors.Trace(err)) } } b.cfg.Logger.Infof("kill last connection id %d", id) } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/replication/const.go ================================================ package replication const ( //we only support MySQL 5.0.0+ binlog format, maybe??? MinBinlogVersion = 4 ) var ( //binlog header [ fe `bin` ] BinLogFileHeader = []byte{0xfe, 0x62, 0x69, 0x6e} SemiSyncIndicator byte = 0xef ) const ( LOG_EVENT_BINLOG_IN_USE_F uint16 = 0x0001 LOG_EVENT_FORCED_ROTATE_F uint16 = 0x0002 LOG_EVENT_THREAD_SPECIFIC_F uint16 = 0x0004 LOG_EVENT_SUPPRESS_USE_F uint16 = 0x0008 LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F uint16 = 0x0010 LOG_EVENT_ARTIFICIAL_F uint16 = 0x0020 LOG_EVENT_RELAY_LOG_F uint16 = 0x0040 LOG_EVENT_IGNORABLE_F uint16 = 0x0080 LOG_EVENT_NO_FILTER_F uint16 = 0x0100 LOG_EVENT_MTS_ISOLATE_F uint16 = 0x0200 ) const ( BINLOG_DUMP_NEVER_STOP uint16 = 0x00 BINLOG_DUMP_NON_BLOCK uint16 = 0x01 BINLOG_SEND_ANNOTATE_ROWS_EVENT uint16 = 0x02 BINLOG_THROUGH_POSITION uint16 = 0x02 BINLOG_THROUGH_GTID uint16 = 0x04 ) const ( BINLOG_ROW_IMAGE_FULL = "FULL" BINLOG_ROW_IMAGE_MINIMAL = "MINIMAL" BINLOG_ROW_IMAGE_NOBLOB = "NOBLOB" ) const ( BINLOG_MARIADB_FL_STANDALONE = 1 << iota /*1 - FL_STANDALONE is set when there is no terminating COMMIT event*/ BINLOG_MARIADB_FL_GROUP_COMMIT_ID /*2 - FL_GROUP_COMMIT_ID is set when event group is part of a group commit on the master. Groups with same commit_id are part of the same group commit.*/ BINLOG_MARIADB_FL_TRANSACTIONAL /*4 - FL_TRANSACTIONAL is set for an event group that can be safely rolled back (no MyISAM, eg.).*/ BINLOG_MARIADB_FL_ALLOW_PARALLEL /*8 - FL_ALLOW_PARALLEL reflects the (negation of the) value of @@SESSION.skip_parallel_replication at the time of commit*/ BINLOG_MARIADB_FL_WAITED /*16 = FL_WAITED is set if a row lock wait (or other wait) is detected during the execution of the transaction*/ BINLOG_MARIADB_FL_DDL /*32 - FL_DDL is set for event group containing DDL*/ ) // See `Log_event_type` in binlog_event.h // https://github.com/mysql/mysql-server/blob/trunk/libs/mysql/binlog/event/binlog_event.h type EventType byte const ( UNKNOWN_EVENT EventType = iota START_EVENT_V3 QUERY_EVENT STOP_EVENT ROTATE_EVENT INTVAR_EVENT LOAD_EVENT SLAVE_EVENT CREATE_FILE_EVENT APPEND_BLOCK_EVENT EXEC_LOAD_EVENT DELETE_FILE_EVENT NEW_LOAD_EVENT RAND_EVENT USER_VAR_EVENT FORMAT_DESCRIPTION_EVENT XID_EVENT BEGIN_LOAD_QUERY_EVENT EXECUTE_LOAD_QUERY_EVENT TABLE_MAP_EVENT WRITE_ROWS_EVENTv0 UPDATE_ROWS_EVENTv0 DELETE_ROWS_EVENTv0 WRITE_ROWS_EVENTv1 UPDATE_ROWS_EVENTv1 DELETE_ROWS_EVENTv1 INCIDENT_EVENT HEARTBEAT_EVENT IGNORABLE_EVENT ROWS_QUERY_EVENT WRITE_ROWS_EVENTv2 UPDATE_ROWS_EVENTv2 DELETE_ROWS_EVENTv2 GTID_EVENT ANONYMOUS_GTID_EVENT PREVIOUS_GTIDS_EVENT TRANSACTION_CONTEXT_EVENT VIEW_CHANGE_EVENT XA_PREPARE_LOG_EVENT PARTIAL_UPDATE_ROWS_EVENT TRANSACTION_PAYLOAD_EVENT HEARTBEAT_LOG_EVENT_V2 GTID_TAGGED_LOG_EVENT ) const ( // MariaDB event starts from 160 MARIADB_ANNOTATE_ROWS_EVENT EventType = 160 + iota MARIADB_BINLOG_CHECKPOINT_EVENT MARIADB_GTID_EVENT MARIADB_GTID_LIST_EVENT MARIADB_START_ENCRYPTION_EVENT MARIADB_QUERY_COMPRESSED_EVENT MARIADB_WRITE_ROWS_COMPRESSED_EVENT_V1 MARIADB_UPDATE_ROWS_COMPRESSED_EVENT_V1 MARIADB_DELETE_ROWS_COMPRESSED_EVENT_V1 ) func (e EventType) String() string { switch e { case UNKNOWN_EVENT: return "UnknownEvent" case START_EVENT_V3: return "StartEventV3" case QUERY_EVENT: return "QueryEvent" case STOP_EVENT: return "StopEvent" case ROTATE_EVENT: return "RotateEvent" case INTVAR_EVENT: return "IntVarEvent" case LOAD_EVENT: return "LoadEvent" case SLAVE_EVENT: return "SlaveEvent" case CREATE_FILE_EVENT: return "CreateFileEvent" case APPEND_BLOCK_EVENT: return "AppendBlockEvent" case EXEC_LOAD_EVENT: return "ExecLoadEvent" case DELETE_FILE_EVENT: return "DeleteFileEvent" case NEW_LOAD_EVENT: return "NewLoadEvent" case RAND_EVENT: return "RandEvent" case USER_VAR_EVENT: return "UserVarEvent" case FORMAT_DESCRIPTION_EVENT: return "FormatDescriptionEvent" case XID_EVENT: return "XIDEvent" case BEGIN_LOAD_QUERY_EVENT: return "BeginLoadQueryEvent" case EXECUTE_LOAD_QUERY_EVENT: return "ExectueLoadQueryEvent" case TABLE_MAP_EVENT: return "TableMapEvent" case WRITE_ROWS_EVENTv0: return "WriteRowsEventV0" case UPDATE_ROWS_EVENTv0: return "UpdateRowsEventV0" case DELETE_ROWS_EVENTv0: return "DeleteRowsEventV0" case WRITE_ROWS_EVENTv1: return "WriteRowsEventV1" case UPDATE_ROWS_EVENTv1: return "UpdateRowsEventV1" case DELETE_ROWS_EVENTv1: return "DeleteRowsEventV1" case INCIDENT_EVENT: return "IncidentEvent" case HEARTBEAT_EVENT: return "HeartbeatEvent" case IGNORABLE_EVENT: return "IgnorableEvent" case ROWS_QUERY_EVENT: return "RowsQueryEvent" case WRITE_ROWS_EVENTv2: return "WriteRowsEventV2" case UPDATE_ROWS_EVENTv2: return "UpdateRowsEventV2" case DELETE_ROWS_EVENTv2: return "DeleteRowsEventV2" case GTID_EVENT: return "GTIDEvent" case ANONYMOUS_GTID_EVENT: return "AnonymousGTIDEvent" case PREVIOUS_GTIDS_EVENT: return "PreviousGTIDsEvent" case MARIADB_ANNOTATE_ROWS_EVENT: return "MariadbAnnotateRowsEvent" case MARIADB_BINLOG_CHECKPOINT_EVENT: return "MariadbBinLogCheckPointEvent" case MARIADB_GTID_EVENT: return "MariadbGTIDEvent" case MARIADB_GTID_LIST_EVENT: return "MariadbGTIDListEvent" case TRANSACTION_CONTEXT_EVENT: return "TransactionContextEvent" case VIEW_CHANGE_EVENT: return "ViewChangeEvent" case XA_PREPARE_LOG_EVENT: return "XAPrepareLogEvent" case PARTIAL_UPDATE_ROWS_EVENT: return "PartialUpdateRowsEvent" case TRANSACTION_PAYLOAD_EVENT: return "TransactionPayloadEvent" case HEARTBEAT_LOG_EVENT_V2: return "HeartbeatLogEventV2" case GTID_TAGGED_LOG_EVENT: return "Gtid_tagged_log_event" case MARIADB_START_ENCRYPTION_EVENT: return "MariadbStartEncryptionEvent" case MARIADB_QUERY_COMPRESSED_EVENT: return "MariadbQueryCompressedEvent" case MARIADB_WRITE_ROWS_COMPRESSED_EVENT_V1: return "MariadbWriteRowsCompressedEventV1" case MARIADB_UPDATE_ROWS_COMPRESSED_EVENT_V1: return "MariadbUpdateRowsCompressedEventV1" case MARIADB_DELETE_ROWS_COMPRESSED_EVENT_V1: return "MariadbDeleteRowsCompressedEventV1" default: return "UnknownEvent" } } const ( BINLOG_CHECKSUM_ALG_OFF byte = 0 // Events are without checksum though its generator // is checksum-capable New Master (NM). BINLOG_CHECKSUM_ALG_CRC32 byte = 1 // CRC32 of zlib algorithm. // BINLOG_CHECKSUM_ALG_ENUM_END, // the cut line: valid alg range is [1, 0x7f]. BINLOG_CHECKSUM_ALG_UNDEF byte = 255 // special value to tag undetermined yet checksum // or events from checksum-unaware servers ) // These are TABLE_MAP_EVENT's optional metadata field type, from: libbinlogevents/include/rows_event.h const ( TABLE_MAP_OPT_META_SIGNEDNESS byte = iota + 1 TABLE_MAP_OPT_META_DEFAULT_CHARSET TABLE_MAP_OPT_META_COLUMN_CHARSET TABLE_MAP_OPT_META_COLUMN_NAME TABLE_MAP_OPT_META_SET_STR_VALUE TABLE_MAP_OPT_META_ENUM_STR_VALUE TABLE_MAP_OPT_META_GEOMETRY_TYPE TABLE_MAP_OPT_META_SIMPLE_PRIMARY_KEY TABLE_MAP_OPT_META_PRIMARY_KEY_WITH_PREFIX TABLE_MAP_OPT_META_ENUM_AND_SET_DEFAULT_CHARSET TABLE_MAP_OPT_META_ENUM_AND_SET_COLUMN_CHARSET TABLE_MAP_OPT_META_COLUMN_VISIBILITY ) type IntVarEventType byte const ( INVALID IntVarEventType = iota LAST_INSERT_ID INSERT_ID ) const ( ENUM_EXTRA_ROW_INFO_TYPECODE_NDB byte = iota ENUM_EXTRA_ROW_INFO_TYPECODE_PARTITION ) ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/replication/doc.go ================================================ /* Replication package is to handle MySQL replication protocol. Todo: + Get table information when handing rows event. */ package replication ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/replication/event.go ================================================ package replication import ( "bytes" "encoding/binary" "encoding/hex" "fmt" "io" "strconv" "strings" "time" "unicode" "github.com/google/uuid" "github.com/pingcap/errors" . "github.com/go-mysql-org/go-mysql/mysql" ) const ( EventHeaderSize = 19 SidLength = 16 LogicalTimestampTypeCode = 2 PartLogicalTimestampLength = 8 BinlogChecksumLength = 4 UndefinedServerVer = 999999 // UNDEFINED_SERVER_VERSION ) type BinlogEvent struct { // raw binlog data which contains all data, including binlog header and event body, and including crc32 checksum if exists RawData []byte Header *EventHeader Event Event } func (e *BinlogEvent) Dump(w io.Writer) { e.Header.Dump(w) e.Event.Dump(w) } type Event interface { //Dump Event, format like python-mysql-replication Dump(w io.Writer) Decode(data []byte) error } type EventError struct { Header *EventHeader //Error message Err string //Event data Data []byte } func (e *EventError) Error() string { return fmt.Sprintf("Header %#v, Data %q, Err: %v", e.Header, e.Data, e.Err) } type EventHeader struct { Timestamp uint32 EventType EventType ServerID uint32 EventSize uint32 LogPos uint32 Flags uint16 } func (h *EventHeader) Decode(data []byte) error { if len(data) < EventHeaderSize { return errors.Errorf("header size too short %d, must 19", len(data)) } pos := 0 h.Timestamp = binary.LittleEndian.Uint32(data[pos:]) pos += 4 h.EventType = EventType(data[pos]) pos++ h.ServerID = binary.LittleEndian.Uint32(data[pos:]) pos += 4 h.EventSize = binary.LittleEndian.Uint32(data[pos:]) pos += 4 h.LogPos = binary.LittleEndian.Uint32(data[pos:]) pos += 4 h.Flags = binary.LittleEndian.Uint16(data[pos:]) // pos += 2 if h.EventSize < uint32(EventHeaderSize) { return errors.Errorf("invalid event size %d, must >= 19", h.EventSize) } return nil } func (h *EventHeader) Dump(w io.Writer) { fmt.Fprintf(w, "=== %s ===\n", h.EventType) fmt.Fprintf(w, "Date: %s\n", time.Unix(int64(h.Timestamp), 0).Format(TimeFormat)) fmt.Fprintf(w, "Log position: %d\n", h.LogPos) fmt.Fprintf(w, "Event size: %d\n", h.EventSize) } var ( checksumVersionSplitMysql = []int{5, 6, 1} checksumVersionProductMysql = (checksumVersionSplitMysql[0]*256+checksumVersionSplitMysql[1])*256 + checksumVersionSplitMysql[2] checksumVersionSplitMariaDB = []int{5, 3, 0} checksumVersionProductMariaDB = (checksumVersionSplitMariaDB[0]*256+checksumVersionSplitMariaDB[1])*256 + checksumVersionSplitMariaDB[2] ) // server version format X.Y.Zabc, a is not . or number func splitServerVersion(server string) []int { seps := strings.Split(server, ".") if len(seps) < 3 { return []int{0, 0, 0} } x, _ := strconv.Atoi(seps[0]) y, _ := strconv.Atoi(seps[1]) index := 0 for i, c := range seps[2] { if !unicode.IsNumber(c) { index = i break } } z, _ := strconv.Atoi(seps[2][0:index]) return []int{x, y, z} } func calcVersionProduct(server string) int { versionSplit := splitServerVersion(server) return (versionSplit[0]*256+versionSplit[1])*256 + versionSplit[2] } type FormatDescriptionEvent struct { Version uint16 ServerVersion string CreateTimestamp uint32 EventHeaderLength uint8 EventTypeHeaderLengths []byte // 0 is off, 1 is for CRC32, 255 is undefined ChecksumAlgorithm byte } func (e *FormatDescriptionEvent) Decode(data []byte) error { pos := 0 e.Version = binary.LittleEndian.Uint16(data[pos:]) pos += 2 serverVersionRaw := make([]byte, 50) copy(serverVersionRaw, data[pos:]) pos += 50 e.CreateTimestamp = binary.LittleEndian.Uint32(data[pos:]) pos += 4 e.EventHeaderLength = data[pos] pos++ if e.EventHeaderLength != byte(EventHeaderSize) { return errors.Errorf("invalid event header length %d, must 19", e.EventHeaderLength) } serverVersionLength := bytes.Index(serverVersionRaw, []byte{0x0}) if serverVersionLength < 0 { e.ServerVersion = string(serverVersionRaw) } else { e.ServerVersion = string(serverVersionRaw[:serverVersionLength]) } checksumProduct := checksumVersionProductMysql if strings.Contains(strings.ToLower(e.ServerVersion), "mariadb") { checksumProduct = checksumVersionProductMariaDB } if calcVersionProduct(e.ServerVersion) >= checksumProduct { // here, the last 5 bytes is 1 byte check sum alg type and 4 byte checksum if exists e.ChecksumAlgorithm = data[len(data)-5] e.EventTypeHeaderLengths = data[pos : len(data)-5] } else { e.ChecksumAlgorithm = BINLOG_CHECKSUM_ALG_UNDEF e.EventTypeHeaderLengths = data[pos:] } return nil } func (e *FormatDescriptionEvent) Dump(w io.Writer) { fmt.Fprintf(w, "Version: %d\n", e.Version) fmt.Fprintf(w, "Server version: %s\n", e.ServerVersion) //fmt.Fprintf(w, "Create date: %s\n", time.Unix(int64(e.CreateTimestamp), 0).Format(TimeFormat)) fmt.Fprintf(w, "Checksum algorithm: %d\n", e.ChecksumAlgorithm) //fmt.Fprintf(w, "Event header lengths: \n%s", hex.Dump(e.EventTypeHeaderLengths)) fmt.Fprintln(w) } type RotateEvent struct { Position uint64 NextLogName []byte } func (e *RotateEvent) Decode(data []byte) error { e.Position = binary.LittleEndian.Uint64(data[0:]) e.NextLogName = data[8:] return nil } func (e *RotateEvent) Dump(w io.Writer) { fmt.Fprintf(w, "Position: %d\n", e.Position) fmt.Fprintf(w, "Next log name: %s\n", e.NextLogName) fmt.Fprintln(w) } type PreviousGTIDsEvent struct { GTIDSets string } type GtidFormat int const ( GtidFormatClassic = iota GtidFormatTagged ) // Decode the number of sids (source identifiers) and if it is using // tagged GTIDs or classic (non-tagged) GTIDs. // // Note that each gtid tag increases the sidno here, so a single UUID // might turn up multiple times if there are multipl tags. // // see also: // decode_nsids_format in mysql/mysql-server // https://github.com/mysql/mysql-server/blob/61a3a1d8ef15512396b4c2af46e922a19bf2b174/sql/rpl_gtid_set.cc#L1363-L1378 func decodeSid(data []byte) (format GtidFormat, sidnr uint64) { if data[7] == 1 { format = GtidFormatTagged } if format == GtidFormatTagged { masked := make([]byte, 8) copy(masked, data[1:7]) sidnr = binary.LittleEndian.Uint64(masked) return } sidnr = binary.LittleEndian.Uint64(data[:8]) return } func (e *PreviousGTIDsEvent) Decode(data []byte) error { pos := 0 format, uuidCount := decodeSid(data) pos += 8 previousGTIDSets := make([]string, uuidCount) currentSetnr := 0 var buf strings.Builder for range previousGTIDSets { uuid := e.decodeUuid(data[pos : pos+16]) pos += 16 var tag string if format == GtidFormatTagged { tagLength := int(data[pos]) / 2 pos += 1 if tagLength > 0 { // 0 == no tag, >0 == tag tag = string(data[pos : pos+tagLength]) pos += tagLength } } if len(tag) > 0 { buf.WriteString(":") buf.WriteString(tag) } else { if currentSetnr != 0 { buf.WriteString(",") } buf.WriteString(uuid) currentSetnr += 1 } sliceCount := binary.LittleEndian.Uint16(data[pos : pos+8]) pos += 8 for range sliceCount { buf.WriteString(":") start := e.decodeInterval(data[pos : pos+8]) pos += 8 stop := e.decodeInterval(data[pos : pos+8]) pos += 8 if stop == start+1 { fmt.Fprintf(&buf, "%d", start) } else { fmt.Fprintf(&buf, "%d-%d", start, stop-1) } } if len(tag) == 0 { currentSetnr += 1 } } e.GTIDSets = buf.String() return nil } func (e *PreviousGTIDsEvent) Dump(w io.Writer) { fmt.Fprintf(w, "Previous GTID Event: %s\n", e.GTIDSets) fmt.Fprintln(w) } func (e *PreviousGTIDsEvent) decodeUuid(data []byte) string { return fmt.Sprintf("%s-%s-%s-%s-%s", hex.EncodeToString(data[0:4]), hex.EncodeToString(data[4:6]), hex.EncodeToString(data[6:8]), hex.EncodeToString(data[8:10]), hex.EncodeToString(data[10:])) } func (e *PreviousGTIDsEvent) decodeInterval(data []byte) uint64 { return binary.LittleEndian.Uint64(data) } type XIDEvent struct { XID uint64 // in fact XIDEvent dosen't have the GTIDSet information, just for beneficial to use GSet GTIDSet } func (e *XIDEvent) Decode(data []byte) error { e.XID = binary.LittleEndian.Uint64(data) return nil } func (e *XIDEvent) Dump(w io.Writer) { fmt.Fprintf(w, "XID: %d\n", e.XID) if e.GSet != nil { fmt.Fprintf(w, "GTIDSet: %s\n", e.GSet.String()) } fmt.Fprintln(w) } type QueryEvent struct { SlaveProxyID uint32 ExecutionTime uint32 ErrorCode uint16 StatusVars []byte Schema []byte Query []byte // for mariadb QUERY_COMPRESSED_EVENT compressed bool // in fact QueryEvent dosen't have the GTIDSet information, just for beneficial to use GSet GTIDSet } func (e *QueryEvent) Decode(data []byte) error { pos := 0 e.SlaveProxyID = binary.LittleEndian.Uint32(data[pos:]) pos += 4 e.ExecutionTime = binary.LittleEndian.Uint32(data[pos:]) pos += 4 schemaLength := data[pos] pos++ e.ErrorCode = binary.LittleEndian.Uint16(data[pos:]) pos += 2 statusVarsLength := binary.LittleEndian.Uint16(data[pos:]) pos += 2 e.StatusVars = data[pos : pos+int(statusVarsLength)] pos += int(statusVarsLength) e.Schema = data[pos : pos+int(schemaLength)] pos += int(schemaLength) //skip 0x00 pos++ if e.compressed { decompressedQuery, err := DecompressMariadbData(data[pos:]) if err != nil { return err } e.Query = decompressedQuery } else { e.Query = data[pos:] } return nil } func (e *QueryEvent) Dump(w io.Writer) { fmt.Fprintf(w, "Slave proxy ID: %d\n", e.SlaveProxyID) fmt.Fprintf(w, "Execution time: %d\n", e.ExecutionTime) fmt.Fprintf(w, "Error code: %d\n", e.ErrorCode) //fmt.Fprintf(w, "Status vars: \n%s", hex.Dump(e.StatusVars)) fmt.Fprintf(w, "Schema: %s\n", e.Schema) fmt.Fprintf(w, "Query: %s\n", e.Query) if e.GSet != nil { fmt.Fprintf(w, "GTIDSet: %s\n", e.GSet.String()) } fmt.Fprintln(w) } type GTIDEvent struct { CommitFlag uint8 SID []byte GNO int64 LastCommitted int64 SequenceNumber int64 // ImmediateCommitTimestamp/OriginalCommitTimestamp are introduced in MySQL-8.0.1, see: // https://mysqlhighavailability.com/replication-features-in-mysql-8-0-1/ ImmediateCommitTimestamp uint64 OriginalCommitTimestamp uint64 // Total transaction length (including this GTIDEvent), introduced in MySQL-8.0.2, see: // https://mysqlhighavailability.com/taking-advantage-of-new-transaction-length-metadata/ TransactionLength uint64 // ImmediateServerVersion/OriginalServerVersion are introduced in MySQL-8.0.14, see // https://dev.mysql.com/doc/refman/8.0/en/replication-compatibility.html ImmediateServerVersion uint32 OriginalServerVersion uint32 } func (e *GTIDEvent) Decode(data []byte) error { pos := 0 e.CommitFlag = data[pos] pos++ e.SID = data[pos : pos+SidLength] pos += SidLength e.GNO = int64(binary.LittleEndian.Uint64(data[pos:])) pos += 8 if len(data) >= 42 { if data[pos] == LogicalTimestampTypeCode { pos++ e.LastCommitted = int64(binary.LittleEndian.Uint64(data[pos:])) pos += PartLogicalTimestampLength e.SequenceNumber = int64(binary.LittleEndian.Uint64(data[pos:])) pos += 8 // IMMEDIATE_COMMIT_TIMESTAMP_LENGTH = 7 if len(data)-pos < 7 { return nil } e.ImmediateCommitTimestamp = FixedLengthInt(data[pos : pos+7]) pos += 7 if (e.ImmediateCommitTimestamp & (uint64(1) << 55)) != 0 { // If the most significant bit set, another 7 byte follows representing OriginalCommitTimestamp e.ImmediateCommitTimestamp &= ^(uint64(1) << 55) e.OriginalCommitTimestamp = FixedLengthInt(data[pos : pos+7]) pos += 7 } else { // Otherwise OriginalCommitTimestamp == ImmediateCommitTimestamp e.OriginalCommitTimestamp = e.ImmediateCommitTimestamp } // TRANSACTION_LENGTH_MIN_LENGTH = 1 if len(data)-pos < 1 { return nil } var n int e.TransactionLength, _, n = LengthEncodedInt(data[pos:]) pos += n // IMMEDIATE_SERVER_VERSION_LENGTH = 4 e.ImmediateServerVersion = UndefinedServerVer e.OriginalServerVersion = UndefinedServerVer if len(data)-pos < 4 { return nil } e.ImmediateServerVersion = binary.LittleEndian.Uint32(data[pos:]) pos += 4 if (e.ImmediateServerVersion & (uint32(1) << 31)) != 0 { // If the most significant bit set, another 4 byte follows representing OriginalServerVersion e.ImmediateServerVersion &= ^(uint32(1) << 31) e.OriginalServerVersion = binary.LittleEndian.Uint32(data[pos:]) // pos += 4 } else { // Otherwise OriginalServerVersion == ImmediateServerVersion e.OriginalServerVersion = e.ImmediateServerVersion } } } return nil } func (e *GTIDEvent) Dump(w io.Writer) { fmtTime := func(t time.Time) string { if t.IsZero() { return "" } return t.Format(time.RFC3339Nano) } fmt.Fprintf(w, "Commit flag: %d\n", e.CommitFlag) u, _ := uuid.FromBytes(e.SID) fmt.Fprintf(w, "GTID_NEXT: %s:%d\n", u.String(), e.GNO) fmt.Fprintf(w, "LAST_COMMITTED: %d\n", e.LastCommitted) fmt.Fprintf(w, "SEQUENCE_NUMBER: %d\n", e.SequenceNumber) fmt.Fprintf(w, "Immediate commmit timestamp: %d (%s)\n", e.ImmediateCommitTimestamp, fmtTime(e.ImmediateCommitTime())) fmt.Fprintf(w, "Orignal commmit timestamp: %d (%s)\n", e.OriginalCommitTimestamp, fmtTime(e.OriginalCommitTime())) fmt.Fprintf(w, "Transaction length: %d\n", e.TransactionLength) fmt.Fprintf(w, "Immediate server version: %d\n", e.ImmediateServerVersion) fmt.Fprintf(w, "Orignal server version: %d\n", e.OriginalServerVersion) fmt.Fprintln(w) } func (e *GTIDEvent) GTIDNext() (GTIDSet, error) { u, err := uuid.FromBytes(e.SID) if err != nil { return nil, err } return ParseMysqlGTIDSet(strings.Join([]string{u.String(), strconv.FormatInt(e.GNO, 10)}, ":")) } // ImmediateCommitTime returns the commit time of this trx on the immediate server // or zero time if not available. func (e *GTIDEvent) ImmediateCommitTime() time.Time { return microSecTimestampToTime(e.ImmediateCommitTimestamp) } // OriginalCommitTime returns the commit time of this trx on the original server // or zero time if not available. func (e *GTIDEvent) OriginalCommitTime() time.Time { return microSecTimestampToTime(e.OriginalCommitTimestamp) } type BeginLoadQueryEvent struct { FileID uint32 BlockData []byte } func (e *BeginLoadQueryEvent) Decode(data []byte) error { pos := 0 e.FileID = binary.LittleEndian.Uint32(data[pos:]) pos += 4 e.BlockData = data[pos:] return nil } func (e *BeginLoadQueryEvent) Dump(w io.Writer) { fmt.Fprintf(w, "File ID: %d\n", e.FileID) fmt.Fprintf(w, "Block data: %s\n", e.BlockData) fmt.Fprintln(w) } type ExecuteLoadQueryEvent struct { SlaveProxyID uint32 ExecutionTime uint32 SchemaLength uint8 ErrorCode uint16 StatusVars uint16 FileID uint32 StartPos uint32 EndPos uint32 DupHandlingFlags uint8 } func (e *ExecuteLoadQueryEvent) Decode(data []byte) error { pos := 0 e.SlaveProxyID = binary.LittleEndian.Uint32(data[pos:]) pos += 4 e.ExecutionTime = binary.LittleEndian.Uint32(data[pos:]) pos += 4 e.SchemaLength = data[pos] pos++ e.ErrorCode = binary.LittleEndian.Uint16(data[pos:]) pos += 2 e.StatusVars = binary.LittleEndian.Uint16(data[pos:]) pos += 2 e.FileID = binary.LittleEndian.Uint32(data[pos:]) pos += 4 e.StartPos = binary.LittleEndian.Uint32(data[pos:]) pos += 4 e.EndPos = binary.LittleEndian.Uint32(data[pos:]) pos += 4 e.DupHandlingFlags = data[pos] return nil } func (e *ExecuteLoadQueryEvent) Dump(w io.Writer) { fmt.Fprintf(w, "Slave proxy ID: %d\n", e.SlaveProxyID) fmt.Fprintf(w, "Execution time: %d\n", e.ExecutionTime) fmt.Fprintf(w, "Schame length: %d\n", e.SchemaLength) fmt.Fprintf(w, "Error code: %d\n", e.ErrorCode) fmt.Fprintf(w, "Status vars length: %d\n", e.StatusVars) fmt.Fprintf(w, "File ID: %d\n", e.FileID) fmt.Fprintf(w, "Start pos: %d\n", e.StartPos) fmt.Fprintf(w, "End pos: %d\n", e.EndPos) fmt.Fprintf(w, "Dup handling flags: %d\n", e.DupHandlingFlags) fmt.Fprintln(w) } // case MARIADB_ANNOTATE_ROWS_EVENT: // return "MariadbAnnotateRowsEvent" type MariadbAnnotateRowsEvent struct { Query []byte } func (e *MariadbAnnotateRowsEvent) Decode(data []byte) error { e.Query = data return nil } func (e *MariadbAnnotateRowsEvent) Dump(w io.Writer) { fmt.Fprintf(w, "Query: %s\n", e.Query) fmt.Fprintln(w) } type MariadbBinlogCheckPointEvent struct { Info []byte } func (e *MariadbBinlogCheckPointEvent) Decode(data []byte) error { e.Info = data return nil } func (e *MariadbBinlogCheckPointEvent) Dump(w io.Writer) { fmt.Fprintf(w, "Info: %s\n", e.Info) fmt.Fprintln(w) } type MariadbGTIDEvent struct { GTID MariadbGTID Flags byte CommitID uint64 } func (e *MariadbGTIDEvent) IsDDL() bool { return (e.Flags & BINLOG_MARIADB_FL_DDL) != 0 } func (e *MariadbGTIDEvent) IsStandalone() bool { return (e.Flags & BINLOG_MARIADB_FL_STANDALONE) != 0 } func (e *MariadbGTIDEvent) IsGroupCommit() bool { return (e.Flags & BINLOG_MARIADB_FL_GROUP_COMMIT_ID) != 0 } func (e *MariadbGTIDEvent) Decode(data []byte) error { pos := 0 e.GTID.SequenceNumber = binary.LittleEndian.Uint64(data) pos += 8 e.GTID.DomainID = binary.LittleEndian.Uint32(data[pos:]) pos += 4 e.Flags = data[pos] pos += 1 if (e.Flags & BINLOG_MARIADB_FL_GROUP_COMMIT_ID) > 0 { e.CommitID = binary.LittleEndian.Uint64(data[pos:]) } return nil } func (e *MariadbGTIDEvent) Dump(w io.Writer) { fmt.Fprintf(w, "GTID: %v\n", e.GTID) fmt.Fprintf(w, "Flags: %v\n", e.Flags) fmt.Fprintf(w, "CommitID: %v\n", e.CommitID) fmt.Fprintln(w) } func (e *MariadbGTIDEvent) GTIDNext() (GTIDSet, error) { return ParseMariadbGTIDSet(e.GTID.String()) } type MariadbGTIDListEvent struct { GTIDs []MariadbGTID } func (e *MariadbGTIDListEvent) Decode(data []byte) error { pos := 0 v := binary.LittleEndian.Uint32(data[pos:]) pos += 4 count := v & uint32((1<<28)-1) e.GTIDs = make([]MariadbGTID, count) for i := uint32(0); i < count; i++ { e.GTIDs[i].DomainID = binary.LittleEndian.Uint32(data[pos:]) pos += 4 e.GTIDs[i].ServerID = binary.LittleEndian.Uint32(data[pos:]) pos += 4 e.GTIDs[i].SequenceNumber = binary.LittleEndian.Uint64(data[pos:]) pos += 8 } return nil } func (e *MariadbGTIDListEvent) Dump(w io.Writer) { fmt.Fprintf(w, "Lists: %v\n", e.GTIDs) fmt.Fprintln(w) } type IntVarEvent struct { Type IntVarEventType Value uint64 } func (i *IntVarEvent) Decode(data []byte) error { i.Type = IntVarEventType(data[0]) i.Value = binary.LittleEndian.Uint64(data[1:]) return nil } func (i *IntVarEvent) Dump(w io.Writer) { fmt.Fprintf(w, "Type: %d\n", i.Type) fmt.Fprintf(w, "Value: %d\n", i.Value) } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/replication/generic_event.go ================================================ package replication import ( "encoding/hex" "fmt" "io" ) // we don't parse all event, so some we will use GenericEvent instead type GenericEvent struct { Data []byte } func (e *GenericEvent) Dump(w io.Writer) { fmt.Fprintf(w, "Event data: \n%s", hex.Dump(e.Data)) fmt.Fprintln(w) } func (e *GenericEvent) Decode(data []byte) error { e.Data = data return nil } //below events are generic events, maybe later I will consider handle some. // type StartEventV3 struct { // Version uint16 // ServerVersion [50]byte // CreateTimestamp uint32 // } // type StopEvent struct{} // type LoadEvent struct { // SlaveProxyID uint32 // ExecTime uint32 // SkipLines uint32 // TableNameLen uint8 // SchemaLen uint8 // NumFileds uint32 // FieldTerm uint8 // EnclosedBy uint8 // LineTerm uint8 // LineStart uint8 // EscapedBy uint8 // OptFlags uint8 // EmptyFlags uint8 // //len = 1 * NumFields // FieldNameLengths []byte // //len = sum(FieldNameLengths) + NumFields // //array of nul-terminated strings // FieldNames []byte // //len = TableNameLen + 1, nul-terminated string // TableName []byte // //len = SchemaLen + 1, nul-terminated string // SchemaName []byte // //string.NUL // FileName []byte // } // type NewLoadEvent struct { // SlaveProxyID uint32 // ExecTime uint32 // SkipLines uint32 // TableNameLen uint8 // SchemaLen uint8 // NumFields uint32 // FieldTermLen uint8 // FieldTerm []byte // EnclosedByLen uint8 // EnclosedBy []byte // LineTermLen uint8 // LineTerm []byte // LineStartLen uint8 // LineStart []byte // EscapedByLen uint8 // EscapedBy []byte // OptFlags uint8 // //len = 1 * NumFields // FieldNameLengths []byte // //len = sum(FieldNameLengths) + NumFields // //array of nul-terminated strings // FieldNames []byte // //len = TableNameLen, nul-terminated string // TableName []byte // //len = SchemaLen, nul-terminated string // SchemaName []byte // //string.EOF // FileName []byte // } // type CreateFileEvent struct { // FileID uint32 // BlockData []byte // } // type AppendBlockEvent struct { // FileID uint32 // BlockData []byte // } // type ExecLoadEvent struct { // FileID uint32 // } // type BeginLoadQueryEvent struct { // FileID uint32 // BlockData []byte // } // type ExecuteLoadQueryEvent struct { // SlaveProxyID uint32 // ExecutionTime uint32 // SchemaLength uint8 // ErrorCode uint16 // StatusVarsLength uint16 // FileID uint32 // StartPos uint32 // EndPos uint32 // DupHandlingFlags uint8 // } // type DeleteFileEvent struct { // FileID uint32 // } // type RandEvent struct { // Seed1 uint64 // Seed2 uint64 // } // type UserVarEvent struct { // NameLength uint32 // Name []byte // IsNull uint8 // //if not is null // Type uint8 // Charset uint32 // ValueLength uint32 // Value []byte // //if more data // Flags uint8 // } // type IncidentEvent struct { // Type uint16 // MessageLength uint8 // Message []byte // } // type HeartbeatEvent struct { // } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/replication/json_binary.go ================================================ package replication import ( "fmt" "math" "github.com/go-mysql-org/go-mysql/utils" "github.com/goccy/go-json" "github.com/pingcap/errors" . "github.com/go-mysql-org/go-mysql/mysql" ) const ( JSONB_SMALL_OBJECT byte = iota // small JSON object JSONB_LARGE_OBJECT // large JSON object JSONB_SMALL_ARRAY // small JSON array JSONB_LARGE_ARRAY // large JSON array JSONB_LITERAL // literal (true/false/null) JSONB_INT16 // int16 JSONB_UINT16 // uint16 JSONB_INT32 // int32 JSONB_UINT32 // uint32 JSONB_INT64 // int64 JSONB_UINT64 // uint64 JSONB_DOUBLE // double JSONB_STRING // string JSONB_OPAQUE byte = 0x0f // custom data (any MySQL data type) ) const ( JSONB_NULL_LITERAL byte = 0x00 JSONB_TRUE_LITERAL byte = 0x01 JSONB_FALSE_LITERAL byte = 0x02 ) const ( jsonbSmallOffsetSize = 2 jsonbLargeOffsetSize = 4 jsonbKeyEntrySizeSmall = 2 + jsonbSmallOffsetSize jsonbKeyEntrySizeLarge = 2 + jsonbLargeOffsetSize jsonbValueEntrySizeSmall = 1 + jsonbSmallOffsetSize jsonbValueEntrySizeLarge = 1 + jsonbLargeOffsetSize ) var ( ErrCorruptedJSONDiff = fmt.Errorf("corrupted JSON diff") // ER_CORRUPTED_JSON_DIFF ) type ( // JsonDiffOperation is an enum that describes what kind of operation a JsonDiff object represents. // https://github.com/mysql/mysql-server/blob/8.0/sql/json_diff.h JsonDiffOperation byte ) const ( // The JSON value in the given path is replaced with a new value. // // It has the same effect as `JSON_REPLACE(col, path, value)`. JsonDiffOperationReplace = JsonDiffOperation(iota) // Add a new element at the given path. // // If the path specifies an array element, it has the same effect as `JSON_ARRAY_INSERT(col, path, value)`. // // If the path specifies an object member, it has the same effect as `JSON_INSERT(col, path, value)`. JsonDiffOperationInsert // The JSON value at the given path is removed from an array or object. // // It has the same effect as `JSON_REMOVE(col, path)`. JsonDiffOperationRemove ) type ( JsonDiff struct { Op JsonDiffOperation Path string Value string } ) func (op JsonDiffOperation) String() string { switch op { case JsonDiffOperationReplace: return "Replace" case JsonDiffOperationInsert: return "Insert" case JsonDiffOperationRemove: return "Remove" default: return fmt.Sprintf("Unknown(%d)", op) } } func (jd *JsonDiff) String() string { return fmt.Sprintf("json_diff(op:%s path:%s value:%s)", jd.Op, jd.Path, jd.Value) } func jsonbGetOffsetSize(isSmall bool) int { if isSmall { return jsonbSmallOffsetSize } return jsonbLargeOffsetSize } func jsonbGetKeyEntrySize(isSmall bool) int { if isSmall { return jsonbKeyEntrySizeSmall } return jsonbKeyEntrySizeLarge } func jsonbGetValueEntrySize(isSmall bool) int { if isSmall { return jsonbValueEntrySizeSmall } return jsonbValueEntrySizeLarge } // decodeJsonBinary decodes the JSON binary encoding data and returns // the common JSON encoding data. func (e *RowsEvent) decodeJsonBinary(data []byte) ([]byte, error) { d := jsonBinaryDecoder{ useDecimal: e.useDecimal, ignoreDecodeErr: e.ignoreJSONDecodeErr, } if d.isDataShort(data, 1) { return nil, d.err } v := d.decodeValue(data[0], data[1:]) if d.err != nil { return nil, d.err } return json.Marshal(v) } type jsonBinaryDecoder struct { useDecimal bool ignoreDecodeErr bool err error } func (d *jsonBinaryDecoder) decodeValue(tp byte, data []byte) interface{} { if d.err != nil { return nil } switch tp { case JSONB_SMALL_OBJECT: return d.decodeObjectOrArray(data, true, true) case JSONB_LARGE_OBJECT: return d.decodeObjectOrArray(data, false, true) case JSONB_SMALL_ARRAY: return d.decodeObjectOrArray(data, true, false) case JSONB_LARGE_ARRAY: return d.decodeObjectOrArray(data, false, false) case JSONB_LITERAL: return d.decodeLiteral(data) case JSONB_INT16: return d.decodeInt16(data) case JSONB_UINT16: return d.decodeUint16(data) case JSONB_INT32: return d.decodeInt32(data) case JSONB_UINT32: return d.decodeUint32(data) case JSONB_INT64: return d.decodeInt64(data) case JSONB_UINT64: return d.decodeUint64(data) case JSONB_DOUBLE: return d.decodeDouble(data) case JSONB_STRING: return d.decodeString(data) case JSONB_OPAQUE: return d.decodeOpaque(data) default: d.err = errors.Errorf("invalid json type %d", tp) } return nil } func (d *jsonBinaryDecoder) decodeObjectOrArray(data []byte, isSmall bool, isObject bool) interface{} { offsetSize := jsonbGetOffsetSize(isSmall) if d.isDataShort(data, 2*offsetSize) { return nil } count := d.decodeCount(data, isSmall) size := d.decodeCount(data[offsetSize:], isSmall) if d.isDataShort(data, size) { // Before MySQL 5.7.22, json type generated column may have invalid value, // bug ref: https://bugs.mysql.com/bug.php?id=88791 // As generated column value is not used in replication, we can just ignore // this error and return a dummy value for this column. if d.ignoreDecodeErr { d.err = nil } return nil } keyEntrySize := jsonbGetKeyEntrySize(isSmall) valueEntrySize := jsonbGetValueEntrySize(isSmall) headerSize := 2*offsetSize + count*valueEntrySize if isObject { headerSize += count * keyEntrySize } if headerSize > size { d.err = errors.Errorf("header size %d > size %d", headerSize, size) return nil } var keys []string if isObject { keys = make([]string, count) for i := 0; i < count; i++ { // decode key entryOffset := 2*offsetSize + keyEntrySize*i keyOffset := d.decodeCount(data[entryOffset:], isSmall) keyLength := int(d.decodeUint16(data[entryOffset+offsetSize:])) // Key must start after value entry if keyOffset < headerSize { d.err = errors.Errorf("invalid key offset %d, must > %d", keyOffset, headerSize) return nil } if d.isDataShort(data, keyOffset+keyLength) { return nil } keys[i] = utils.ByteSliceToString(data[keyOffset : keyOffset+keyLength]) } } if d.err != nil { return nil } values := make([]interface{}, count) for i := 0; i < count; i++ { // decode value entryOffset := 2*offsetSize + valueEntrySize*i if isObject { entryOffset += keyEntrySize * count } tp := data[entryOffset] if isInlineValue(tp, isSmall) { values[i] = d.decodeValue(tp, data[entryOffset+1:entryOffset+valueEntrySize]) continue } valueOffset := d.decodeCount(data[entryOffset+1:], isSmall) if d.isDataShort(data, valueOffset) { return nil } values[i] = d.decodeValue(tp, data[valueOffset:]) } if d.err != nil { return nil } if !isObject { return values } m := make(map[string]interface{}, count) for i := 0; i < count; i++ { m[keys[i]] = values[i] } return m } func isInlineValue(tp byte, isSmall bool) bool { switch tp { case JSONB_INT16, JSONB_UINT16, JSONB_LITERAL: return true case JSONB_INT32, JSONB_UINT32: return !isSmall } return false } func (d *jsonBinaryDecoder) decodeLiteral(data []byte) interface{} { if d.isDataShort(data, 1) { return nil } tp := data[0] switch tp { case JSONB_NULL_LITERAL: return nil case JSONB_TRUE_LITERAL: return true case JSONB_FALSE_LITERAL: return false } d.err = errors.Errorf("invalid literal %c", tp) return nil } func (d *jsonBinaryDecoder) isDataShort(data []byte, expected int) bool { if d.err != nil { return true } if len(data) < expected { d.err = errors.Errorf("data len %d < expected %d", len(data), expected) } return d.err != nil } func (d *jsonBinaryDecoder) decodeInt16(data []byte) int16 { if d.isDataShort(data, 2) { return 0 } v := ParseBinaryInt16(data[0:2]) return v } func (d *jsonBinaryDecoder) decodeUint16(data []byte) uint16 { if d.isDataShort(data, 2) { return 0 } v := ParseBinaryUint16(data[0:2]) return v } func (d *jsonBinaryDecoder) decodeInt32(data []byte) int32 { if d.isDataShort(data, 4) { return 0 } v := ParseBinaryInt32(data[0:4]) return v } func (d *jsonBinaryDecoder) decodeUint32(data []byte) uint32 { if d.isDataShort(data, 4) { return 0 } v := ParseBinaryUint32(data[0:4]) return v } func (d *jsonBinaryDecoder) decodeInt64(data []byte) int64 { if d.isDataShort(data, 8) { return 0 } v := ParseBinaryInt64(data[0:8]) return v } func (d *jsonBinaryDecoder) decodeUint64(data []byte) uint64 { if d.isDataShort(data, 8) { return 0 } v := ParseBinaryUint64(data[0:8]) return v } func (d *jsonBinaryDecoder) decodeDouble(data []byte) float64 { if d.isDataShort(data, 8) { return 0 } v := ParseBinaryFloat64(data[0:8]) return v } func (d *jsonBinaryDecoder) decodeString(data []byte) string { if d.err != nil { return "" } l, n := d.decodeVariableLength(data) if d.isDataShort(data, l+n) { return "" } data = data[n:] v := utils.ByteSliceToString(data[0:l]) return v } func (d *jsonBinaryDecoder) decodeOpaque(data []byte) interface{} { if d.isDataShort(data, 1) { return nil } tp := data[0] data = data[1:] l, n := d.decodeVariableLength(data) if d.isDataShort(data, l+n) { return nil } data = data[n : l+n] switch tp { case MYSQL_TYPE_NEWDECIMAL: return d.decodeDecimal(data) case MYSQL_TYPE_TIME: return d.decodeTime(data) case MYSQL_TYPE_DATE, MYSQL_TYPE_DATETIME, MYSQL_TYPE_TIMESTAMP: return d.decodeDateTime(data) default: return utils.ByteSliceToString(data) } } func (d *jsonBinaryDecoder) decodeDecimal(data []byte) interface{} { precision := int(data[0]) scale := int(data[1]) v, _, err := decodeDecimal(data[2:], precision, scale, d.useDecimal) d.err = err return v } func (d *jsonBinaryDecoder) decodeTime(data []byte) interface{} { v := d.decodeInt64(data) if v == 0 { return "00:00:00" } sign := "" if v < 0 { sign = "-" v = -v } intPart := v >> 24 hour := (intPart >> 12) % (1 << 10) min := (intPart >> 6) % (1 << 6) sec := intPart % (1 << 6) frac := v % (1 << 24) return fmt.Sprintf("%s%02d:%02d:%02d.%06d", sign, hour, min, sec, frac) } func (d *jsonBinaryDecoder) decodeDateTime(data []byte) interface{} { v := d.decodeInt64(data) if v == 0 { return "0000-00-00 00:00:00" } // handle negative? if v < 0 { v = -v } intPart := v >> 24 ymd := intPart >> 17 ym := ymd >> 5 hms := intPart % (1 << 17) year := ym / 13 month := ym % 13 day := ymd % (1 << 5) hour := hms >> 12 minute := (hms >> 6) % (1 << 6) second := hms % (1 << 6) frac := v % (1 << 24) return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d.%06d", year, month, day, hour, minute, second, frac) } func (d *jsonBinaryDecoder) decodeCount(data []byte, isSmall bool) int { if isSmall { v := d.decodeUint16(data) return int(v) } return int(d.decodeUint32(data)) } func (d *jsonBinaryDecoder) decodeVariableLength(data []byte) (int, int) { // The max size for variable length is math.MaxUint32, so // here we can use 5 bytes to save it. maxCount := 5 if len(data) < maxCount { maxCount = len(data) } pos := 0 length := uint64(0) for ; pos < maxCount; pos++ { v := data[pos] length |= uint64(v&0x7F) << uint(7*pos) if v&0x80 == 0 { if length > math.MaxUint32 { d.err = errors.Errorf("variable length %d must <= %d", length, int64(math.MaxUint32)) return 0, 0 } pos += 1 // TODO: should consider length overflow int here. return int(length), pos } } d.err = errors.New("decode variable length failed") return 0, 0 } func (e *RowsEvent) decodeJsonPartialBinary(data []byte) (*JsonDiff, error) { // see Json_diff_vector::read_binary() in mysql-server/sql/json_diff.cc operationNumber := JsonDiffOperation(data[0]) switch operationNumber { case JsonDiffOperationReplace: case JsonDiffOperationInsert: case JsonDiffOperationRemove: default: return nil, ErrCorruptedJSONDiff } data = data[1:] pathLength, _, n := LengthEncodedInt(data) data = data[n:] path := data[:pathLength] data = data[pathLength:] diff := &JsonDiff{ Op: operationNumber, Path: string(path), // Value will be filled below } if operationNumber == JsonDiffOperationRemove { return diff, nil } valueLength, _, n := LengthEncodedInt(data) data = data[n:] d, err := e.decodeJsonBinary(data[:valueLength]) if err != nil { return nil, fmt.Errorf("cannot read json diff for field %q: %w", path, err) } diff.Value = string(d) return diff, nil } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/replication/parser.go ================================================ package replication import ( "bytes" "encoding/binary" "fmt" "hash/crc32" "io" "os" "sync/atomic" "time" "github.com/pingcap/errors" "github.com/go-mysql-org/go-mysql/utils" ) var ( // ErrChecksumMismatch indicates binlog checksum mismatch. ErrChecksumMismatch = errors.New("binlog checksum mismatch, data may be corrupted") ) type BinlogParser struct { // "mysql" or "mariadb", if not set, use "mysql" by default flavor string format *FormatDescriptionEvent tables map[uint64]*TableMapEvent // for rawMode, we only parse FormatDescriptionEvent and RotateEvent rawMode bool parseTime bool timestampStringLocation *time.Location // used to start/stop processing stopProcessing uint32 useDecimal bool ignoreJSONDecodeErr bool verifyChecksum bool rowsEventDecodeFunc func(*RowsEvent, []byte) error tableMapOptionalMetaDecodeFunc func([]byte) error } func NewBinlogParser() *BinlogParser { p := new(BinlogParser) p.tables = make(map[uint64]*TableMapEvent) return p } func (p *BinlogParser) Stop() { atomic.StoreUint32(&p.stopProcessing, 1) } func (p *BinlogParser) Resume() { atomic.StoreUint32(&p.stopProcessing, 0) } func (p *BinlogParser) Reset() { p.format = nil } type OnEventFunc func(*BinlogEvent) error func (p *BinlogParser) ParseFile(name string, offset int64, onEvent OnEventFunc) error { f, err := os.Open(name) if err != nil { return errors.Trace(err) } defer f.Close() b := make([]byte, 4) if _, err = f.Read(b); err != nil { return errors.Trace(err) } else if !bytes.Equal(b, BinLogFileHeader) { return errors.Errorf("%s is not a valid binlog file, head 4 bytes must fe'bin' ", name) } if offset < 4 { offset = 4 } else if offset > 4 { // FORMAT_DESCRIPTION event should be read by default always (despite that fact passed offset may be higher than 4) if _, err = f.Seek(4, io.SeekStart); err != nil { return errors.Errorf("seek %s to %d error %v", name, offset, err) } if err = p.parseFormatDescriptionEvent(f, onEvent); err != nil { return errors.Annotatef(err, "parse FormatDescriptionEvent") } } if _, err = f.Seek(offset, io.SeekStart); err != nil { return errors.Errorf("seek %s to %d error %v", name, offset, err) } return p.ParseReader(f, onEvent) } func (p *BinlogParser) parseFormatDescriptionEvent(r io.Reader, onEvent OnEventFunc) error { _, err := p.parseSingleEvent(r, onEvent) return err } // ParseSingleEvent parses single binlog event and passes the event to onEvent function. func (p *BinlogParser) ParseSingleEvent(r io.Reader, onEvent OnEventFunc) (bool, error) { return p.parseSingleEvent(r, onEvent) } func (p *BinlogParser) parseSingleEvent(r io.Reader, onEvent OnEventFunc) (bool, error) { var err error var n int64 // Here we use `sync.Pool` to avoid allocate/destroy buffers frequently. buf := utils.BytesBufferGet() defer utils.BytesBufferPut(buf) if n, err = io.CopyN(buf, r, EventHeaderSize); err == io.EOF { return true, nil } else if err != nil { return false, errors.Errorf("get event header err %v, need %d but got %d", err, EventHeaderSize, n) } var h *EventHeader h, err = p.parseHeader(buf.Bytes()) if err != nil { return false, errors.Trace(err) } if h.EventSize < uint32(EventHeaderSize) { return false, errors.Errorf("invalid event header, event size is %d, too small", h.EventSize) } if n, err = io.CopyN(buf, r, int64(h.EventSize-EventHeaderSize)); err != nil { return false, errors.Errorf("get event err %v, need %d but got %d", err, h.EventSize, n) } if buf.Len() != int(h.EventSize) { return false, errors.Errorf("invalid raw data size in event %s, need %d but got %d", h.EventType, h.EventSize, buf.Len()) } var rawData []byte rawData = append(rawData, buf.Bytes()...) bodyLen := int(h.EventSize) - EventHeaderSize body := rawData[EventHeaderSize:] if len(body) != bodyLen { return false, errors.Errorf("invalid body data size in event %s, need %d but got %d", h.EventType, bodyLen, len(body)) } var e Event e, err = p.parseEvent(h, body, rawData) if err != nil { if err == errMissingTableMapEvent { return false, nil } return false, errors.Trace(err) } if err = onEvent(&BinlogEvent{RawData: rawData, Header: h, Event: e}); err != nil { return false, errors.Trace(err) } return false, nil } func (p *BinlogParser) ParseReader(r io.Reader, onEvent OnEventFunc) error { for { if atomic.LoadUint32(&p.stopProcessing) == 1 { break } done, err := p.parseSingleEvent(r, onEvent) if err != nil { if err == errMissingTableMapEvent { continue } return errors.Trace(err) } if done { break } } return nil } func (p *BinlogParser) SetRawMode(mode bool) { p.rawMode = mode } func (p *BinlogParser) SetParseTime(parseTime bool) { p.parseTime = parseTime } func (p *BinlogParser) SetTimestampStringLocation(timestampStringLocation *time.Location) { p.timestampStringLocation = timestampStringLocation } func (p *BinlogParser) SetUseDecimal(useDecimal bool) { p.useDecimal = useDecimal } func (p *BinlogParser) SetIgnoreJSONDecodeError(ignoreJSONDecodeErr bool) { p.ignoreJSONDecodeErr = ignoreJSONDecodeErr } func (p *BinlogParser) SetVerifyChecksum(verify bool) { p.verifyChecksum = verify } func (p *BinlogParser) SetFlavor(flavor string) { p.flavor = flavor } func (p *BinlogParser) SetRowsEventDecodeFunc(rowsEventDecodeFunc func(*RowsEvent, []byte) error) { p.rowsEventDecodeFunc = rowsEventDecodeFunc } func (p *BinlogParser) SetTableMapOptionalMetaDecodeFunc(tableMapOptionalMetaDecondeFunc func([]byte) error) { p.tableMapOptionalMetaDecodeFunc = tableMapOptionalMetaDecondeFunc } func (p *BinlogParser) parseHeader(data []byte) (*EventHeader, error) { h := new(EventHeader) err := h.Decode(data) if err != nil { return nil, err } return h, nil } func (p *BinlogParser) parseEvent(h *EventHeader, data []byte, rawData []byte) (Event, error) { var e Event if h.EventType == FORMAT_DESCRIPTION_EVENT { p.format = &FormatDescriptionEvent{} e = p.format } else { if p.format != nil && p.format.ChecksumAlgorithm == BINLOG_CHECKSUM_ALG_CRC32 { err := p.verifyCrc32Checksum(rawData) if err != nil { return nil, err } data = data[0 : len(data)-BinlogChecksumLength] } if h.EventType == ROTATE_EVENT { e = &RotateEvent{} } else if !p.rawMode { switch h.EventType { case QUERY_EVENT: e = &QueryEvent{} case MARIADB_QUERY_COMPRESSED_EVENT: e = &QueryEvent{ compressed: true, } case XID_EVENT: e = &XIDEvent{} case TABLE_MAP_EVENT: te := &TableMapEvent{ flavor: p.flavor, optionalMetaDecodeFunc: p.tableMapOptionalMetaDecodeFunc, } if p.format.EventTypeHeaderLengths[TABLE_MAP_EVENT-1] == 6 { te.tableIDSize = 4 } else { te.tableIDSize = 6 } e = te case WRITE_ROWS_EVENTv0, UPDATE_ROWS_EVENTv0, DELETE_ROWS_EVENTv0, WRITE_ROWS_EVENTv1, DELETE_ROWS_EVENTv1, UPDATE_ROWS_EVENTv1, WRITE_ROWS_EVENTv2, UPDATE_ROWS_EVENTv2, DELETE_ROWS_EVENTv2, MARIADB_WRITE_ROWS_COMPRESSED_EVENT_V1, MARIADB_UPDATE_ROWS_COMPRESSED_EVENT_V1, MARIADB_DELETE_ROWS_COMPRESSED_EVENT_V1, PARTIAL_UPDATE_ROWS_EVENT: // Extension of UPDATE_ROWS_EVENT, allowing partial values according to binlog_row_value_options e = p.newRowsEvent(h) case ROWS_QUERY_EVENT: e = &RowsQueryEvent{} case GTID_EVENT: e = >IDEvent{} case ANONYMOUS_GTID_EVENT: e = >IDEvent{} case BEGIN_LOAD_QUERY_EVENT: e = &BeginLoadQueryEvent{} case EXECUTE_LOAD_QUERY_EVENT: e = &ExecuteLoadQueryEvent{} case MARIADB_ANNOTATE_ROWS_EVENT: e = &MariadbAnnotateRowsEvent{} case MARIADB_BINLOG_CHECKPOINT_EVENT: e = &MariadbBinlogCheckPointEvent{} case MARIADB_GTID_LIST_EVENT: e = &MariadbGTIDListEvent{} case MARIADB_GTID_EVENT: ee := &MariadbGTIDEvent{} ee.GTID.ServerID = h.ServerID e = ee case PREVIOUS_GTIDS_EVENT: e = &PreviousGTIDsEvent{} case INTVAR_EVENT: e = &IntVarEvent{} case TRANSACTION_PAYLOAD_EVENT: e = p.newTransactionPayloadEvent() default: e = &GenericEvent{} } } else { e = &GenericEvent{} } } var err error if re, ok := e.(*RowsEvent); ok && p.rowsEventDecodeFunc != nil { err = p.rowsEventDecodeFunc(re, data) } else { err = e.Decode(data) } if err != nil { return nil, &EventError{h, err.Error(), data} } if te, ok := e.(*TableMapEvent); ok { p.tables[te.TableID] = te } if re, ok := e.(*RowsEvent); ok { if (re.Flags & RowsEventStmtEndFlag) > 0 { // Refer https://github.com/alibaba/canal/blob/38cc81b7dab29b51371096fb6763ca3a8432ffee/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/RowsLogEvent.java#L176 p.tables = make(map[uint64]*TableMapEvent) } } return e, nil } // Parse: Given the bytes for a a binary log event: return the decoded event. // With the exception of the FORMAT_DESCRIPTION_EVENT event type // there must have previously been passed a FORMAT_DESCRIPTION_EVENT // into the parser for this to work properly on any given event. // Passing a new FORMAT_DESCRIPTION_EVENT into the parser will replace // an existing one. func (p *BinlogParser) Parse(data []byte) (*BinlogEvent, error) { rawData := data h, err := p.parseHeader(data) if err != nil { return nil, err } data = data[EventHeaderSize:] eventLen := int(h.EventSize) - EventHeaderSize if len(data) != eventLen { return nil, fmt.Errorf("invalid data size %d in event %s, less event length %d", len(data), h.EventType, eventLen) } e, err := p.parseEvent(h, data, rawData) if err != nil { return nil, err } return &BinlogEvent{RawData: rawData, Header: h, Event: e}, nil } func (p *BinlogParser) verifyCrc32Checksum(rawData []byte) error { if !p.verifyChecksum { return nil } calculatedPart := rawData[0 : len(rawData)-BinlogChecksumLength] expectedChecksum := rawData[len(rawData)-BinlogChecksumLength:] // mysql use zlib's CRC32 implementation, which uses polynomial 0xedb88320UL. // reference: https://github.com/madler/zlib/blob/master/crc32.c // https://github.com/madler/zlib/blob/master/doc/rfc1952.txt#L419 checksum := crc32.ChecksumIEEE(calculatedPart) computed := make([]byte, BinlogChecksumLength) binary.LittleEndian.PutUint32(computed, checksum) if !bytes.Equal(expectedChecksum, computed) { return ErrChecksumMismatch } return nil } func (p *BinlogParser) newRowsEvent(h *EventHeader) *RowsEvent { e := &RowsEvent{} postHeaderLen := p.format.EventTypeHeaderLengths[h.EventType-1] if postHeaderLen == 6 { e.tableIDSize = 4 } else { e.tableIDSize = 6 } e.needBitmap2 = false e.tables = p.tables e.eventType = h.EventType e.parseTime = p.parseTime e.timestampStringLocation = p.timestampStringLocation e.useDecimal = p.useDecimal e.ignoreJSONDecodeErr = p.ignoreJSONDecodeErr switch h.EventType { case WRITE_ROWS_EVENTv0: e.Version = 0 case UPDATE_ROWS_EVENTv0: e.Version = 0 case DELETE_ROWS_EVENTv0: e.Version = 0 case WRITE_ROWS_EVENTv1: e.Version = 1 case DELETE_ROWS_EVENTv1: e.Version = 1 case UPDATE_ROWS_EVENTv1: e.Version = 1 e.needBitmap2 = true case MARIADB_WRITE_ROWS_COMPRESSED_EVENT_V1: e.Version = 1 e.compressed = true case MARIADB_DELETE_ROWS_COMPRESSED_EVENT_V1: e.Version = 1 e.compressed = true case MARIADB_UPDATE_ROWS_COMPRESSED_EVENT_V1: e.Version = 1 e.compressed = true e.needBitmap2 = true case WRITE_ROWS_EVENTv2: e.Version = 2 case UPDATE_ROWS_EVENTv2: e.Version = 2 e.needBitmap2 = true case DELETE_ROWS_EVENTv2: e.Version = 2 case PARTIAL_UPDATE_ROWS_EVENT: e.Version = 2 e.needBitmap2 = true } return e } func (p *BinlogParser) newTransactionPayloadEvent() *TransactionPayloadEvent { e := &TransactionPayloadEvent{} e.format = *p.format return e } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/replication/row_event.go ================================================ package replication import ( "encoding/binary" "encoding/hex" "fmt" "io" "strconv" "strings" "time" "github.com/pingcap/errors" "github.com/shopspring/decimal" . "github.com/go-mysql-org/go-mysql/mysql" "github.com/go-mysql-org/go-mysql/utils" ) var errMissingTableMapEvent = errors.New("invalid table id, no corresponding table map event") type TableMapEvent struct { flavor string tableIDSize int TableID uint64 Flags uint16 Schema []byte Table []byte ColumnCount uint64 ColumnType []byte ColumnMeta []uint16 // len = (ColumnCount + 7) / 8 NullBitmap []byte /* The following are available only after MySQL-8.0.1 or MariaDB-10.5.0 By default MySQL and MariaDB do not log the full row metadata. see: - https://dev.mysql.com/doc/refman/8.0/en/replication-options-binary-log.html#sysvar_binlog_row_metadata - https://mariadb.com/kb/en/replication-and-binary-log-system-variables/#binlog_row_metadata */ // SignednessBitmap stores signedness info for numeric columns. SignednessBitmap []byte // DefaultCharset/ColumnCharset stores collation info for character columns. // DefaultCharset[0] is the default collation of character columns. // For character columns that have different charset, // (character column index, column collation) pairs follows DefaultCharset []uint64 // ColumnCharset contains collation sequence for all character columns ColumnCharset []uint64 // SetStrValue stores values for set columns. SetStrValue [][][]byte setStrValueString [][]string // EnumStrValue stores values for enum columns. EnumStrValue [][][]byte enumStrValueString [][]string // ColumnName list all column names. ColumnName [][]byte columnNameString []string // the same as ColumnName in string type, just for reuse // GeometryType stores real type for geometry columns. GeometryType []uint64 // PrimaryKey is a sequence of column indexes of primary key. PrimaryKey []uint64 // PrimaryKeyPrefix is the prefix length used for each column of primary key. // 0 means that the whole column length is used. PrimaryKeyPrefix []uint64 // EnumSetDefaultCharset/EnumSetColumnCharset is similar to DefaultCharset/ColumnCharset but for enum/set columns. EnumSetDefaultCharset []uint64 EnumSetColumnCharset []uint64 // VisibilityBitmap stores bits that are set if corresponding column is not invisible (MySQL 8.0.23+) VisibilityBitmap []byte optionalMetaDecodeFunc func(data []byte) (err error) } func (e *TableMapEvent) Decode(data []byte) error { pos := 0 e.TableID = FixedLengthInt(data[0:e.tableIDSize]) pos += e.tableIDSize e.Flags = binary.LittleEndian.Uint16(data[pos:]) pos += 2 schemaLength := data[pos] pos++ e.Schema = data[pos : pos+int(schemaLength)] pos += int(schemaLength) // skip 0x00 pos++ tableLength := data[pos] pos++ e.Table = data[pos : pos+int(tableLength)] pos += int(tableLength) // skip 0x00 pos++ var n int e.ColumnCount, _, n = LengthEncodedInt(data[pos:]) pos += n e.ColumnType = data[pos : pos+int(e.ColumnCount)] pos += int(e.ColumnCount) var err error var metaData []byte if metaData, _, n, err = LengthEncodedString(data[pos:]); err != nil { return errors.Trace(err) } if err = e.decodeMeta(metaData); err != nil { return errors.Trace(err) } pos += n nullBitmapSize := bitmapByteSize(int(e.ColumnCount)) if len(data[pos:]) < nullBitmapSize { return io.EOF } e.NullBitmap = data[pos : pos+nullBitmapSize] pos += nullBitmapSize if e.optionalMetaDecodeFunc != nil { if err = e.optionalMetaDecodeFunc(data[pos:]); err != nil { return err } } else { if err = e.decodeOptionalMeta(data[pos:]); err != nil { return err } } return nil } func bitmapByteSize(columnCount int) int { return (columnCount + 7) / 8 } // see mysql sql/log_event.h /* 0 byte MYSQL_TYPE_DECIMAL MYSQL_TYPE_TINY MYSQL_TYPE_SHORT MYSQL_TYPE_LONG MYSQL_TYPE_NULL MYSQL_TYPE_TIMESTAMP MYSQL_TYPE_LONGLONG MYSQL_TYPE_INT24 MYSQL_TYPE_DATE MYSQL_TYPE_TIME MYSQL_TYPE_DATETIME MYSQL_TYPE_YEAR 1 byte MYSQL_TYPE_FLOAT MYSQL_TYPE_DOUBLE MYSQL_TYPE_BLOB MYSQL_TYPE_GEOMETRY //maybe MYSQL_TYPE_TIME2 MYSQL_TYPE_DATETIME2 MYSQL_TYPE_TIMESTAMP2 2 byte MYSQL_TYPE_VARCHAR MYSQL_TYPE_BIT MYSQL_TYPE_NEWDECIMAL MYSQL_TYPE_VAR_STRING MYSQL_TYPE_STRING This enumeration value is only used internally and cannot exist in a binlog. MYSQL_TYPE_NEWDATE MYSQL_TYPE_ENUM MYSQL_TYPE_SET MYSQL_TYPE_TINY_BLOB MYSQL_TYPE_MEDIUM_BLOB MYSQL_TYPE_LONG_BLOB */ func (e *TableMapEvent) decodeMeta(data []byte) error { pos := 0 e.ColumnMeta = make([]uint16, e.ColumnCount) for i, t := range e.ColumnType { switch t { case MYSQL_TYPE_STRING: var x = uint16(data[pos]) << 8 // real type x += uint16(data[pos+1]) // pack or field length e.ColumnMeta[i] = x pos += 2 case MYSQL_TYPE_NEWDECIMAL: var x = uint16(data[pos]) << 8 // precision x += uint16(data[pos+1]) // decimals e.ColumnMeta[i] = x pos += 2 case MYSQL_TYPE_VAR_STRING, MYSQL_TYPE_VARCHAR, MYSQL_TYPE_BIT: e.ColumnMeta[i] = binary.LittleEndian.Uint16(data[pos:]) pos += 2 case MYSQL_TYPE_BLOB, MYSQL_TYPE_DOUBLE, MYSQL_TYPE_FLOAT, MYSQL_TYPE_GEOMETRY, MYSQL_TYPE_JSON: e.ColumnMeta[i] = uint16(data[pos]) pos++ case MYSQL_TYPE_TIME2, MYSQL_TYPE_DATETIME2, MYSQL_TYPE_TIMESTAMP2: e.ColumnMeta[i] = uint16(data[pos]) pos++ case MYSQL_TYPE_NEWDATE, MYSQL_TYPE_ENUM, MYSQL_TYPE_SET, MYSQL_TYPE_TINY_BLOB, MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB: return errors.Errorf("unsupport type in binlog %d", t) default: e.ColumnMeta[i] = 0 } } return nil } func (e *TableMapEvent) decodeOptionalMeta(data []byte) (err error) { pos := 0 for pos < len(data) { // optional metadata fields are stored in Type, Length, Value(TLV) format // Type takes 1 byte. Length is a packed integer value. Values takes Length bytes t := data[pos] pos++ l, _, n := LengthEncodedInt(data[pos:]) pos += n v := data[pos : pos+int(l)] pos += int(l) switch t { case TABLE_MAP_OPT_META_SIGNEDNESS: e.SignednessBitmap = v case TABLE_MAP_OPT_META_DEFAULT_CHARSET: e.DefaultCharset, err = e.decodeDefaultCharset(v) if err != nil { return err } case TABLE_MAP_OPT_META_COLUMN_CHARSET: e.ColumnCharset, err = e.decodeIntSeq(v) if err != nil { return err } case TABLE_MAP_OPT_META_COLUMN_NAME: if err = e.decodeColumnNames(v); err != nil { return err } case TABLE_MAP_OPT_META_SET_STR_VALUE: e.SetStrValue, err = e.decodeStrValue(v) if err != nil { return err } case TABLE_MAP_OPT_META_ENUM_STR_VALUE: e.EnumStrValue, err = e.decodeStrValue(v) if err != nil { return err } case TABLE_MAP_OPT_META_GEOMETRY_TYPE: e.GeometryType, err = e.decodeIntSeq(v) if err != nil { return err } case TABLE_MAP_OPT_META_SIMPLE_PRIMARY_KEY: if err = e.decodeSimplePrimaryKey(v); err != nil { return err } case TABLE_MAP_OPT_META_PRIMARY_KEY_WITH_PREFIX: if err = e.decodePrimaryKeyWithPrefix(v); err != nil { return err } case TABLE_MAP_OPT_META_ENUM_AND_SET_DEFAULT_CHARSET: e.EnumSetDefaultCharset, err = e.decodeDefaultCharset(v) if err != nil { return err } case TABLE_MAP_OPT_META_ENUM_AND_SET_COLUMN_CHARSET: e.EnumSetColumnCharset, err = e.decodeIntSeq(v) if err != nil { return err } case TABLE_MAP_OPT_META_COLUMN_VISIBILITY: e.VisibilityBitmap = v default: // Ignore for future extension } } return nil } func (e *TableMapEvent) decodeIntSeq(v []byte) (ret []uint64, err error) { p := 0 for p < len(v) { i, _, n := LengthEncodedInt(v[p:]) p += n ret = append(ret, i) } return } func (e *TableMapEvent) decodeDefaultCharset(v []byte) (ret []uint64, err error) { ret, err = e.decodeIntSeq(v) if err != nil { return } if len(ret)%2 != 1 { return nil, errors.Errorf("Expect odd item in DefaultCharset but got %d", len(ret)) } return } func (e *TableMapEvent) decodeColumnNames(v []byte) error { p := 0 e.ColumnName = make([][]byte, 0, e.ColumnCount) for p < len(v) { n := int(v[p]) p++ e.ColumnName = append(e.ColumnName, v[p:p+n]) p += n } if len(e.ColumnName) != int(e.ColumnCount) { return errors.Errorf("Expect %d column names but got %d", e.ColumnCount, len(e.ColumnName)) } return nil } func (e *TableMapEvent) decodeStrValue(v []byte) (ret [][][]byte, err error) { p := 0 for p < len(v) { nVal, _, n := LengthEncodedInt(v[p:]) p += n vals := make([][]byte, 0, int(nVal)) for i := 0; i < int(nVal); i++ { val, _, n, err := LengthEncodedString(v[p:]) if err != nil { return nil, err } p += n vals = append(vals, val) } ret = append(ret, vals) } return } func (e *TableMapEvent) decodeSimplePrimaryKey(v []byte) error { p := 0 for p < len(v) { i, _, n := LengthEncodedInt(v[p:]) e.PrimaryKey = append(e.PrimaryKey, i) e.PrimaryKeyPrefix = append(e.PrimaryKeyPrefix, 0) p += n } return nil } func (e *TableMapEvent) decodePrimaryKeyWithPrefix(v []byte) error { p := 0 for p < len(v) { i, _, n := LengthEncodedInt(v[p:]) e.PrimaryKey = append(e.PrimaryKey, i) p += n i, _, n = LengthEncodedInt(v[p:]) e.PrimaryKeyPrefix = append(e.PrimaryKeyPrefix, i) p += n } return nil } func (e *TableMapEvent) Dump(w io.Writer) { fmt.Fprintf(w, "TableID: %d\n", e.TableID) fmt.Fprintf(w, "TableID size: %d\n", e.tableIDSize) fmt.Fprintf(w, "Flags: %d\n", e.Flags) fmt.Fprintf(w, "Schema: %s\n", e.Schema) fmt.Fprintf(w, "Table: %s\n", e.Table) fmt.Fprintf(w, "Column count: %d\n", e.ColumnCount) fmt.Fprintf(w, "Column type: \n%s", hex.Dump(e.ColumnType)) fmt.Fprintf(w, "NULL bitmap: \n%s", hex.Dump(e.NullBitmap)) fmt.Fprintf(w, "Signedness bitmap: \n%s", hex.Dump(e.SignednessBitmap)) fmt.Fprintf(w, "Default charset: %v\n", e.DefaultCharset) fmt.Fprintf(w, "Column charset: %v\n", e.ColumnCharset) fmt.Fprintf(w, "Set str value: %v\n", e.SetStrValueString()) fmt.Fprintf(w, "Enum str value: %v\n", e.EnumStrValueString()) fmt.Fprintf(w, "Column name: %v\n", e.ColumnNameString()) fmt.Fprintf(w, "Geometry type: %v\n", e.GeometryType) fmt.Fprintf(w, "Primary key: %v\n", e.PrimaryKey) fmt.Fprintf(w, "Primary key prefix: %v\n", e.PrimaryKeyPrefix) fmt.Fprintf(w, "Enum/set default charset: %v\n", e.EnumSetDefaultCharset) fmt.Fprintf(w, "Enum/set column charset: %v\n", e.EnumSetColumnCharset) fmt.Fprintf(w, "Invisible Column bitmap: \n%s", hex.Dump(e.VisibilityBitmap)) unsignedMap := e.UnsignedMap() fmt.Fprintf(w, "UnsignedMap: %#v\n", unsignedMap) collationMap := e.CollationMap() fmt.Fprintf(w, "CollationMap: %#v\n", collationMap) enumSetCollationMap := e.EnumSetCollationMap() fmt.Fprintf(w, "EnumSetCollationMap: %#v\n", enumSetCollationMap) enumStrValueMap := e.EnumStrValueMap() fmt.Fprintf(w, "EnumStrValueMap: %#v\n", enumStrValueMap) setStrValueMap := e.SetStrValueMap() fmt.Fprintf(w, "SetStrValueMap: %#v\n", setStrValueMap) geometryTypeMap := e.GeometryTypeMap() fmt.Fprintf(w, "GeometryTypeMap: %#v\n", geometryTypeMap) visibilityMap := e.VisibilityMap() fmt.Fprintf(w, "VisibilityMap: %#v\n", visibilityMap) nameMaxLen := 0 for _, name := range e.ColumnName { if len(name) > nameMaxLen { nameMaxLen = len(name) } } nameFmt := " %s" if nameMaxLen > 0 { nameFmt = fmt.Sprintf(" %%-%ds", nameMaxLen) } primaryKey := map[int]struct{}{} for _, pk := range e.PrimaryKey { primaryKey[int(pk)] = struct{}{} } fmt.Fprintf(w, "Columns: \n") for i := 0; i < int(e.ColumnCount); i++ { if len(e.ColumnName) == 0 { fmt.Fprintf(w, nameFmt, "") } else { fmt.Fprintf(w, nameFmt, e.ColumnName[i]) } fmt.Fprintf(w, " type=%-3d", e.realType(i)) if e.IsNumericColumn(i) { if len(unsignedMap) == 0 { fmt.Fprintf(w, " unsigned=") } else if unsignedMap[i] { fmt.Fprintf(w, " unsigned=yes") } else { fmt.Fprintf(w, " unsigned=no ") } } if e.IsCharacterColumn(i) { if len(collationMap) == 0 { fmt.Fprintf(w, " collation=") } else { fmt.Fprintf(w, " collation=%d ", collationMap[i]) } } if e.IsEnumColumn(i) { if len(enumSetCollationMap) == 0 { fmt.Fprintf(w, " enum_collation=") } else { fmt.Fprintf(w, " enum_collation=%d", enumSetCollationMap[i]) } if len(enumStrValueMap) == 0 { fmt.Fprintf(w, " enum=") } else { fmt.Fprintf(w, " enum=%v", enumStrValueMap[i]) } } if e.IsSetColumn(i) { if len(enumSetCollationMap) == 0 { fmt.Fprintf(w, " set_collation=") } else { fmt.Fprintf(w, " set_collation=%d", enumSetCollationMap[i]) } if len(setStrValueMap) == 0 { fmt.Fprintf(w, " set=") } else { fmt.Fprintf(w, " set=%v", setStrValueMap[i]) } } if e.IsGeometryColumn(i) { if len(geometryTypeMap) == 0 { fmt.Fprintf(w, " geometry_type=") } else { fmt.Fprintf(w, " geometry_type=%v", geometryTypeMap[i]) } } available, nullable := e.Nullable(i) if !available { fmt.Fprintf(w, " null=") } else if nullable { fmt.Fprintf(w, " null=yes") } else { fmt.Fprintf(w, " null=no ") } if _, ok := primaryKey[i]; ok { fmt.Fprintf(w, " pri") } fmt.Fprintf(w, "\n") } fmt.Fprintln(w) } // Nullable returns the nullablity of the i-th column. // If null bits are not available, available is false. // i must be in range [0, ColumnCount). func (e *TableMapEvent) Nullable(i int) (available, nullable bool) { if len(e.NullBitmap) == 0 { return } return true, e.NullBitmap[i/8]&(1< unsigned. // Note that only numeric columns will be returned. // nil is returned if not available or no numeric columns at all. func (e *TableMapEvent) UnsignedMap() map[int]bool { if len(e.SignednessBitmap) == 0 { return nil } ret := make(map[int]bool) i := 0 for _, field := range e.SignednessBitmap { for c := 0x80; c != 0; { if e.IsNumericColumn(i) { ret[i] = field&byte(c) != 0 c >>= 1 } i++ if i >= int(e.ColumnCount) { return ret } } } return ret } // CollationMap returns a map: column index -> collation id. // Note that only character columns will be returned. // nil is returned if not available or no character columns at all. func (e *TableMapEvent) CollationMap() map[int]uint64 { return e.collationMap(e.IsCharacterColumn, e.DefaultCharset, e.ColumnCharset) } // EnumSetCollationMap returns a map: column index -> collation id. // Note that only enum or set columns will be returned. // nil is returned if not available or no enum/set columns at all. func (e *TableMapEvent) EnumSetCollationMap() map[int]uint64 { return e.collationMap(e.IsEnumOrSetColumn, e.EnumSetDefaultCharset, e.EnumSetColumnCharset) } func (e *TableMapEvent) collationMap(includeType func(int) bool, defaultCharset, columnCharset []uint64) map[int]uint64 { if len(defaultCharset) != 0 { defaultCollation := defaultCharset[0] // character column index -> collation collations := make(map[int]uint64) for i := 1; i < len(defaultCharset); i += 2 { collations[int(defaultCharset[i])] = defaultCharset[i+1] } p := 0 ret := make(map[int]uint64) for i := 0; i < int(e.ColumnCount); i++ { if !includeType(i) { continue } if collation, ok := collations[p]; ok { ret[i] = collation } else { ret[i] = defaultCollation } p++ } return ret } if len(columnCharset) != 0 { p := 0 ret := make(map[int]uint64) for i := 0; i < int(e.ColumnCount); i++ { if !includeType(i) { continue } ret[i] = columnCharset[p] p++ } return ret } return nil } // EnumStrValueMap returns a map: column index -> enum string value. // Note that only enum columns will be returned. // nil is returned if not available or no enum columns at all. func (e *TableMapEvent) EnumStrValueMap() map[int][]string { return e.strValueMap(e.IsEnumColumn, e.EnumStrValueString()) } // SetStrValueMap returns a map: column index -> set string value. // Note that only set columns will be returned. // nil is returned if not available or no set columns at all. func (e *TableMapEvent) SetStrValueMap() map[int][]string { return e.strValueMap(e.IsSetColumn, e.SetStrValueString()) } func (e *TableMapEvent) strValueMap(includeType func(int) bool, strValue [][]string) map[int][]string { if len(strValue) == 0 { return nil } p := 0 ret := make(map[int][]string) for i := 0; i < int(e.ColumnCount); i++ { if !includeType(i) { continue } ret[i] = strValue[p] p++ } return ret } // GeometryTypeMap returns a map: column index -> geometry type. // Note that only geometry columns will be returned. // nil is returned if not available or no geometry columns at all. func (e *TableMapEvent) GeometryTypeMap() map[int]uint64 { if len(e.GeometryType) == 0 { return nil } p := 0 ret := make(map[int]uint64) for i := 0; i < int(e.ColumnCount); i++ { if !e.IsGeometryColumn(i) { continue } ret[i] = e.GeometryType[p] p++ } return ret } // VisibilityMap returns a map: column index -> visiblity. // Invisible column was introduced in MySQL 8.0.23 // nil is returned if not available. func (e *TableMapEvent) VisibilityMap() map[int]bool { if len(e.VisibilityBitmap) == 0 { return nil } ret := make(map[int]bool) i := 0 for _, field := range e.VisibilityBitmap { for c := 0x80; c != 0; c >>= 1 { ret[i] = field&byte(c) != 0 i++ if uint64(i) >= e.ColumnCount { return ret } } } return ret } // Below realType and IsXXXColumn are base from: // table_def::type in sql/rpl_utility.h // Table_map_log_event::print_columns in mysql-8.0/sql/log_event.cc and mariadb-10.5/sql/log_event_client.cc func (e *TableMapEvent) realType(i int) byte { typ := e.ColumnType[i] switch typ { case MYSQL_TYPE_STRING: rtyp := byte(e.ColumnMeta[i] >> 8) if rtyp == MYSQL_TYPE_ENUM || rtyp == MYSQL_TYPE_SET { return rtyp } case MYSQL_TYPE_DATE: return MYSQL_TYPE_NEWDATE } return typ } func (e *TableMapEvent) IsNumericColumn(i int) bool { switch e.realType(i) { case MYSQL_TYPE_TINY, MYSQL_TYPE_SHORT, MYSQL_TYPE_INT24, MYSQL_TYPE_LONG, MYSQL_TYPE_LONGLONG, MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_FLOAT, MYSQL_TYPE_DOUBLE: return true default: return false } } // IsCharacterColumn returns true if the column type is considered as character type. // Note that JSON/GEOMETRY types are treated as character type in mariadb. // (JSON is an alias for LONGTEXT in mariadb: https://mariadb.com/kb/en/json-data-type/) func (e *TableMapEvent) IsCharacterColumn(i int) bool { switch e.realType(i) { case MYSQL_TYPE_STRING, MYSQL_TYPE_VAR_STRING, MYSQL_TYPE_VARCHAR, MYSQL_TYPE_BLOB: return true case MYSQL_TYPE_GEOMETRY: if e.flavor == "mariadb" { return true } return false default: return false } } func (e *TableMapEvent) IsEnumColumn(i int) bool { return e.realType(i) == MYSQL_TYPE_ENUM } func (e *TableMapEvent) IsSetColumn(i int) bool { return e.realType(i) == MYSQL_TYPE_SET } func (e *TableMapEvent) IsGeometryColumn(i int) bool { return e.realType(i) == MYSQL_TYPE_GEOMETRY } func (e *TableMapEvent) IsEnumOrSetColumn(i int) bool { rtyp := e.realType(i) return rtyp == MYSQL_TYPE_ENUM || rtyp == MYSQL_TYPE_SET } // JsonColumnCount returns the number of JSON columns in this table func (e *TableMapEvent) JsonColumnCount() uint64 { count := uint64(0) for _, t := range e.ColumnType { if t == MYSQL_TYPE_JSON { count++ } } return count } // RowsEventStmtEndFlag is set in the end of the statement. const RowsEventStmtEndFlag = 0x01 // RowsEvent represents a MySQL rows event like DELETE_ROWS_EVENT, // UPDATE_ROWS_EVENT, etc. // RowsEvent.Rows saves the rows data, and the MySQL type to golang type mapping // is // - MYSQL_TYPE_NULL: nil // - MYSQL_TYPE_LONG: int32 // - MYSQL_TYPE_TINY: int8 // - MYSQL_TYPE_SHORT: int16 // - MYSQL_TYPE_INT24: int32 // - MYSQL_TYPE_LONGLONG: int64 // - MYSQL_TYPE_NEWDECIMAL: string / "github.com/shopspring/decimal".Decimal // - MYSQL_TYPE_FLOAT: float32 // - MYSQL_TYPE_DOUBLE: float64 // - MYSQL_TYPE_BIT: int64 // - MYSQL_TYPE_TIMESTAMP: string / time.Time // - MYSQL_TYPE_TIMESTAMP2: string / time.Time // - MYSQL_TYPE_DATETIME: string / time.Time // - MYSQL_TYPE_DATETIME2: string / time.Time // - MYSQL_TYPE_TIME: string // - MYSQL_TYPE_TIME2: string // - MYSQL_TYPE_DATE: string // - MYSQL_TYPE_YEAR: int // - MYSQL_TYPE_ENUM: int64 // - MYSQL_TYPE_SET: int64 // - MYSQL_TYPE_BLOB: []byte // - MYSQL_TYPE_VARCHAR: string // - MYSQL_TYPE_VAR_STRING: string // - MYSQL_TYPE_STRING: string // - MYSQL_TYPE_JSON: []byte / *replication.JsonDiff // - MYSQL_TYPE_GEOMETRY: []byte type RowsEvent struct { // 0, 1, 2 Version int tableIDSize int tables map[uint64]*TableMapEvent needBitmap2 bool // for mariadb *_COMPRESSED_EVENT_V1 compressed bool eventType EventType Table *TableMapEvent TableID uint64 Flags uint16 // if version == 2 // Use when DataLen value is greater than 2 NdbFormat byte NdbData []byte PartitionId uint16 SourcePartitionId uint16 // lenenc_int ColumnCount uint64 /* By default MySQL and MariaDB log the full row image. see - https://dev.mysql.com/doc/refman/8.0/en/replication-options-binary-log.html#sysvar_binlog_row_image - https://mariadb.com/kb/en/replication-and-binary-log-system-variables/#binlog_row_image ColumnBitmap1, ColumnBitmap2 and SkippedColumns are not set on the full row image. */ // len = (ColumnCount + 7) / 8 ColumnBitmap1 []byte // if UPDATE_ROWS_EVENTv1 or v2, or PARTIAL_UPDATE_ROWS_EVENT // len = (ColumnCount + 7) / 8 ColumnBitmap2 []byte // rows: all return types from RowsEvent.decodeValue() Rows [][]interface{} SkippedColumns [][]int parseTime bool timestampStringLocation *time.Location useDecimal bool ignoreJSONDecodeErr bool } // EnumRowImageType is allowed types for every row in mysql binlog. // See https://github.com/mysql/mysql-server/blob/1bfe02bdad6604d54913c62614bde57a055c8332/sql/rpl_record.h#L39 // enum class enum_row_image_type { WRITE_AI, UPDATE_BI, UPDATE_AI, DELETE_BI }; type EnumRowImageType byte const ( EnumRowImageTypeWriteAI = EnumRowImageType(iota) EnumRowImageTypeUpdateBI EnumRowImageTypeUpdateAI EnumRowImageTypeDeleteBI ) func (t EnumRowImageType) String() string { switch t { case EnumRowImageTypeWriteAI: return "WriteAI" case EnumRowImageTypeUpdateBI: return "UpdateBI" case EnumRowImageTypeUpdateAI: return "UpdateAI" case EnumRowImageTypeDeleteBI: return "DeleteBI" default: return fmt.Sprintf("(%d)", t) } } // Bits for binlog_row_value_options sysvar type EnumBinlogRowValueOptions byte const ( // Store JSON updates in partial form EnumBinlogRowValueOptionsPartialJsonUpdates = EnumBinlogRowValueOptions(iota + 1) ) func (e *RowsEvent) DecodeHeader(data []byte) (int, error) { pos := 0 e.TableID = FixedLengthInt(data[0:e.tableIDSize]) pos += e.tableIDSize e.Flags = binary.LittleEndian.Uint16(data[pos:]) pos += 2 if e.Version == 2 { dataLen := binary.LittleEndian.Uint16(data[pos:]) pos += 2 if dataLen > 2 { err := e.decodeExtraData(data[pos:]) if err != nil { return 0, err } } pos += int(dataLen - 2) } var n int e.ColumnCount, _, n = LengthEncodedInt(data[pos:]) pos += n bitCount := bitmapByteSize(int(e.ColumnCount)) e.ColumnBitmap1 = data[pos : pos+bitCount] pos += bitCount if e.needBitmap2 { e.ColumnBitmap2 = data[pos : pos+bitCount] pos += bitCount } var ok bool e.Table, ok = e.tables[e.TableID] if !ok { if len(e.tables) > 0 { return 0, errors.Errorf("invalid table id %d, no corresponding table map event", e.TableID) } else { return 0, errors.Annotatef(errMissingTableMapEvent, "table id %d", e.TableID) } } return pos, nil } func (e *RowsEvent) decodeExtraData(data []byte) (err2 error) { pos := 0 extraDataType := data[pos] pos += 1 switch extraDataType { case ENUM_EXTRA_ROW_INFO_TYPECODE_NDB: var ndbLength int = int(data[pos]) pos += 1 e.NdbFormat = data[pos] pos += 1 e.NdbData = data[pos : pos+ndbLength-2] case ENUM_EXTRA_ROW_INFO_TYPECODE_PARTITION: if e.eventType == UPDATE_ROWS_EVENTv1 || e.eventType == UPDATE_ROWS_EVENTv2 || e.eventType == PARTIAL_UPDATE_ROWS_EVENT { e.PartitionId = binary.LittleEndian.Uint16(data[pos:]) pos += 2 e.SourcePartitionId = binary.LittleEndian.Uint16(data[pos:]) } else { e.PartitionId = binary.LittleEndian.Uint16(data[pos:]) } } return nil } func (e *RowsEvent) DecodeData(pos int, data []byte) (err2 error) { if e.compressed { data, err2 = DecompressMariadbData(data[pos:]) if err2 != nil { //nolint:nakedret return } } // Rows_log_event::print_verbose() var ( n int err error ) // ... repeat rows until event-end defer func() { if r := recover(); r != nil { err2 = errors.Errorf("parse rows event panic %v, data %q, parsed rows %#v, table map %#v", r, data, e, e.Table) } }() // Pre-allocate memory for rows: before image + (optional) after image rowsLen := 1 if e.needBitmap2 { rowsLen++ } e.SkippedColumns = make([][]int, 0, rowsLen) e.Rows = make([][]interface{}, 0, rowsLen) var rowImageType EnumRowImageType switch e.eventType { case WRITE_ROWS_EVENTv0, WRITE_ROWS_EVENTv1, WRITE_ROWS_EVENTv2, MARIADB_WRITE_ROWS_COMPRESSED_EVENT_V1: rowImageType = EnumRowImageTypeWriteAI case DELETE_ROWS_EVENTv0, DELETE_ROWS_EVENTv1, DELETE_ROWS_EVENTv2, MARIADB_DELETE_ROWS_COMPRESSED_EVENT_V1: rowImageType = EnumRowImageTypeDeleteBI default: rowImageType = EnumRowImageTypeUpdateBI } for pos < len(data) { // Parse the first image if n, err = e.decodeImage(data[pos:], e.ColumnBitmap1, rowImageType); err != nil { return errors.Trace(err) } pos += n // Parse the second image (for UPDATE only) if e.needBitmap2 { if n, err = e.decodeImage(data[pos:], e.ColumnBitmap2, EnumRowImageTypeUpdateAI); err != nil { return errors.Trace(err) } pos += n } } return nil } func (e *RowsEvent) Decode(data []byte) error { pos, err := e.DecodeHeader(data) if err != nil { return err } return e.DecodeData(pos, data) } func isBitSet(bitmap []byte, i int) bool { return bitmap[i>>3]&(1<<(uint(i)&7)) > 0 } func isBitSetIncr(bitmap []byte, i *int) bool { v := isBitSet(bitmap, *i) *i++ return v } func (e *RowsEvent) decodeImage(data []byte, bitmap []byte, rowImageType EnumRowImageType) (int, error) { // Rows_log_event::print_verbose_one_row() pos := 0 var isPartialJsonUpdate bool var partialBitmap []byte if e.eventType == PARTIAL_UPDATE_ROWS_EVENT && rowImageType == EnumRowImageTypeUpdateAI { binlogRowValueOptions, _, n := LengthEncodedInt(data[pos:]) // binlog_row_value_options pos += n isPartialJsonUpdate = EnumBinlogRowValueOptions(binlogRowValueOptions)&EnumBinlogRowValueOptionsPartialJsonUpdates != 0 if isPartialJsonUpdate { byteCount := bitmapByteSize(int(e.Table.JsonColumnCount())) partialBitmap = data[pos : pos+byteCount] pos += byteCount } } row := make([]interface{}, e.ColumnCount) skips := make([]int, 0) // refer: https://github.com/alibaba/canal/blob/c3e38e50e269adafdd38a48c63a1740cde304c67/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/RowsLogBuffer.java#L63 count := 0 for i := 0; i < int(e.ColumnCount); i++ { if isBitSet(bitmap, i) { count++ } } count = bitmapByteSize(count) nullBitmap := data[pos : pos+count] pos += count partialBitmapIndex := 0 nullBitmapIndex := 0 for i := 0; i < int(e.ColumnCount); i++ { /* Note: need to read partial bit before reading cols_bitmap, since the partial_bits bitmap has a bit for every JSON column regardless of whether it is included in the bitmap or not. */ isPartial := isPartialJsonUpdate && (rowImageType == EnumRowImageTypeUpdateAI) && (e.Table.ColumnType[i] == MYSQL_TYPE_JSON) && isBitSetIncr(partialBitmap, &partialBitmapIndex) if !isBitSet(bitmap, i) { skips = append(skips, i) continue } if isBitSetIncr(nullBitmap, &nullBitmapIndex) { row[i] = nil continue } var n int var err error row[i], n, err = e.decodeValue(data[pos:], e.Table.ColumnType[i], e.Table.ColumnMeta[i], isPartial) if err != nil { return 0, err } pos += n } e.Rows = append(e.Rows, row) e.SkippedColumns = append(e.SkippedColumns, skips) return pos, nil } func (e *RowsEvent) parseFracTime(t interface{}) interface{} { v, ok := t.(fracTime) if !ok { return t } if !e.parseTime { // Don't parse time, return string directly return v.String() } // return Golang time directly return v.Time } // see mysql sql/log_event.cc log_event_print_value func (e *RowsEvent) decodeValue(data []byte, tp byte, meta uint16, isPartial bool) (v interface{}, n int, err error) { var length = 0 if tp == MYSQL_TYPE_STRING { if meta >= 256 { b0 := uint8(meta >> 8) b1 := uint8(meta & 0xFF) if b0&0x30 != 0x30 { length = int(uint16(b1) | (uint16((b0&0x30)^0x30) << 4)) tp = b0 | 0x30 } else { length = int(meta & 0xFF) tp = b0 } } else { length = int(meta) } } switch tp { case MYSQL_TYPE_NULL: return nil, 0, nil case MYSQL_TYPE_LONG: n = 4 v = ParseBinaryInt32(data) case MYSQL_TYPE_TINY: n = 1 v = ParseBinaryInt8(data) case MYSQL_TYPE_SHORT: n = 2 v = ParseBinaryInt16(data) case MYSQL_TYPE_INT24: n = 3 v = ParseBinaryInt24(data) case MYSQL_TYPE_LONGLONG: n = 8 v = ParseBinaryInt64(data) case MYSQL_TYPE_NEWDECIMAL: prec := uint8(meta >> 8) scale := uint8(meta & 0xFF) v, n, err = decodeDecimal(data, int(prec), int(scale), e.useDecimal) case MYSQL_TYPE_FLOAT: n = 4 v = ParseBinaryFloat32(data) case MYSQL_TYPE_DOUBLE: n = 8 v = ParseBinaryFloat64(data) case MYSQL_TYPE_BIT: nbits := ((meta >> 8) * 8) + (meta & 0xFF) n = int(nbits+7) / 8 // use int64 for bit v, err = decodeBit(data, int(nbits), n) case MYSQL_TYPE_TIMESTAMP: n = 4 t := binary.LittleEndian.Uint32(data) if t == 0 { v = formatZeroTime(0, 0) } else { v = e.parseFracTime(fracTime{ Time: time.Unix(int64(t), 0), Dec: 0, timestampStringLocation: e.timestampStringLocation, }) } case MYSQL_TYPE_TIMESTAMP2: v, n, err = decodeTimestamp2(data, meta, e.timestampStringLocation) v = e.parseFracTime(v) case MYSQL_TYPE_DATETIME: n = 8 i64 := binary.LittleEndian.Uint64(data) if i64 == 0 { v = formatZeroTime(0, 0) } else { d := i64 / 1000000 t := i64 % 1000000 v = e.parseFracTime(fracTime{ Time: time.Date( int(d/10000), time.Month((d%10000)/100), int(d%100), int(t/10000), int((t%10000)/100), int(t%100), 0, time.UTC, ), Dec: 0, }) } case MYSQL_TYPE_DATETIME2: v, n, err = decodeDatetime2(data, meta) v = e.parseFracTime(v) case MYSQL_TYPE_TIME: n = 3 i32 := uint32(FixedLengthInt(data[0:3])) if i32 == 0 { v = "00:00:00" } else { v = fmt.Sprintf("%02d:%02d:%02d", i32/10000, (i32%10000)/100, i32%100) } case MYSQL_TYPE_TIME2: v, n, err = decodeTime2(data, meta) case MYSQL_TYPE_DATE: n = 3 i32 := uint32(FixedLengthInt(data[0:3])) if i32 == 0 { v = "0000-00-00" } else { v = fmt.Sprintf("%04d-%02d-%02d", i32/(16*32), i32/32%16, i32%32) } case MYSQL_TYPE_YEAR: n = 1 year := int(data[0]) if year == 0 { v = year } else { v = year + 1900 } case MYSQL_TYPE_ENUM: l := meta & 0xFF switch l { case 1: v = int64(data[0]) n = 1 case 2: v = int64(binary.LittleEndian.Uint16(data)) n = 2 default: err = fmt.Errorf("Unknown ENUM packlen=%d", l) } case MYSQL_TYPE_SET: n = int(meta & 0xFF) nbits := n * 8 v, err = littleDecodeBit(data, nbits, n) case MYSQL_TYPE_BLOB: v, n, err = decodeBlob(data, meta) case MYSQL_TYPE_VARCHAR, MYSQL_TYPE_VAR_STRING: length = int(meta) v, n = decodeString(data, length) case MYSQL_TYPE_STRING: v, n = decodeString(data, length) case MYSQL_TYPE_JSON: // Refer: https://github.com/shyiko/mysql-binlog-connector-java/blob/master/src/main/java/com/github/shyiko/mysql/binlog/event/deserialization/AbstractRowsEventDataDeserializer.java#L404 length = int(FixedLengthInt(data[0:meta])) n = length + int(meta) /* See https://github.com/mysql/mysql-server/blob/7b6fb0753b428537410f5b1b8dc60e5ccabc9f70/sql-common/json_binary.cc#L1077 Each document should start with a one-byte type specifier, so an empty document is invalid according to the format specification. Empty documents may appear due to inserts using the IGNORE keyword or with non-strict SQL mode, which will insert an empty string if the value NULL is inserted into a NOT NULL column. We choose to interpret empty values as the JSON null literal. In our implementation (go-mysql) for backward compatibility we prefer return empty slice. */ if length == 0 { v = []byte{} } else { if isPartial { var diff *JsonDiff diff, err = e.decodeJsonPartialBinary(data[meta:n]) if err == nil { v = diff } else { fmt.Printf("decodeJsonPartialBinary(%q) fail: %s\n", data[meta:n], err) } } else { var d []byte d, err = e.decodeJsonBinary(data[meta:n]) if err == nil { v = utils.ByteSliceToString(d) } } } case MYSQL_TYPE_GEOMETRY: // MySQL saves Geometry as Blob in binlog // Seem that the binary format is SRID (4 bytes) + WKB, outer can use // MySQL GeoFromWKB or others to create the geometry data. // Refer https://dev.mysql.com/doc/refman/5.7/en/gis-wkb-functions.html // I also find some go libs to handle WKB if possible // see https://github.com/twpayne/go-geom or https://github.com/paulmach/go.geo v, n, err = decodeBlob(data, meta) default: err = fmt.Errorf("unsupport type %d in binlog and don't know how to handle", tp) } return v, n, err } func decodeString(data []byte, length int) (v string, n int) { if length < 256 { length = int(data[0]) n = length + 1 v = utils.ByteSliceToString(data[1:n]) } else { length = int(binary.LittleEndian.Uint16(data[0:])) n = length + 2 v = utils.ByteSliceToString(data[2:n]) } return } // ref: https://github.com/mysql/mysql-server/blob/a9b0c712de3509d8d08d3ba385d41a4df6348775/strings/decimal.c#L137 const digitsPerInteger int = 9 var compressedBytes = []int{0, 1, 1, 2, 2, 3, 3, 4, 4, 4} func decodeDecimalDecompressValue(compIndx int, data []byte, mask uint8) (size int, value uint32) { size = compressedBytes[compIndx] switch size { case 0: case 1: value = uint32(data[0] ^ mask) case 2: value = uint32(data[1]^mask) | uint32(data[0]^mask)<<8 case 3: value = uint32(data[2]^mask) | uint32(data[1]^mask)<<8 | uint32(data[0]^mask)<<16 case 4: value = uint32(data[3]^mask) | uint32(data[2]^mask)<<8 | uint32(data[1]^mask)<<16 | uint32(data[0]^mask)<<24 } return } var zeros = [digitsPerInteger]byte{48, 48, 48, 48, 48, 48, 48, 48, 48} func decodeDecimal(data []byte, precision int, decimals int, useDecimal bool) (interface{}, int, error) { // see python mysql replication and https://github.com/jeremycole/mysql_binlog integral := precision - decimals uncompIntegral := integral / digitsPerInteger uncompFractional := decimals / digitsPerInteger compIntegral := integral - (uncompIntegral * digitsPerInteger) compFractional := decimals - (uncompFractional * digitsPerInteger) binSize := uncompIntegral*4 + compressedBytes[compIntegral] + uncompFractional*4 + compressedBytes[compFractional] buf := make([]byte, binSize) copy(buf, data[:binSize]) // must copy the data for later change data = buf // Support negative // The sign is encoded in the high bit of the the byte // But this bit can also be used in the value value := uint32(data[0]) var res strings.Builder res.Grow(precision + 2) var mask uint32 = 0 if value&0x80 == 0 { mask = uint32((1 << 32) - 1) res.WriteString("-") } // clear sign data[0] ^= 0x80 zeroLeading := true pos, value := decodeDecimalDecompressValue(compIntegral, data, uint8(mask)) if value != 0 { zeroLeading = false res.WriteString(strconv.FormatUint(uint64(value), 10)) } for i := 0; i < uncompIntegral; i++ { value = binary.BigEndian.Uint32(data[pos:]) ^ mask pos += 4 if zeroLeading { if value != 0 { zeroLeading = false res.WriteString(strconv.FormatUint(uint64(value), 10)) } } else { toWrite := strconv.FormatUint(uint64(value), 10) res.Write(zeros[:digitsPerInteger-len(toWrite)]) res.WriteString(toWrite) } } if zeroLeading { res.WriteString("0") } if pos < len(data) { res.WriteString(".") for i := 0; i < uncompFractional; i++ { value = binary.BigEndian.Uint32(data[pos:]) ^ mask pos += 4 toWrite := strconv.FormatUint(uint64(value), 10) res.Write(zeros[:digitsPerInteger-len(toWrite)]) res.WriteString(toWrite) } if size, value := decodeDecimalDecompressValue(compFractional, data[pos:], uint8(mask)); size > 0 { toWrite := strconv.FormatUint(uint64(value), 10) padding := compFractional - len(toWrite) if padding > 0 { res.Write(zeros[:padding]) } res.WriteString(toWrite) pos += size } } if useDecimal { f, err := decimal.NewFromString(res.String()) return f, pos, err } return res.String(), pos, nil } func decodeBit(data []byte, nbits int, length int) (value int64, err error) { if nbits > 1 { switch length { case 1: value = int64(data[0]) case 2: value = int64(binary.BigEndian.Uint16(data)) case 3: value = int64(BFixedLengthInt(data[0:3])) case 4: value = int64(binary.BigEndian.Uint32(data)) case 5: value = int64(BFixedLengthInt(data[0:5])) case 6: value = int64(BFixedLengthInt(data[0:6])) case 7: value = int64(BFixedLengthInt(data[0:7])) case 8: value = int64(binary.BigEndian.Uint64(data)) default: err = fmt.Errorf("invalid bit length %d", length) } } else { if length != 1 { err = fmt.Errorf("invalid bit length %d", length) } else { value = int64(data[0]) } } return } func littleDecodeBit(data []byte, nbits int, length int) (value int64, err error) { if nbits > 1 { switch length { case 1: value = int64(data[0]) case 2: value = int64(binary.LittleEndian.Uint16(data)) case 3: value = int64(FixedLengthInt(data[0:3])) case 4: value = int64(binary.LittleEndian.Uint32(data)) case 5: value = int64(FixedLengthInt(data[0:5])) case 6: value = int64(FixedLengthInt(data[0:6])) case 7: value = int64(FixedLengthInt(data[0:7])) case 8: value = int64(binary.LittleEndian.Uint64(data)) default: err = fmt.Errorf("invalid bit length %d", length) } } else { if length != 1 { err = fmt.Errorf("invalid bit length %d", length) } else { value = int64(data[0]) } } return } func decodeTimestamp2(data []byte, dec uint16, timestampStringLocation *time.Location) (interface{}, int, error) { // get timestamp binary length n := int(4 + (dec+1)/2) sec := int64(binary.BigEndian.Uint32(data[0:4])) usec := int64(0) switch dec { case 1, 2: usec = int64(data[4]) * 10000 case 3, 4: usec = int64(binary.BigEndian.Uint16(data[4:])) * 100 case 5, 6: usec = int64(BFixedLengthInt(data[4:7])) } if sec == 0 { return formatZeroTime(int(usec), int(dec)), n, nil } return fracTime{ Time: time.Unix(sec, usec*1000), Dec: int(dec), timestampStringLocation: timestampStringLocation, }, n, nil } const DATETIMEF_INT_OFS int64 = 0x8000000000 func decodeDatetime2(data []byte, dec uint16) (interface{}, int, error) { // get datetime binary length n := int(5 + (dec+1)/2) intPart := int64(BFixedLengthInt(data[0:5])) - DATETIMEF_INT_OFS var frac int64 = 0 switch dec { case 1, 2: frac = int64(data[5]) * 10000 case 3, 4: frac = int64(binary.BigEndian.Uint16(data[5:7])) * 100 case 5, 6: frac = int64(BFixedLengthInt(data[5:8])) } if intPart == 0 { return formatZeroTime(int(frac), int(dec)), n, nil } tmp := intPart<<24 + frac // handle sign??? if tmp < 0 { tmp = -tmp } // var secPart int64 = tmp % (1 << 24) ymdhms := tmp >> 24 ymd := ymdhms >> 17 ym := ymd >> 5 hms := ymdhms % (1 << 17) day := int(ymd % (1 << 5)) month := int(ym % 13) year := int(ym / 13) second := int(hms % (1 << 6)) minute := int((hms >> 6) % (1 << 6)) hour := int(hms >> 12) // DATETIME encoding for nonfractional part after MySQL 5.6.4 // https://dev.mysql.com/doc/internals/en/date-and-time-data-type-representation.html // integer value for 1970-01-01 00:00:00 is // year*13+month = 25611 = 0b110010000001011 // day = 1 = 0b00001 // hour = 0 = 0b00000 // minute = 0 = 0b000000 // second = 0 = 0b000000 // integer value = 0b1100100000010110000100000000000000000 = 107420450816 if intPart < 107420450816 { return formatBeforeUnixZeroTime(year, month, day, hour, minute, second, int(frac), int(dec)), n, nil } return fracTime{ Time: time.Date(year, time.Month(month), day, hour, minute, second, int(frac*1000), time.UTC), Dec: int(dec), }, n, nil } const TIMEF_OFS int64 = 0x800000000000 const TIMEF_INT_OFS int64 = 0x800000 func decodeTime2(data []byte, dec uint16) (string, int, error) { // time binary length n := int(3 + (dec+1)/2) tmp := int64(0) intPart := int64(0) frac := int64(0) switch dec { case 1, 2: intPart = int64(BFixedLengthInt(data[0:3])) - TIMEF_INT_OFS frac = int64(data[3]) if intPart < 0 && frac != 0 { /* Negative values are stored with reverse fractional part order, for binary sort compatibility. Disk value intpart frac Time value Memory value 800000.00 0 0 00:00:00.00 0000000000.000000 7FFFFF.FF -1 255 -00:00:00.01 FFFFFFFFFF.FFD8F0 7FFFFF.9D -1 99 -00:00:00.99 FFFFFFFFFF.F0E4D0 7FFFFF.00 -1 0 -00:00:01.00 FFFFFFFFFF.000000 7FFFFE.FF -1 255 -00:00:01.01 FFFFFFFFFE.FFD8F0 7FFFFE.F6 -2 246 -00:00:01.10 FFFFFFFFFE.FE7960 Formula to convert fractional part from disk format (now stored in "frac" variable) to absolute value: "0x100 - frac". To reconstruct in-memory value, we shift to the next integer value and then substruct fractional part. */ intPart++ /* Shift to the next integer value */ frac -= 0x100 /* -(0x100 - frac) */ } tmp = intPart<<24 + frac*10000 case 3, 4: intPart = int64(BFixedLengthInt(data[0:3])) - TIMEF_INT_OFS frac = int64(binary.BigEndian.Uint16(data[3:5])) if intPart < 0 && frac != 0 { /* Fix reverse fractional part order: "0x10000 - frac". See comments for FSP=1 and FSP=2 above. */ intPart++ /* Shift to the next integer value */ frac -= 0x10000 /* -(0x10000-frac) */ } tmp = intPart<<24 + frac*100 case 5, 6: tmp = int64(BFixedLengthInt(data[0:6])) - TIMEF_OFS return timeFormat(tmp, dec, n) default: intPart = int64(BFixedLengthInt(data[0:3])) - TIMEF_INT_OFS tmp = intPart << 24 } if intPart == 0 && frac == 0 { return "00:00:00", n, nil } return timeFormat(tmp, dec, n) } func timeFormat(tmp int64, dec uint16, n int) (string, int, error) { hms := int64(0) sign := "" if tmp < 0 { tmp = -tmp sign = "-" } hms = tmp >> 24 hour := (hms >> 12) % (1 << 10) /* 10 bits starting at 12th */ minute := (hms >> 6) % (1 << 6) /* 6 bits starting at 6th */ second := hms % (1 << 6) /* 6 bits starting at 0th */ secPart := tmp % (1 << 24) if secPart != 0 { s := fmt.Sprintf("%s%02d:%02d:%02d.%06d", sign, hour, minute, second, secPart) return s[0 : len(s)-(6-int(dec))], n, nil } return fmt.Sprintf("%s%02d:%02d:%02d", sign, hour, minute, second), n, nil } func decodeBlob(data []byte, meta uint16) (v []byte, n int, err error) { var length int switch meta { case 1: length = int(data[0]) v = data[1 : 1+length] n = length + 1 case 2: length = int(binary.LittleEndian.Uint16(data)) v = data[2 : 2+length] n = length + 2 case 3: length = int(FixedLengthInt(data[0:3])) v = data[3 : 3+length] n = length + 3 case 4: length = int(binary.LittleEndian.Uint32(data)) v = data[4 : 4+length] n = length + 4 default: err = fmt.Errorf("invalid blob packlen = %d", meta) } return } func (e *RowsEvent) Dump(w io.Writer) { fmt.Fprintf(w, "TableID: %d\n", e.TableID) fmt.Fprintf(w, "Flags: %d\n", e.Flags) fmt.Fprintf(w, "Column count: %d\n", e.ColumnCount) fmt.Fprintf(w, "NDB data: %s\n", e.NdbData) fmt.Fprintf(w, "Values:\n") for _, rows := range e.Rows { fmt.Fprintf(w, "--\n") for j, d := range rows { switch dt := d.(type) { case []byte: fmt.Fprintf(w, "%d:%q\n", j, dt) case *JsonDiff: fmt.Fprintf(w, "%d:%s\n", j, dt) default: fmt.Fprintf(w, "%d:%#v\n", j, d) } } } fmt.Fprintln(w) } type RowsQueryEvent struct { Query []byte } func (e *RowsQueryEvent) Decode(data []byte) error { // ignore length byte 1 e.Query = data[1:] return nil } func (e *RowsQueryEvent) Dump(w io.Writer) { fmt.Fprintf(w, "Query: %s\n", e.Query) fmt.Fprintln(w) } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/replication/time.go ================================================ package replication import ( "fmt" "strings" "time" ) var ( fracTimeFormat []string ) // fracTime is a help structure wrapping Golang Time. type fracTime struct { time.Time // Dec must in [0, 6] Dec int timestampStringLocation *time.Location } func (t fracTime) String() string { tt := t.Time if t.timestampStringLocation != nil { tt = tt.In(t.timestampStringLocation) } return tt.Format(fracTimeFormat[t.Dec]) } func formatZeroTime(frac int, dec int) string { if dec == 0 { return "0000-00-00 00:00:00" } s := fmt.Sprintf("0000-00-00 00:00:00.%06d", frac) // dec must < 6, if frac is 924000, but dec is 3, we must output 924 here. return s[0 : len(s)-(6-dec)] } func formatBeforeUnixZeroTime(year, month, day, hour, minute, second, frac, dec int) string { if dec == 0 { return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second) } s := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d.%06d", year, month, day, hour, minute, second, frac) // dec must < 6, if frac is 924000, but dec is 3, we must output 924 here. return s[0 : len(s)-(6-dec)] } func microSecTimestampToTime(ts uint64) time.Time { if ts == 0 { return time.Time{} } return time.Unix(int64(ts/1000000), int64(ts%1000000)*1000) } func init() { fracTimeFormat = make([]string, 7) fracTimeFormat[0] = "2006-01-02 15:04:05" for i := 1; i <= 6; i++ { fracTimeFormat[i] = fmt.Sprintf("2006-01-02 15:04:05.%s", strings.Repeat("0", i)) } } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/replication/transaction_payload_event.go ================================================ package replication import ( "encoding/binary" "encoding/hex" "fmt" "io" "github.com/klauspost/compress/zstd" . "github.com/go-mysql-org/go-mysql/mysql" ) // On The Wire: Field Types // See also binary_log::codecs::binary::Transaction_payload::fields in MySQL // https://dev.mysql.com/doc/dev/mysql-server/latest/classbinary__log_1_1codecs_1_1binary_1_1Transaction__payload.html#a9fff7ac12ba064f40e9216565c53d07b const ( OTW_PAYLOAD_HEADER_END_MARK = iota OTW_PAYLOAD_SIZE_FIELD OTW_PAYLOAD_COMPRESSION_TYPE_FIELD OTW_PAYLOAD_UNCOMPRESSED_SIZE_FIELD ) // Compression Types const ( ZSTD = 0 NONE = 255 ) type TransactionPayloadEvent struct { format FormatDescriptionEvent Size uint64 UncompressedSize uint64 CompressionType uint64 Payload []byte Events []*BinlogEvent } func (e *TransactionPayloadEvent) compressionType() string { switch e.CompressionType { case ZSTD: return "ZSTD" case NONE: return "NONE" default: return "Unknown" } } func (e *TransactionPayloadEvent) Dump(w io.Writer) { fmt.Fprintf(w, "Payload Size: %d\n", e.Size) fmt.Fprintf(w, "Payload Uncompressed Size: %d\n", e.UncompressedSize) fmt.Fprintf(w, "Payload CompressionType: %s\n", e.compressionType()) fmt.Fprintf(w, "Payload Body: \n%s", hex.Dump(e.Payload)) fmt.Fprintln(w, "=== Start of events decoded from compressed payload ===") for _, event := range e.Events { event.Dump(w) } fmt.Fprintln(w, "=== End of events decoded from compressed payload ===") fmt.Fprintln(w) } func (e *TransactionPayloadEvent) Decode(data []byte) error { err := e.decodeFields(data) if err != nil { return err } return e.decodePayload() } func (e *TransactionPayloadEvent) decodeFields(data []byte) error { offset := uint64(0) for { fieldType := FixedLengthInt(data[offset : offset+1]) offset++ if fieldType == OTW_PAYLOAD_HEADER_END_MARK { e.Payload = data[offset:] break } else { fieldLength := FixedLengthInt(data[offset : offset+1]) offset++ switch fieldType { case OTW_PAYLOAD_SIZE_FIELD: e.Size = FixedLengthInt(data[offset : offset+fieldLength]) case OTW_PAYLOAD_COMPRESSION_TYPE_FIELD: e.CompressionType = FixedLengthInt(data[offset : offset+fieldLength]) case OTW_PAYLOAD_UNCOMPRESSED_SIZE_FIELD: e.UncompressedSize = FixedLengthInt(data[offset : offset+fieldLength]) } offset += fieldLength } } return nil } func (e *TransactionPayloadEvent) decodePayload() error { if e.CompressionType != ZSTD { return fmt.Errorf("TransactionPayloadEvent has compression type %d (%s)", e.CompressionType, e.compressionType()) } var decoder, err = zstd.NewReader(nil, zstd.WithDecoderConcurrency(0)) if err != nil { return err } defer decoder.Close() payloadUncompressed, err := decoder.DecodeAll(e.Payload, nil) if err != nil { return err } // The uncompressed data needs to be split up into individual events for Parse() // to work on them. We can't use e.parser directly as we need to disable checksums // but we still need the initialization from the FormatDescriptionEvent. We can't // modify e.parser as it is used elsewhere. parser := NewBinlogParser() parser.format = &FormatDescriptionEvent{ Version: e.format.Version, ServerVersion: e.format.ServerVersion, CreateTimestamp: e.format.CreateTimestamp, EventHeaderLength: e.format.EventHeaderLength, EventTypeHeaderLengths: e.format.EventTypeHeaderLengths, ChecksumAlgorithm: BINLOG_CHECKSUM_ALG_OFF, } offset := uint32(0) for { payloadUncompressedLength := uint32(len(payloadUncompressed)) if offset+13 > payloadUncompressedLength { break } eventLength := binary.LittleEndian.Uint32(payloadUncompressed[offset+9 : offset+13]) if offset+eventLength > payloadUncompressedLength { return fmt.Errorf("Event length of %d with offset %d in uncompressed payload exceeds payload length of %d", eventLength, offset, payloadUncompressedLength) } data := payloadUncompressed[offset : offset+eventLength] pe, err := parser.Parse(data) if err != nil { return err } e.Events = append(e.Events, pe) offset += eventLength } return nil } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/utils/byte_slice_pool.go ================================================ package utils import "sync" type ByteSlice struct { B []byte } var ( byteSlicePool = sync.Pool{ New: func() interface{} { return new(ByteSlice) }, } ) func ByteSliceGet(length int) *ByteSlice { data := byteSlicePool.Get().(*ByteSlice) if cap(data.B) < length { data.B = make([]byte, length) } else { data.B = data.B[:length] } return data } func ByteSlicePut(data *ByteSlice) { data.B = data.B[:0] byteSlicePool.Put(data) } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/utils/bytes_buffer_pool.go ================================================ package utils import ( "bytes" "sync" ) const ( TooBigBlockSize = 1024 * 1024 * 4 ) var ( bytesBufferPool = sync.Pool{ New: func() interface{} { return &bytes.Buffer{} }, } ) func BytesBufferGet() (data *bytes.Buffer) { data = bytesBufferPool.Get().(*bytes.Buffer) data.Reset() return data } func BytesBufferPut(data *bytes.Buffer) { if data == nil || data.Len() > TooBigBlockSize { return } bytesBufferPool.Put(data) } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/utils/now.go ================================================ //go:build !unix package utils import "time" var Now = time.Now ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/utils/now_unix.go ================================================ //go:build unix package utils import ( "syscall" "time" ) // Now is a faster method to get current time func Now() time.Time { var tv syscall.Timeval if err := syscall.Gettimeofday(&tv); err != nil { // If it failed at syscall, use time package instead return time.Now() } return time.Unix(0, syscall.TimevalToNsec(tv)) } ================================================ FILE: vendor/github.com/go-mysql-org/go-mysql/utils/zeroalloc.go ================================================ package utils import ( "math" "unsafe" ) func StringToByteSlice(s string) []byte { return unsafe.Slice(unsafe.StringData(s), len(s)) } func ByteSliceToString(b []byte) string { return unsafe.String(unsafe.SliceData(b), len(b)) } func Uint64ToInt64(val uint64) int64 { return int64(val) } func Uint64ToFloat64(val uint64) float64 { return math.Float64frombits(val) } func Int64ToUint64(val int64) uint64 { return uint64(val) } func Float64ToUint64(val float64) uint64 { return math.Float64bits(val) } ================================================ FILE: vendor/github.com/go-ole/go-ole/.travis.yml ================================================ language: go sudo: false go: - 1.9.x - 1.10.x - 1.11.x - tip ================================================ FILE: vendor/github.com/go-ole/go-ole/ChangeLog.md ================================================ # Version 1.x.x * **Add more test cases and reference new test COM server project.** (Placeholder for future additions) # Version 1.2.0-alphaX **Minimum supported version is now Go 1.4. Go 1.1 support is deprecated, but should still build.** * Added CI configuration for Travis-CI and AppVeyor. * Added test InterfaceID and ClassID for the COM Test Server project. * Added more inline documentation (#83). * Added IEnumVARIANT implementation (#88). * Added IEnumVARIANT test cases (#99, #100, #101). * Added support for retrieving `time.Time` from VARIANT (#92). * Added test case for IUnknown (#64). * Added test case for IDispatch (#64). * Added test cases for scalar variants (#64, #76). # Version 1.1.1 * Fixes for Linux build. * Fixes for Windows build. # Version 1.1.0 The change to provide building on all platforms is a new feature. The increase in minor version reflects that and allows those who wish to stay on 1.0.x to continue to do so. Support for 1.0.x will be limited to bug fixes. * Move GUID out of variables.go into its own file to make new documentation available. * Move OleError out of ole.go into its own file to make new documentation available. * Add documentation to utility functions. * Add documentation to variant receiver functions. * Add documentation to ole structures. * Make variant available to other systems outside of Windows. * Make OLE structures available to other systems outside of Windows. ## New Features * Library should now be built on all platforms supported by Go. Library will NOOP on any platform that is not Windows. * More functions are now documented and available on godoc.org. # Version 1.0.1 1. Fix package references from repository location change. # Version 1.0.0 This version is stable enough for use. The COM API is still incomplete, but provides enough functionality for accessing COM servers using IDispatch interface. There is no changelog for this version. Check commits for history. ================================================ FILE: vendor/github.com/go-ole/go-ole/LICENSE ================================================ The MIT License (MIT) Copyright © 2013-2017 Yasuhiro Matsumoto, Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: vendor/github.com/go-ole/go-ole/README.md ================================================ # Go OLE [![Build status](https://ci.appveyor.com/api/projects/status/qr0u2sf7q43us9fj?svg=true)](https://ci.appveyor.com/project/jacobsantos/go-ole-jgs28) [![Build Status](https://travis-ci.org/go-ole/go-ole.svg?branch=master)](https://travis-ci.org/go-ole/go-ole) [![GoDoc](https://godoc.org/github.com/go-ole/go-ole?status.svg)](https://godoc.org/github.com/go-ole/go-ole) Go bindings for Windows COM using shared libraries instead of cgo. By Yasuhiro Matsumoto. ## Install To experiment with go-ole, you can just compile and run the example program: ``` go get github.com/go-ole/go-ole cd /path/to/go-ole/ go test cd /path/to/go-ole/example/excel go run excel.go ``` ## Continuous Integration Continuous integration configuration has been added for both Travis-CI and AppVeyor. You will have to add these to your own account for your fork in order for it to run. **Travis-CI** Travis-CI was added to check builds on Linux to ensure that `go get` works when cross building. Currently, Travis-CI is not used to test cross-building, but this may be changed in the future. It is also not currently possible to test the library on Linux, since COM API is specific to Windows and it is not currently possible to run a COM server on Linux or even connect to a remote COM server. **AppVeyor** AppVeyor is used to build on Windows using the (in-development) test COM server. It is currently only used to test the build and ensure that the code works on Windows. It will be used to register a COM server and then run the test cases based on the test COM server. The tests currently do run and do pass and this should be maintained with commits. ## Versioning Go OLE uses [semantic versioning](http://semver.org) for version numbers, which is similar to the version contract of the Go language. Which means that the major version will always maintain backwards compatibility with minor versions. Minor versions will only add new additions and changes. Fixes will always be in patch. This contract should allow you to upgrade to new minor and patch versions without breakage or modifications to your existing code. Leave a ticket, if there is breakage, so that it could be fixed. ## LICENSE Under the MIT License: http://mattn.mit-license.org/2013 ================================================ FILE: vendor/github.com/go-ole/go-ole/appveyor.yml ================================================ # Notes: # - Minimal appveyor.yml file is an empty file. All sections are optional. # - Indent each level of configuration with 2 spaces. Do not use tabs! # - All section names are case-sensitive. # - Section names should be unique on each level. version: "1.3.0.{build}-alpha-{branch}" os: Windows Server 2012 R2 branches: only: - master - v1.2 - v1.1 - v1.0 skip_tags: true clone_folder: c:\gopath\src\github.com\go-ole\go-ole environment: GOPATH: c:\gopath matrix: - GOARCH: amd64 GOVERSION: 1.5 GOROOT: c:\go DOWNLOADPLATFORM: "x64" install: - choco install mingw - SET PATH=c:\tools\mingw64\bin;%PATH% # - Download COM Server - ps: Start-FileDownload "https://github.com/go-ole/test-com-server/releases/download/v1.0.2/test-com-server-${env:DOWNLOADPLATFORM}.zip" - 7z e test-com-server-%DOWNLOADPLATFORM%.zip -oc:\gopath\src\github.com\go-ole\go-ole > NUL - c:\gopath\src\github.com\go-ole\go-ole\build\register-assembly.bat # - set - go version - go env - go get -u golang.org/x/tools/cmd/cover - go get -u golang.org/x/tools/cmd/godoc - go get -u golang.org/x/tools/cmd/stringer build_script: - cd c:\gopath\src\github.com\go-ole\go-ole - go get -v -t ./... - go build - go test -v -cover ./... # disable automatic tests test: off # disable deployment deploy: off ================================================ FILE: vendor/github.com/go-ole/go-ole/com.go ================================================ // +build windows package ole import ( "syscall" "unicode/utf16" "unsafe" ) var ( procCoInitialize = modole32.NewProc("CoInitialize") procCoInitializeEx = modole32.NewProc("CoInitializeEx") procCoUninitialize = modole32.NewProc("CoUninitialize") procCoCreateInstance = modole32.NewProc("CoCreateInstance") procCoTaskMemFree = modole32.NewProc("CoTaskMemFree") procCLSIDFromProgID = modole32.NewProc("CLSIDFromProgID") procCLSIDFromString = modole32.NewProc("CLSIDFromString") procStringFromCLSID = modole32.NewProc("StringFromCLSID") procStringFromIID = modole32.NewProc("StringFromIID") procIIDFromString = modole32.NewProc("IIDFromString") procCoGetObject = modole32.NewProc("CoGetObject") procGetUserDefaultLCID = modkernel32.NewProc("GetUserDefaultLCID") procCopyMemory = modkernel32.NewProc("RtlMoveMemory") procVariantInit = modoleaut32.NewProc("VariantInit") procVariantClear = modoleaut32.NewProc("VariantClear") procVariantTimeToSystemTime = modoleaut32.NewProc("VariantTimeToSystemTime") procSysAllocString = modoleaut32.NewProc("SysAllocString") procSysAllocStringLen = modoleaut32.NewProc("SysAllocStringLen") procSysFreeString = modoleaut32.NewProc("SysFreeString") procSysStringLen = modoleaut32.NewProc("SysStringLen") procCreateDispTypeInfo = modoleaut32.NewProc("CreateDispTypeInfo") procCreateStdDispatch = modoleaut32.NewProc("CreateStdDispatch") procGetActiveObject = modoleaut32.NewProc("GetActiveObject") procGetMessageW = moduser32.NewProc("GetMessageW") procDispatchMessageW = moduser32.NewProc("DispatchMessageW") ) // coInitialize initializes COM library on current thread. // // MSDN documentation suggests that this function should not be called. Call // CoInitializeEx() instead. The reason has to do with threading and this // function is only for single-threaded apartments. // // That said, most users of the library have gotten away with just this // function. If you are experiencing threading issues, then use // CoInitializeEx(). func coInitialize() (err error) { // http://msdn.microsoft.com/en-us/library/windows/desktop/ms678543(v=vs.85).aspx // Suggests that no value should be passed to CoInitialized. // Could just be Call() since the parameter is optional. <-- Needs testing to be sure. hr, _, _ := procCoInitialize.Call(uintptr(0)) if hr != 0 { err = NewError(hr) } return } // coInitializeEx initializes COM library with concurrency model. func coInitializeEx(coinit uint32) (err error) { // http://msdn.microsoft.com/en-us/library/windows/desktop/ms695279(v=vs.85).aspx // Suggests that the first parameter is not only optional but should always be NULL. hr, _, _ := procCoInitializeEx.Call(uintptr(0), uintptr(coinit)) if hr != 0 { err = NewError(hr) } return } // CoInitialize initializes COM library on current thread. // // MSDN documentation suggests that this function should not be called. Call // CoInitializeEx() instead. The reason has to do with threading and this // function is only for single-threaded apartments. // // That said, most users of the library have gotten away with just this // function. If you are experiencing threading issues, then use // CoInitializeEx(). func CoInitialize(p uintptr) (err error) { // p is ignored and won't be used. // Avoid any variable not used errors. p = uintptr(0) return coInitialize() } // CoInitializeEx initializes COM library with concurrency model. func CoInitializeEx(p uintptr, coinit uint32) (err error) { // Avoid any variable not used errors. p = uintptr(0) return coInitializeEx(coinit) } // CoUninitialize uninitializes COM Library. func CoUninitialize() { procCoUninitialize.Call() } // CoTaskMemFree frees memory pointer. func CoTaskMemFree(memptr uintptr) { procCoTaskMemFree.Call(memptr) } // CLSIDFromProgID retrieves Class Identifier with the given Program Identifier. // // The Programmatic Identifier must be registered, because it will be looked up // in the Windows Registry. The registry entry has the following keys: CLSID, // Insertable, Protocol and Shell // (https://msdn.microsoft.com/en-us/library/dd542719(v=vs.85).aspx). // // programID identifies the class id with less precision and is not guaranteed // to be unique. These are usually found in the registry under // HKEY_LOCAL_MACHINE\SOFTWARE\Classes, usually with the format of // "Program.Component.Version" with version being optional. // // CLSIDFromProgID in Windows API. func CLSIDFromProgID(progId string) (clsid *GUID, err error) { var guid GUID lpszProgID := uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(progId))) hr, _, _ := procCLSIDFromProgID.Call(lpszProgID, uintptr(unsafe.Pointer(&guid))) if hr != 0 { err = NewError(hr) } clsid = &guid return } // CLSIDFromString retrieves Class ID from string representation. // // This is technically the string version of the GUID and will convert the // string to object. // // CLSIDFromString in Windows API. func CLSIDFromString(str string) (clsid *GUID, err error) { var guid GUID lpsz := uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(str))) hr, _, _ := procCLSIDFromString.Call(lpsz, uintptr(unsafe.Pointer(&guid))) if hr != 0 { err = NewError(hr) } clsid = &guid return } // StringFromCLSID returns GUID formated string from GUID object. func StringFromCLSID(clsid *GUID) (str string, err error) { var p *uint16 hr, _, _ := procStringFromCLSID.Call(uintptr(unsafe.Pointer(clsid)), uintptr(unsafe.Pointer(&p))) if hr != 0 { err = NewError(hr) } str = LpOleStrToString(p) return } // IIDFromString returns GUID from program ID. func IIDFromString(progId string) (clsid *GUID, err error) { var guid GUID lpsz := uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(progId))) hr, _, _ := procIIDFromString.Call(lpsz, uintptr(unsafe.Pointer(&guid))) if hr != 0 { err = NewError(hr) } clsid = &guid return } // StringFromIID returns GUID formatted string from GUID object. func StringFromIID(iid *GUID) (str string, err error) { var p *uint16 hr, _, _ := procStringFromIID.Call(uintptr(unsafe.Pointer(iid)), uintptr(unsafe.Pointer(&p))) if hr != 0 { err = NewError(hr) } str = LpOleStrToString(p) return } // CreateInstance of single uninitialized object with GUID. func CreateInstance(clsid *GUID, iid *GUID) (unk *IUnknown, err error) { if iid == nil { iid = IID_IUnknown } hr, _, _ := procCoCreateInstance.Call( uintptr(unsafe.Pointer(clsid)), 0, CLSCTX_SERVER, uintptr(unsafe.Pointer(iid)), uintptr(unsafe.Pointer(&unk))) if hr != 0 { err = NewError(hr) } return } // GetActiveObject retrieves pointer to active object. func GetActiveObject(clsid *GUID, iid *GUID) (unk *IUnknown, err error) { if iid == nil { iid = IID_IUnknown } hr, _, _ := procGetActiveObject.Call( uintptr(unsafe.Pointer(clsid)), uintptr(unsafe.Pointer(iid)), uintptr(unsafe.Pointer(&unk))) if hr != 0 { err = NewError(hr) } return } type BindOpts struct { CbStruct uint32 GrfFlags uint32 GrfMode uint32 TickCountDeadline uint32 } // GetObject retrieves pointer to active object. func GetObject(programID string, bindOpts *BindOpts, iid *GUID) (unk *IUnknown, err error) { if bindOpts != nil { bindOpts.CbStruct = uint32(unsafe.Sizeof(BindOpts{})) } if iid == nil { iid = IID_IUnknown } hr, _, _ := procCoGetObject.Call( uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(programID))), uintptr(unsafe.Pointer(bindOpts)), uintptr(unsafe.Pointer(iid)), uintptr(unsafe.Pointer(&unk))) if hr != 0 { err = NewError(hr) } return } // VariantInit initializes variant. func VariantInit(v *VARIANT) (err error) { hr, _, _ := procVariantInit.Call(uintptr(unsafe.Pointer(v))) if hr != 0 { err = NewError(hr) } return } // VariantClear clears value in Variant settings to VT_EMPTY. func VariantClear(v *VARIANT) (err error) { hr, _, _ := procVariantClear.Call(uintptr(unsafe.Pointer(v))) if hr != 0 { err = NewError(hr) } return } // SysAllocString allocates memory for string and copies string into memory. func SysAllocString(v string) (ss *int16) { pss, _, _ := procSysAllocString.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(v)))) ss = (*int16)(unsafe.Pointer(pss)) return } // SysAllocStringLen copies up to length of given string returning pointer. func SysAllocStringLen(v string) (ss *int16) { utf16 := utf16.Encode([]rune(v + "\x00")) ptr := &utf16[0] pss, _, _ := procSysAllocStringLen.Call(uintptr(unsafe.Pointer(ptr)), uintptr(len(utf16)-1)) ss = (*int16)(unsafe.Pointer(pss)) return } // SysFreeString frees string system memory. This must be called with SysAllocString. func SysFreeString(v *int16) (err error) { hr, _, _ := procSysFreeString.Call(uintptr(unsafe.Pointer(v))) if hr != 0 { err = NewError(hr) } return } // SysStringLen is the length of the system allocated string. func SysStringLen(v *int16) uint32 { l, _, _ := procSysStringLen.Call(uintptr(unsafe.Pointer(v))) return uint32(l) } // CreateStdDispatch provides default IDispatch implementation for IUnknown. // // This handles default IDispatch implementation for objects. It haves a few // limitations with only supporting one language. It will also only return // default exception codes. func CreateStdDispatch(unk *IUnknown, v uintptr, ptinfo *IUnknown) (disp *IDispatch, err error) { hr, _, _ := procCreateStdDispatch.Call( uintptr(unsafe.Pointer(unk)), v, uintptr(unsafe.Pointer(ptinfo)), uintptr(unsafe.Pointer(&disp))) if hr != 0 { err = NewError(hr) } return } // CreateDispTypeInfo provides default ITypeInfo implementation for IDispatch. // // This will not handle the full implementation of the interface. func CreateDispTypeInfo(idata *INTERFACEDATA) (pptinfo *IUnknown, err error) { hr, _, _ := procCreateDispTypeInfo.Call( uintptr(unsafe.Pointer(idata)), uintptr(GetUserDefaultLCID()), uintptr(unsafe.Pointer(&pptinfo))) if hr != 0 { err = NewError(hr) } return } // copyMemory moves location of a block of memory. func copyMemory(dest unsafe.Pointer, src unsafe.Pointer, length uint32) { procCopyMemory.Call(uintptr(dest), uintptr(src), uintptr(length)) } // GetUserDefaultLCID retrieves current user default locale. func GetUserDefaultLCID() (lcid uint32) { ret, _, _ := procGetUserDefaultLCID.Call() lcid = uint32(ret) return } // GetMessage in message queue from runtime. // // This function appears to block. PeekMessage does not block. func GetMessage(msg *Msg, hwnd uint32, MsgFilterMin uint32, MsgFilterMax uint32) (ret int32, err error) { r0, _, err := procGetMessageW.Call(uintptr(unsafe.Pointer(msg)), uintptr(hwnd), uintptr(MsgFilterMin), uintptr(MsgFilterMax)) ret = int32(r0) return } // DispatchMessage to window procedure. func DispatchMessage(msg *Msg) (ret int32) { r0, _, _ := procDispatchMessageW.Call(uintptr(unsafe.Pointer(msg))) ret = int32(r0) return } ================================================ FILE: vendor/github.com/go-ole/go-ole/com_func.go ================================================ // +build !windows package ole import ( "time" "unsafe" ) // coInitialize initializes COM library on current thread. // // MSDN documentation suggests that this function should not be called. Call // CoInitializeEx() instead. The reason has to do with threading and this // function is only for single-threaded apartments. // // That said, most users of the library have gotten away with just this // function. If you are experiencing threading issues, then use // CoInitializeEx(). func coInitialize() error { return NewError(E_NOTIMPL) } // coInitializeEx initializes COM library with concurrency model. func coInitializeEx(coinit uint32) error { return NewError(E_NOTIMPL) } // CoInitialize initializes COM library on current thread. // // MSDN documentation suggests that this function should not be called. Call // CoInitializeEx() instead. The reason has to do with threading and this // function is only for single-threaded apartments. // // That said, most users of the library have gotten away with just this // function. If you are experiencing threading issues, then use // CoInitializeEx(). func CoInitialize(p uintptr) error { return NewError(E_NOTIMPL) } // CoInitializeEx initializes COM library with concurrency model. func CoInitializeEx(p uintptr, coinit uint32) error { return NewError(E_NOTIMPL) } // CoUninitialize uninitializes COM Library. func CoUninitialize() {} // CoTaskMemFree frees memory pointer. func CoTaskMemFree(memptr uintptr) {} // CLSIDFromProgID retrieves Class Identifier with the given Program Identifier. // // The Programmatic Identifier must be registered, because it will be looked up // in the Windows Registry. The registry entry has the following keys: CLSID, // Insertable, Protocol and Shell // (https://msdn.microsoft.com/en-us/library/dd542719(v=vs.85).aspx). // // programID identifies the class id with less precision and is not guaranteed // to be unique. These are usually found in the registry under // HKEY_LOCAL_MACHINE\SOFTWARE\Classes, usually with the format of // "Program.Component.Version" with version being optional. // // CLSIDFromProgID in Windows API. func CLSIDFromProgID(progId string) (*GUID, error) { return nil, NewError(E_NOTIMPL) } // CLSIDFromString retrieves Class ID from string representation. // // This is technically the string version of the GUID and will convert the // string to object. // // CLSIDFromString in Windows API. func CLSIDFromString(str string) (*GUID, error) { return nil, NewError(E_NOTIMPL) } // StringFromCLSID returns GUID formated string from GUID object. func StringFromCLSID(clsid *GUID) (string, error) { return "", NewError(E_NOTIMPL) } // IIDFromString returns GUID from program ID. func IIDFromString(progId string) (*GUID, error) { return nil, NewError(E_NOTIMPL) } // StringFromIID returns GUID formatted string from GUID object. func StringFromIID(iid *GUID) (string, error) { return "", NewError(E_NOTIMPL) } // CreateInstance of single uninitialized object with GUID. func CreateInstance(clsid *GUID, iid *GUID) (*IUnknown, error) { return nil, NewError(E_NOTIMPL) } // GetActiveObject retrieves pointer to active object. func GetActiveObject(clsid *GUID, iid *GUID) (*IUnknown, error) { return nil, NewError(E_NOTIMPL) } // VariantInit initializes variant. func VariantInit(v *VARIANT) error { return NewError(E_NOTIMPL) } // VariantClear clears value in Variant settings to VT_EMPTY. func VariantClear(v *VARIANT) error { return NewError(E_NOTIMPL) } // SysAllocString allocates memory for string and copies string into memory. func SysAllocString(v string) *int16 { u := int16(0) return &u } // SysAllocStringLen copies up to length of given string returning pointer. func SysAllocStringLen(v string) *int16 { u := int16(0) return &u } // SysFreeString frees string system memory. This must be called with SysAllocString. func SysFreeString(v *int16) error { return NewError(E_NOTIMPL) } // SysStringLen is the length of the system allocated string. func SysStringLen(v *int16) uint32 { return uint32(0) } // CreateStdDispatch provides default IDispatch implementation for IUnknown. // // This handles default IDispatch implementation for objects. It haves a few // limitations with only supporting one language. It will also only return // default exception codes. func CreateStdDispatch(unk *IUnknown, v uintptr, ptinfo *IUnknown) (*IDispatch, error) { return nil, NewError(E_NOTIMPL) } // CreateDispTypeInfo provides default ITypeInfo implementation for IDispatch. // // This will not handle the full implementation of the interface. func CreateDispTypeInfo(idata *INTERFACEDATA) (*IUnknown, error) { return nil, NewError(E_NOTIMPL) } // copyMemory moves location of a block of memory. func copyMemory(dest unsafe.Pointer, src unsafe.Pointer, length uint32) {} // GetUserDefaultLCID retrieves current user default locale. func GetUserDefaultLCID() uint32 { return uint32(0) } // GetMessage in message queue from runtime. // // This function appears to block. PeekMessage does not block. func GetMessage(msg *Msg, hwnd uint32, MsgFilterMin uint32, MsgFilterMax uint32) (int32, error) { return int32(0), NewError(E_NOTIMPL) } // DispatchMessage to window procedure. func DispatchMessage(msg *Msg) int32 { return int32(0) } func GetVariantDate(value uint64) (time.Time, error) { return time.Now(), NewError(E_NOTIMPL) } ================================================ FILE: vendor/github.com/go-ole/go-ole/connect.go ================================================ package ole // Connection contains IUnknown for fluent interface interaction. // // Deprecated. Use oleutil package instead. type Connection struct { Object *IUnknown // Access COM } // Initialize COM. func (*Connection) Initialize() (err error) { return coInitialize() } // Uninitialize COM. func (*Connection) Uninitialize() { CoUninitialize() } // Create IUnknown object based first on ProgId and then from String. func (c *Connection) Create(progId string) (err error) { var clsid *GUID clsid, err = CLSIDFromProgID(progId) if err != nil { clsid, err = CLSIDFromString(progId) if err != nil { return } } unknown, err := CreateInstance(clsid, IID_IUnknown) if err != nil { return } c.Object = unknown return } // Release IUnknown object. func (c *Connection) Release() { c.Object.Release() } // Load COM object from list of programIDs or strings. func (c *Connection) Load(names ...string) (errors []error) { var tempErrors []error = make([]error, len(names)) var numErrors int = 0 for _, name := range names { err := c.Create(name) if err != nil { tempErrors = append(tempErrors, err) numErrors += 1 continue } break } copy(errors, tempErrors[0:numErrors]) return } // Dispatch returns Dispatch object. func (c *Connection) Dispatch() (object *Dispatch, err error) { dispatch, err := c.Object.QueryInterface(IID_IDispatch) if err != nil { return } object = &Dispatch{dispatch} return } // Dispatch stores IDispatch object. type Dispatch struct { Object *IDispatch // Dispatch object. } // Call method on IDispatch with parameters. func (d *Dispatch) Call(method string, params ...interface{}) (result *VARIANT, err error) { id, err := d.GetId(method) if err != nil { return } result, err = d.Invoke(id, DISPATCH_METHOD, params) return } // MustCall method on IDispatch with parameters. func (d *Dispatch) MustCall(method string, params ...interface{}) (result *VARIANT) { id, err := d.GetId(method) if err != nil { panic(err) } result, err = d.Invoke(id, DISPATCH_METHOD, params) if err != nil { panic(err) } return } // Get property on IDispatch with parameters. func (d *Dispatch) Get(name string, params ...interface{}) (result *VARIANT, err error) { id, err := d.GetId(name) if err != nil { return } result, err = d.Invoke(id, DISPATCH_PROPERTYGET, params) return } // MustGet property on IDispatch with parameters. func (d *Dispatch) MustGet(name string, params ...interface{}) (result *VARIANT) { id, err := d.GetId(name) if err != nil { panic(err) } result, err = d.Invoke(id, DISPATCH_PROPERTYGET, params) if err != nil { panic(err) } return } // Set property on IDispatch with parameters. func (d *Dispatch) Set(name string, params ...interface{}) (result *VARIANT, err error) { id, err := d.GetId(name) if err != nil { return } result, err = d.Invoke(id, DISPATCH_PROPERTYPUT, params) return } // MustSet property on IDispatch with parameters. func (d *Dispatch) MustSet(name string, params ...interface{}) (result *VARIANT) { id, err := d.GetId(name) if err != nil { panic(err) } result, err = d.Invoke(id, DISPATCH_PROPERTYPUT, params) if err != nil { panic(err) } return } // GetId retrieves ID of name on IDispatch. func (d *Dispatch) GetId(name string) (id int32, err error) { var dispid []int32 dispid, err = d.Object.GetIDsOfName([]string{name}) if err != nil { return } id = dispid[0] return } // GetIds retrieves all IDs of names on IDispatch. func (d *Dispatch) GetIds(names ...string) (dispid []int32, err error) { dispid, err = d.Object.GetIDsOfName(names) return } // Invoke IDispatch on DisplayID of dispatch type with parameters. // // There have been problems where if send cascading params..., it would error // out because the parameters would be empty. func (d *Dispatch) Invoke(id int32, dispatch int16, params []interface{}) (result *VARIANT, err error) { if len(params) < 1 { result, err = d.Object.Invoke(id, dispatch) } else { result, err = d.Object.Invoke(id, dispatch, params...) } return } // Release IDispatch object. func (d *Dispatch) Release() { d.Object.Release() } // Connect initializes COM and attempts to load IUnknown based on given names. func Connect(names ...string) (connection *Connection) { connection.Initialize() connection.Load(names...) return } ================================================ FILE: vendor/github.com/go-ole/go-ole/constants.go ================================================ package ole const ( CLSCTX_INPROC_SERVER = 1 CLSCTX_INPROC_HANDLER = 2 CLSCTX_LOCAL_SERVER = 4 CLSCTX_INPROC_SERVER16 = 8 CLSCTX_REMOTE_SERVER = 16 CLSCTX_ALL = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER CLSCTX_INPROC = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER CLSCTX_SERVER = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER ) const ( COINIT_APARTMENTTHREADED = 0x2 COINIT_MULTITHREADED = 0x0 COINIT_DISABLE_OLE1DDE = 0x4 COINIT_SPEED_OVER_MEMORY = 0x8 ) const ( DISPATCH_METHOD = 1 DISPATCH_PROPERTYGET = 2 DISPATCH_PROPERTYPUT = 4 DISPATCH_PROPERTYPUTREF = 8 ) const ( S_OK = 0x00000000 E_UNEXPECTED = 0x8000FFFF E_NOTIMPL = 0x80004001 E_OUTOFMEMORY = 0x8007000E E_INVALIDARG = 0x80070057 E_NOINTERFACE = 0x80004002 E_POINTER = 0x80004003 E_HANDLE = 0x80070006 E_ABORT = 0x80004004 E_FAIL = 0x80004005 E_ACCESSDENIED = 0x80070005 E_PENDING = 0x8000000A CO_E_CLASSSTRING = 0x800401F3 ) const ( CC_FASTCALL = iota CC_CDECL CC_MSCPASCAL CC_PASCAL = CC_MSCPASCAL CC_MACPASCAL CC_STDCALL CC_FPFASTCALL CC_SYSCALL CC_MPWCDECL CC_MPWPASCAL CC_MAX = CC_MPWPASCAL ) type VT uint16 const ( VT_EMPTY VT = 0x0 VT_NULL VT = 0x1 VT_I2 VT = 0x2 VT_I4 VT = 0x3 VT_R4 VT = 0x4 VT_R8 VT = 0x5 VT_CY VT = 0x6 VT_DATE VT = 0x7 VT_BSTR VT = 0x8 VT_DISPATCH VT = 0x9 VT_ERROR VT = 0xa VT_BOOL VT = 0xb VT_VARIANT VT = 0xc VT_UNKNOWN VT = 0xd VT_DECIMAL VT = 0xe VT_I1 VT = 0x10 VT_UI1 VT = 0x11 VT_UI2 VT = 0x12 VT_UI4 VT = 0x13 VT_I8 VT = 0x14 VT_UI8 VT = 0x15 VT_INT VT = 0x16 VT_UINT VT = 0x17 VT_VOID VT = 0x18 VT_HRESULT VT = 0x19 VT_PTR VT = 0x1a VT_SAFEARRAY VT = 0x1b VT_CARRAY VT = 0x1c VT_USERDEFINED VT = 0x1d VT_LPSTR VT = 0x1e VT_LPWSTR VT = 0x1f VT_RECORD VT = 0x24 VT_INT_PTR VT = 0x25 VT_UINT_PTR VT = 0x26 VT_FILETIME VT = 0x40 VT_BLOB VT = 0x41 VT_STREAM VT = 0x42 VT_STORAGE VT = 0x43 VT_STREAMED_OBJECT VT = 0x44 VT_STORED_OBJECT VT = 0x45 VT_BLOB_OBJECT VT = 0x46 VT_CF VT = 0x47 VT_CLSID VT = 0x48 VT_BSTR_BLOB VT = 0xfff VT_VECTOR VT = 0x1000 VT_ARRAY VT = 0x2000 VT_BYREF VT = 0x4000 VT_RESERVED VT = 0x8000 VT_ILLEGAL VT = 0xffff VT_ILLEGALMASKED VT = 0xfff VT_TYPEMASK VT = 0xfff ) const ( DISPID_UNKNOWN = -1 DISPID_VALUE = 0 DISPID_PROPERTYPUT = -3 DISPID_NEWENUM = -4 DISPID_EVALUATE = -5 DISPID_CONSTRUCTOR = -6 DISPID_DESTRUCTOR = -7 DISPID_COLLECT = -8 ) const ( TKIND_ENUM = 1 TKIND_RECORD = 2 TKIND_MODULE = 3 TKIND_INTERFACE = 4 TKIND_DISPATCH = 5 TKIND_COCLASS = 6 TKIND_ALIAS = 7 TKIND_UNION = 8 TKIND_MAX = 9 ) // Safe Array Feature Flags const ( FADF_AUTO = 0x0001 FADF_STATIC = 0x0002 FADF_EMBEDDED = 0x0004 FADF_FIXEDSIZE = 0x0010 FADF_RECORD = 0x0020 FADF_HAVEIID = 0x0040 FADF_HAVEVARTYPE = 0x0080 FADF_BSTR = 0x0100 FADF_UNKNOWN = 0x0200 FADF_DISPATCH = 0x0400 FADF_VARIANT = 0x0800 FADF_RESERVED = 0xF008 ) ================================================ FILE: vendor/github.com/go-ole/go-ole/error.go ================================================ package ole // OleError stores COM errors. type OleError struct { hr uintptr description string subError error } // NewError creates new error with HResult. func NewError(hr uintptr) *OleError { return &OleError{hr: hr} } // NewErrorWithDescription creates new COM error with HResult and description. func NewErrorWithDescription(hr uintptr, description string) *OleError { return &OleError{hr: hr, description: description} } // NewErrorWithSubError creates new COM error with parent error. func NewErrorWithSubError(hr uintptr, description string, err error) *OleError { return &OleError{hr: hr, description: description, subError: err} } // Code is the HResult. func (v *OleError) Code() uintptr { return uintptr(v.hr) } // String description, either manually set or format message with error code. func (v *OleError) String() string { if v.description != "" { return errstr(int(v.hr)) + " (" + v.description + ")" } return errstr(int(v.hr)) } // Error implements error interface. func (v *OleError) Error() string { return v.String() } // Description retrieves error summary, if there is one. func (v *OleError) Description() string { return v.description } // SubError returns parent error, if there is one. func (v *OleError) SubError() error { return v.subError } ================================================ FILE: vendor/github.com/go-ole/go-ole/error_func.go ================================================ // +build !windows package ole // errstr converts error code to string. func errstr(errno int) string { return "" } ================================================ FILE: vendor/github.com/go-ole/go-ole/error_windows.go ================================================ // +build windows package ole import ( "fmt" "syscall" "unicode/utf16" ) // errstr converts error code to string. func errstr(errno int) string { // ask windows for the remaining errors var flags uint32 = syscall.FORMAT_MESSAGE_FROM_SYSTEM | syscall.FORMAT_MESSAGE_ARGUMENT_ARRAY | syscall.FORMAT_MESSAGE_IGNORE_INSERTS b := make([]uint16, 300) n, err := syscall.FormatMessage(flags, 0, uint32(errno), 0, b, nil) if err != nil { return fmt.Sprintf("error %d (FormatMessage failed with: %v)", errno, err) } // trim terminating \r and \n for ; n > 0 && (b[n-1] == '\n' || b[n-1] == '\r'); n-- { } return string(utf16.Decode(b[:n])) } ================================================ FILE: vendor/github.com/go-ole/go-ole/guid.go ================================================ package ole var ( // IID_NULL is null Interface ID, used when no other Interface ID is known. IID_NULL = NewGUID("{00000000-0000-0000-0000-000000000000}") // IID_IUnknown is for IUnknown interfaces. IID_IUnknown = NewGUID("{00000000-0000-0000-C000-000000000046}") // IID_IDispatch is for IDispatch interfaces. IID_IDispatch = NewGUID("{00020400-0000-0000-C000-000000000046}") // IID_IEnumVariant is for IEnumVariant interfaces IID_IEnumVariant = NewGUID("{00020404-0000-0000-C000-000000000046}") // IID_IConnectionPointContainer is for IConnectionPointContainer interfaces. IID_IConnectionPointContainer = NewGUID("{B196B284-BAB4-101A-B69C-00AA00341D07}") // IID_IConnectionPoint is for IConnectionPoint interfaces. IID_IConnectionPoint = NewGUID("{B196B286-BAB4-101A-B69C-00AA00341D07}") // IID_IInspectable is for IInspectable interfaces. IID_IInspectable = NewGUID("{AF86E2E0-B12D-4C6A-9C5A-D7AA65101E90}") // IID_IProvideClassInfo is for IProvideClassInfo interfaces. IID_IProvideClassInfo = NewGUID("{B196B283-BAB4-101A-B69C-00AA00341D07}") ) // These are for testing and not part of any library. var ( // IID_ICOMTestString is for ICOMTestString interfaces. // // {E0133EB4-C36F-469A-9D3D-C66B84BE19ED} IID_ICOMTestString = NewGUID("{E0133EB4-C36F-469A-9D3D-C66B84BE19ED}") // IID_ICOMTestInt8 is for ICOMTestInt8 interfaces. // // {BEB06610-EB84-4155-AF58-E2BFF53680B4} IID_ICOMTestInt8 = NewGUID("{BEB06610-EB84-4155-AF58-E2BFF53680B4}") // IID_ICOMTestInt16 is for ICOMTestInt16 interfaces. // // {DAA3F9FA-761E-4976-A860-8364CE55F6FC} IID_ICOMTestInt16 = NewGUID("{DAA3F9FA-761E-4976-A860-8364CE55F6FC}") // IID_ICOMTestInt32 is for ICOMTestInt32 interfaces. // // {E3DEDEE7-38A2-4540-91D1-2EEF1D8891B0} IID_ICOMTestInt32 = NewGUID("{E3DEDEE7-38A2-4540-91D1-2EEF1D8891B0}") // IID_ICOMTestInt64 is for ICOMTestInt64 interfaces. // // {8D437CBC-B3ED-485C-BC32-C336432A1623} IID_ICOMTestInt64 = NewGUID("{8D437CBC-B3ED-485C-BC32-C336432A1623}") // IID_ICOMTestFloat is for ICOMTestFloat interfaces. // // {BF1ED004-EA02-456A-AA55-2AC8AC6B054C} IID_ICOMTestFloat = NewGUID("{BF1ED004-EA02-456A-AA55-2AC8AC6B054C}") // IID_ICOMTestDouble is for ICOMTestDouble interfaces. // // {BF908A81-8687-4E93-999F-D86FAB284BA0} IID_ICOMTestDouble = NewGUID("{BF908A81-8687-4E93-999F-D86FAB284BA0}") // IID_ICOMTestBoolean is for ICOMTestBoolean interfaces. // // {D530E7A6-4EE8-40D1-8931-3D63B8605010} IID_ICOMTestBoolean = NewGUID("{D530E7A6-4EE8-40D1-8931-3D63B8605010}") // IID_ICOMEchoTestObject is for ICOMEchoTestObject interfaces. // // {6485B1EF-D780-4834-A4FE-1EBB51746CA3} IID_ICOMEchoTestObject = NewGUID("{6485B1EF-D780-4834-A4FE-1EBB51746CA3}") // IID_ICOMTestTypes is for ICOMTestTypes interfaces. // // {CCA8D7AE-91C0-4277-A8B3-FF4EDF28D3C0} IID_ICOMTestTypes = NewGUID("{CCA8D7AE-91C0-4277-A8B3-FF4EDF28D3C0}") // CLSID_COMEchoTestObject is for COMEchoTestObject class. // // {3C24506A-AE9E-4D50-9157-EF317281F1B0} CLSID_COMEchoTestObject = NewGUID("{3C24506A-AE9E-4D50-9157-EF317281F1B0}") // CLSID_COMTestScalarClass is for COMTestScalarClass class. // // {865B85C5-0334-4AC6-9EF6-AACEC8FC5E86} CLSID_COMTestScalarClass = NewGUID("{865B85C5-0334-4AC6-9EF6-AACEC8FC5E86}") ) const hextable = "0123456789ABCDEF" const emptyGUID = "{00000000-0000-0000-0000-000000000000}" // GUID is Windows API specific GUID type. // // This exists to match Windows GUID type for direct passing for COM. // Format is in xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx. type GUID struct { Data1 uint32 Data2 uint16 Data3 uint16 Data4 [8]byte } // NewGUID converts the given string into a globally unique identifier that is // compliant with the Windows API. // // The supplied string may be in any of these formats: // // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX // XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX // {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} // // The conversion of the supplied string is not case-sensitive. func NewGUID(guid string) *GUID { d := []byte(guid) var d1, d2, d3, d4a, d4b []byte switch len(d) { case 38: if d[0] != '{' || d[37] != '}' { return nil } d = d[1:37] fallthrough case 36: if d[8] != '-' || d[13] != '-' || d[18] != '-' || d[23] != '-' { return nil } d1 = d[0:8] d2 = d[9:13] d3 = d[14:18] d4a = d[19:23] d4b = d[24:36] case 32: d1 = d[0:8] d2 = d[8:12] d3 = d[12:16] d4a = d[16:20] d4b = d[20:32] default: return nil } var g GUID var ok1, ok2, ok3, ok4 bool g.Data1, ok1 = decodeHexUint32(d1) g.Data2, ok2 = decodeHexUint16(d2) g.Data3, ok3 = decodeHexUint16(d3) g.Data4, ok4 = decodeHexByte64(d4a, d4b) if ok1 && ok2 && ok3 && ok4 { return &g } return nil } func decodeHexUint32(src []byte) (value uint32, ok bool) { var b1, b2, b3, b4 byte var ok1, ok2, ok3, ok4 bool b1, ok1 = decodeHexByte(src[0], src[1]) b2, ok2 = decodeHexByte(src[2], src[3]) b3, ok3 = decodeHexByte(src[4], src[5]) b4, ok4 = decodeHexByte(src[6], src[7]) value = (uint32(b1) << 24) | (uint32(b2) << 16) | (uint32(b3) << 8) | uint32(b4) ok = ok1 && ok2 && ok3 && ok4 return } func decodeHexUint16(src []byte) (value uint16, ok bool) { var b1, b2 byte var ok1, ok2 bool b1, ok1 = decodeHexByte(src[0], src[1]) b2, ok2 = decodeHexByte(src[2], src[3]) value = (uint16(b1) << 8) | uint16(b2) ok = ok1 && ok2 return } func decodeHexByte64(s1 []byte, s2 []byte) (value [8]byte, ok bool) { var ok1, ok2, ok3, ok4, ok5, ok6, ok7, ok8 bool value[0], ok1 = decodeHexByte(s1[0], s1[1]) value[1], ok2 = decodeHexByte(s1[2], s1[3]) value[2], ok3 = decodeHexByte(s2[0], s2[1]) value[3], ok4 = decodeHexByte(s2[2], s2[3]) value[4], ok5 = decodeHexByte(s2[4], s2[5]) value[5], ok6 = decodeHexByte(s2[6], s2[7]) value[6], ok7 = decodeHexByte(s2[8], s2[9]) value[7], ok8 = decodeHexByte(s2[10], s2[11]) ok = ok1 && ok2 && ok3 && ok4 && ok5 && ok6 && ok7 && ok8 return } func decodeHexByte(c1, c2 byte) (value byte, ok bool) { var n1, n2 byte var ok1, ok2 bool n1, ok1 = decodeHexChar(c1) n2, ok2 = decodeHexChar(c2) value = (n1 << 4) | n2 ok = ok1 && ok2 return } func decodeHexChar(c byte) (byte, bool) { switch { case '0' <= c && c <= '9': return c - '0', true case 'a' <= c && c <= 'f': return c - 'a' + 10, true case 'A' <= c && c <= 'F': return c - 'A' + 10, true } return 0, false } // String converts the GUID to string form. It will adhere to this pattern: // // {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} // // If the GUID is nil, the string representation of an empty GUID is returned: // // {00000000-0000-0000-0000-000000000000} func (guid *GUID) String() string { if guid == nil { return emptyGUID } var c [38]byte c[0] = '{' putUint32Hex(c[1:9], guid.Data1) c[9] = '-' putUint16Hex(c[10:14], guid.Data2) c[14] = '-' putUint16Hex(c[15:19], guid.Data3) c[19] = '-' putByteHex(c[20:24], guid.Data4[0:2]) c[24] = '-' putByteHex(c[25:37], guid.Data4[2:8]) c[37] = '}' return string(c[:]) } func putUint32Hex(b []byte, v uint32) { b[0] = hextable[byte(v>>24)>>4] b[1] = hextable[byte(v>>24)&0x0f] b[2] = hextable[byte(v>>16)>>4] b[3] = hextable[byte(v>>16)&0x0f] b[4] = hextable[byte(v>>8)>>4] b[5] = hextable[byte(v>>8)&0x0f] b[6] = hextable[byte(v)>>4] b[7] = hextable[byte(v)&0x0f] } func putUint16Hex(b []byte, v uint16) { b[0] = hextable[byte(v>>8)>>4] b[1] = hextable[byte(v>>8)&0x0f] b[2] = hextable[byte(v)>>4] b[3] = hextable[byte(v)&0x0f] } func putByteHex(dst, src []byte) { for i := 0; i < len(src); i++ { dst[i*2] = hextable[src[i]>>4] dst[i*2+1] = hextable[src[i]&0x0f] } } // IsEqualGUID compares two GUID. // // Not constant time comparison. func IsEqualGUID(guid1 *GUID, guid2 *GUID) bool { return guid1.Data1 == guid2.Data1 && guid1.Data2 == guid2.Data2 && guid1.Data3 == guid2.Data3 && guid1.Data4[0] == guid2.Data4[0] && guid1.Data4[1] == guid2.Data4[1] && guid1.Data4[2] == guid2.Data4[2] && guid1.Data4[3] == guid2.Data4[3] && guid1.Data4[4] == guid2.Data4[4] && guid1.Data4[5] == guid2.Data4[5] && guid1.Data4[6] == guid2.Data4[6] && guid1.Data4[7] == guid2.Data4[7] } ================================================ FILE: vendor/github.com/go-ole/go-ole/iconnectionpoint.go ================================================ package ole import "unsafe" type IConnectionPoint struct { IUnknown } type IConnectionPointVtbl struct { IUnknownVtbl GetConnectionInterface uintptr GetConnectionPointContainer uintptr Advise uintptr Unadvise uintptr EnumConnections uintptr } func (v *IConnectionPoint) VTable() *IConnectionPointVtbl { return (*IConnectionPointVtbl)(unsafe.Pointer(v.RawVTable)) } ================================================ FILE: vendor/github.com/go-ole/go-ole/iconnectionpoint_func.go ================================================ // +build !windows package ole import "unsafe" func (v *IConnectionPoint) GetConnectionInterface(piid **GUID) int32 { return int32(0) } func (v *IConnectionPoint) Advise(unknown *IUnknown) (uint32, error) { return uint32(0), NewError(E_NOTIMPL) } func (v *IConnectionPoint) Unadvise(cookie uint32) error { return NewError(E_NOTIMPL) } func (v *IConnectionPoint) EnumConnections(p *unsafe.Pointer) (err error) { return NewError(E_NOTIMPL) } ================================================ FILE: vendor/github.com/go-ole/go-ole/iconnectionpoint_windows.go ================================================ // +build windows package ole import ( "syscall" "unsafe" ) func (v *IConnectionPoint) GetConnectionInterface(piid **GUID) int32 { // XXX: This doesn't look like it does what it's supposed to return release((*IUnknown)(unsafe.Pointer(v))) } func (v *IConnectionPoint) Advise(unknown *IUnknown) (cookie uint32, err error) { hr, _, _ := syscall.Syscall( v.VTable().Advise, 3, uintptr(unsafe.Pointer(v)), uintptr(unsafe.Pointer(unknown)), uintptr(unsafe.Pointer(&cookie))) if hr != 0 { err = NewError(hr) } return } func (v *IConnectionPoint) Unadvise(cookie uint32) (err error) { hr, _, _ := syscall.Syscall( v.VTable().Unadvise, 2, uintptr(unsafe.Pointer(v)), uintptr(cookie), 0) if hr != 0 { err = NewError(hr) } return } func (v *IConnectionPoint) EnumConnections(p *unsafe.Pointer) error { return NewError(E_NOTIMPL) } ================================================ FILE: vendor/github.com/go-ole/go-ole/iconnectionpointcontainer.go ================================================ package ole import "unsafe" type IConnectionPointContainer struct { IUnknown } type IConnectionPointContainerVtbl struct { IUnknownVtbl EnumConnectionPoints uintptr FindConnectionPoint uintptr } func (v *IConnectionPointContainer) VTable() *IConnectionPointContainerVtbl { return (*IConnectionPointContainerVtbl)(unsafe.Pointer(v.RawVTable)) } ================================================ FILE: vendor/github.com/go-ole/go-ole/iconnectionpointcontainer_func.go ================================================ // +build !windows package ole func (v *IConnectionPointContainer) EnumConnectionPoints(points interface{}) error { return NewError(E_NOTIMPL) } func (v *IConnectionPointContainer) FindConnectionPoint(iid *GUID, point **IConnectionPoint) error { return NewError(E_NOTIMPL) } ================================================ FILE: vendor/github.com/go-ole/go-ole/iconnectionpointcontainer_windows.go ================================================ // +build windows package ole import ( "syscall" "unsafe" ) func (v *IConnectionPointContainer) EnumConnectionPoints(points interface{}) error { return NewError(E_NOTIMPL) } func (v *IConnectionPointContainer) FindConnectionPoint(iid *GUID, point **IConnectionPoint) (err error) { hr, _, _ := syscall.Syscall( v.VTable().FindConnectionPoint, 3, uintptr(unsafe.Pointer(v)), uintptr(unsafe.Pointer(iid)), uintptr(unsafe.Pointer(point))) if hr != 0 { err = NewError(hr) } return } ================================================ FILE: vendor/github.com/go-ole/go-ole/idispatch.go ================================================ package ole import "unsafe" type IDispatch struct { IUnknown } type IDispatchVtbl struct { IUnknownVtbl GetTypeInfoCount uintptr GetTypeInfo uintptr GetIDsOfNames uintptr Invoke uintptr } func (v *IDispatch) VTable() *IDispatchVtbl { return (*IDispatchVtbl)(unsafe.Pointer(v.RawVTable)) } func (v *IDispatch) GetIDsOfName(names []string) (dispid []int32, err error) { dispid, err = getIDsOfName(v, names) return } func (v *IDispatch) Invoke(dispid int32, dispatch int16, params ...interface{}) (result *VARIANT, err error) { result, err = invoke(v, dispid, dispatch, params...) return } func (v *IDispatch) GetTypeInfoCount() (c uint32, err error) { c, err = getTypeInfoCount(v) return } func (v *IDispatch) GetTypeInfo() (tinfo *ITypeInfo, err error) { tinfo, err = getTypeInfo(v) return } // GetSingleIDOfName is a helper that returns single display ID for IDispatch name. // // This replaces the common pattern of attempting to get a single name from the list of available // IDs. It gives the first ID, if it is available. func (v *IDispatch) GetSingleIDOfName(name string) (displayID int32, err error) { var displayIDs []int32 displayIDs, err = v.GetIDsOfName([]string{name}) if err != nil { return } displayID = displayIDs[0] return } // InvokeWithOptionalArgs accepts arguments as an array, works like Invoke. // // Accepts name and will attempt to retrieve Display ID to pass to Invoke. // // Passing params as an array is a workaround that could be fixed in later versions of Go that // prevent passing empty params. During testing it was discovered that this is an acceptable way of // getting around not being able to pass params normally. func (v *IDispatch) InvokeWithOptionalArgs(name string, dispatch int16, params []interface{}) (result *VARIANT, err error) { displayID, err := v.GetSingleIDOfName(name) if err != nil { return } if len(params) < 1 { result, err = v.Invoke(displayID, dispatch) } else { result, err = v.Invoke(displayID, dispatch, params...) } return } // CallMethod invokes named function with arguments on object. func (v *IDispatch) CallMethod(name string, params ...interface{}) (*VARIANT, error) { return v.InvokeWithOptionalArgs(name, DISPATCH_METHOD, params) } // GetProperty retrieves the property with the name with the ability to pass arguments. // // Most of the time you will not need to pass arguments as most objects do not allow for this // feature. Or at least, should not allow for this feature. Some servers don't follow best practices // and this is provided for those edge cases. func (v *IDispatch) GetProperty(name string, params ...interface{}) (*VARIANT, error) { return v.InvokeWithOptionalArgs(name, DISPATCH_PROPERTYGET, params) } // PutProperty attempts to mutate a property in the object. func (v *IDispatch) PutProperty(name string, params ...interface{}) (*VARIANT, error) { return v.InvokeWithOptionalArgs(name, DISPATCH_PROPERTYPUT, params) } ================================================ FILE: vendor/github.com/go-ole/go-ole/idispatch_func.go ================================================ // +build !windows package ole func getIDsOfName(disp *IDispatch, names []string) ([]int32, error) { return []int32{}, NewError(E_NOTIMPL) } func getTypeInfoCount(disp *IDispatch) (uint32, error) { return uint32(0), NewError(E_NOTIMPL) } func getTypeInfo(disp *IDispatch) (*ITypeInfo, error) { return nil, NewError(E_NOTIMPL) } func invoke(disp *IDispatch, dispid int32, dispatch int16, params ...interface{}) (*VARIANT, error) { return nil, NewError(E_NOTIMPL) } ================================================ FILE: vendor/github.com/go-ole/go-ole/idispatch_windows.go ================================================ // +build windows package ole import ( "math/big" "syscall" "time" "unsafe" ) func getIDsOfName(disp *IDispatch, names []string) (dispid []int32, err error) { wnames := make([]*uint16, len(names)) for i := 0; i < len(names); i++ { wnames[i] = syscall.StringToUTF16Ptr(names[i]) } dispid = make([]int32, len(names)) namelen := uint32(len(names)) hr, _, _ := syscall.Syscall6( disp.VTable().GetIDsOfNames, 6, uintptr(unsafe.Pointer(disp)), uintptr(unsafe.Pointer(IID_NULL)), uintptr(unsafe.Pointer(&wnames[0])), uintptr(namelen), uintptr(GetUserDefaultLCID()), uintptr(unsafe.Pointer(&dispid[0]))) if hr != 0 { err = NewError(hr) } return } func getTypeInfoCount(disp *IDispatch) (c uint32, err error) { hr, _, _ := syscall.Syscall( disp.VTable().GetTypeInfoCount, 2, uintptr(unsafe.Pointer(disp)), uintptr(unsafe.Pointer(&c)), 0) if hr != 0 { err = NewError(hr) } return } func getTypeInfo(disp *IDispatch) (tinfo *ITypeInfo, err error) { hr, _, _ := syscall.Syscall( disp.VTable().GetTypeInfo, 3, uintptr(unsafe.Pointer(disp)), uintptr(GetUserDefaultLCID()), uintptr(unsafe.Pointer(&tinfo))) if hr != 0 { err = NewError(hr) } return } func invoke(disp *IDispatch, dispid int32, dispatch int16, params ...interface{}) (result *VARIANT, err error) { var dispparams DISPPARAMS if dispatch&DISPATCH_PROPERTYPUT != 0 { dispnames := [1]int32{DISPID_PROPERTYPUT} dispparams.rgdispidNamedArgs = uintptr(unsafe.Pointer(&dispnames[0])) dispparams.cNamedArgs = 1 } else if dispatch&DISPATCH_PROPERTYPUTREF != 0 { dispnames := [1]int32{DISPID_PROPERTYPUT} dispparams.rgdispidNamedArgs = uintptr(unsafe.Pointer(&dispnames[0])) dispparams.cNamedArgs = 1 } var vargs []VARIANT if len(params) > 0 { vargs = make([]VARIANT, len(params)) for i, v := range params { //n := len(params)-i-1 n := len(params) - i - 1 VariantInit(&vargs[n]) switch vv := v.(type) { case bool: if vv { vargs[n] = NewVariant(VT_BOOL, 0xffff) } else { vargs[n] = NewVariant(VT_BOOL, 0) } case *bool: vargs[n] = NewVariant(VT_BOOL|VT_BYREF, int64(uintptr(unsafe.Pointer(v.(*bool))))) case uint8: vargs[n] = NewVariant(VT_I1, int64(v.(uint8))) case *uint8: vargs[n] = NewVariant(VT_I1|VT_BYREF, int64(uintptr(unsafe.Pointer(v.(*uint8))))) case int8: vargs[n] = NewVariant(VT_I1, int64(v.(int8))) case *int8: vargs[n] = NewVariant(VT_I1|VT_BYREF, int64(uintptr(unsafe.Pointer(v.(*uint8))))) case int16: vargs[n] = NewVariant(VT_I2, int64(v.(int16))) case *int16: vargs[n] = NewVariant(VT_I2|VT_BYREF, int64(uintptr(unsafe.Pointer(v.(*int16))))) case uint16: vargs[n] = NewVariant(VT_UI2, int64(v.(uint16))) case *uint16: vargs[n] = NewVariant(VT_UI2|VT_BYREF, int64(uintptr(unsafe.Pointer(v.(*uint16))))) case int32: vargs[n] = NewVariant(VT_I4, int64(v.(int32))) case *int32: vargs[n] = NewVariant(VT_I4|VT_BYREF, int64(uintptr(unsafe.Pointer(v.(*int32))))) case uint32: vargs[n] = NewVariant(VT_UI4, int64(v.(uint32))) case *uint32: vargs[n] = NewVariant(VT_UI4|VT_BYREF, int64(uintptr(unsafe.Pointer(v.(*uint32))))) case int64: vargs[n] = NewVariant(VT_I8, int64(v.(int64))) case *int64: vargs[n] = NewVariant(VT_I8|VT_BYREF, int64(uintptr(unsafe.Pointer(v.(*int64))))) case uint64: vargs[n] = NewVariant(VT_UI8, int64(uintptr(v.(uint64)))) case *uint64: vargs[n] = NewVariant(VT_UI8|VT_BYREF, int64(uintptr(unsafe.Pointer(v.(*uint64))))) case int: vargs[n] = NewVariant(VT_I4, int64(v.(int))) case *int: vargs[n] = NewVariant(VT_I4|VT_BYREF, int64(uintptr(unsafe.Pointer(v.(*int))))) case uint: vargs[n] = NewVariant(VT_UI4, int64(v.(uint))) case *uint: vargs[n] = NewVariant(VT_UI4|VT_BYREF, int64(uintptr(unsafe.Pointer(v.(*uint))))) case float32: vargs[n] = NewVariant(VT_R4, *(*int64)(unsafe.Pointer(&vv))) case *float32: vargs[n] = NewVariant(VT_R4|VT_BYREF, int64(uintptr(unsafe.Pointer(v.(*float32))))) case float64: vargs[n] = NewVariant(VT_R8, *(*int64)(unsafe.Pointer(&vv))) case *float64: vargs[n] = NewVariant(VT_R8|VT_BYREF, int64(uintptr(unsafe.Pointer(v.(*float64))))) case *big.Int: vargs[n] = NewVariant(VT_DECIMAL, v.(*big.Int).Int64()) case string: vargs[n] = NewVariant(VT_BSTR, int64(uintptr(unsafe.Pointer(SysAllocStringLen(v.(string)))))) case *string: vargs[n] = NewVariant(VT_BSTR|VT_BYREF, int64(uintptr(unsafe.Pointer(v.(*string))))) case time.Time: s := vv.Format("2006-01-02 15:04:05") vargs[n] = NewVariant(VT_BSTR, int64(uintptr(unsafe.Pointer(SysAllocStringLen(s))))) case *time.Time: s := vv.Format("2006-01-02 15:04:05") vargs[n] = NewVariant(VT_BSTR|VT_BYREF, int64(uintptr(unsafe.Pointer(&s)))) case *IDispatch: vargs[n] = NewVariant(VT_DISPATCH, int64(uintptr(unsafe.Pointer(v.(*IDispatch))))) case **IDispatch: vargs[n] = NewVariant(VT_DISPATCH|VT_BYREF, int64(uintptr(unsafe.Pointer(v.(**IDispatch))))) case nil: vargs[n] = NewVariant(VT_NULL, 0) case *VARIANT: vargs[n] = NewVariant(VT_VARIANT|VT_BYREF, int64(uintptr(unsafe.Pointer(v.(*VARIANT))))) case []byte: safeByteArray := safeArrayFromByteSlice(v.([]byte)) vargs[n] = NewVariant(VT_ARRAY|VT_UI1, int64(uintptr(unsafe.Pointer(safeByteArray)))) defer VariantClear(&vargs[n]) case []string: safeByteArray := safeArrayFromStringSlice(v.([]string)) vargs[n] = NewVariant(VT_ARRAY|VT_BSTR, int64(uintptr(unsafe.Pointer(safeByteArray)))) defer VariantClear(&vargs[n]) default: panic("unknown type") } } dispparams.rgvarg = uintptr(unsafe.Pointer(&vargs[0])) dispparams.cArgs = uint32(len(params)) } result = new(VARIANT) var excepInfo EXCEPINFO VariantInit(result) hr, _, _ := syscall.Syscall9( disp.VTable().Invoke, 9, uintptr(unsafe.Pointer(disp)), uintptr(dispid), uintptr(unsafe.Pointer(IID_NULL)), uintptr(GetUserDefaultLCID()), uintptr(dispatch), uintptr(unsafe.Pointer(&dispparams)), uintptr(unsafe.Pointer(result)), uintptr(unsafe.Pointer(&excepInfo)), 0) if hr != 0 { excepInfo.renderStrings() excepInfo.Clear() err = NewErrorWithSubError(hr, excepInfo.description, excepInfo) } for i, varg := range vargs { n := len(params) - i - 1 if varg.VT == VT_BSTR && varg.Val != 0 { SysFreeString(((*int16)(unsafe.Pointer(uintptr(varg.Val))))) } if varg.VT == (VT_BSTR|VT_BYREF) && varg.Val != 0 { *(params[n].(*string)) = LpOleStrToString(*(**uint16)(unsafe.Pointer(uintptr(varg.Val)))) } } return } ================================================ FILE: vendor/github.com/go-ole/go-ole/ienumvariant.go ================================================ package ole import "unsafe" type IEnumVARIANT struct { IUnknown } type IEnumVARIANTVtbl struct { IUnknownVtbl Next uintptr Skip uintptr Reset uintptr Clone uintptr } func (v *IEnumVARIANT) VTable() *IEnumVARIANTVtbl { return (*IEnumVARIANTVtbl)(unsafe.Pointer(v.RawVTable)) } ================================================ FILE: vendor/github.com/go-ole/go-ole/ienumvariant_func.go ================================================ // +build !windows package ole func (enum *IEnumVARIANT) Clone() (*IEnumVARIANT, error) { return nil, NewError(E_NOTIMPL) } func (enum *IEnumVARIANT) Reset() error { return NewError(E_NOTIMPL) } func (enum *IEnumVARIANT) Skip(celt uint) error { return NewError(E_NOTIMPL) } func (enum *IEnumVARIANT) Next(celt uint) (VARIANT, uint, error) { return NewVariant(VT_NULL, int64(0)), 0, NewError(E_NOTIMPL) } ================================================ FILE: vendor/github.com/go-ole/go-ole/ienumvariant_windows.go ================================================ // +build windows package ole import ( "syscall" "unsafe" ) func (enum *IEnumVARIANT) Clone() (cloned *IEnumVARIANT, err error) { hr, _, _ := syscall.Syscall( enum.VTable().Clone, 2, uintptr(unsafe.Pointer(enum)), uintptr(unsafe.Pointer(&cloned)), 0) if hr != 0 { err = NewError(hr) } return } func (enum *IEnumVARIANT) Reset() (err error) { hr, _, _ := syscall.Syscall( enum.VTable().Reset, 1, uintptr(unsafe.Pointer(enum)), 0, 0) if hr != 0 { err = NewError(hr) } return } func (enum *IEnumVARIANT) Skip(celt uint) (err error) { hr, _, _ := syscall.Syscall( enum.VTable().Skip, 2, uintptr(unsafe.Pointer(enum)), uintptr(celt), 0) if hr != 0 { err = NewError(hr) } return } func (enum *IEnumVARIANT) Next(celt uint) (array VARIANT, length uint, err error) { hr, _, _ := syscall.Syscall6( enum.VTable().Next, 4, uintptr(unsafe.Pointer(enum)), uintptr(celt), uintptr(unsafe.Pointer(&array)), uintptr(unsafe.Pointer(&length)), 0, 0) if hr != 0 { err = NewError(hr) } return } ================================================ FILE: vendor/github.com/go-ole/go-ole/iinspectable.go ================================================ package ole import "unsafe" type IInspectable struct { IUnknown } type IInspectableVtbl struct { IUnknownVtbl GetIIds uintptr GetRuntimeClassName uintptr GetTrustLevel uintptr } func (v *IInspectable) VTable() *IInspectableVtbl { return (*IInspectableVtbl)(unsafe.Pointer(v.RawVTable)) } ================================================ FILE: vendor/github.com/go-ole/go-ole/iinspectable_func.go ================================================ // +build !windows package ole func (v *IInspectable) GetIids() ([]*GUID, error) { return []*GUID{}, NewError(E_NOTIMPL) } func (v *IInspectable) GetRuntimeClassName() (string, error) { return "", NewError(E_NOTIMPL) } func (v *IInspectable) GetTrustLevel() (uint32, error) { return uint32(0), NewError(E_NOTIMPL) } ================================================ FILE: vendor/github.com/go-ole/go-ole/iinspectable_windows.go ================================================ // +build windows package ole import ( "bytes" "encoding/binary" "reflect" "syscall" "unsafe" ) func (v *IInspectable) GetIids() (iids []*GUID, err error) { var count uint32 var array uintptr hr, _, _ := syscall.Syscall( v.VTable().GetIIds, 3, uintptr(unsafe.Pointer(v)), uintptr(unsafe.Pointer(&count)), uintptr(unsafe.Pointer(&array))) if hr != 0 { err = NewError(hr) return } defer CoTaskMemFree(array) iids = make([]*GUID, count) byteCount := count * uint32(unsafe.Sizeof(GUID{})) slicehdr := reflect.SliceHeader{Data: array, Len: int(byteCount), Cap: int(byteCount)} byteSlice := *(*[]byte)(unsafe.Pointer(&slicehdr)) reader := bytes.NewReader(byteSlice) for i := range iids { guid := GUID{} err = binary.Read(reader, binary.LittleEndian, &guid) if err != nil { return } iids[i] = &guid } return } func (v *IInspectable) GetRuntimeClassName() (s string, err error) { var hstring HString hr, _, _ := syscall.Syscall( v.VTable().GetRuntimeClassName, 2, uintptr(unsafe.Pointer(v)), uintptr(unsafe.Pointer(&hstring)), 0) if hr != 0 { err = NewError(hr) return } s = hstring.String() DeleteHString(hstring) return } func (v *IInspectable) GetTrustLevel() (level uint32, err error) { hr, _, _ := syscall.Syscall( v.VTable().GetTrustLevel, 2, uintptr(unsafe.Pointer(v)), uintptr(unsafe.Pointer(&level)), 0) if hr != 0 { err = NewError(hr) } return } ================================================ FILE: vendor/github.com/go-ole/go-ole/iprovideclassinfo.go ================================================ package ole import "unsafe" type IProvideClassInfo struct { IUnknown } type IProvideClassInfoVtbl struct { IUnknownVtbl GetClassInfo uintptr } func (v *IProvideClassInfo) VTable() *IProvideClassInfoVtbl { return (*IProvideClassInfoVtbl)(unsafe.Pointer(v.RawVTable)) } func (v *IProvideClassInfo) GetClassInfo() (cinfo *ITypeInfo, err error) { cinfo, err = getClassInfo(v) return } ================================================ FILE: vendor/github.com/go-ole/go-ole/iprovideclassinfo_func.go ================================================ // +build !windows package ole func getClassInfo(disp *IProvideClassInfo) (tinfo *ITypeInfo, err error) { return nil, NewError(E_NOTIMPL) } ================================================ FILE: vendor/github.com/go-ole/go-ole/iprovideclassinfo_windows.go ================================================ // +build windows package ole import ( "syscall" "unsafe" ) func getClassInfo(disp *IProvideClassInfo) (tinfo *ITypeInfo, err error) { hr, _, _ := syscall.Syscall( disp.VTable().GetClassInfo, 2, uintptr(unsafe.Pointer(disp)), uintptr(unsafe.Pointer(&tinfo)), 0) if hr != 0 { err = NewError(hr) } return } ================================================ FILE: vendor/github.com/go-ole/go-ole/itypeinfo.go ================================================ package ole import "unsafe" type ITypeInfo struct { IUnknown } type ITypeInfoVtbl struct { IUnknownVtbl GetTypeAttr uintptr GetTypeComp uintptr GetFuncDesc uintptr GetVarDesc uintptr GetNames uintptr GetRefTypeOfImplType uintptr GetImplTypeFlags uintptr GetIDsOfNames uintptr Invoke uintptr GetDocumentation uintptr GetDllEntry uintptr GetRefTypeInfo uintptr AddressOfMember uintptr CreateInstance uintptr GetMops uintptr GetContainingTypeLib uintptr ReleaseTypeAttr uintptr ReleaseFuncDesc uintptr ReleaseVarDesc uintptr } func (v *ITypeInfo) VTable() *ITypeInfoVtbl { return (*ITypeInfoVtbl)(unsafe.Pointer(v.RawVTable)) } ================================================ FILE: vendor/github.com/go-ole/go-ole/itypeinfo_func.go ================================================ // +build !windows package ole func (v *ITypeInfo) GetTypeAttr() (*TYPEATTR, error) { return nil, NewError(E_NOTIMPL) } ================================================ FILE: vendor/github.com/go-ole/go-ole/itypeinfo_windows.go ================================================ // +build windows package ole import ( "syscall" "unsafe" ) func (v *ITypeInfo) GetTypeAttr() (tattr *TYPEATTR, err error) { hr, _, _ := syscall.Syscall( uintptr(v.VTable().GetTypeAttr), 2, uintptr(unsafe.Pointer(v)), uintptr(unsafe.Pointer(&tattr)), 0) if hr != 0 { err = NewError(hr) } return } ================================================ FILE: vendor/github.com/go-ole/go-ole/iunknown.go ================================================ package ole import "unsafe" type IUnknown struct { RawVTable *interface{} } type IUnknownVtbl struct { QueryInterface uintptr AddRef uintptr Release uintptr } type UnknownLike interface { QueryInterface(iid *GUID) (disp *IDispatch, err error) AddRef() int32 Release() int32 } func (v *IUnknown) VTable() *IUnknownVtbl { return (*IUnknownVtbl)(unsafe.Pointer(v.RawVTable)) } func (v *IUnknown) PutQueryInterface(interfaceID *GUID, obj interface{}) error { return reflectQueryInterface(v, v.VTable().QueryInterface, interfaceID, obj) } func (v *IUnknown) IDispatch(interfaceID *GUID) (dispatch *IDispatch, err error) { err = v.PutQueryInterface(interfaceID, &dispatch) return } func (v *IUnknown) IEnumVARIANT(interfaceID *GUID) (enum *IEnumVARIANT, err error) { err = v.PutQueryInterface(interfaceID, &enum) return } func (v *IUnknown) QueryInterface(iid *GUID) (*IDispatch, error) { return queryInterface(v, iid) } func (v *IUnknown) MustQueryInterface(iid *GUID) (disp *IDispatch) { unk, err := queryInterface(v, iid) if err != nil { panic(err) } return unk } func (v *IUnknown) AddRef() int32 { return addRef(v) } func (v *IUnknown) Release() int32 { return release(v) } ================================================ FILE: vendor/github.com/go-ole/go-ole/iunknown_func.go ================================================ // +build !windows package ole func reflectQueryInterface(self interface{}, method uintptr, interfaceID *GUID, obj interface{}) (err error) { return NewError(E_NOTIMPL) } func queryInterface(unk *IUnknown, iid *GUID) (disp *IDispatch, err error) { return nil, NewError(E_NOTIMPL) } func addRef(unk *IUnknown) int32 { return 0 } func release(unk *IUnknown) int32 { return 0 } ================================================ FILE: vendor/github.com/go-ole/go-ole/iunknown_windows.go ================================================ // +build windows package ole import ( "reflect" "syscall" "unsafe" ) func reflectQueryInterface(self interface{}, method uintptr, interfaceID *GUID, obj interface{}) (err error) { selfValue := reflect.ValueOf(self).Elem() objValue := reflect.ValueOf(obj).Elem() hr, _, _ := syscall.Syscall( method, 3, selfValue.UnsafeAddr(), uintptr(unsafe.Pointer(interfaceID)), objValue.Addr().Pointer()) if hr != 0 { err = NewError(hr) } return } func queryInterface(unk *IUnknown, iid *GUID) (disp *IDispatch, err error) { hr, _, _ := syscall.Syscall( unk.VTable().QueryInterface, 3, uintptr(unsafe.Pointer(unk)), uintptr(unsafe.Pointer(iid)), uintptr(unsafe.Pointer(&disp))) if hr != 0 { err = NewError(hr) } return } func addRef(unk *IUnknown) int32 { ret, _, _ := syscall.Syscall( unk.VTable().AddRef, 1, uintptr(unsafe.Pointer(unk)), 0, 0) return int32(ret) } func release(unk *IUnknown) int32 { ret, _, _ := syscall.Syscall( unk.VTable().Release, 1, uintptr(unsafe.Pointer(unk)), 0, 0) return int32(ret) } ================================================ FILE: vendor/github.com/go-ole/go-ole/ole.go ================================================ package ole import ( "fmt" "strings" "unsafe" ) // DISPPARAMS are the arguments that passed to methods or property. type DISPPARAMS struct { rgvarg uintptr rgdispidNamedArgs uintptr cArgs uint32 cNamedArgs uint32 } // EXCEPINFO defines exception info. type EXCEPINFO struct { wCode uint16 wReserved uint16 bstrSource *uint16 bstrDescription *uint16 bstrHelpFile *uint16 dwHelpContext uint32 pvReserved uintptr pfnDeferredFillIn uintptr scode uint32 // Go-specific part. Don't move upper cos it'll break structure layout for native code. rendered bool source string description string helpFile string } // renderStrings translates BSTR strings to Go ones so `.Error` and `.String` // could be safely called after `.Clear`. We need this when we can't rely on // a caller to call `.Clear`. func (e *EXCEPINFO) renderStrings() { e.rendered = true if e.bstrSource == nil { e.source = "" } else { e.source = BstrToString(e.bstrSource) } if e.bstrDescription == nil { e.description = "" } else { e.description = BstrToString(e.bstrDescription) } if e.bstrHelpFile == nil { e.helpFile = "" } else { e.helpFile = BstrToString(e.bstrHelpFile) } } // Clear frees BSTR strings inside an EXCEPINFO and set it to NULL. func (e *EXCEPINFO) Clear() { freeBSTR := func(s *uint16) { // SysFreeString don't return errors and is safe for call's on NULL. // https://docs.microsoft.com/en-us/windows/win32/api/oleauto/nf-oleauto-sysfreestring _ = SysFreeString((*int16)(unsafe.Pointer(s))) } if e.bstrSource != nil { freeBSTR(e.bstrSource) e.bstrSource = nil } if e.bstrDescription != nil { freeBSTR(e.bstrDescription) e.bstrDescription = nil } if e.bstrHelpFile != nil { freeBSTR(e.bstrHelpFile) e.bstrHelpFile = nil } } // WCode return wCode in EXCEPINFO. func (e EXCEPINFO) WCode() uint16 { return e.wCode } // SCODE return scode in EXCEPINFO. func (e EXCEPINFO) SCODE() uint32 { return e.scode } // String convert EXCEPINFO to string. func (e EXCEPINFO) String() string { if !e.rendered { e.renderStrings() } return fmt.Sprintf( "wCode: %#x, bstrSource: %v, bstrDescription: %v, bstrHelpFile: %v, dwHelpContext: %#x, scode: %#x", e.wCode, e.source, e.description, e.helpFile, e.dwHelpContext, e.scode, ) } // Error implements error interface and returns error string. func (e EXCEPINFO) Error() string { if !e.rendered { e.renderStrings() } if e.description != "" { return strings.TrimSpace(e.description) } code := e.scode if e.wCode != 0 { code = uint32(e.wCode) } return fmt.Sprintf("%v: %#x", e.source, code) } // PARAMDATA defines parameter data type. type PARAMDATA struct { Name *int16 Vt uint16 } // METHODDATA defines method info. type METHODDATA struct { Name *uint16 Data *PARAMDATA Dispid int32 Meth uint32 CC int32 CArgs uint32 Flags uint16 VtReturn uint32 } // INTERFACEDATA defines interface info. type INTERFACEDATA struct { MethodData *METHODDATA CMembers uint32 } // Point is 2D vector type. type Point struct { X int32 Y int32 } // Msg is message between processes. type Msg struct { Hwnd uint32 Message uint32 Wparam int32 Lparam int32 Time uint32 Pt Point } // TYPEDESC defines data type. type TYPEDESC struct { Hreftype uint32 VT uint16 } // IDLDESC defines IDL info. type IDLDESC struct { DwReserved uint32 WIDLFlags uint16 } // TYPEATTR defines type info. type TYPEATTR struct { Guid GUID Lcid uint32 dwReserved uint32 MemidConstructor int32 MemidDestructor int32 LpstrSchema *uint16 CbSizeInstance uint32 Typekind int32 CFuncs uint16 CVars uint16 CImplTypes uint16 CbSizeVft uint16 CbAlignment uint16 WTypeFlags uint16 WMajorVerNum uint16 WMinorVerNum uint16 TdescAlias TYPEDESC IdldescType IDLDESC } ================================================ FILE: vendor/github.com/go-ole/go-ole/oleutil/connection.go ================================================ // +build windows package oleutil import ( "reflect" "unsafe" ole "github.com/go-ole/go-ole" ) type stdDispatch struct { lpVtbl *stdDispatchVtbl ref int32 iid *ole.GUID iface interface{} funcMap map[string]int32 } type stdDispatchVtbl struct { pQueryInterface uintptr pAddRef uintptr pRelease uintptr pGetTypeInfoCount uintptr pGetTypeInfo uintptr pGetIDsOfNames uintptr pInvoke uintptr } func dispQueryInterface(this *ole.IUnknown, iid *ole.GUID, punk **ole.IUnknown) uint32 { pthis := (*stdDispatch)(unsafe.Pointer(this)) *punk = nil if ole.IsEqualGUID(iid, ole.IID_IUnknown) || ole.IsEqualGUID(iid, ole.IID_IDispatch) { dispAddRef(this) *punk = this return ole.S_OK } if ole.IsEqualGUID(iid, pthis.iid) { dispAddRef(this) *punk = this return ole.S_OK } return ole.E_NOINTERFACE } func dispAddRef(this *ole.IUnknown) int32 { pthis := (*stdDispatch)(unsafe.Pointer(this)) pthis.ref++ return pthis.ref } func dispRelease(this *ole.IUnknown) int32 { pthis := (*stdDispatch)(unsafe.Pointer(this)) pthis.ref-- return pthis.ref } func dispGetIDsOfNames(this *ole.IUnknown, iid *ole.GUID, wnames []*uint16, namelen int, lcid int, pdisp []int32) uintptr { pthis := (*stdDispatch)(unsafe.Pointer(this)) names := make([]string, len(wnames)) for i := 0; i < len(names); i++ { names[i] = ole.LpOleStrToString(wnames[i]) } for n := 0; n < namelen; n++ { if id, ok := pthis.funcMap[names[n]]; ok { pdisp[n] = id } } return ole.S_OK } func dispGetTypeInfoCount(pcount *int) uintptr { if pcount != nil { *pcount = 0 } return ole.S_OK } func dispGetTypeInfo(ptypeif *uintptr) uintptr { return ole.E_NOTIMPL } func dispInvoke(this *ole.IDispatch, dispid int32, riid *ole.GUID, lcid int, flags int16, dispparams *ole.DISPPARAMS, result *ole.VARIANT, pexcepinfo *ole.EXCEPINFO, nerr *uint) uintptr { pthis := (*stdDispatch)(unsafe.Pointer(this)) found := "" for name, id := range pthis.funcMap { if id == dispid { found = name } } if found != "" { rv := reflect.ValueOf(pthis.iface).Elem() rm := rv.MethodByName(found) rr := rm.Call([]reflect.Value{}) println(len(rr)) return ole.S_OK } return ole.E_NOTIMPL } ================================================ FILE: vendor/github.com/go-ole/go-ole/oleutil/connection_func.go ================================================ // +build !windows package oleutil import ole "github.com/go-ole/go-ole" // ConnectObject creates a connection point between two services for communication. func ConnectObject(disp *ole.IDispatch, iid *ole.GUID, idisp interface{}) (uint32, error) { return 0, ole.NewError(ole.E_NOTIMPL) } ================================================ FILE: vendor/github.com/go-ole/go-ole/oleutil/connection_windows.go ================================================ // +build windows package oleutil import ( "reflect" "syscall" "unsafe" ole "github.com/go-ole/go-ole" ) // ConnectObject creates a connection point between two services for communication. func ConnectObject(disp *ole.IDispatch, iid *ole.GUID, idisp interface{}) (cookie uint32, err error) { unknown, err := disp.QueryInterface(ole.IID_IConnectionPointContainer) if err != nil { return } container := (*ole.IConnectionPointContainer)(unsafe.Pointer(unknown)) var point *ole.IConnectionPoint err = container.FindConnectionPoint(iid, &point) if err != nil { return } if edisp, ok := idisp.(*ole.IUnknown); ok { cookie, err = point.Advise(edisp) container.Release() if err != nil { return } } rv := reflect.ValueOf(disp).Elem() if rv.Type().Kind() == reflect.Struct { dest := &stdDispatch{} dest.lpVtbl = &stdDispatchVtbl{} dest.lpVtbl.pQueryInterface = syscall.NewCallback(dispQueryInterface) dest.lpVtbl.pAddRef = syscall.NewCallback(dispAddRef) dest.lpVtbl.pRelease = syscall.NewCallback(dispRelease) dest.lpVtbl.pGetTypeInfoCount = syscall.NewCallback(dispGetTypeInfoCount) dest.lpVtbl.pGetTypeInfo = syscall.NewCallback(dispGetTypeInfo) dest.lpVtbl.pGetIDsOfNames = syscall.NewCallback(dispGetIDsOfNames) dest.lpVtbl.pInvoke = syscall.NewCallback(dispInvoke) dest.iface = disp dest.iid = iid cookie, err = point.Advise((*ole.IUnknown)(unsafe.Pointer(dest))) container.Release() if err != nil { point.Release() return } return } container.Release() return 0, ole.NewError(ole.E_INVALIDARG) } ================================================ FILE: vendor/github.com/go-ole/go-ole/oleutil/go-get.go ================================================ // This file is here so go get succeeds as without it errors with: // no buildable Go source files in ... // // +build !windows package oleutil ================================================ FILE: vendor/github.com/go-ole/go-ole/oleutil/oleutil.go ================================================ package oleutil import ole "github.com/go-ole/go-ole" // ClassIDFrom retrieves class ID whether given is program ID or application string. func ClassIDFrom(programID string) (classID *ole.GUID, err error) { return ole.ClassIDFrom(programID) } // CreateObject creates object from programID based on interface type. // // Only supports IUnknown. // // Program ID can be either program ID or application string. func CreateObject(programID string) (unknown *ole.IUnknown, err error) { classID, err := ole.ClassIDFrom(programID) if err != nil { return } unknown, err = ole.CreateInstance(classID, ole.IID_IUnknown) if err != nil { return } return } // GetActiveObject retrieves active object for program ID and interface ID based // on interface type. // // Only supports IUnknown. // // Program ID can be either program ID or application string. func GetActiveObject(programID string) (unknown *ole.IUnknown, err error) { classID, err := ole.ClassIDFrom(programID) if err != nil { return } unknown, err = ole.GetActiveObject(classID, ole.IID_IUnknown) if err != nil { return } return } // CallMethod calls method on IDispatch with parameters. func CallMethod(disp *ole.IDispatch, name string, params ...interface{}) (result *ole.VARIANT, err error) { return disp.InvokeWithOptionalArgs(name, ole.DISPATCH_METHOD, params) } // MustCallMethod calls method on IDispatch with parameters or panics. func MustCallMethod(disp *ole.IDispatch, name string, params ...interface{}) (result *ole.VARIANT) { r, err := CallMethod(disp, name, params...) if err != nil { panic(err.Error()) } return r } // GetProperty retrieves property from IDispatch. func GetProperty(disp *ole.IDispatch, name string, params ...interface{}) (result *ole.VARIANT, err error) { return disp.InvokeWithOptionalArgs(name, ole.DISPATCH_PROPERTYGET, params) } // MustGetProperty retrieves property from IDispatch or panics. func MustGetProperty(disp *ole.IDispatch, name string, params ...interface{}) (result *ole.VARIANT) { r, err := GetProperty(disp, name, params...) if err != nil { panic(err.Error()) } return r } // PutProperty mutates property. func PutProperty(disp *ole.IDispatch, name string, params ...interface{}) (result *ole.VARIANT, err error) { return disp.InvokeWithOptionalArgs(name, ole.DISPATCH_PROPERTYPUT, params) } // MustPutProperty mutates property or panics. func MustPutProperty(disp *ole.IDispatch, name string, params ...interface{}) (result *ole.VARIANT) { r, err := PutProperty(disp, name, params...) if err != nil { panic(err.Error()) } return r } // PutPropertyRef mutates property reference. func PutPropertyRef(disp *ole.IDispatch, name string, params ...interface{}) (result *ole.VARIANT, err error) { return disp.InvokeWithOptionalArgs(name, ole.DISPATCH_PROPERTYPUTREF, params) } // MustPutPropertyRef mutates property reference or panics. func MustPutPropertyRef(disp *ole.IDispatch, name string, params ...interface{}) (result *ole.VARIANT) { r, err := PutPropertyRef(disp, name, params...) if err != nil { panic(err.Error()) } return r } func ForEach(disp *ole.IDispatch, f func(v *ole.VARIANT) error) error { newEnum, err := disp.GetProperty("_NewEnum") if err != nil { return err } defer newEnum.Clear() enum, err := newEnum.ToIUnknown().IEnumVARIANT(ole.IID_IEnumVariant) if err != nil { return err } defer enum.Release() for item, length, err := enum.Next(1); length > 0; item, length, err = enum.Next(1) { if err != nil { return err } if ferr := f(&item); ferr != nil { return ferr } } return nil } ================================================ FILE: vendor/github.com/go-ole/go-ole/safearray.go ================================================ // Package is meant to retrieve and process safe array data returned from COM. package ole // SafeArrayBound defines the SafeArray boundaries. type SafeArrayBound struct { Elements uint32 LowerBound int32 } // SafeArray is how COM handles arrays. type SafeArray struct { Dimensions uint16 FeaturesFlag uint16 ElementsSize uint32 LocksAmount uint32 Data uint32 Bounds [16]byte } // SAFEARRAY is obsolete, exists for backwards compatibility. // Use SafeArray type SAFEARRAY SafeArray // SAFEARRAYBOUND is obsolete, exists for backwards compatibility. // Use SafeArrayBound type SAFEARRAYBOUND SafeArrayBound ================================================ FILE: vendor/github.com/go-ole/go-ole/safearray_func.go ================================================ // +build !windows package ole import ( "unsafe" ) // safeArrayAccessData returns raw array pointer. // // AKA: SafeArrayAccessData in Windows API. func safeArrayAccessData(safearray *SafeArray) (uintptr, error) { return uintptr(0), NewError(E_NOTIMPL) } // safeArrayUnaccessData releases raw array. // // AKA: SafeArrayUnaccessData in Windows API. func safeArrayUnaccessData(safearray *SafeArray) error { return NewError(E_NOTIMPL) } // safeArrayAllocData allocates SafeArray. // // AKA: SafeArrayAllocData in Windows API. func safeArrayAllocData(safearray *SafeArray) error { return NewError(E_NOTIMPL) } // safeArrayAllocDescriptor allocates SafeArray. // // AKA: SafeArrayAllocDescriptor in Windows API. func safeArrayAllocDescriptor(dimensions uint32) (*SafeArray, error) { return nil, NewError(E_NOTIMPL) } // safeArrayAllocDescriptorEx allocates SafeArray. // // AKA: SafeArrayAllocDescriptorEx in Windows API. func safeArrayAllocDescriptorEx(variantType VT, dimensions uint32) (*SafeArray, error) { return nil, NewError(E_NOTIMPL) } // safeArrayCopy returns copy of SafeArray. // // AKA: SafeArrayCopy in Windows API. func safeArrayCopy(original *SafeArray) (*SafeArray, error) { return nil, NewError(E_NOTIMPL) } // safeArrayCopyData duplicates SafeArray into another SafeArray object. // // AKA: SafeArrayCopyData in Windows API. func safeArrayCopyData(original *SafeArray, duplicate *SafeArray) error { return NewError(E_NOTIMPL) } // safeArrayCreate creates SafeArray. // // AKA: SafeArrayCreate in Windows API. func safeArrayCreate(variantType VT, dimensions uint32, bounds *SafeArrayBound) (*SafeArray, error) { return nil, NewError(E_NOTIMPL) } // safeArrayCreateEx creates SafeArray. // // AKA: SafeArrayCreateEx in Windows API. func safeArrayCreateEx(variantType VT, dimensions uint32, bounds *SafeArrayBound, extra uintptr) (*SafeArray, error) { return nil, NewError(E_NOTIMPL) } // safeArrayCreateVector creates SafeArray. // // AKA: SafeArrayCreateVector in Windows API. func safeArrayCreateVector(variantType VT, lowerBound int32, length uint32) (*SafeArray, error) { return nil, NewError(E_NOTIMPL) } // safeArrayCreateVectorEx creates SafeArray. // // AKA: SafeArrayCreateVectorEx in Windows API. func safeArrayCreateVectorEx(variantType VT, lowerBound int32, length uint32, extra uintptr) (*SafeArray, error) { return nil, NewError(E_NOTIMPL) } // safeArrayDestroy destroys SafeArray object. // // AKA: SafeArrayDestroy in Windows API. func safeArrayDestroy(safearray *SafeArray) error { return NewError(E_NOTIMPL) } // safeArrayDestroyData destroys SafeArray object. // // AKA: SafeArrayDestroyData in Windows API. func safeArrayDestroyData(safearray *SafeArray) error { return NewError(E_NOTIMPL) } // safeArrayDestroyDescriptor destroys SafeArray object. // // AKA: SafeArrayDestroyDescriptor in Windows API. func safeArrayDestroyDescriptor(safearray *SafeArray) error { return NewError(E_NOTIMPL) } // safeArrayGetDim is the amount of dimensions in the SafeArray. // // SafeArrays may have multiple dimensions. Meaning, it could be // multidimensional array. // // AKA: SafeArrayGetDim in Windows API. func safeArrayGetDim(safearray *SafeArray) (*uint32, error) { u := uint32(0) return &u, NewError(E_NOTIMPL) } // safeArrayGetElementSize is the element size in bytes. // // AKA: SafeArrayGetElemsize in Windows API. func safeArrayGetElementSize(safearray *SafeArray) (*uint32, error) { u := uint32(0) return &u, NewError(E_NOTIMPL) } // safeArrayGetElement retrieves element at given index. func safeArrayGetElement(safearray *SafeArray, index int32, pv unsafe.Pointer) error { return NewError(E_NOTIMPL) } // safeArrayGetElement retrieves element at given index and converts to string. func safeArrayGetElementString(safearray *SafeArray, index int32) (string, error) { return "", NewError(E_NOTIMPL) } // safeArrayGetIID is the InterfaceID of the elements in the SafeArray. // // AKA: SafeArrayGetIID in Windows API. func safeArrayGetIID(safearray *SafeArray) (*GUID, error) { return nil, NewError(E_NOTIMPL) } // safeArrayGetLBound returns lower bounds of SafeArray. // // SafeArrays may have multiple dimensions. Meaning, it could be // multidimensional array. // // AKA: SafeArrayGetLBound in Windows API. func safeArrayGetLBound(safearray *SafeArray, dimension uint32) (int32, error) { return int32(0), NewError(E_NOTIMPL) } // safeArrayGetUBound returns upper bounds of SafeArray. // // SafeArrays may have multiple dimensions. Meaning, it could be // multidimensional array. // // AKA: SafeArrayGetUBound in Windows API. func safeArrayGetUBound(safearray *SafeArray, dimension uint32) (int32, error) { return int32(0), NewError(E_NOTIMPL) } // safeArrayGetVartype returns data type of SafeArray. // // AKA: SafeArrayGetVartype in Windows API. func safeArrayGetVartype(safearray *SafeArray) (uint16, error) { return uint16(0), NewError(E_NOTIMPL) } // safeArrayLock locks SafeArray for reading to modify SafeArray. // // This must be called during some calls to ensure that another process does not // read or write to the SafeArray during editing. // // AKA: SafeArrayLock in Windows API. func safeArrayLock(safearray *SafeArray) error { return NewError(E_NOTIMPL) } // safeArrayUnlock unlocks SafeArray for reading. // // AKA: SafeArrayUnlock in Windows API. func safeArrayUnlock(safearray *SafeArray) error { return NewError(E_NOTIMPL) } // safeArrayPutElement stores the data element at the specified location in the // array. // // AKA: SafeArrayPutElement in Windows API. func safeArrayPutElement(safearray *SafeArray, index int64, element uintptr) error { return NewError(E_NOTIMPL) } // safeArrayGetRecordInfo accesses IRecordInfo info for custom types. // // AKA: SafeArrayGetRecordInfo in Windows API. // // XXX: Must implement IRecordInfo interface for this to return. func safeArrayGetRecordInfo(safearray *SafeArray) (interface{}, error) { return nil, NewError(E_NOTIMPL) } // safeArraySetRecordInfo mutates IRecordInfo info for custom types. // // AKA: SafeArraySetRecordInfo in Windows API. // // XXX: Must implement IRecordInfo interface for this to return. func safeArraySetRecordInfo(safearray *SafeArray, recordInfo interface{}) error { return NewError(E_NOTIMPL) } ================================================ FILE: vendor/github.com/go-ole/go-ole/safearray_windows.go ================================================ // +build windows package ole import ( "unsafe" ) var ( procSafeArrayAccessData = modoleaut32.NewProc("SafeArrayAccessData") procSafeArrayAllocData = modoleaut32.NewProc("SafeArrayAllocData") procSafeArrayAllocDescriptor = modoleaut32.NewProc("SafeArrayAllocDescriptor") procSafeArrayAllocDescriptorEx = modoleaut32.NewProc("SafeArrayAllocDescriptorEx") procSafeArrayCopy = modoleaut32.NewProc("SafeArrayCopy") procSafeArrayCopyData = modoleaut32.NewProc("SafeArrayCopyData") procSafeArrayCreate = modoleaut32.NewProc("SafeArrayCreate") procSafeArrayCreateEx = modoleaut32.NewProc("SafeArrayCreateEx") procSafeArrayCreateVector = modoleaut32.NewProc("SafeArrayCreateVector") procSafeArrayCreateVectorEx = modoleaut32.NewProc("SafeArrayCreateVectorEx") procSafeArrayDestroy = modoleaut32.NewProc("SafeArrayDestroy") procSafeArrayDestroyData = modoleaut32.NewProc("SafeArrayDestroyData") procSafeArrayDestroyDescriptor = modoleaut32.NewProc("SafeArrayDestroyDescriptor") procSafeArrayGetDim = modoleaut32.NewProc("SafeArrayGetDim") procSafeArrayGetElement = modoleaut32.NewProc("SafeArrayGetElement") procSafeArrayGetElemsize = modoleaut32.NewProc("SafeArrayGetElemsize") procSafeArrayGetIID = modoleaut32.NewProc("SafeArrayGetIID") procSafeArrayGetLBound = modoleaut32.NewProc("SafeArrayGetLBound") procSafeArrayGetUBound = modoleaut32.NewProc("SafeArrayGetUBound") procSafeArrayGetVartype = modoleaut32.NewProc("SafeArrayGetVartype") procSafeArrayLock = modoleaut32.NewProc("SafeArrayLock") procSafeArrayPtrOfIndex = modoleaut32.NewProc("SafeArrayPtrOfIndex") procSafeArrayUnaccessData = modoleaut32.NewProc("SafeArrayUnaccessData") procSafeArrayUnlock = modoleaut32.NewProc("SafeArrayUnlock") procSafeArrayPutElement = modoleaut32.NewProc("SafeArrayPutElement") //procSafeArrayRedim = modoleaut32.NewProc("SafeArrayRedim") // TODO //procSafeArraySetIID = modoleaut32.NewProc("SafeArraySetIID") // TODO procSafeArrayGetRecordInfo = modoleaut32.NewProc("SafeArrayGetRecordInfo") procSafeArraySetRecordInfo = modoleaut32.NewProc("SafeArraySetRecordInfo") ) // safeArrayAccessData returns raw array pointer. // // AKA: SafeArrayAccessData in Windows API. // Todo: Test func safeArrayAccessData(safearray *SafeArray) (element uintptr, err error) { err = convertHresultToError( procSafeArrayAccessData.Call( uintptr(unsafe.Pointer(safearray)), uintptr(unsafe.Pointer(&element)))) return } // safeArrayUnaccessData releases raw array. // // AKA: SafeArrayUnaccessData in Windows API. func safeArrayUnaccessData(safearray *SafeArray) (err error) { err = convertHresultToError(procSafeArrayUnaccessData.Call(uintptr(unsafe.Pointer(safearray)))) return } // safeArrayAllocData allocates SafeArray. // // AKA: SafeArrayAllocData in Windows API. func safeArrayAllocData(safearray *SafeArray) (err error) { err = convertHresultToError(procSafeArrayAllocData.Call(uintptr(unsafe.Pointer(safearray)))) return } // safeArrayAllocDescriptor allocates SafeArray. // // AKA: SafeArrayAllocDescriptor in Windows API. func safeArrayAllocDescriptor(dimensions uint32) (safearray *SafeArray, err error) { err = convertHresultToError( procSafeArrayAllocDescriptor.Call(uintptr(dimensions), uintptr(unsafe.Pointer(&safearray)))) return } // safeArrayAllocDescriptorEx allocates SafeArray. // // AKA: SafeArrayAllocDescriptorEx in Windows API. func safeArrayAllocDescriptorEx(variantType VT, dimensions uint32) (safearray *SafeArray, err error) { err = convertHresultToError( procSafeArrayAllocDescriptorEx.Call( uintptr(variantType), uintptr(dimensions), uintptr(unsafe.Pointer(&safearray)))) return } // safeArrayCopy returns copy of SafeArray. // // AKA: SafeArrayCopy in Windows API. func safeArrayCopy(original *SafeArray) (safearray *SafeArray, err error) { err = convertHresultToError( procSafeArrayCopy.Call( uintptr(unsafe.Pointer(original)), uintptr(unsafe.Pointer(&safearray)))) return } // safeArrayCopyData duplicates SafeArray into another SafeArray object. // // AKA: SafeArrayCopyData in Windows API. func safeArrayCopyData(original *SafeArray, duplicate *SafeArray) (err error) { err = convertHresultToError( procSafeArrayCopyData.Call( uintptr(unsafe.Pointer(original)), uintptr(unsafe.Pointer(duplicate)))) return } // safeArrayCreate creates SafeArray. // // AKA: SafeArrayCreate in Windows API. func safeArrayCreate(variantType VT, dimensions uint32, bounds *SafeArrayBound) (safearray *SafeArray, err error) { sa, _, err := procSafeArrayCreate.Call( uintptr(variantType), uintptr(dimensions), uintptr(unsafe.Pointer(bounds))) safearray = (*SafeArray)(unsafe.Pointer(&sa)) return } // safeArrayCreateEx creates SafeArray. // // AKA: SafeArrayCreateEx in Windows API. func safeArrayCreateEx(variantType VT, dimensions uint32, bounds *SafeArrayBound, extra uintptr) (safearray *SafeArray, err error) { sa, _, err := procSafeArrayCreateEx.Call( uintptr(variantType), uintptr(dimensions), uintptr(unsafe.Pointer(bounds)), extra) safearray = (*SafeArray)(unsafe.Pointer(sa)) return } // safeArrayCreateVector creates SafeArray. // // AKA: SafeArrayCreateVector in Windows API. func safeArrayCreateVector(variantType VT, lowerBound int32, length uint32) (safearray *SafeArray, err error) { sa, _, err := procSafeArrayCreateVector.Call( uintptr(variantType), uintptr(lowerBound), uintptr(length)) safearray = (*SafeArray)(unsafe.Pointer(sa)) return } // safeArrayCreateVectorEx creates SafeArray. // // AKA: SafeArrayCreateVectorEx in Windows API. func safeArrayCreateVectorEx(variantType VT, lowerBound int32, length uint32, extra uintptr) (safearray *SafeArray, err error) { sa, _, err := procSafeArrayCreateVectorEx.Call( uintptr(variantType), uintptr(lowerBound), uintptr(length), extra) safearray = (*SafeArray)(unsafe.Pointer(sa)) return } // safeArrayDestroy destroys SafeArray object. // // AKA: SafeArrayDestroy in Windows API. func safeArrayDestroy(safearray *SafeArray) (err error) { err = convertHresultToError(procSafeArrayDestroy.Call(uintptr(unsafe.Pointer(safearray)))) return } // safeArrayDestroyData destroys SafeArray object. // // AKA: SafeArrayDestroyData in Windows API. func safeArrayDestroyData(safearray *SafeArray) (err error) { err = convertHresultToError(procSafeArrayDestroyData.Call(uintptr(unsafe.Pointer(safearray)))) return } // safeArrayDestroyDescriptor destroys SafeArray object. // // AKA: SafeArrayDestroyDescriptor in Windows API. func safeArrayDestroyDescriptor(safearray *SafeArray) (err error) { err = convertHresultToError(procSafeArrayDestroyDescriptor.Call(uintptr(unsafe.Pointer(safearray)))) return } // safeArrayGetDim is the amount of dimensions in the SafeArray. // // SafeArrays may have multiple dimensions. Meaning, it could be // multidimensional array. // // AKA: SafeArrayGetDim in Windows API. func safeArrayGetDim(safearray *SafeArray) (dimensions *uint32, err error) { l, _, err := procSafeArrayGetDim.Call(uintptr(unsafe.Pointer(safearray))) dimensions = (*uint32)(unsafe.Pointer(l)) return } // safeArrayGetElementSize is the element size in bytes. // // AKA: SafeArrayGetElemsize in Windows API. func safeArrayGetElementSize(safearray *SafeArray) (length *uint32, err error) { l, _, err := procSafeArrayGetElemsize.Call(uintptr(unsafe.Pointer(safearray))) length = (*uint32)(unsafe.Pointer(l)) return } // safeArrayGetElement retrieves element at given index. func safeArrayGetElement(safearray *SafeArray, index int32, pv unsafe.Pointer) error { return convertHresultToError( procSafeArrayGetElement.Call( uintptr(unsafe.Pointer(safearray)), uintptr(unsafe.Pointer(&index)), uintptr(pv))) } // safeArrayGetElementString retrieves element at given index and converts to string. func safeArrayGetElementString(safearray *SafeArray, index int32) (str string, err error) { var element *int16 err = convertHresultToError( procSafeArrayGetElement.Call( uintptr(unsafe.Pointer(safearray)), uintptr(unsafe.Pointer(&index)), uintptr(unsafe.Pointer(&element)))) str = BstrToString(*(**uint16)(unsafe.Pointer(&element))) SysFreeString(element) return } // safeArrayGetIID is the InterfaceID of the elements in the SafeArray. // // AKA: SafeArrayGetIID in Windows API. func safeArrayGetIID(safearray *SafeArray) (guid *GUID, err error) { err = convertHresultToError( procSafeArrayGetIID.Call( uintptr(unsafe.Pointer(safearray)), uintptr(unsafe.Pointer(&guid)))) return } // safeArrayGetLBound returns lower bounds of SafeArray. // // SafeArrays may have multiple dimensions. Meaning, it could be // multidimensional array. // // AKA: SafeArrayGetLBound in Windows API. func safeArrayGetLBound(safearray *SafeArray, dimension uint32) (lowerBound int32, err error) { err = convertHresultToError( procSafeArrayGetLBound.Call( uintptr(unsafe.Pointer(safearray)), uintptr(dimension), uintptr(unsafe.Pointer(&lowerBound)))) return } // safeArrayGetUBound returns upper bounds of SafeArray. // // SafeArrays may have multiple dimensions. Meaning, it could be // multidimensional array. // // AKA: SafeArrayGetUBound in Windows API. func safeArrayGetUBound(safearray *SafeArray, dimension uint32) (upperBound int32, err error) { err = convertHresultToError( procSafeArrayGetUBound.Call( uintptr(unsafe.Pointer(safearray)), uintptr(dimension), uintptr(unsafe.Pointer(&upperBound)))) return } // safeArrayGetVartype returns data type of SafeArray. // // AKA: SafeArrayGetVartype in Windows API. func safeArrayGetVartype(safearray *SafeArray) (varType uint16, err error) { err = convertHresultToError( procSafeArrayGetVartype.Call( uintptr(unsafe.Pointer(safearray)), uintptr(unsafe.Pointer(&varType)))) return } // safeArrayLock locks SafeArray for reading to modify SafeArray. // // This must be called during some calls to ensure that another process does not // read or write to the SafeArray during editing. // // AKA: SafeArrayLock in Windows API. func safeArrayLock(safearray *SafeArray) (err error) { err = convertHresultToError(procSafeArrayLock.Call(uintptr(unsafe.Pointer(safearray)))) return } // safeArrayUnlock unlocks SafeArray for reading. // // AKA: SafeArrayUnlock in Windows API. func safeArrayUnlock(safearray *SafeArray) (err error) { err = convertHresultToError(procSafeArrayUnlock.Call(uintptr(unsafe.Pointer(safearray)))) return } // safeArrayPutElement stores the data element at the specified location in the // array. // // AKA: SafeArrayPutElement in Windows API. func safeArrayPutElement(safearray *SafeArray, index int64, element uintptr) (err error) { err = convertHresultToError( procSafeArrayPutElement.Call( uintptr(unsafe.Pointer(safearray)), uintptr(unsafe.Pointer(&index)), uintptr(unsafe.Pointer(element)))) return } // safeArrayGetRecordInfo accesses IRecordInfo info for custom types. // // AKA: SafeArrayGetRecordInfo in Windows API. // // XXX: Must implement IRecordInfo interface for this to return. func safeArrayGetRecordInfo(safearray *SafeArray) (recordInfo interface{}, err error) { err = convertHresultToError( procSafeArrayGetRecordInfo.Call( uintptr(unsafe.Pointer(safearray)), uintptr(unsafe.Pointer(&recordInfo)))) return } // safeArraySetRecordInfo mutates IRecordInfo info for custom types. // // AKA: SafeArraySetRecordInfo in Windows API. // // XXX: Must implement IRecordInfo interface for this to return. func safeArraySetRecordInfo(safearray *SafeArray, recordInfo interface{}) (err error) { err = convertHresultToError( procSafeArraySetRecordInfo.Call( uintptr(unsafe.Pointer(safearray)), uintptr(unsafe.Pointer(&recordInfo)))) return } ================================================ FILE: vendor/github.com/go-ole/go-ole/safearrayconversion.go ================================================ // Helper for converting SafeArray to array of objects. package ole import ( "unsafe" ) type SafeArrayConversion struct { Array *SafeArray } func (sac *SafeArrayConversion) ToStringArray() (strings []string) { totalElements, _ := sac.TotalElements(0) strings = make([]string, totalElements) for i := int32(0); i < totalElements; i++ { strings[int32(i)], _ = safeArrayGetElementString(sac.Array, i) } return } func (sac *SafeArrayConversion) ToByteArray() (bytes []byte) { totalElements, _ := sac.TotalElements(0) bytes = make([]byte, totalElements) for i := int32(0); i < totalElements; i++ { safeArrayGetElement(sac.Array, i, unsafe.Pointer(&bytes[int32(i)])) } return } func (sac *SafeArrayConversion) ToValueArray() (values []interface{}) { totalElements, _ := sac.TotalElements(0) values = make([]interface{}, totalElements) vt, _ := safeArrayGetVartype(sac.Array) for i := int32(0); i < totalElements; i++ { switch VT(vt) { case VT_BOOL: var v bool safeArrayGetElement(sac.Array, i, unsafe.Pointer(&v)) values[i] = v case VT_I1: var v int8 safeArrayGetElement(sac.Array, i, unsafe.Pointer(&v)) values[i] = v case VT_I2: var v int16 safeArrayGetElement(sac.Array, i, unsafe.Pointer(&v)) values[i] = v case VT_I4: var v int32 safeArrayGetElement(sac.Array, i, unsafe.Pointer(&v)) values[i] = v case VT_I8: var v int64 safeArrayGetElement(sac.Array, i, unsafe.Pointer(&v)) values[i] = v case VT_UI1: var v uint8 safeArrayGetElement(sac.Array, i, unsafe.Pointer(&v)) values[i] = v case VT_UI2: var v uint16 safeArrayGetElement(sac.Array, i, unsafe.Pointer(&v)) values[i] = v case VT_UI4: var v uint32 safeArrayGetElement(sac.Array, i, unsafe.Pointer(&v)) values[i] = v case VT_UI8: var v uint64 safeArrayGetElement(sac.Array, i, unsafe.Pointer(&v)) values[i] = v case VT_R4: var v float32 safeArrayGetElement(sac.Array, i, unsafe.Pointer(&v)) values[i] = v case VT_R8: var v float64 safeArrayGetElement(sac.Array, i, unsafe.Pointer(&v)) values[i] = v case VT_BSTR: v , _ := safeArrayGetElementString(sac.Array, i) values[i] = v case VT_VARIANT: var v VARIANT safeArrayGetElement(sac.Array, i, unsafe.Pointer(&v)) values[i] = v.Value() v.Clear() default: // TODO } } return } func (sac *SafeArrayConversion) GetType() (varType uint16, err error) { return safeArrayGetVartype(sac.Array) } func (sac *SafeArrayConversion) GetDimensions() (dimensions *uint32, err error) { return safeArrayGetDim(sac.Array) } func (sac *SafeArrayConversion) GetSize() (length *uint32, err error) { return safeArrayGetElementSize(sac.Array) } func (sac *SafeArrayConversion) TotalElements(index uint32) (totalElements int32, err error) { if index < 1 { index = 1 } // Get array bounds var LowerBounds int32 var UpperBounds int32 LowerBounds, err = safeArrayGetLBound(sac.Array, index) if err != nil { return } UpperBounds, err = safeArrayGetUBound(sac.Array, index) if err != nil { return } totalElements = UpperBounds - LowerBounds + 1 return } // Release Safe Array memory func (sac *SafeArrayConversion) Release() { safeArrayDestroy(sac.Array) } ================================================ FILE: vendor/github.com/go-ole/go-ole/safearrayslices.go ================================================ // +build windows package ole import ( "unsafe" ) func safeArrayFromByteSlice(slice []byte) *SafeArray { array, _ := safeArrayCreateVector(VT_UI1, 0, uint32(len(slice))) if array == nil { panic("Could not convert []byte to SAFEARRAY") } for i, v := range slice { safeArrayPutElement(array, int64(i), uintptr(unsafe.Pointer(&v))) } return array } func safeArrayFromStringSlice(slice []string) *SafeArray { array, _ := safeArrayCreateVector(VT_BSTR, 0, uint32(len(slice))) if array == nil { panic("Could not convert []string to SAFEARRAY") } // SysAllocStringLen(s) for i, v := range slice { safeArrayPutElement(array, int64(i), uintptr(unsafe.Pointer(SysAllocStringLen(v)))) } return array } ================================================ FILE: vendor/github.com/go-ole/go-ole/utility.go ================================================ package ole import ( "unicode/utf16" "unsafe" ) // ClassIDFrom retrieves class ID whether given is program ID or application string. // // Helper that provides check against both Class ID from Program ID and Class ID from string. It is // faster, if you know which you are using, to use the individual functions, but this will check // against available functions for you. func ClassIDFrom(programID string) (classID *GUID, err error) { classID, err = CLSIDFromProgID(programID) if err != nil { classID, err = CLSIDFromString(programID) if err != nil { return } } return } // BytePtrToString converts byte pointer to a Go string. func BytePtrToString(p *byte) string { a := (*[10000]uint8)(unsafe.Pointer(p)) i := 0 for a[i] != 0 { i++ } return string(a[:i]) } // UTF16PtrToString is alias for LpOleStrToString. // // Kept for compatibility reasons. func UTF16PtrToString(p *uint16) string { return LpOleStrToString(p) } // LpOleStrToString converts COM Unicode to Go string. func LpOleStrToString(p *uint16) string { if p == nil { return "" } length := lpOleStrLen(p) a := make([]uint16, length) ptr := unsafe.Pointer(p) for i := 0; i < int(length); i++ { a[i] = *(*uint16)(ptr) ptr = unsafe.Pointer(uintptr(ptr) + 2) } return string(utf16.Decode(a)) } // BstrToString converts COM binary string to Go string. func BstrToString(p *uint16) string { if p == nil { return "" } length := SysStringLen((*int16)(unsafe.Pointer(p))) a := make([]uint16, length) ptr := unsafe.Pointer(p) for i := 0; i < int(length); i++ { a[i] = *(*uint16)(ptr) ptr = unsafe.Pointer(uintptr(ptr) + 2) } return string(utf16.Decode(a)) } // lpOleStrLen returns the length of Unicode string. func lpOleStrLen(p *uint16) (length int64) { if p == nil { return 0 } ptr := unsafe.Pointer(p) for i := 0; ; i++ { if 0 == *(*uint16)(ptr) { length = int64(i) break } ptr = unsafe.Pointer(uintptr(ptr) + 2) } return } // convertHresultToError converts syscall to error, if call is unsuccessful. func convertHresultToError(hr uintptr, r2 uintptr, ignore error) (err error) { if hr != 0 { err = NewError(hr) } return } ================================================ FILE: vendor/github.com/go-ole/go-ole/variables.go ================================================ // +build windows package ole import ( "golang.org/x/sys/windows" ) var ( modcombase = windows.NewLazySystemDLL("combase.dll") modkernel32 = windows.NewLazySystemDLL("kernel32.dll") modole32 = windows.NewLazySystemDLL("ole32.dll") modoleaut32 = windows.NewLazySystemDLL("oleaut32.dll") moduser32 = windows.NewLazySystemDLL("user32.dll") ) ================================================ FILE: vendor/github.com/go-ole/go-ole/variant.go ================================================ package ole import "unsafe" // NewVariant returns new variant based on type and value. func NewVariant(vt VT, val int64) VARIANT { return VARIANT{VT: vt, Val: val} } // ToIUnknown converts Variant to Unknown object. func (v *VARIANT) ToIUnknown() *IUnknown { if v.VT != VT_UNKNOWN { return nil } return (*IUnknown)(unsafe.Pointer(uintptr(v.Val))) } // ToIDispatch converts variant to dispatch object. func (v *VARIANT) ToIDispatch() *IDispatch { if v.VT != VT_DISPATCH { return nil } return (*IDispatch)(unsafe.Pointer(uintptr(v.Val))) } // ToArray converts variant to SafeArray helper. func (v *VARIANT) ToArray() *SafeArrayConversion { if v.VT != VT_SAFEARRAY { if v.VT&VT_ARRAY == 0 { return nil } } var safeArray *SafeArray = (*SafeArray)(unsafe.Pointer(uintptr(v.Val))) return &SafeArrayConversion{safeArray} } // ToString converts variant to Go string. func (v *VARIANT) ToString() string { if v.VT != VT_BSTR { return "" } return BstrToString(*(**uint16)(unsafe.Pointer(&v.Val))) } // Clear the memory of variant object. func (v *VARIANT) Clear() error { return VariantClear(v) } // Value returns variant value based on its type. // // Currently supported types: 2- and 4-byte integers, strings, bools. // Note that 64-bit integers, datetimes, and other types are stored as strings // and will be returned as strings. // // Needs to be further converted, because this returns an interface{}. func (v *VARIANT) Value() interface{} { switch v.VT { case VT_I1: return int8(v.Val) case VT_UI1: return uint8(v.Val) case VT_I2: return int16(v.Val) case VT_UI2: return uint16(v.Val) case VT_I4: return int32(v.Val) case VT_UI4: return uint32(v.Val) case VT_I8: return int64(v.Val) case VT_UI8: return uint64(v.Val) case VT_INT: return int(v.Val) case VT_UINT: return uint(v.Val) case VT_INT_PTR: return uintptr(v.Val) // TODO case VT_UINT_PTR: return uintptr(v.Val) case VT_R4: return *(*float32)(unsafe.Pointer(&v.Val)) case VT_R8: return *(*float64)(unsafe.Pointer(&v.Val)) case VT_BSTR: return v.ToString() case VT_DATE: // VT_DATE type will either return float64 or time.Time. d := uint64(v.Val) date, err := GetVariantDate(d) if err != nil { return float64(v.Val) } return date case VT_UNKNOWN: return v.ToIUnknown() case VT_DISPATCH: return v.ToIDispatch() case VT_BOOL: return v.Val != 0 } return nil } ================================================ FILE: vendor/github.com/go-ole/go-ole/variant_386.go ================================================ // +build 386 package ole type VARIANT struct { VT VT // 2 wReserved1 uint16 // 4 wReserved2 uint16 // 6 wReserved3 uint16 // 8 Val int64 // 16 } ================================================ FILE: vendor/github.com/go-ole/go-ole/variant_amd64.go ================================================ // +build amd64 package ole type VARIANT struct { VT VT // 2 wReserved1 uint16 // 4 wReserved2 uint16 // 6 wReserved3 uint16 // 8 Val int64 // 16 _ [8]byte // 24 } ================================================ FILE: vendor/github.com/go-ole/go-ole/variant_arm.go ================================================ // +build arm package ole type VARIANT struct { VT VT // 2 wReserved1 uint16 // 4 wReserved2 uint16 // 6 wReserved3 uint16 // 8 Val int64 // 16 } ================================================ FILE: vendor/github.com/go-ole/go-ole/variant_arm64.go ================================================ //go:build arm64 // +build arm64 package ole type VARIANT struct { VT VT // 2 wReserved1 uint16 // 4 wReserved2 uint16 // 6 wReserved3 uint16 // 8 Val int64 // 16 _ [8]byte // 24 } ================================================ FILE: vendor/github.com/go-ole/go-ole/variant_date_386.go ================================================ // +build windows,386 package ole import ( "errors" "syscall" "time" "unsafe" ) // GetVariantDate converts COM Variant Time value to Go time.Time. func GetVariantDate(value uint64) (time.Time, error) { var st syscall.Systemtime v1 := uint32(value) v2 := uint32(value >> 32) r, _, _ := procVariantTimeToSystemTime.Call(uintptr(v1), uintptr(v2), uintptr(unsafe.Pointer(&st))) if r != 0 { return time.Date(int(st.Year), time.Month(st.Month), int(st.Day), int(st.Hour), int(st.Minute), int(st.Second), int(st.Milliseconds/1000), time.UTC), nil } return time.Now(), errors.New("Could not convert to time, passing current time.") } ================================================ FILE: vendor/github.com/go-ole/go-ole/variant_date_amd64.go ================================================ // +build windows,amd64 package ole import ( "errors" "syscall" "time" "unsafe" ) // GetVariantDate converts COM Variant Time value to Go time.Time. func GetVariantDate(value uint64) (time.Time, error) { var st syscall.Systemtime r, _, _ := procVariantTimeToSystemTime.Call(uintptr(value), uintptr(unsafe.Pointer(&st))) if r != 0 { return time.Date(int(st.Year), time.Month(st.Month), int(st.Day), int(st.Hour), int(st.Minute), int(st.Second), int(st.Milliseconds/1000), time.UTC), nil } return time.Now(), errors.New("Could not convert to time, passing current time.") } ================================================ FILE: vendor/github.com/go-ole/go-ole/variant_date_arm.go ================================================ // +build windows,arm package ole import ( "errors" "syscall" "time" "unsafe" ) // GetVariantDate converts COM Variant Time value to Go time.Time. func GetVariantDate(value uint64) (time.Time, error) { var st syscall.Systemtime v1 := uint32(value) v2 := uint32(value >> 32) r, _, _ := procVariantTimeToSystemTime.Call(uintptr(v1), uintptr(v2), uintptr(unsafe.Pointer(&st))) if r != 0 { return time.Date(int(st.Year), time.Month(st.Month), int(st.Day), int(st.Hour), int(st.Minute), int(st.Second), int(st.Milliseconds/1000), time.UTC), nil } return time.Now(), errors.New("Could not convert to time, passing current time.") } ================================================ FILE: vendor/github.com/go-ole/go-ole/variant_date_arm64.go ================================================ //go:build windows && arm64 // +build windows,arm64 package ole import ( "errors" "syscall" "time" "unsafe" ) // GetVariantDate converts COM Variant Time value to Go time.Time. func GetVariantDate(value uint64) (time.Time, error) { var st syscall.Systemtime v1 := uint32(value) v2 := uint32(value >> 32) r, _, _ := procVariantTimeToSystemTime.Call(uintptr(v1), uintptr(v2), uintptr(unsafe.Pointer(&st))) if r != 0 { return time.Date(int(st.Year), time.Month(st.Month), int(st.Day), int(st.Hour), int(st.Minute), int(st.Second), int(st.Milliseconds/1000), time.UTC), nil } return time.Now(), errors.New("Could not convert to time, passing current time.") } ================================================ FILE: vendor/github.com/go-ole/go-ole/variant_ppc64le.go ================================================ // +build ppc64le package ole type VARIANT struct { VT VT // 2 wReserved1 uint16 // 4 wReserved2 uint16 // 6 wReserved3 uint16 // 8 Val int64 // 16 _ [8]byte // 24 } ================================================ FILE: vendor/github.com/go-ole/go-ole/variant_s390x.go ================================================ // +build s390x package ole type VARIANT struct { VT VT // 2 wReserved1 uint16 // 4 wReserved2 uint16 // 6 wReserved3 uint16 // 8 Val int64 // 16 _ [8]byte // 24 } ================================================ FILE: vendor/github.com/go-ole/go-ole/vt_string.go ================================================ // generated by stringer -output vt_string.go -type VT; DO NOT EDIT package ole import "fmt" const ( _VT_name_0 = "VT_EMPTYVT_NULLVT_I2VT_I4VT_R4VT_R8VT_CYVT_DATEVT_BSTRVT_DISPATCHVT_ERRORVT_BOOLVT_VARIANTVT_UNKNOWNVT_DECIMAL" _VT_name_1 = "VT_I1VT_UI1VT_UI2VT_UI4VT_I8VT_UI8VT_INTVT_UINTVT_VOIDVT_HRESULTVT_PTRVT_SAFEARRAYVT_CARRAYVT_USERDEFINEDVT_LPSTRVT_LPWSTR" _VT_name_2 = "VT_RECORDVT_INT_PTRVT_UINT_PTR" _VT_name_3 = "VT_FILETIMEVT_BLOBVT_STREAMVT_STORAGEVT_STREAMED_OBJECTVT_STORED_OBJECTVT_BLOB_OBJECTVT_CFVT_CLSID" _VT_name_4 = "VT_BSTR_BLOBVT_VECTOR" _VT_name_5 = "VT_ARRAY" _VT_name_6 = "VT_BYREF" _VT_name_7 = "VT_RESERVED" _VT_name_8 = "VT_ILLEGAL" ) var ( _VT_index_0 = [...]uint8{0, 8, 15, 20, 25, 30, 35, 40, 47, 54, 65, 73, 80, 90, 100, 110} _VT_index_1 = [...]uint8{0, 5, 11, 17, 23, 28, 34, 40, 47, 54, 64, 70, 82, 91, 105, 113, 122} _VT_index_2 = [...]uint8{0, 9, 19, 30} _VT_index_3 = [...]uint8{0, 11, 18, 27, 37, 55, 71, 85, 90, 98} _VT_index_4 = [...]uint8{0, 12, 21} _VT_index_5 = [...]uint8{0, 8} _VT_index_6 = [...]uint8{0, 8} _VT_index_7 = [...]uint8{0, 11} _VT_index_8 = [...]uint8{0, 10} ) func (i VT) String() string { switch { case 0 <= i && i <= 14: return _VT_name_0[_VT_index_0[i]:_VT_index_0[i+1]] case 16 <= i && i <= 31: i -= 16 return _VT_name_1[_VT_index_1[i]:_VT_index_1[i+1]] case 36 <= i && i <= 38: i -= 36 return _VT_name_2[_VT_index_2[i]:_VT_index_2[i+1]] case 64 <= i && i <= 72: i -= 64 return _VT_name_3[_VT_index_3[i]:_VT_index_3[i+1]] case 4095 <= i && i <= 4096: i -= 4095 return _VT_name_4[_VT_index_4[i]:_VT_index_4[i+1]] case i == 8192: return _VT_name_5 case i == 16384: return _VT_name_6 case i == 32768: return _VT_name_7 case i == 65535: return _VT_name_8 default: return fmt.Sprintf("VT(%d)", i) } } ================================================ FILE: vendor/github.com/go-ole/go-ole/winrt.go ================================================ // +build windows package ole import ( "reflect" "syscall" "unicode/utf8" "unsafe" ) var ( procRoInitialize = modcombase.NewProc("RoInitialize") procRoActivateInstance = modcombase.NewProc("RoActivateInstance") procRoGetActivationFactory = modcombase.NewProc("RoGetActivationFactory") procWindowsCreateString = modcombase.NewProc("WindowsCreateString") procWindowsDeleteString = modcombase.NewProc("WindowsDeleteString") procWindowsGetStringRawBuffer = modcombase.NewProc("WindowsGetStringRawBuffer") ) func RoInitialize(thread_type uint32) (err error) { hr, _, _ := procRoInitialize.Call(uintptr(thread_type)) if hr != 0 { err = NewError(hr) } return } func RoActivateInstance(clsid string) (ins *IInspectable, err error) { hClsid, err := NewHString(clsid) if err != nil { return nil, err } defer DeleteHString(hClsid) hr, _, _ := procRoActivateInstance.Call( uintptr(unsafe.Pointer(hClsid)), uintptr(unsafe.Pointer(&ins))) if hr != 0 { err = NewError(hr) } return } func RoGetActivationFactory(clsid string, iid *GUID) (ins *IInspectable, err error) { hClsid, err := NewHString(clsid) if err != nil { return nil, err } defer DeleteHString(hClsid) hr, _, _ := procRoGetActivationFactory.Call( uintptr(unsafe.Pointer(hClsid)), uintptr(unsafe.Pointer(iid)), uintptr(unsafe.Pointer(&ins))) if hr != 0 { err = NewError(hr) } return } // HString is handle string for pointers. type HString uintptr // NewHString returns a new HString for Go string. func NewHString(s string) (hstring HString, err error) { u16 := syscall.StringToUTF16Ptr(s) len := uint32(utf8.RuneCountInString(s)) hr, _, _ := procWindowsCreateString.Call( uintptr(unsafe.Pointer(u16)), uintptr(len), uintptr(unsafe.Pointer(&hstring))) if hr != 0 { err = NewError(hr) } return } // DeleteHString deletes HString. func DeleteHString(hstring HString) (err error) { hr, _, _ := procWindowsDeleteString.Call(uintptr(hstring)) if hr != 0 { err = NewError(hr) } return } // String returns Go string value of HString. func (h HString) String() string { var u16buf uintptr var u16len uint32 u16buf, _, _ = procWindowsGetStringRawBuffer.Call( uintptr(h), uintptr(unsafe.Pointer(&u16len))) u16hdr := reflect.SliceHeader{Data: u16buf, Len: int(u16len), Cap: int(u16len)} u16 := *(*[]uint16)(unsafe.Pointer(&u16hdr)) return syscall.UTF16ToString(u16) } ================================================ FILE: vendor/github.com/go-ole/go-ole/winrt_doc.go ================================================ // +build !windows package ole // RoInitialize func RoInitialize(thread_type uint32) (err error) { return NewError(E_NOTIMPL) } // RoActivateInstance func RoActivateInstance(clsid string) (ins *IInspectable, err error) { return nil, NewError(E_NOTIMPL) } // RoGetActivationFactory func RoGetActivationFactory(clsid string, iid *GUID) (ins *IInspectable, err error) { return nil, NewError(E_NOTIMPL) } // HString is handle string for pointers. type HString uintptr // NewHString returns a new HString for Go string. func NewHString(s string) (hstring HString, err error) { return HString(uintptr(0)), NewError(E_NOTIMPL) } // DeleteHString deletes HString. func DeleteHString(hstring HString) (err error) { return NewError(E_NOTIMPL) } // String returns Go string value of HString. func (h HString) String() string { return "" } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/.gitignore ================================================ .DS_Store .DS_Store? ._* .Spotlight-V100 .Trashes Icon? ehthumbs.db Thumbs.db .idea ================================================ FILE: vendor/github.com/go-sql-driver/mysql/AUTHORS ================================================ # This is the official list of Go-MySQL-Driver authors for copyright purposes. # If you are submitting a patch, please add your name or the name of the # organization which holds the copyright to this list in alphabetical order. # Names should be added to this file as # Name # The email address is not required for organizations. # Please keep the list sorted. # Individual Persons Aaron Hopkins Achille Roussel Aidan Alex Snast Alexey Palazhchenko Andrew Reid Animesh Ray Arne Hormann Ariel Mashraki Asta Xie Brian Hendriks Bulat Gaifullin Caine Jette Carlos Nieto Chris Kirkland Chris Moos Craig Wilson Daemonxiao <735462752 at qq.com> Daniel Montoya Daniel Nichter Daniël van Eeden Dave Protasowski DisposaBoy Egor Smolyakov Erwan Martin Evan Elias Evan Shaw Frederick Mayle Gustavo Kristic Gusted Hajime Nakagami Hanno Braun Henri Yandell Hirotaka Yamamoto Huyiguang ICHINOSE Shogo Ilia Cimpoes INADA Naoki Jacek Szwec James Harr Janek Vedock Jason Ng Jean-Yves Pellé Jeff Hodges Jeffrey Charles Jennifer Purevsuren Jerome Meyer Jiajia Zhong Jian Zhen Joshua Prunier Julien Lefevre Julien Schmidt Justin Li Justin Nuß Kamil Dziedzic Kei Kamikawa Kevin Malachowski Kieron Woodhouse Lance Tian Lennart Rudolph Leonardo YongUk Kim Linh Tran Tuan Lion Yang Luca Looz Lucas Liu Lunny Xiao Luke Scott Maciej Zimnoch Michael Woolnough Nathanial Murphy Nicola Peduzzi Oliver Bone Olivier Mengué oscarzhao Paul Bonser Paulius Lozys Peter Schultz Phil Porada Rebecca Chin Reed Allman Richard Wilkes Robert Russell Runrioter Wung Samantha Frank Santhosh Kumar Tekuri Sho Iizuka Sho Ikeda Shuode Li Simon J Mudd Soroush Pour Stan Putrya Stanley Gunawan Steven Hartland Tan Jinhua <312841925 at qq.com> Tetsuro Aoki Thomas Wodarek Tim Ruffles Tom Jenkinson Vladimir Kovpak Vladyslav Zhelezniak Xiangyu Hu Xiaobing Jiang Xiuming Chen Xuehong Chan Zhang Xiang Zhenye Xie Zhixin Wen Ziheng Lyu # Organizations Barracuda Networks, Inc. Counting Ltd. DigitalOcean Inc. Dolthub Inc. dyves labs AG Facebook Inc. GitHub Inc. Google Inc. InfoSum Ltd. Keybase Inc. Microsoft Corp. Multiplay Ltd. Percona LLC PingCAP Inc. Pivotal Inc. Shattered Silicon Ltd. Stripe Inc. Zendesk Inc. ================================================ FILE: vendor/github.com/go-sql-driver/mysql/CHANGELOG.md ================================================ ## Version 1.8.1 (2024-03-26) Bugfixes: - fix race condition when context is canceled in [#1562](https://github.com/go-sql-driver/mysql/pull/1562) and [#1570](https://github.com/go-sql-driver/mysql/pull/1570) ## Version 1.8.0 (2024-03-09) Major Changes: - Use `SET NAMES charset COLLATE collation`. by @methane in [#1437](https://github.com/go-sql-driver/mysql/pull/1437) - Older go-mysql-driver used `collation_id` in the handshake packet. But it caused collation mismatch in some situation. - If you don't specify charset nor collation, go-mysql-driver sends `SET NAMES utf8mb4` for new connection. This uses server's default collation for utf8mb4. - If you specify charset, go-mysql-driver sends `SET NAMES `. This uses the server's default collation for ``. - If you specify collation and/or charset, go-mysql-driver sends `SET NAMES charset COLLATE collation`. - PathEscape dbname in DSN. by @methane in [#1432](https://github.com/go-sql-driver/mysql/pull/1432) - This is backward incompatible in rare case. Check your DSN. - Drop Go 1.13-17 support by @methane in [#1420](https://github.com/go-sql-driver/mysql/pull/1420) - Use Go 1.18+ - Parse numbers on text protocol too by @methane in [#1452](https://github.com/go-sql-driver/mysql/pull/1452) - When text protocol is used, go-mysql-driver passed bare `[]byte` to database/sql for avoid unnecessary allocation and conversion. - If user specified `*any` to `Scan()`, database/sql passed the `[]byte` into the target variable. - This confused users because most user doesn't know when text/binary protocol used. - go-mysql-driver 1.8 converts integer/float values into int64/double even in text protocol. This doesn't increase allocation compared to `[]byte` and conversion cost is negatable. - New options start using the Functional Option Pattern to avoid increasing technical debt in the Config object. Future version may introduce Functional Option for existing options, but not for now. - Make TimeTruncate functional option by @methane in [1552](https://github.com/go-sql-driver/mysql/pull/1552) - Add BeforeConnect callback to configuration object by @ItalyPaleAle in [#1469](https://github.com/go-sql-driver/mysql/pull/1469) Other changes: - Adding DeregisterDialContext to prevent memory leaks with dialers we don't need anymore by @jypelle in https://github.com/go-sql-driver/mysql/pull/1422 - Make logger configurable per connection by @frozenbonito in https://github.com/go-sql-driver/mysql/pull/1408 - Fix ColumnType.DatabaseTypeName for mediumint unsigned by @evanelias in https://github.com/go-sql-driver/mysql/pull/1428 - Add connection attributes by @Daemonxiao in https://github.com/go-sql-driver/mysql/pull/1389 - Stop `ColumnTypeScanType()` from returning `sql.RawBytes` by @methane in https://github.com/go-sql-driver/mysql/pull/1424 - Exec() now provides access to status of multiple statements. by @mherr-google in https://github.com/go-sql-driver/mysql/pull/1309 - Allow to change (or disable) the default driver name for registration by @dolmen in https://github.com/go-sql-driver/mysql/pull/1499 - Add default connection attribute '_server_host' by @oblitorum in https://github.com/go-sql-driver/mysql/pull/1506 - QueryUnescape DSN ConnectionAttribute value by @zhangyangyu in https://github.com/go-sql-driver/mysql/pull/1470 - Add client_ed25519 authentication by @Gusted in https://github.com/go-sql-driver/mysql/pull/1518 ## Version 1.7.1 (2023-04-25) Changes: - bump actions/checkout@v3 and actions/setup-go@v3 (#1375) - Add go1.20 and mariadb10.11 to the testing matrix (#1403) - Increase default maxAllowedPacket size. (#1411) Bugfixes: - Use SET syntax as specified in the MySQL documentation (#1402) ## Version 1.7 (2022-11-29) Changes: - Drop support of Go 1.12 (#1211) - Refactoring `(*textRows).readRow` in a more clear way (#1230) - util: Reduce boundary check in escape functions. (#1316) - enhancement for mysqlConn handleAuthResult (#1250) New Features: - support Is comparison on MySQLError (#1210) - return unsigned in database type name when necessary (#1238) - Add API to express like a --ssl-mode=PREFERRED MySQL client (#1370) - Add SQLState to MySQLError (#1321) Bugfixes: - Fix parsing 0 year. (#1257) ## Version 1.6 (2021-04-01) Changes: - Migrate the CI service from travis-ci to GitHub Actions (#1176, #1183, #1190) - `NullTime` is deprecated (#960, #1144) - Reduce allocations when building SET command (#1111) - Performance improvement for time formatting (#1118) - Performance improvement for time parsing (#1098, #1113) New Features: - Implement `driver.Validator` interface (#1106, #1174) - Support returning `uint64` from `Valuer` in `ConvertValue` (#1143) - Add `json.RawMessage` for converter and prepared statement (#1059) - Interpolate `json.RawMessage` as `string` (#1058) - Implements `CheckNamedValue` (#1090) Bugfixes: - Stop rounding times (#1121, #1172) - Put zero filler into the SSL handshake packet (#1066) - Fix checking cancelled connections back into the connection pool (#1095) - Fix remove last 0 byte for mysql_old_password when password is empty (#1133) ## Version 1.5 (2020-01-07) Changes: - Dropped support Go 1.9 and lower (#823, #829, #886, #1016, #1017) - Improve buffer handling (#890) - Document potentially insecure TLS configs (#901) - Use a double-buffering scheme to prevent data races (#943) - Pass uint64 values without converting them to string (#838, #955) - Update collations and make utf8mb4 default (#877, #1054) - Make NullTime compatible with sql.NullTime in Go 1.13+ (#995) - Removed CloudSQL support (#993, #1007) - Add Go Module support (#1003) New Features: - Implement support of optional TLS (#900) - Check connection liveness (#934, #964, #997, #1048, #1051, #1052) - Implement Connector Interface (#941, #958, #1020, #1035) Bugfixes: - Mark connections as bad on error during ping (#875) - Mark connections as bad on error during dial (#867) - Fix connection leak caused by rapid context cancellation (#1024) - Mark connections as bad on error during Conn.Prepare (#1030) ## Version 1.4.1 (2018-11-14) Bugfixes: - Fix TIME format for binary columns (#818) - Fix handling of empty auth plugin names (#835) - Fix caching_sha2_password with empty password (#826) - Fix canceled context broke mysqlConn (#862) - Fix OldAuthSwitchRequest support (#870) - Fix Auth Response packet for cleartext password (#887) ## Version 1.4 (2018-06-03) Changes: - Documentation fixes (#530, #535, #567) - Refactoring (#575, #579, #580, #581, #603, #615, #704) - Cache column names (#444) - Sort the DSN parameters in DSNs generated from a config (#637) - Allow native password authentication by default (#644) - Use the default port if it is missing in the DSN (#668) - Removed the `strict` mode (#676) - Do not query `max_allowed_packet` by default (#680) - Dropped support Go 1.6 and lower (#696) - Updated `ConvertValue()` to match the database/sql/driver implementation (#760) - Document the usage of `0000-00-00T00:00:00` as the time.Time zero value (#783) - Improved the compatibility of the authentication system (#807) New Features: - Multi-Results support (#537) - `rejectReadOnly` DSN option (#604) - `context.Context` support (#608, #612, #627, #761) - Transaction isolation level support (#619, #744) - Read-Only transactions support (#618, #634) - `NewConfig` function which initializes a config with default values (#679) - Implemented the `ColumnType` interfaces (#667, #724) - Support for custom string types in `ConvertValue` (#623) - Implemented `NamedValueChecker`, improving support for uint64 with high bit set (#690, #709, #710) - `caching_sha2_password` authentication plugin support (#794, #800, #801, #802) - Implemented `driver.SessionResetter` (#779) - `sha256_password` authentication plugin support (#808) Bugfixes: - Use the DSN hostname as TLS default ServerName if `tls=true` (#564, #718) - Fixed LOAD LOCAL DATA INFILE for empty files (#590) - Removed columns definition cache since it sometimes cached invalid data (#592) - Don't mutate registered TLS configs (#600) - Make RegisterTLSConfig concurrency-safe (#613) - Handle missing auth data in the handshake packet correctly (#646) - Do not retry queries when data was written to avoid data corruption (#302, #736) - Cache the connection pointer for error handling before invalidating it (#678) - Fixed imports for appengine/cloudsql (#700) - Fix sending STMT_LONG_DATA for 0 byte data (#734) - Set correct capacity for []bytes read from length-encoded strings (#766) - Make RegisterDial concurrency-safe (#773) ## Version 1.3 (2016-12-01) Changes: - Go 1.1 is no longer supported - Use decimals fields in MySQL to format time types (#249) - Buffer optimizations (#269) - TLS ServerName defaults to the host (#283) - Refactoring (#400, #410, #437) - Adjusted documentation for second generation CloudSQL (#485) - Documented DSN system var quoting rules (#502) - Made statement.Close() calls idempotent to avoid errors in Go 1.6+ (#512) New Features: - Enable microsecond resolution on TIME, DATETIME and TIMESTAMP (#249) - Support for returning table alias on Columns() (#289, #359, #382) - Placeholder interpolation, can be activated with the DSN parameter `interpolateParams=true` (#309, #318, #490) - Support for uint64 parameters with high bit set (#332, #345) - Cleartext authentication plugin support (#327) - Exported ParseDSN function and the Config struct (#403, #419, #429) - Read / Write timeouts (#401) - Support for JSON field type (#414) - Support for multi-statements and multi-results (#411, #431) - DSN parameter to set the driver-side max_allowed_packet value manually (#489) - Native password authentication plugin support (#494, #524) Bugfixes: - Fixed handling of queries without columns and rows (#255) - Fixed a panic when SetKeepAlive() failed (#298) - Handle ERR packets while reading rows (#321) - Fixed reading NULL length-encoded integers in MySQL 5.6+ (#349) - Fixed absolute paths support in LOAD LOCAL DATA INFILE (#356) - Actually zero out bytes in handshake response (#378) - Fixed race condition in registering LOAD DATA INFILE handler (#383) - Fixed tests with MySQL 5.7.9+ (#380) - QueryUnescape TLS config names (#397) - Fixed "broken pipe" error by writing to closed socket (#390) - Fixed LOAD LOCAL DATA INFILE buffering (#424) - Fixed parsing of floats into float64 when placeholders are used (#434) - Fixed DSN tests with Go 1.7+ (#459) - Handle ERR packets while waiting for EOF (#473) - Invalidate connection on error while discarding additional results (#513) - Allow terminating packets of length 0 (#516) ## Version 1.2 (2014-06-03) Changes: - We switched back to a "rolling release". `go get` installs the current master branch again - Version v1 of the driver will not be maintained anymore. Go 1.0 is no longer supported by this driver - Exported errors to allow easy checking from application code - Enabled TCP Keepalives on TCP connections - Optimized INFILE handling (better buffer size calculation, lazy init, ...) - The DSN parser also checks for a missing separating slash - Faster binary date / datetime to string formatting - Also exported the MySQLWarning type - mysqlConn.Close returns the first error encountered instead of ignoring all errors - writePacket() automatically writes the packet size to the header - readPacket() uses an iterative approach instead of the recursive approach to merge split packets New Features: - `RegisterDial` allows the usage of a custom dial function to establish the network connection - Setting the connection collation is possible with the `collation` DSN parameter. This parameter should be preferred over the `charset` parameter - Logging of critical errors is configurable with `SetLogger` - Google CloudSQL support Bugfixes: - Allow more than 32 parameters in prepared statements - Various old_password fixes - Fixed TestConcurrent test to pass Go's race detection - Fixed appendLengthEncodedInteger for large numbers - Renamed readLengthEnodedString to readLengthEncodedString and skipLengthEnodedString to skipLengthEncodedString (fixed typo) ## Version 1.1 (2013-11-02) Changes: - Go-MySQL-Driver now requires Go 1.1 - Connections now use the collation `utf8_general_ci` by default. Adding `&charset=UTF8` to the DSN should not be necessary anymore - Made closing rows and connections error tolerant. This allows for example deferring rows.Close() without checking for errors - `[]byte(nil)` is now treated as a NULL value. Before, it was treated like an empty string / `[]byte("")` - DSN parameter values must now be url.QueryEscape'ed. This allows text values to contain special characters, such as '&'. - Use the IO buffer also for writing. This results in zero allocations (by the driver) for most queries - Optimized the buffer for reading - stmt.Query now caches column metadata - New Logo - Changed the copyright header to include all contributors - Improved the LOAD INFILE documentation - The driver struct is now exported to make the driver directly accessible - Refactored the driver tests - Added more benchmarks and moved all to a separate file - Other small refactoring New Features: - Added *old_passwords* support: Required in some cases, but must be enabled by adding `allowOldPasswords=true` to the DSN since it is insecure - Added a `clientFoundRows` parameter: Return the number of matching rows instead of the number of rows changed on UPDATEs - Added TLS/SSL support: Use a TLS/SSL encrypted connection to the server. Custom TLS configs can be registered and used Bugfixes: - Fixed MySQL 4.1 support: MySQL 4.1 sends packets with lengths which differ from the specification - Convert to DB timezone when inserting `time.Time` - Split packets (more than 16MB) are now merged correctly - Fixed false positive `io.EOF` errors when the data was fully read - Avoid panics on reuse of closed connections - Fixed empty string producing false nil values - Fixed sign byte for positive TIME fields ## Version 1.0 (2013-05-14) Initial Release ================================================ FILE: vendor/github.com/go-sql-driver/mysql/LICENSE ================================================ Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. ================================================ FILE: vendor/github.com/go-sql-driver/mysql/README.md ================================================ # Go-MySQL-Driver A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) package ![Go-MySQL-Driver logo](https://raw.github.com/wiki/go-sql-driver/mysql/gomysql_m.png "Golang Gopher holding the MySQL Dolphin") --------------------------------------- * [Features](#features) * [Requirements](#requirements) * [Installation](#installation) * [Usage](#usage) * [DSN (Data Source Name)](#dsn-data-source-name) * [Password](#password) * [Protocol](#protocol) * [Address](#address) * [Parameters](#parameters) * [Examples](#examples) * [Connection pool and timeouts](#connection-pool-and-timeouts) * [context.Context Support](#contextcontext-support) * [ColumnType Support](#columntype-support) * [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support) * [time.Time support](#timetime-support) * [Unicode support](#unicode-support) * [Testing / Development](#testing--development) * [License](#license) --------------------------------------- ## Features * Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance") * Native Go implementation. No C-bindings, just pure Go * Connections over TCP/IPv4, TCP/IPv6, Unix domain sockets or [custom protocols](https://godoc.org/github.com/go-sql-driver/mysql#DialFunc) * Automatic handling of broken connections * Automatic Connection Pooling *(by database/sql package)* * Supports queries larger than 16MB * Full [`sql.RawBytes`](https://golang.org/pkg/database/sql/#RawBytes) support. * Intelligent `LONG DATA` handling in prepared statements * Secure `LOAD DATA LOCAL INFILE` support with file allowlisting and `io.Reader` support * Optional `time.Time` parsing * Optional placeholder interpolation ## Requirements * Go 1.19 or higher. We aim to support the 3 latest versions of Go. * MySQL (5.7+) and MariaDB (10.3+) are supported. * [TiDB](https://github.com/pingcap/tidb) is supported by PingCAP. * Do not ask questions about TiDB in our issue tracker or forum. * [Document](https://docs.pingcap.com/tidb/v6.1/dev-guide-sample-application-golang) * [Forum](https://ask.pingcap.com/) * go-mysql would work with Percona Server, Google CloudSQL or Sphinx (2.2.3+). * Maintainers won't support them. Do not expect issues are investigated and resolved by maintainers. * Investigate issues yourself and please send a pull request to fix it. --------------------------------------- ## Installation Simple install the package to your [$GOPATH](https://github.com/golang/go/wiki/GOPATH "GOPATH") with the [go tool](https://golang.org/cmd/go/ "go command") from shell: ```bash go get -u github.com/go-sql-driver/mysql ``` Make sure [Git is installed](https://git-scm.com/downloads) on your machine and in your system's `PATH`. ## Usage _Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface. You only need to import the driver and can use the full [`database/sql`](https://golang.org/pkg/database/sql/) API then. Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name) as `dataSourceName`: ```go import ( "database/sql" "time" _ "github.com/go-sql-driver/mysql" ) // ... db, err := sql.Open("mysql", "user:password@/dbname") if err != nil { panic(err) } // See "Important settings" section. db.SetConnMaxLifetime(time.Minute * 3) db.SetMaxOpenConns(10) db.SetMaxIdleConns(10) ``` [Examples are available in our Wiki](https://github.com/go-sql-driver/mysql/wiki/Examples "Go-MySQL-Driver Examples"). ### Important settings `db.SetConnMaxLifetime()` is required to ensure connections are closed by the driver safely before connection is closed by MySQL server, OS, or other middlewares. Since some middlewares close idle connections by 5 minutes, we recommend timeout shorter than 5 minutes. This setting helps load balancing and changing system variables too. `db.SetMaxOpenConns()` is highly recommended to limit the number of connection used by the application. There is no recommended limit number because it depends on application and MySQL server. `db.SetMaxIdleConns()` is recommended to be set same to `db.SetMaxOpenConns()`. When it is smaller than `SetMaxOpenConns()`, connections can be opened and closed much more frequently than you expect. Idle connections can be closed by the `db.SetConnMaxLifetime()`. If you want to close idle connections more rapidly, you can use `db.SetConnMaxIdleTime()` since Go 1.15. ### DSN (Data Source Name) The Data Source Name has a common format, like e.g. [PEAR DB](http://pear.php.net/manual/en/package.database.db.intro-dsn.php) uses it, but without type-prefix (optional parts marked by squared brackets): ``` [username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN] ``` A DSN in its fullest form: ``` username:password@protocol(address)/dbname?param=value ``` Except for the databasename, all values are optional. So the minimal DSN is: ``` /dbname ``` If you do not want to preselect a database, leave `dbname` empty: ``` / ``` This has the same effect as an empty DSN string: ``` ``` `dbname` is escaped by [PathEscape()](https://pkg.go.dev/net/url#PathEscape) since v1.8.0. If your database name is `dbname/withslash`, it becomes: ``` /dbname%2Fwithslash ``` Alternatively, [Config.FormatDSN](https://godoc.org/github.com/go-sql-driver/mysql#Config.FormatDSN) can be used to create a DSN string by filling a struct. #### Password Passwords can consist of any character. Escaping is **not** necessary. #### Protocol See [net.Dial](https://golang.org/pkg/net/#Dial) for more information which networks are available. In general you should use a Unix domain socket if available and TCP otherwise for best performance. #### Address For TCP and UDP networks, addresses have the form `host[:port]`. If `port` is omitted, the default port will be used. If `host` is a literal IPv6 address, it must be enclosed in square brackets. The functions [net.JoinHostPort](https://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](https://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form. For Unix domain sockets the address is the absolute path to the MySQL-Server-socket, e.g. `/var/run/mysqld/mysqld.sock` or `/tmp/mysql.sock`. #### Parameters *Parameters are case-sensitive!* Notice that any of `true`, `TRUE`, `True` or `1` is accepted to stand for a true boolean value. Not surprisingly, false can be specified as any of: `false`, `FALSE`, `False` or `0`. ##### `allowAllFiles` ``` Type: bool Valid Values: true, false Default: false ``` `allowAllFiles=true` disables the file allowlist for `LOAD DATA LOCAL INFILE` and allows *all* files. [*Might be insecure!*](https://dev.mysql.com/doc/refman/8.0/en/load-data.html#load-data-local) ##### `allowCleartextPasswords` ``` Type: bool Valid Values: true, false Default: false ``` `allowCleartextPasswords=true` allows using the [cleartext client side plugin](https://dev.mysql.com/doc/en/cleartext-pluggable-authentication.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network. ##### `allowFallbackToPlaintext` ``` Type: bool Valid Values: true, false Default: false ``` `allowFallbackToPlaintext=true` acts like a `--ssl-mode=PREFERRED` MySQL client as described in [Command Options for Connecting to the Server](https://dev.mysql.com/doc/refman/5.7/en/connection-options.html#option_general_ssl-mode) ##### `allowNativePasswords` ``` Type: bool Valid Values: true, false Default: true ``` `allowNativePasswords=false` disallows the usage of MySQL native password method. ##### `allowOldPasswords` ``` Type: bool Valid Values: true, false Default: false ``` `allowOldPasswords=true` allows the usage of the insecure old password method. This should be avoided, but is necessary in some cases. See also [the old_passwords wiki page](https://github.com/go-sql-driver/mysql/wiki/old_passwords). ##### `charset` ``` Type: string Valid Values: Default: none ``` Sets the charset used for client-server interaction (`"SET NAMES "`). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset fails. This enables for example support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`). See also [Unicode Support](#unicode-support). ##### `checkConnLiveness` ``` Type: bool Valid Values: true, false Default: true ``` On supported platforms connections retrieved from the connection pool are checked for liveness before using them. If the check fails, the respective connection is marked as bad and the query retried with another connection. `checkConnLiveness=false` disables this liveness check of connections. ##### `collation` ``` Type: string Valid Values: Default: utf8mb4_general_ci ``` Sets the collation used for client-server interaction on connection. In contrast to `charset`, `collation` does not issue additional queries. If the specified collation is unavailable on the target server, the connection will fail. A list of valid charsets for a server is retrievable with `SHOW COLLATION`. The default collation (`utf8mb4_general_ci`) is supported from MySQL 5.5. You should use an older collation (e.g. `utf8_general_ci`) for older MySQL. Collations for charset "ucs2", "utf16", "utf16le", and "utf32" can not be used ([ref](https://dev.mysql.com/doc/refman/5.7/en/charset-connection.html#charset-connection-impermissible-client-charset)). See also [Unicode Support](#unicode-support). ##### `clientFoundRows` ``` Type: bool Valid Values: true, false Default: false ``` `clientFoundRows=true` causes an UPDATE to return the number of matching rows instead of the number of rows changed. ##### `columnsWithAlias` ``` Type: bool Valid Values: true, false Default: false ``` When `columnsWithAlias` is true, calls to `sql.Rows.Columns()` will return the table alias and the column name separated by a dot. For example: ``` SELECT u.id FROM users as u ``` will return `u.id` instead of just `id` if `columnsWithAlias=true`. ##### `interpolateParams` ``` Type: bool Valid Values: true, false Default: false ``` If `interpolateParams` is true, placeholders (`?`) in calls to `db.Query()` and `db.Exec()` are interpolated into a single query string with given parameters. This reduces the number of roundtrips, since the driver has to prepare a statement, execute it with given parameters and close the statement again with `interpolateParams=false`. *This can not be used together with the multibyte encodings BIG5, CP932, GB2312, GBK or SJIS. These are rejected as they may [introduce a SQL injection vulnerability](http://stackoverflow.com/a/12118602/3430118)!* ##### `loc` ``` Type: string Valid Values: Default: UTC ``` Sets the location for time.Time values (when using `parseTime=true`). *"Local"* sets the system's location. See [time.LoadLocation](https://golang.org/pkg/time/#LoadLocation) for details. Note that this sets the location for time.Time values but does not change MySQL's [time_zone setting](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html). For that see the [time_zone system variable](#system-variables), which can also be set as a DSN parameter. Please keep in mind, that param values must be [url.QueryEscape](https://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`. ##### `timeTruncate` ``` Type: duration Default: 0 ``` [Truncate time values](https://pkg.go.dev/time#Duration.Truncate) to the specified duration. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*. ##### `maxAllowedPacket` ``` Type: decimal number Default: 64*1024*1024 ``` Max packet size allowed in bytes. The default value is 64 MiB and should be adjusted to match the server settings. `maxAllowedPacket=0` can be used to automatically fetch the `max_allowed_packet` variable from server *on every connection*. ##### `multiStatements` ``` Type: bool Valid Values: true, false Default: false ``` Allow multiple statements in one query. This can be used to bach multiple queries. Use [Rows.NextResultSet()](https://pkg.go.dev/database/sql#Rows.NextResultSet) to get result of the second and subsequent queries. When `multiStatements` is used, `?` parameters must only be used in the first statement. [interpolateParams](#interpolateparams) can be used to avoid this limitation unless prepared statement is used explicitly. It's possible to access the last inserted ID and number of affected rows for multiple statements by using `sql.Conn.Raw()` and the `mysql.Result`. For example: ```go conn, _ := db.Conn(ctx) conn.Raw(func(conn any) error { ex := conn.(driver.Execer) res, err := ex.Exec(` UPDATE point SET x = 1 WHERE y = 2; UPDATE point SET x = 2 WHERE y = 3; `, nil) // Both slices have 2 elements. log.Print(res.(mysql.Result).AllRowsAffected()) log.Print(res.(mysql.Result).AllLastInsertIds()) }) ``` ##### `parseTime` ``` Type: bool Valid Values: true, false Default: false ``` `parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string` The date or datetime like `0000-00-00 00:00:00` is converted into zero value of `time.Time`. ##### `readTimeout` ``` Type: duration Default: 0 ``` I/O read timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*. ##### `rejectReadOnly` ``` Type: bool Valid Values: true, false Default: false ``` `rejectReadOnly=true` causes the driver to reject read-only connections. This is for a possible race condition during an automatic failover, where the mysql client gets connected to a read-only replica after the failover. Note that this should be a fairly rare case, as an automatic failover normally happens when the primary is down, and the race condition shouldn't happen unless it comes back up online as soon as the failover is kicked off. On the other hand, when this happens, a MySQL application can get stuck on a read-only connection until restarted. It is however fairly easy to reproduce, for example, using a manual failover on AWS Aurora's MySQL-compatible cluster. If you are not relying on read-only transactions to reject writes that aren't supposed to happen, setting this on some MySQL providers (such as AWS Aurora) is safer for failovers. Note that ERROR 1290 can be returned for a `read-only` server and this option will cause a retry for that error. However the same error number is used for some other cases. You should ensure your application will never cause an ERROR 1290 except for `read-only` mode when enabling this option. ##### `serverPubKey` ``` Type: string Valid Values: Default: none ``` Server public keys can be registered with [`mysql.RegisterServerPubKey`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterServerPubKey), which can then be used by the assigned name in the DSN. Public keys are used to transmit encrypted data, e.g. for authentication. If the server's public key is known, it should be set manually to avoid expensive and potentially insecure transmissions of the public key from the server to the client each time it is required. ##### `timeout` ``` Type: duration Default: OS default ``` Timeout for establishing connections, aka dial timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*. ##### `tls` ``` Type: bool / string Valid Values: true, false, skip-verify, preferred, Default: false ``` `tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side) or use `preferred` to use TLS only when advertised by the server. This is similar to `skip-verify`, but additionally allows a fallback to a connection which is not encrypted. Neither `skip-verify` nor `preferred` add any reliable security. You can use a custom TLS config after registering it with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig). ##### `writeTimeout` ``` Type: duration Default: 0 ``` I/O write timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*. ##### `connectionAttributes` ``` Type: comma-delimited string of user-defined "key:value" pairs Valid Values: (:,:,...) Default: none ``` [Connection attributes](https://dev.mysql.com/doc/refman/8.0/en/performance-schema-connection-attribute-tables.html) are key-value pairs that application programs can pass to the server at connect time. ##### System Variables Any other parameters are interpreted as system variables: * `=`: `SET =` * `=`: `SET =` * `=%27%27`: `SET =''` Rules: * The values for string variables must be quoted with `'`. * The values must also be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed! (which implies values of string variables must be wrapped with `%27`). Examples: * `autocommit=1`: `SET autocommit=1` * [`time_zone=%27Europe%2FParis%27`](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html): `SET time_zone='Europe/Paris'` * [`transaction_isolation=%27REPEATABLE-READ%27`](https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_transaction_isolation): `SET transaction_isolation='REPEATABLE-READ'` #### Examples ``` user@unix(/path/to/socket)/dbname ``` ``` root:pw@unix(/tmp/mysql.sock)/myDatabase?loc=Local ``` ``` user:password@tcp(localhost:5555)/dbname?tls=skip-verify&autocommit=true ``` Treat warnings as errors by setting the system variable [`sql_mode`](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html): ``` user:password@/dbname?sql_mode=TRADITIONAL ``` TCP via IPv6: ``` user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?timeout=90s&collation=utf8mb4_unicode_ci ``` TCP on a remote host, e.g. Amazon RDS: ``` id:password@tcp(your-amazonaws-uri.com:3306)/dbname ``` Google Cloud SQL on App Engine: ``` user:password@unix(/cloudsql/project-id:region-name:instance-name)/dbname ``` TCP using default port (3306) on localhost: ``` user:password@tcp/dbname?charset=utf8mb4,utf8&sys_var=esc%40ped ``` Use the default protocol (tcp) and host (localhost:3306): ``` user:password@/dbname ``` No Database preselected: ``` user:password@/ ``` ### Connection pool and timeouts The connection pool is managed by Go's database/sql package. For details on how to configure the size of the pool and how long connections stay in the pool see `*DB.SetMaxOpenConns`, `*DB.SetMaxIdleConns`, and `*DB.SetConnMaxLifetime` in the [database/sql documentation](https://golang.org/pkg/database/sql/). The read, write, and dial timeouts for each individual connection are configured with the DSN parameters [`readTimeout`](#readtimeout), [`writeTimeout`](#writetimeout), and [`timeout`](#timeout), respectively. ## `ColumnType` Support This driver supports the [`ColumnType` interface](https://golang.org/pkg/database/sql/#ColumnType) introduced in Go 1.8, with the exception of [`ColumnType.Length()`](https://golang.org/pkg/database/sql/#ColumnType.Length), which is currently not supported. All Unsigned database type names will be returned `UNSIGNED ` with `INT`, `TINYINT`, `SMALLINT`, `MEDIUMINT`, `BIGINT`. ## `context.Context` Support Go 1.8 added `database/sql` support for `context.Context`. This driver supports query timeouts and cancellation via contexts. See [context support in the database/sql package](https://golang.org/doc/go1.8#database_sql) for more details. ### `LOAD DATA LOCAL INFILE` support For this feature you need direct access to the package. Therefore you must change the import path (no `_`): ```go import "github.com/go-sql-driver/mysql" ``` Files must be explicitly allowed by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the allowlist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](https://dev.mysql.com/doc/refman/8.0/en/load-data.html#load-data-local)). To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::` then. Choose different names for different handlers and `DeregisterReaderHandler` when you don't need it anymore. See the [godoc of Go-MySQL-Driver](https://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details. ### `time.Time` support The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your program. However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical equivalent in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](https://golang.org/pkg/time/#Location) with the `loc` DSN parameter. **Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes). ### Unicode support Since version 1.5 Go-MySQL-Driver automatically uses the collation ` utf8mb4_general_ci` by default. Other charsets / collations can be set using the [`charset`](#charset) or [`collation`](#collation) DSN parameter. - When only the `charset` is specified, the `SET NAMES ` query is sent and the server's default collation is used. - When both the `charset` and `collation` are specified, the `SET NAMES COLLATE ` query is sent. - When only the `collation` is specified, the collation is specified in the protocol handshake and the `SET NAMES` query is not sent. This can save one roundtrip, but note that the server may ignore the specified collation silently and use the server's default charset/collation instead. See http://dev.mysql.com/doc/refman/8.0/en/charset-unicode.html for more details on MySQL's Unicode support. ## Testing / Development To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details. Go-MySQL-Driver is not feature-complete yet. Your help is very appreciated. If you want to contribute, you can work on an [open issue](https://github.com/go-sql-driver/mysql/issues?state=open) or review a [pull request](https://github.com/go-sql-driver/mysql/pulls). See the [Contribution Guidelines](https://github.com/go-sql-driver/mysql/blob/master/.github/CONTRIBUTING.md) for details. --------------------------------------- ## License Go-MySQL-Driver is licensed under the [Mozilla Public License Version 2.0](https://raw.github.com/go-sql-driver/mysql/master/LICENSE) Mozilla summarizes the license scope as follows: > MPL: The copyleft applies to any files containing MPLed code. That means: * You can **use** the **unchanged** source code both in private and commercially. * When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0). * You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged**. Please read the [MPL 2.0 FAQ](https://www.mozilla.org/en-US/MPL/2.0/FAQ/) if you have further questions regarding the license. You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE). ![Go Gopher and MySQL Dolphin](https://raw.github.com/wiki/go-sql-driver/mysql/go-mysql-driver_m.jpg "Golang Gopher transporting the MySQL Dolphin in a wheelbarrow") ================================================ FILE: vendor/github.com/go-sql-driver/mysql/atomic_bool.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package. // // Copyright 2022 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. //go:build go1.19 // +build go1.19 package mysql import "sync/atomic" /****************************************************************************** * Sync utils * ******************************************************************************/ type atomicBool = atomic.Bool ================================================ FILE: vendor/github.com/go-sql-driver/mysql/atomic_bool_go118.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package. // // Copyright 2022 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. //go:build !go1.19 // +build !go1.19 package mysql import "sync/atomic" /****************************************************************************** * Sync utils * ******************************************************************************/ // atomicBool is an implementation of atomic.Bool for older version of Go. // it is a wrapper around uint32 for usage as a boolean value with // atomic access. type atomicBool struct { _ noCopy value uint32 } // Load returns whether the current boolean value is true func (ab *atomicBool) Load() bool { return atomic.LoadUint32(&ab.value) > 0 } // Store sets the value of the bool regardless of the previous value func (ab *atomicBool) Store(value bool) { if value { atomic.StoreUint32(&ab.value, 1) } else { atomic.StoreUint32(&ab.value, 0) } } // Swap sets the value of the bool and returns the old value. func (ab *atomicBool) Swap(value bool) bool { if value { return atomic.SwapUint32(&ab.value, 1) > 0 } return atomic.SwapUint32(&ab.value, 0) > 0 } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/auth.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // // Copyright 2018 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. package mysql import ( "crypto/rand" "crypto/rsa" "crypto/sha1" "crypto/sha256" "crypto/sha512" "crypto/x509" "encoding/pem" "fmt" "sync" "filippo.io/edwards25519" ) // server pub keys registry var ( serverPubKeyLock sync.RWMutex serverPubKeyRegistry map[string]*rsa.PublicKey ) // RegisterServerPubKey registers a server RSA public key which can be used to // send data in a secure manner to the server without receiving the public key // in a potentially insecure way from the server first. // Registered keys can afterwards be used adding serverPubKey= to the DSN. // // Note: The provided rsa.PublicKey instance is exclusively owned by the driver // after registering it and may not be modified. // // data, err := os.ReadFile("mykey.pem") // if err != nil { // log.Fatal(err) // } // // block, _ := pem.Decode(data) // if block == nil || block.Type != "PUBLIC KEY" { // log.Fatal("failed to decode PEM block containing public key") // } // // pub, err := x509.ParsePKIXPublicKey(block.Bytes) // if err != nil { // log.Fatal(err) // } // // if rsaPubKey, ok := pub.(*rsa.PublicKey); ok { // mysql.RegisterServerPubKey("mykey", rsaPubKey) // } else { // log.Fatal("not a RSA public key") // } func RegisterServerPubKey(name string, pubKey *rsa.PublicKey) { serverPubKeyLock.Lock() if serverPubKeyRegistry == nil { serverPubKeyRegistry = make(map[string]*rsa.PublicKey) } serverPubKeyRegistry[name] = pubKey serverPubKeyLock.Unlock() } // DeregisterServerPubKey removes the public key registered with the given name. func DeregisterServerPubKey(name string) { serverPubKeyLock.Lock() if serverPubKeyRegistry != nil { delete(serverPubKeyRegistry, name) } serverPubKeyLock.Unlock() } func getServerPubKey(name string) (pubKey *rsa.PublicKey) { serverPubKeyLock.RLock() if v, ok := serverPubKeyRegistry[name]; ok { pubKey = v } serverPubKeyLock.RUnlock() return } // Hash password using pre 4.1 (old password) method // https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c type myRnd struct { seed1, seed2 uint32 } const myRndMaxVal = 0x3FFFFFFF // Pseudo random number generator func newMyRnd(seed1, seed2 uint32) *myRnd { return &myRnd{ seed1: seed1 % myRndMaxVal, seed2: seed2 % myRndMaxVal, } } // Tested to be equivalent to MariaDB's floating point variant // http://play.golang.org/p/QHvhd4qved // http://play.golang.org/p/RG0q4ElWDx func (r *myRnd) NextByte() byte { r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal return byte(uint64(r.seed1) * 31 / myRndMaxVal) } // Generate binary hash from byte string using insecure pre 4.1 method func pwHash(password []byte) (result [2]uint32) { var add uint32 = 7 var tmp uint32 result[0] = 1345345333 result[1] = 0x12345671 for _, c := range password { // skip spaces and tabs in password if c == ' ' || c == '\t' { continue } tmp = uint32(c) result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8) result[1] += (result[1] << 8) ^ result[0] add += tmp } // Remove sign bit (1<<31)-1) result[0] &= 0x7FFFFFFF result[1] &= 0x7FFFFFFF return } // Hash password using insecure pre 4.1 method func scrambleOldPassword(scramble []byte, password string) []byte { scramble = scramble[:8] hashPw := pwHash([]byte(password)) hashSc := pwHash(scramble) r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1]) var out [8]byte for i := range out { out[i] = r.NextByte() + 64 } mask := r.NextByte() for i := range out { out[i] ^= mask } return out[:] } // Hash password using 4.1+ method (SHA1) func scramblePassword(scramble []byte, password string) []byte { if len(password) == 0 { return nil } // stage1Hash = SHA1(password) crypt := sha1.New() crypt.Write([]byte(password)) stage1 := crypt.Sum(nil) // scrambleHash = SHA1(scramble + SHA1(stage1Hash)) // inner Hash crypt.Reset() crypt.Write(stage1) hash := crypt.Sum(nil) // outer Hash crypt.Reset() crypt.Write(scramble) crypt.Write(hash) scramble = crypt.Sum(nil) // token = scrambleHash XOR stage1Hash for i := range scramble { scramble[i] ^= stage1[i] } return scramble } // Hash password using MySQL 8+ method (SHA256) func scrambleSHA256Password(scramble []byte, password string) []byte { if len(password) == 0 { return nil } // XOR(SHA256(password), SHA256(SHA256(SHA256(password)), scramble)) crypt := sha256.New() crypt.Write([]byte(password)) message1 := crypt.Sum(nil) crypt.Reset() crypt.Write(message1) message1Hash := crypt.Sum(nil) crypt.Reset() crypt.Write(message1Hash) crypt.Write(scramble) message2 := crypt.Sum(nil) for i := range message1 { message1[i] ^= message2[i] } return message1 } func encryptPassword(password string, seed []byte, pub *rsa.PublicKey) ([]byte, error) { plain := make([]byte, len(password)+1) copy(plain, password) for i := range plain { j := i % len(seed) plain[i] ^= seed[j] } sha1 := sha1.New() return rsa.EncryptOAEP(sha1, rand.Reader, pub, plain, nil) } // authEd25519 does ed25519 authentication used by MariaDB. func authEd25519(scramble []byte, password string) ([]byte, error) { // Derived from https://github.com/MariaDB/server/blob/d8e6bb00888b1f82c031938f4c8ac5d97f6874c3/plugin/auth_ed25519/ref10/sign.c // Code style is from https://cs.opensource.google/go/go/+/refs/tags/go1.21.5:src/crypto/ed25519/ed25519.go;l=207 h := sha512.Sum512([]byte(password)) s, err := edwards25519.NewScalar().SetBytesWithClamping(h[:32]) if err != nil { return nil, err } A := (&edwards25519.Point{}).ScalarBaseMult(s) mh := sha512.New() mh.Write(h[32:]) mh.Write(scramble) messageDigest := mh.Sum(nil) r, err := edwards25519.NewScalar().SetUniformBytes(messageDigest) if err != nil { return nil, err } R := (&edwards25519.Point{}).ScalarBaseMult(r) kh := sha512.New() kh.Write(R.Bytes()) kh.Write(A.Bytes()) kh.Write(scramble) hramDigest := kh.Sum(nil) k, err := edwards25519.NewScalar().SetUniformBytes(hramDigest) if err != nil { return nil, err } S := k.MultiplyAdd(k, s, r) return append(R.Bytes(), S.Bytes()...), nil } func (mc *mysqlConn) sendEncryptedPassword(seed []byte, pub *rsa.PublicKey) error { enc, err := encryptPassword(mc.cfg.Passwd, seed, pub) if err != nil { return err } return mc.writeAuthSwitchPacket(enc) } func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) { switch plugin { case "caching_sha2_password": authResp := scrambleSHA256Password(authData, mc.cfg.Passwd) return authResp, nil case "mysql_old_password": if !mc.cfg.AllowOldPasswords { return nil, ErrOldPassword } if len(mc.cfg.Passwd) == 0 { return nil, nil } // Note: there are edge cases where this should work but doesn't; // this is currently "wontfix": // https://github.com/go-sql-driver/mysql/issues/184 authResp := append(scrambleOldPassword(authData[:8], mc.cfg.Passwd), 0) return authResp, nil case "mysql_clear_password": if !mc.cfg.AllowCleartextPasswords { return nil, ErrCleartextPassword } // http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html // http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html return append([]byte(mc.cfg.Passwd), 0), nil case "mysql_native_password": if !mc.cfg.AllowNativePasswords { return nil, ErrNativePassword } // https://dev.mysql.com/doc/internals/en/secure-password-authentication.html // Native password authentication only need and will need 20-byte challenge. authResp := scramblePassword(authData[:20], mc.cfg.Passwd) return authResp, nil case "sha256_password": if len(mc.cfg.Passwd) == 0 { return []byte{0}, nil } // unlike caching_sha2_password, sha256_password does not accept // cleartext password on unix transport. if mc.cfg.TLS != nil { // write cleartext auth packet return append([]byte(mc.cfg.Passwd), 0), nil } pubKey := mc.cfg.pubKey if pubKey == nil { // request public key from server return []byte{1}, nil } // encrypted password enc, err := encryptPassword(mc.cfg.Passwd, authData, pubKey) return enc, err case "client_ed25519": if len(authData) != 32 { return nil, ErrMalformPkt } return authEd25519(authData, mc.cfg.Passwd) default: mc.log("unknown auth plugin:", plugin) return nil, ErrUnknownPlugin } } func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error { // Read Result Packet authData, newPlugin, err := mc.readAuthResult() if err != nil { return err } // handle auth plugin switch, if requested if newPlugin != "" { // If CLIENT_PLUGIN_AUTH capability is not supported, no new cipher is // sent and we have to keep using the cipher sent in the init packet. if authData == nil { authData = oldAuthData } else { // copy data from read buffer to owned slice copy(oldAuthData, authData) } plugin = newPlugin authResp, err := mc.auth(authData, plugin) if err != nil { return err } if err = mc.writeAuthSwitchPacket(authResp); err != nil { return err } // Read Result Packet authData, newPlugin, err = mc.readAuthResult() if err != nil { return err } // Do not allow to change the auth plugin more than once if newPlugin != "" { return ErrMalformPkt } } switch plugin { // https://dev.mysql.com/blog-archive/preparing-your-community-connector-for-mysql-8-part-2-sha256/ case "caching_sha2_password": switch len(authData) { case 0: return nil // auth successful case 1: switch authData[0] { case cachingSha2PasswordFastAuthSuccess: if err = mc.resultUnchanged().readResultOK(); err == nil { return nil // auth successful } case cachingSha2PasswordPerformFullAuthentication: if mc.cfg.TLS != nil || mc.cfg.Net == "unix" { // write cleartext auth packet err = mc.writeAuthSwitchPacket(append([]byte(mc.cfg.Passwd), 0)) if err != nil { return err } } else { pubKey := mc.cfg.pubKey if pubKey == nil { // request public key from server data, err := mc.buf.takeSmallBuffer(4 + 1) if err != nil { return err } data[4] = cachingSha2PasswordRequestPublicKey err = mc.writePacket(data) if err != nil { return err } if data, err = mc.readPacket(); err != nil { return err } if data[0] != iAuthMoreData { return fmt.Errorf("unexpected resp from server for caching_sha2_password, perform full authentication") } // parse public key block, rest := pem.Decode(data[1:]) if block == nil { return fmt.Errorf("no pem data found, data: %s", rest) } pkix, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { return err } pubKey = pkix.(*rsa.PublicKey) } // send encrypted password err = mc.sendEncryptedPassword(oldAuthData, pubKey) if err != nil { return err } } return mc.resultUnchanged().readResultOK() default: return ErrMalformPkt } default: return ErrMalformPkt } case "sha256_password": switch len(authData) { case 0: return nil // auth successful default: block, _ := pem.Decode(authData) if block == nil { return fmt.Errorf("no Pem data found, data: %s", authData) } pub, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { return err } // send encrypted password err = mc.sendEncryptedPassword(oldAuthData, pub.(*rsa.PublicKey)) if err != nil { return err } return mc.resultUnchanged().readResultOK() } default: return nil // auth successful } return err } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/buffer.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // // Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. package mysql import ( "io" "net" "time" ) const defaultBufSize = 4096 const maxCachedBufSize = 256 * 1024 // A buffer which is used for both reading and writing. // This is possible since communication on each connection is synchronous. // In other words, we can't write and read simultaneously on the same connection. // The buffer is similar to bufio.Reader / Writer but zero-copy-ish // Also highly optimized for this particular use case. // This buffer is backed by two byte slices in a double-buffering scheme type buffer struct { buf []byte // buf is a byte buffer who's length and capacity are equal. nc net.Conn idx int length int timeout time.Duration dbuf [2][]byte // dbuf is an array with the two byte slices that back this buffer flipcnt uint // flipccnt is the current buffer counter for double-buffering } // newBuffer allocates and returns a new buffer. func newBuffer(nc net.Conn) buffer { fg := make([]byte, defaultBufSize) return buffer{ buf: fg, nc: nc, dbuf: [2][]byte{fg, nil}, } } // flip replaces the active buffer with the background buffer // this is a delayed flip that simply increases the buffer counter; // the actual flip will be performed the next time we call `buffer.fill` func (b *buffer) flip() { b.flipcnt += 1 } // fill reads into the buffer until at least _need_ bytes are in it func (b *buffer) fill(need int) error { n := b.length // fill data into its double-buffering target: if we've called // flip on this buffer, we'll be copying to the background buffer, // and then filling it with network data; otherwise we'll just move // the contents of the current buffer to the front before filling it dest := b.dbuf[b.flipcnt&1] // grow buffer if necessary to fit the whole packet. if need > len(dest) { // Round up to the next multiple of the default size dest = make([]byte, ((need/defaultBufSize)+1)*defaultBufSize) // if the allocated buffer is not too large, move it to backing storage // to prevent extra allocations on applications that perform large reads if len(dest) <= maxCachedBufSize { b.dbuf[b.flipcnt&1] = dest } } // if we're filling the fg buffer, move the existing data to the start of it. // if we're filling the bg buffer, copy over the data if n > 0 { copy(dest[:n], b.buf[b.idx:]) } b.buf = dest b.idx = 0 for { if b.timeout > 0 { if err := b.nc.SetReadDeadline(time.Now().Add(b.timeout)); err != nil { return err } } nn, err := b.nc.Read(b.buf[n:]) n += nn switch err { case nil: if n < need { continue } b.length = n return nil case io.EOF: if n >= need { b.length = n return nil } return io.ErrUnexpectedEOF default: return err } } } // returns next N bytes from buffer. // The returned slice is only guaranteed to be valid until the next read func (b *buffer) readNext(need int) ([]byte, error) { if b.length < need { // refill if err := b.fill(need); err != nil { return nil, err } } offset := b.idx b.idx += need b.length -= need return b.buf[offset:b.idx], nil } // takeBuffer returns a buffer with the requested size. // If possible, a slice from the existing buffer is returned. // Otherwise a bigger buffer is made. // Only one buffer (total) can be used at a time. func (b *buffer) takeBuffer(length int) ([]byte, error) { if b.length > 0 { return nil, ErrBusyBuffer } // test (cheap) general case first if length <= cap(b.buf) { return b.buf[:length], nil } if length < maxPacketSize { b.buf = make([]byte, length) return b.buf, nil } // buffer is larger than we want to store. return make([]byte, length), nil } // takeSmallBuffer is shortcut which can be used if length is // known to be smaller than defaultBufSize. // Only one buffer (total) can be used at a time. func (b *buffer) takeSmallBuffer(length int) ([]byte, error) { if b.length > 0 { return nil, ErrBusyBuffer } return b.buf[:length], nil } // takeCompleteBuffer returns the complete existing buffer. // This can be used if the necessary buffer size is unknown. // cap and len of the returned buffer will be equal. // Only one buffer (total) can be used at a time. func (b *buffer) takeCompleteBuffer() ([]byte, error) { if b.length > 0 { return nil, ErrBusyBuffer } return b.buf, nil } // store stores buf, an updated buffer, if its suitable to do so. func (b *buffer) store(buf []byte) error { if b.length > 0 { return ErrBusyBuffer } else if cap(buf) <= maxPacketSize && cap(buf) > cap(b.buf) { b.buf = buf[:cap(buf)] } return nil } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/collations.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // // Copyright 2014 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. package mysql const defaultCollation = "utf8mb4_general_ci" const binaryCollationID = 63 // A list of available collations mapped to the internal ID. // To update this map use the following MySQL query: // // SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS WHERE ID<256 ORDER BY ID // // Handshake packet have only 1 byte for collation_id. So we can't use collations with ID > 255. // // ucs2, utf16, and utf32 can't be used for connection charset. // https://dev.mysql.com/doc/refman/5.7/en/charset-connection.html#charset-connection-impermissible-client-charset // They are commented out to reduce this map. var collations = map[string]byte{ "big5_chinese_ci": 1, "latin2_czech_cs": 2, "dec8_swedish_ci": 3, "cp850_general_ci": 4, "latin1_german1_ci": 5, "hp8_english_ci": 6, "koi8r_general_ci": 7, "latin1_swedish_ci": 8, "latin2_general_ci": 9, "swe7_swedish_ci": 10, "ascii_general_ci": 11, "ujis_japanese_ci": 12, "sjis_japanese_ci": 13, "cp1251_bulgarian_ci": 14, "latin1_danish_ci": 15, "hebrew_general_ci": 16, "tis620_thai_ci": 18, "euckr_korean_ci": 19, "latin7_estonian_cs": 20, "latin2_hungarian_ci": 21, "koi8u_general_ci": 22, "cp1251_ukrainian_ci": 23, "gb2312_chinese_ci": 24, "greek_general_ci": 25, "cp1250_general_ci": 26, "latin2_croatian_ci": 27, "gbk_chinese_ci": 28, "cp1257_lithuanian_ci": 29, "latin5_turkish_ci": 30, "latin1_german2_ci": 31, "armscii8_general_ci": 32, "utf8_general_ci": 33, "cp1250_czech_cs": 34, //"ucs2_general_ci": 35, "cp866_general_ci": 36, "keybcs2_general_ci": 37, "macce_general_ci": 38, "macroman_general_ci": 39, "cp852_general_ci": 40, "latin7_general_ci": 41, "latin7_general_cs": 42, "macce_bin": 43, "cp1250_croatian_ci": 44, "utf8mb4_general_ci": 45, "utf8mb4_bin": 46, "latin1_bin": 47, "latin1_general_ci": 48, "latin1_general_cs": 49, "cp1251_bin": 50, "cp1251_general_ci": 51, "cp1251_general_cs": 52, "macroman_bin": 53, //"utf16_general_ci": 54, //"utf16_bin": 55, //"utf16le_general_ci": 56, "cp1256_general_ci": 57, "cp1257_bin": 58, "cp1257_general_ci": 59, //"utf32_general_ci": 60, //"utf32_bin": 61, //"utf16le_bin": 62, "binary": 63, "armscii8_bin": 64, "ascii_bin": 65, "cp1250_bin": 66, "cp1256_bin": 67, "cp866_bin": 68, "dec8_bin": 69, "greek_bin": 70, "hebrew_bin": 71, "hp8_bin": 72, "keybcs2_bin": 73, "koi8r_bin": 74, "koi8u_bin": 75, "utf8_tolower_ci": 76, "latin2_bin": 77, "latin5_bin": 78, "latin7_bin": 79, "cp850_bin": 80, "cp852_bin": 81, "swe7_bin": 82, "utf8_bin": 83, "big5_bin": 84, "euckr_bin": 85, "gb2312_bin": 86, "gbk_bin": 87, "sjis_bin": 88, "tis620_bin": 89, //"ucs2_bin": 90, "ujis_bin": 91, "geostd8_general_ci": 92, "geostd8_bin": 93, "latin1_spanish_ci": 94, "cp932_japanese_ci": 95, "cp932_bin": 96, "eucjpms_japanese_ci": 97, "eucjpms_bin": 98, "cp1250_polish_ci": 99, //"utf16_unicode_ci": 101, //"utf16_icelandic_ci": 102, //"utf16_latvian_ci": 103, //"utf16_romanian_ci": 104, //"utf16_slovenian_ci": 105, //"utf16_polish_ci": 106, //"utf16_estonian_ci": 107, //"utf16_spanish_ci": 108, //"utf16_swedish_ci": 109, //"utf16_turkish_ci": 110, //"utf16_czech_ci": 111, //"utf16_danish_ci": 112, //"utf16_lithuanian_ci": 113, //"utf16_slovak_ci": 114, //"utf16_spanish2_ci": 115, //"utf16_roman_ci": 116, //"utf16_persian_ci": 117, //"utf16_esperanto_ci": 118, //"utf16_hungarian_ci": 119, //"utf16_sinhala_ci": 120, //"utf16_german2_ci": 121, //"utf16_croatian_ci": 122, //"utf16_unicode_520_ci": 123, //"utf16_vietnamese_ci": 124, //"ucs2_unicode_ci": 128, //"ucs2_icelandic_ci": 129, //"ucs2_latvian_ci": 130, //"ucs2_romanian_ci": 131, //"ucs2_slovenian_ci": 132, //"ucs2_polish_ci": 133, //"ucs2_estonian_ci": 134, //"ucs2_spanish_ci": 135, //"ucs2_swedish_ci": 136, //"ucs2_turkish_ci": 137, //"ucs2_czech_ci": 138, //"ucs2_danish_ci": 139, //"ucs2_lithuanian_ci": 140, //"ucs2_slovak_ci": 141, //"ucs2_spanish2_ci": 142, //"ucs2_roman_ci": 143, //"ucs2_persian_ci": 144, //"ucs2_esperanto_ci": 145, //"ucs2_hungarian_ci": 146, //"ucs2_sinhala_ci": 147, //"ucs2_german2_ci": 148, //"ucs2_croatian_ci": 149, //"ucs2_unicode_520_ci": 150, //"ucs2_vietnamese_ci": 151, //"ucs2_general_mysql500_ci": 159, //"utf32_unicode_ci": 160, //"utf32_icelandic_ci": 161, //"utf32_latvian_ci": 162, //"utf32_romanian_ci": 163, //"utf32_slovenian_ci": 164, //"utf32_polish_ci": 165, //"utf32_estonian_ci": 166, //"utf32_spanish_ci": 167, //"utf32_swedish_ci": 168, //"utf32_turkish_ci": 169, //"utf32_czech_ci": 170, //"utf32_danish_ci": 171, //"utf32_lithuanian_ci": 172, //"utf32_slovak_ci": 173, //"utf32_spanish2_ci": 174, //"utf32_roman_ci": 175, //"utf32_persian_ci": 176, //"utf32_esperanto_ci": 177, //"utf32_hungarian_ci": 178, //"utf32_sinhala_ci": 179, //"utf32_german2_ci": 180, //"utf32_croatian_ci": 181, //"utf32_unicode_520_ci": 182, //"utf32_vietnamese_ci": 183, "utf8_unicode_ci": 192, "utf8_icelandic_ci": 193, "utf8_latvian_ci": 194, "utf8_romanian_ci": 195, "utf8_slovenian_ci": 196, "utf8_polish_ci": 197, "utf8_estonian_ci": 198, "utf8_spanish_ci": 199, "utf8_swedish_ci": 200, "utf8_turkish_ci": 201, "utf8_czech_ci": 202, "utf8_danish_ci": 203, "utf8_lithuanian_ci": 204, "utf8_slovak_ci": 205, "utf8_spanish2_ci": 206, "utf8_roman_ci": 207, "utf8_persian_ci": 208, "utf8_esperanto_ci": 209, "utf8_hungarian_ci": 210, "utf8_sinhala_ci": 211, "utf8_german2_ci": 212, "utf8_croatian_ci": 213, "utf8_unicode_520_ci": 214, "utf8_vietnamese_ci": 215, "utf8_general_mysql500_ci": 223, "utf8mb4_unicode_ci": 224, "utf8mb4_icelandic_ci": 225, "utf8mb4_latvian_ci": 226, "utf8mb4_romanian_ci": 227, "utf8mb4_slovenian_ci": 228, "utf8mb4_polish_ci": 229, "utf8mb4_estonian_ci": 230, "utf8mb4_spanish_ci": 231, "utf8mb4_swedish_ci": 232, "utf8mb4_turkish_ci": 233, "utf8mb4_czech_ci": 234, "utf8mb4_danish_ci": 235, "utf8mb4_lithuanian_ci": 236, "utf8mb4_slovak_ci": 237, "utf8mb4_spanish2_ci": 238, "utf8mb4_roman_ci": 239, "utf8mb4_persian_ci": 240, "utf8mb4_esperanto_ci": 241, "utf8mb4_hungarian_ci": 242, "utf8mb4_sinhala_ci": 243, "utf8mb4_german2_ci": 244, "utf8mb4_croatian_ci": 245, "utf8mb4_unicode_520_ci": 246, "utf8mb4_vietnamese_ci": 247, "gb18030_chinese_ci": 248, "gb18030_bin": 249, "gb18030_unicode_520_ci": 250, "utf8mb4_0900_ai_ci": 255, } // A denylist of collations which is unsafe to interpolate parameters. // These multibyte encodings may contains 0x5c (`\`) in their trailing bytes. var unsafeCollations = map[string]bool{ "big5_chinese_ci": true, "sjis_japanese_ci": true, "gbk_chinese_ci": true, "big5_bin": true, "gb2312_bin": true, "gbk_bin": true, "sjis_bin": true, "cp932_japanese_ci": true, "cp932_bin": true, "gb18030_chinese_ci": true, "gb18030_bin": true, "gb18030_unicode_520_ci": true, } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/conncheck.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // // Copyright 2019 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. //go:build linux || darwin || dragonfly || freebsd || netbsd || openbsd || solaris || illumos // +build linux darwin dragonfly freebsd netbsd openbsd solaris illumos package mysql import ( "errors" "io" "net" "syscall" ) var errUnexpectedRead = errors.New("unexpected read from socket") func connCheck(conn net.Conn) error { var sysErr error sysConn, ok := conn.(syscall.Conn) if !ok { return nil } rawConn, err := sysConn.SyscallConn() if err != nil { return err } err = rawConn.Read(func(fd uintptr) bool { var buf [1]byte n, err := syscall.Read(int(fd), buf[:]) switch { case n == 0 && err == nil: sysErr = io.EOF case n > 0: sysErr = errUnexpectedRead case err == syscall.EAGAIN || err == syscall.EWOULDBLOCK: sysErr = nil default: sysErr = err } return true }) if err != nil { return err } return sysErr } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/conncheck_dummy.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // // Copyright 2019 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. //go:build !linux && !darwin && !dragonfly && !freebsd && !netbsd && !openbsd && !solaris && !illumos // +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris,!illumos package mysql import "net" func connCheck(conn net.Conn) error { return nil } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/connection.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. package mysql import ( "context" "database/sql" "database/sql/driver" "encoding/json" "io" "net" "strconv" "strings" "time" ) type mysqlConn struct { buf buffer netConn net.Conn rawConn net.Conn // underlying connection when netConn is TLS connection. result mysqlResult // managed by clearResult() and handleOkPacket(). cfg *Config connector *connector maxAllowedPacket int maxWriteSize int writeTimeout time.Duration flags clientFlag status statusFlag sequence uint8 parseTime bool // for context support (Go 1.8+) watching bool watcher chan<- context.Context closech chan struct{} finished chan<- struct{} canceled atomicError // set non-nil if conn is canceled closed atomicBool // set when conn is closed, before closech is closed } // Helper function to call per-connection logger. func (mc *mysqlConn) log(v ...any) { mc.cfg.Logger.Print(v...) } // Handles parameters set in DSN after the connection is established func (mc *mysqlConn) handleParams() (err error) { var cmdSet strings.Builder for param, val := range mc.cfg.Params { switch param { // Charset: character_set_connection, character_set_client, character_set_results case "charset": charsets := strings.Split(val, ",") for _, cs := range charsets { // ignore errors here - a charset may not exist if mc.cfg.Collation != "" { err = mc.exec("SET NAMES " + cs + " COLLATE " + mc.cfg.Collation) } else { err = mc.exec("SET NAMES " + cs) } if err == nil { break } } if err != nil { return } // Other system vars accumulated in a single SET command default: if cmdSet.Len() == 0 { // Heuristic: 29 chars for each other key=value to reduce reallocations cmdSet.Grow(4 + len(param) + 3 + len(val) + 30*(len(mc.cfg.Params)-1)) cmdSet.WriteString("SET ") } else { cmdSet.WriteString(", ") } cmdSet.WriteString(param) cmdSet.WriteString(" = ") cmdSet.WriteString(val) } } if cmdSet.Len() > 0 { err = mc.exec(cmdSet.String()) if err != nil { return } } return } func (mc *mysqlConn) markBadConn(err error) error { if mc == nil { return err } if err != errBadConnNoWrite { return err } return driver.ErrBadConn } func (mc *mysqlConn) Begin() (driver.Tx, error) { return mc.begin(false) } func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) { if mc.closed.Load() { mc.log(ErrInvalidConn) return nil, driver.ErrBadConn } var q string if readOnly { q = "START TRANSACTION READ ONLY" } else { q = "START TRANSACTION" } err := mc.exec(q) if err == nil { return &mysqlTx{mc}, err } return nil, mc.markBadConn(err) } func (mc *mysqlConn) Close() (err error) { // Makes Close idempotent if !mc.closed.Load() { err = mc.writeCommandPacket(comQuit) } mc.cleanup() mc.clearResult() return } // Closes the network connection and unsets internal variables. Do not call this // function after successfully authentication, call Close instead. This function // is called before auth or on auth failure because MySQL will have already // closed the network connection. func (mc *mysqlConn) cleanup() { if mc.closed.Swap(true) { return } // Makes cleanup idempotent close(mc.closech) conn := mc.rawConn if conn == nil { return } if err := conn.Close(); err != nil { mc.log(err) } // This function can be called from multiple goroutines. // So we can not mc.clearResult() here. // Caller should do it if they are in safe goroutine. } func (mc *mysqlConn) error() error { if mc.closed.Load() { if err := mc.canceled.Value(); err != nil { return err } return ErrInvalidConn } return nil } func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) { if mc.closed.Load() { mc.log(ErrInvalidConn) return nil, driver.ErrBadConn } // Send command err := mc.writeCommandPacketStr(comStmtPrepare, query) if err != nil { // STMT_PREPARE is safe to retry. So we can return ErrBadConn here. mc.log(err) return nil, driver.ErrBadConn } stmt := &mysqlStmt{ mc: mc, } // Read Result columnCount, err := stmt.readPrepareResultPacket() if err == nil { if stmt.paramCount > 0 { if err = mc.readUntilEOF(); err != nil { return nil, err } } if columnCount > 0 { err = mc.readUntilEOF() } } return stmt, err } func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) { // Number of ? should be same to len(args) if strings.Count(query, "?") != len(args) { return "", driver.ErrSkip } buf, err := mc.buf.takeCompleteBuffer() if err != nil { // can not take the buffer. Something must be wrong with the connection mc.log(err) return "", ErrInvalidConn } buf = buf[:0] argPos := 0 for i := 0; i < len(query); i++ { q := strings.IndexByte(query[i:], '?') if q == -1 { buf = append(buf, query[i:]...) break } buf = append(buf, query[i:i+q]...) i += q arg := args[argPos] argPos++ if arg == nil { buf = append(buf, "NULL"...) continue } switch v := arg.(type) { case int64: buf = strconv.AppendInt(buf, v, 10) case uint64: // Handle uint64 explicitly because our custom ConvertValue emits unsigned values buf = strconv.AppendUint(buf, v, 10) case float64: buf = strconv.AppendFloat(buf, v, 'g', -1, 64) case bool: if v { buf = append(buf, '1') } else { buf = append(buf, '0') } case time.Time: if v.IsZero() { buf = append(buf, "'0000-00-00'"...) } else { buf = append(buf, '\'') buf, err = appendDateTime(buf, v.In(mc.cfg.Loc), mc.cfg.timeTruncate) if err != nil { return "", err } buf = append(buf, '\'') } case json.RawMessage: buf = append(buf, '\'') if mc.status&statusNoBackslashEscapes == 0 { buf = escapeBytesBackslash(buf, v) } else { buf = escapeBytesQuotes(buf, v) } buf = append(buf, '\'') case []byte: if v == nil { buf = append(buf, "NULL"...) } else { buf = append(buf, "_binary'"...) if mc.status&statusNoBackslashEscapes == 0 { buf = escapeBytesBackslash(buf, v) } else { buf = escapeBytesQuotes(buf, v) } buf = append(buf, '\'') } case string: buf = append(buf, '\'') if mc.status&statusNoBackslashEscapes == 0 { buf = escapeStringBackslash(buf, v) } else { buf = escapeStringQuotes(buf, v) } buf = append(buf, '\'') default: return "", driver.ErrSkip } if len(buf)+4 > mc.maxAllowedPacket { return "", driver.ErrSkip } } if argPos != len(args) { return "", driver.ErrSkip } return string(buf), nil } func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) { if mc.closed.Load() { mc.log(ErrInvalidConn) return nil, driver.ErrBadConn } if len(args) != 0 { if !mc.cfg.InterpolateParams { return nil, driver.ErrSkip } // try to interpolate the parameters to save extra roundtrips for preparing and closing a statement prepared, err := mc.interpolateParams(query, args) if err != nil { return nil, err } query = prepared } err := mc.exec(query) if err == nil { copied := mc.result return &copied, err } return nil, mc.markBadConn(err) } // Internal function to execute commands func (mc *mysqlConn) exec(query string) error { handleOk := mc.clearResult() // Send command if err := mc.writeCommandPacketStr(comQuery, query); err != nil { return mc.markBadConn(err) } // Read Result resLen, err := handleOk.readResultSetHeaderPacket() if err != nil { return err } if resLen > 0 { // columns if err := mc.readUntilEOF(); err != nil { return err } // rows if err := mc.readUntilEOF(); err != nil { return err } } return handleOk.discardResults() } func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) { return mc.query(query, args) } func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) { handleOk := mc.clearResult() if mc.closed.Load() { mc.log(ErrInvalidConn) return nil, driver.ErrBadConn } if len(args) != 0 { if !mc.cfg.InterpolateParams { return nil, driver.ErrSkip } // try client-side prepare to reduce roundtrip prepared, err := mc.interpolateParams(query, args) if err != nil { return nil, err } query = prepared } // Send command err := mc.writeCommandPacketStr(comQuery, query) if err == nil { // Read Result var resLen int resLen, err = handleOk.readResultSetHeaderPacket() if err == nil { rows := new(textRows) rows.mc = mc if resLen == 0 { rows.rs.done = true switch err := rows.NextResultSet(); err { case nil, io.EOF: return rows, nil default: return nil, err } } // Columns rows.rs.columns, err = mc.readColumns(resLen) return rows, err } } return nil, mc.markBadConn(err) } // Gets the value of the given MySQL System Variable // The returned byte slice is only valid until the next read func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) { // Send command handleOk := mc.clearResult() if err := mc.writeCommandPacketStr(comQuery, "SELECT @@"+name); err != nil { return nil, err } // Read Result resLen, err := handleOk.readResultSetHeaderPacket() if err == nil { rows := new(textRows) rows.mc = mc rows.rs.columns = []mysqlField{{fieldType: fieldTypeVarChar}} if resLen > 0 { // Columns if err := mc.readUntilEOF(); err != nil { return nil, err } } dest := make([]driver.Value, resLen) if err = rows.readRow(dest); err == nil { return dest[0].([]byte), mc.readUntilEOF() } } return nil, err } // finish is called when the query has canceled. func (mc *mysqlConn) cancel(err error) { mc.canceled.Set(err) mc.cleanup() } // finish is called when the query has succeeded. func (mc *mysqlConn) finish() { if !mc.watching || mc.finished == nil { return } select { case mc.finished <- struct{}{}: mc.watching = false case <-mc.closech: } } // Ping implements driver.Pinger interface func (mc *mysqlConn) Ping(ctx context.Context) (err error) { if mc.closed.Load() { mc.log(ErrInvalidConn) return driver.ErrBadConn } if err = mc.watchCancel(ctx); err != nil { return } defer mc.finish() handleOk := mc.clearResult() if err = mc.writeCommandPacket(comPing); err != nil { return mc.markBadConn(err) } return handleOk.readResultOK() } // BeginTx implements driver.ConnBeginTx interface func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { if mc.closed.Load() { return nil, driver.ErrBadConn } if err := mc.watchCancel(ctx); err != nil { return nil, err } defer mc.finish() if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault { level, err := mapIsolationLevel(opts.Isolation) if err != nil { return nil, err } err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level) if err != nil { return nil, err } } return mc.begin(opts.ReadOnly) } func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { dargs, err := namedValueToValue(args) if err != nil { return nil, err } if err := mc.watchCancel(ctx); err != nil { return nil, err } rows, err := mc.query(query, dargs) if err != nil { mc.finish() return nil, err } rows.finish = mc.finish return rows, err } func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { dargs, err := namedValueToValue(args) if err != nil { return nil, err } if err := mc.watchCancel(ctx); err != nil { return nil, err } defer mc.finish() return mc.Exec(query, dargs) } func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { if err := mc.watchCancel(ctx); err != nil { return nil, err } stmt, err := mc.Prepare(query) mc.finish() if err != nil { return nil, err } select { default: case <-ctx.Done(): stmt.Close() return nil, ctx.Err() } return stmt, nil } func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { dargs, err := namedValueToValue(args) if err != nil { return nil, err } if err := stmt.mc.watchCancel(ctx); err != nil { return nil, err } rows, err := stmt.query(dargs) if err != nil { stmt.mc.finish() return nil, err } rows.finish = stmt.mc.finish return rows, err } func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { dargs, err := namedValueToValue(args) if err != nil { return nil, err } if err := stmt.mc.watchCancel(ctx); err != nil { return nil, err } defer stmt.mc.finish() return stmt.Exec(dargs) } func (mc *mysqlConn) watchCancel(ctx context.Context) error { if mc.watching { // Reach here if canceled, // so the connection is already invalid mc.cleanup() return nil } // When ctx is already cancelled, don't watch it. if err := ctx.Err(); err != nil { return err } // When ctx is not cancellable, don't watch it. if ctx.Done() == nil { return nil } // When watcher is not alive, can't watch it. if mc.watcher == nil { return nil } mc.watching = true mc.watcher <- ctx return nil } func (mc *mysqlConn) startWatcher() { watcher := make(chan context.Context, 1) mc.watcher = watcher finished := make(chan struct{}) mc.finished = finished go func() { for { var ctx context.Context select { case ctx = <-watcher: case <-mc.closech: return } select { case <-ctx.Done(): mc.cancel(ctx.Err()) case <-finished: case <-mc.closech: return } } }() } func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) { nv.Value, err = converter{}.ConvertValue(nv.Value) return } // ResetSession implements driver.SessionResetter. // (From Go 1.10) func (mc *mysqlConn) ResetSession(ctx context.Context) error { if mc.closed.Load() { return driver.ErrBadConn } // Perform a stale connection check. We only perform this check for // the first query on a connection that has been checked out of the // connection pool: a fresh connection from the pool is more likely // to be stale, and it has not performed any previous writes that // could cause data corruption, so it's safe to return ErrBadConn // if the check fails. if mc.cfg.CheckConnLiveness { conn := mc.netConn if mc.rawConn != nil { conn = mc.rawConn } var err error if mc.cfg.ReadTimeout != 0 { err = conn.SetReadDeadline(time.Now().Add(mc.cfg.ReadTimeout)) } if err == nil { err = connCheck(conn) } if err != nil { mc.log("closing bad idle connection: ", err) return driver.ErrBadConn } } return nil } // IsValid implements driver.Validator interface // (From Go 1.15) func (mc *mysqlConn) IsValid() bool { return !mc.closed.Load() } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/connector.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // // Copyright 2018 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. package mysql import ( "context" "database/sql/driver" "net" "os" "strconv" "strings" ) type connector struct { cfg *Config // immutable private copy. encodedAttributes string // Encoded connection attributes. } func encodeConnectionAttributes(cfg *Config) string { connAttrsBuf := make([]byte, 0) // default connection attributes connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrClientName) connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrClientNameValue) connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrOS) connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrOSValue) connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrPlatform) connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrPlatformValue) connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrPid) connAttrsBuf = appendLengthEncodedString(connAttrsBuf, strconv.Itoa(os.Getpid())) serverHost, _, _ := net.SplitHostPort(cfg.Addr) if serverHost != "" { connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrServerHost) connAttrsBuf = appendLengthEncodedString(connAttrsBuf, serverHost) } // user-defined connection attributes for _, connAttr := range strings.Split(cfg.ConnectionAttributes, ",") { k, v, found := strings.Cut(connAttr, ":") if !found { continue } connAttrsBuf = appendLengthEncodedString(connAttrsBuf, k) connAttrsBuf = appendLengthEncodedString(connAttrsBuf, v) } return string(connAttrsBuf) } func newConnector(cfg *Config) *connector { encodedAttributes := encodeConnectionAttributes(cfg) return &connector{ cfg: cfg, encodedAttributes: encodedAttributes, } } // Connect implements driver.Connector interface. // Connect returns a connection to the database. func (c *connector) Connect(ctx context.Context) (driver.Conn, error) { var err error // Invoke beforeConnect if present, with a copy of the configuration cfg := c.cfg if c.cfg.beforeConnect != nil { cfg = c.cfg.Clone() err = c.cfg.beforeConnect(ctx, cfg) if err != nil { return nil, err } } // New mysqlConn mc := &mysqlConn{ maxAllowedPacket: maxPacketSize, maxWriteSize: maxPacketSize - 1, closech: make(chan struct{}), cfg: cfg, connector: c, } mc.parseTime = mc.cfg.ParseTime // Connect to Server dialsLock.RLock() dial, ok := dials[mc.cfg.Net] dialsLock.RUnlock() if ok { dctx := ctx if mc.cfg.Timeout > 0 { var cancel context.CancelFunc dctx, cancel = context.WithTimeout(ctx, c.cfg.Timeout) defer cancel() } mc.netConn, err = dial(dctx, mc.cfg.Addr) } else { nd := net.Dialer{Timeout: mc.cfg.Timeout} mc.netConn, err = nd.DialContext(ctx, mc.cfg.Net, mc.cfg.Addr) } if err != nil { return nil, err } mc.rawConn = mc.netConn // Enable TCP Keepalives on TCP connections if tc, ok := mc.netConn.(*net.TCPConn); ok { if err := tc.SetKeepAlive(true); err != nil { c.cfg.Logger.Print(err) } } // Call startWatcher for context support (From Go 1.8) mc.startWatcher() if err := mc.watchCancel(ctx); err != nil { mc.cleanup() return nil, err } defer mc.finish() mc.buf = newBuffer(mc.netConn) // Set I/O timeouts mc.buf.timeout = mc.cfg.ReadTimeout mc.writeTimeout = mc.cfg.WriteTimeout // Reading Handshake Initialization Packet authData, plugin, err := mc.readHandshakePacket() if err != nil { mc.cleanup() return nil, err } if plugin == "" { plugin = defaultAuthPlugin } // Send Client Authentication Packet authResp, err := mc.auth(authData, plugin) if err != nil { // try the default auth plugin, if using the requested plugin failed c.cfg.Logger.Print("could not use requested auth plugin '"+plugin+"': ", err.Error()) plugin = defaultAuthPlugin authResp, err = mc.auth(authData, plugin) if err != nil { mc.cleanup() return nil, err } } if err = mc.writeHandshakeResponsePacket(authResp, plugin); err != nil { mc.cleanup() return nil, err } // Handle response to auth packet, switch methods if possible if err = mc.handleAuthResult(authData, plugin); err != nil { // Authentication failed and MySQL has already closed the connection // (https://dev.mysql.com/doc/internals/en/authentication-fails.html). // Do not send COM_QUIT, just cleanup and return the error. mc.cleanup() return nil, err } if mc.cfg.MaxAllowedPacket > 0 { mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket } else { // Get max allowed packet size maxap, err := mc.getSystemVar("max_allowed_packet") if err != nil { mc.Close() return nil, err } mc.maxAllowedPacket = stringToInt(maxap) - 1 } if mc.maxAllowedPacket < maxPacketSize { mc.maxWriteSize = mc.maxAllowedPacket } // Handle DSN Params err = mc.handleParams() if err != nil { mc.Close() return nil, err } return mc, nil } // Driver implements driver.Connector interface. // Driver returns &MySQLDriver{}. func (c *connector) Driver() driver.Driver { return &MySQLDriver{} } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/const.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. package mysql import "runtime" const ( defaultAuthPlugin = "mysql_native_password" defaultMaxAllowedPacket = 64 << 20 // 64 MiB. See https://github.com/go-sql-driver/mysql/issues/1355 minProtocolVersion = 10 maxPacketSize = 1<<24 - 1 timeFormat = "2006-01-02 15:04:05.999999" // Connection attributes // See https://dev.mysql.com/doc/refman/8.0/en/performance-schema-connection-attribute-tables.html#performance-schema-connection-attributes-available connAttrClientName = "_client_name" connAttrClientNameValue = "Go-MySQL-Driver" connAttrOS = "_os" connAttrOSValue = runtime.GOOS connAttrPlatform = "_platform" connAttrPlatformValue = runtime.GOARCH connAttrPid = "_pid" connAttrServerHost = "_server_host" ) // MySQL constants documentation: // http://dev.mysql.com/doc/internals/en/client-server-protocol.html const ( iOK byte = 0x00 iAuthMoreData byte = 0x01 iLocalInFile byte = 0xfb iEOF byte = 0xfe iERR byte = 0xff ) // https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags type clientFlag uint32 const ( clientLongPassword clientFlag = 1 << iota clientFoundRows clientLongFlag clientConnectWithDB clientNoSchema clientCompress clientODBC clientLocalFiles clientIgnoreSpace clientProtocol41 clientInteractive clientSSL clientIgnoreSIGPIPE clientTransactions clientReserved clientSecureConn clientMultiStatements clientMultiResults clientPSMultiResults clientPluginAuth clientConnectAttrs clientPluginAuthLenEncClientData clientCanHandleExpiredPasswords clientSessionTrack clientDeprecateEOF ) const ( comQuit byte = iota + 1 comInitDB comQuery comFieldList comCreateDB comDropDB comRefresh comShutdown comStatistics comProcessInfo comConnect comProcessKill comDebug comPing comTime comDelayedInsert comChangeUser comBinlogDump comTableDump comConnectOut comRegisterSlave comStmtPrepare comStmtExecute comStmtSendLongData comStmtClose comStmtReset comSetOption comStmtFetch ) // https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType type fieldType byte const ( fieldTypeDecimal fieldType = iota fieldTypeTiny fieldTypeShort fieldTypeLong fieldTypeFloat fieldTypeDouble fieldTypeNULL fieldTypeTimestamp fieldTypeLongLong fieldTypeInt24 fieldTypeDate fieldTypeTime fieldTypeDateTime fieldTypeYear fieldTypeNewDate fieldTypeVarChar fieldTypeBit ) const ( fieldTypeJSON fieldType = iota + 0xf5 fieldTypeNewDecimal fieldTypeEnum fieldTypeSet fieldTypeTinyBLOB fieldTypeMediumBLOB fieldTypeLongBLOB fieldTypeBLOB fieldTypeVarString fieldTypeString fieldTypeGeometry ) type fieldFlag uint16 const ( flagNotNULL fieldFlag = 1 << iota flagPriKey flagUniqueKey flagMultipleKey flagBLOB flagUnsigned flagZeroFill flagBinary flagEnum flagAutoIncrement flagTimestamp flagSet flagUnknown1 flagUnknown2 flagUnknown3 flagUnknown4 ) // http://dev.mysql.com/doc/internals/en/status-flags.html type statusFlag uint16 const ( statusInTrans statusFlag = 1 << iota statusInAutocommit statusReserved // Not in documentation statusMoreResultsExists statusNoGoodIndexUsed statusNoIndexUsed statusCursorExists statusLastRowSent statusDbDropped statusNoBackslashEscapes statusMetadataChanged statusQueryWasSlow statusPsOutParams statusInTransReadonly statusSessionStateChanged ) const ( cachingSha2PasswordRequestPublicKey = 2 cachingSha2PasswordFastAuthSuccess = 3 cachingSha2PasswordPerformFullAuthentication = 4 ) ================================================ FILE: vendor/github.com/go-sql-driver/mysql/driver.go ================================================ // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. // Package mysql provides a MySQL driver for Go's database/sql package. // // The driver should be used via the database/sql package: // // import "database/sql" // import _ "github.com/go-sql-driver/mysql" // // db, err := sql.Open("mysql", "user:password@/dbname") // // See https://github.com/go-sql-driver/mysql#usage for details package mysql import ( "context" "database/sql" "database/sql/driver" "net" "sync" ) // MySQLDriver is exported to make the driver directly accessible. // In general the driver is used via the database/sql package. type MySQLDriver struct{} // DialFunc is a function which can be used to establish the network connection. // Custom dial functions must be registered with RegisterDial // // Deprecated: users should register a DialContextFunc instead type DialFunc func(addr string) (net.Conn, error) // DialContextFunc is a function which can be used to establish the network connection. // Custom dial functions must be registered with RegisterDialContext type DialContextFunc func(ctx context.Context, addr string) (net.Conn, error) var ( dialsLock sync.RWMutex dials map[string]DialContextFunc ) // RegisterDialContext registers a custom dial function. It can then be used by the // network address mynet(addr), where mynet is the registered new network. // The current context for the connection and its address is passed to the dial function. func RegisterDialContext(net string, dial DialContextFunc) { dialsLock.Lock() defer dialsLock.Unlock() if dials == nil { dials = make(map[string]DialContextFunc) } dials[net] = dial } // DeregisterDialContext removes the custom dial function registered with the given net. func DeregisterDialContext(net string) { dialsLock.Lock() defer dialsLock.Unlock() if dials != nil { delete(dials, net) } } // RegisterDial registers a custom dial function. It can then be used by the // network address mynet(addr), where mynet is the registered new network. // addr is passed as a parameter to the dial function. // // Deprecated: users should call RegisterDialContext instead func RegisterDial(network string, dial DialFunc) { RegisterDialContext(network, func(_ context.Context, addr string) (net.Conn, error) { return dial(addr) }) } // Open new Connection. // See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how // the DSN string is formatted func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { cfg, err := ParseDSN(dsn) if err != nil { return nil, err } c := newConnector(cfg) return c.Connect(context.Background()) } // This variable can be replaced with -ldflags like below: // go build "-ldflags=-X github.com/go-sql-driver/mysql.driverName=custom" var driverName = "mysql" func init() { if driverName != "" { sql.Register(driverName, &MySQLDriver{}) } } // NewConnector returns new driver.Connector. func NewConnector(cfg *Config) (driver.Connector, error) { cfg = cfg.Clone() // normalize the contents of cfg so calls to NewConnector have the same // behavior as MySQLDriver.OpenConnector if err := cfg.normalize(); err != nil { return nil, err } return newConnector(cfg), nil } // OpenConnector implements driver.DriverContext. func (d MySQLDriver) OpenConnector(dsn string) (driver.Connector, error) { cfg, err := ParseDSN(dsn) if err != nil { return nil, err } return newConnector(cfg), nil } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/dsn.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // // Copyright 2016 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. package mysql import ( "bytes" "context" "crypto/rsa" "crypto/tls" "errors" "fmt" "math/big" "net" "net/url" "sort" "strconv" "strings" "time" ) var ( errInvalidDSNUnescaped = errors.New("invalid DSN: did you forget to escape a param value?") errInvalidDSNAddr = errors.New("invalid DSN: network address not terminated (missing closing brace)") errInvalidDSNNoSlash = errors.New("invalid DSN: missing the slash separating the database name") errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations") ) // Config is a configuration parsed from a DSN string. // If a new Config is created instead of being parsed from a DSN string, // the NewConfig function should be used, which sets default values. type Config struct { // non boolean fields User string // Username Passwd string // Password (requires User) Net string // Network (e.g. "tcp", "tcp6", "unix". default: "tcp") Addr string // Address (default: "127.0.0.1:3306" for "tcp" and "/tmp/mysql.sock" for "unix") DBName string // Database name Params map[string]string // Connection parameters ConnectionAttributes string // Connection Attributes, comma-delimited string of user-defined "key:value" pairs Collation string // Connection collation Loc *time.Location // Location for time.Time values MaxAllowedPacket int // Max packet size allowed ServerPubKey string // Server public key name TLSConfig string // TLS configuration name TLS *tls.Config // TLS configuration, its priority is higher than TLSConfig Timeout time.Duration // Dial timeout ReadTimeout time.Duration // I/O read timeout WriteTimeout time.Duration // I/O write timeout Logger Logger // Logger // boolean fields AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE AllowCleartextPasswords bool // Allows the cleartext client side plugin AllowFallbackToPlaintext bool // Allows fallback to unencrypted connection if server does not support TLS AllowNativePasswords bool // Allows the native password authentication method AllowOldPasswords bool // Allows the old insecure password method CheckConnLiveness bool // Check connections for liveness before using them ClientFoundRows bool // Return number of matching rows instead of rows changed ColumnsWithAlias bool // Prepend table alias to column names InterpolateParams bool // Interpolate placeholders into query string MultiStatements bool // Allow multiple statements in one query ParseTime bool // Parse time values to time.Time RejectReadOnly bool // Reject read-only connections // unexported fields. new options should be come here beforeConnect func(context.Context, *Config) error // Invoked before a connection is established pubKey *rsa.PublicKey // Server public key timeTruncate time.Duration // Truncate time.Time values to the specified duration } // Functional Options Pattern // https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis type Option func(*Config) error // NewConfig creates a new Config and sets default values. func NewConfig() *Config { cfg := &Config{ Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, } return cfg } // Apply applies the given options to the Config object. func (c *Config) Apply(opts ...Option) error { for _, opt := range opts { err := opt(c) if err != nil { return err } } return nil } // TimeTruncate sets the time duration to truncate time.Time values in // query parameters. func TimeTruncate(d time.Duration) Option { return func(cfg *Config) error { cfg.timeTruncate = d return nil } } // BeforeConnect sets the function to be invoked before a connection is established. func BeforeConnect(fn func(context.Context, *Config) error) Option { return func(cfg *Config) error { cfg.beforeConnect = fn return nil } } func (cfg *Config) Clone() *Config { cp := *cfg if cp.TLS != nil { cp.TLS = cfg.TLS.Clone() } if len(cp.Params) > 0 { cp.Params = make(map[string]string, len(cfg.Params)) for k, v := range cfg.Params { cp.Params[k] = v } } if cfg.pubKey != nil { cp.pubKey = &rsa.PublicKey{ N: new(big.Int).Set(cfg.pubKey.N), E: cfg.pubKey.E, } } return &cp } func (cfg *Config) normalize() error { if cfg.InterpolateParams && cfg.Collation != "" && unsafeCollations[cfg.Collation] { return errInvalidDSNUnsafeCollation } // Set default network if empty if cfg.Net == "" { cfg.Net = "tcp" } // Set default address if empty if cfg.Addr == "" { switch cfg.Net { case "tcp": cfg.Addr = "127.0.0.1:3306" case "unix": cfg.Addr = "/tmp/mysql.sock" default: return errors.New("default addr for network '" + cfg.Net + "' unknown") } } else if cfg.Net == "tcp" { cfg.Addr = ensureHavePort(cfg.Addr) } if cfg.TLS == nil { switch cfg.TLSConfig { case "false", "": // don't set anything case "true": cfg.TLS = &tls.Config{} case "skip-verify": cfg.TLS = &tls.Config{InsecureSkipVerify: true} case "preferred": cfg.TLS = &tls.Config{InsecureSkipVerify: true} cfg.AllowFallbackToPlaintext = true default: cfg.TLS = getTLSConfigClone(cfg.TLSConfig) if cfg.TLS == nil { return errors.New("invalid value / unknown config name: " + cfg.TLSConfig) } } } if cfg.TLS != nil && cfg.TLS.ServerName == "" && !cfg.TLS.InsecureSkipVerify { host, _, err := net.SplitHostPort(cfg.Addr) if err == nil { cfg.TLS.ServerName = host } } if cfg.ServerPubKey != "" { cfg.pubKey = getServerPubKey(cfg.ServerPubKey) if cfg.pubKey == nil { return errors.New("invalid value / unknown server pub key name: " + cfg.ServerPubKey) } } if cfg.Logger == nil { cfg.Logger = defaultLogger } return nil } func writeDSNParam(buf *bytes.Buffer, hasParam *bool, name, value string) { buf.Grow(1 + len(name) + 1 + len(value)) if !*hasParam { *hasParam = true buf.WriteByte('?') } else { buf.WriteByte('&') } buf.WriteString(name) buf.WriteByte('=') buf.WriteString(value) } // FormatDSN formats the given Config into a DSN string which can be passed to // the driver. // // Note: use [NewConnector] and [database/sql.OpenDB] to open a connection from a [*Config]. func (cfg *Config) FormatDSN() string { var buf bytes.Buffer // [username[:password]@] if len(cfg.User) > 0 { buf.WriteString(cfg.User) if len(cfg.Passwd) > 0 { buf.WriteByte(':') buf.WriteString(cfg.Passwd) } buf.WriteByte('@') } // [protocol[(address)]] if len(cfg.Net) > 0 { buf.WriteString(cfg.Net) if len(cfg.Addr) > 0 { buf.WriteByte('(') buf.WriteString(cfg.Addr) buf.WriteByte(')') } } // /dbname buf.WriteByte('/') buf.WriteString(url.PathEscape(cfg.DBName)) // [?param1=value1&...¶mN=valueN] hasParam := false if cfg.AllowAllFiles { hasParam = true buf.WriteString("?allowAllFiles=true") } if cfg.AllowCleartextPasswords { writeDSNParam(&buf, &hasParam, "allowCleartextPasswords", "true") } if cfg.AllowFallbackToPlaintext { writeDSNParam(&buf, &hasParam, "allowFallbackToPlaintext", "true") } if !cfg.AllowNativePasswords { writeDSNParam(&buf, &hasParam, "allowNativePasswords", "false") } if cfg.AllowOldPasswords { writeDSNParam(&buf, &hasParam, "allowOldPasswords", "true") } if !cfg.CheckConnLiveness { writeDSNParam(&buf, &hasParam, "checkConnLiveness", "false") } if cfg.ClientFoundRows { writeDSNParam(&buf, &hasParam, "clientFoundRows", "true") } if col := cfg.Collation; col != "" { writeDSNParam(&buf, &hasParam, "collation", col) } if cfg.ColumnsWithAlias { writeDSNParam(&buf, &hasParam, "columnsWithAlias", "true") } if cfg.InterpolateParams { writeDSNParam(&buf, &hasParam, "interpolateParams", "true") } if cfg.Loc != time.UTC && cfg.Loc != nil { writeDSNParam(&buf, &hasParam, "loc", url.QueryEscape(cfg.Loc.String())) } if cfg.MultiStatements { writeDSNParam(&buf, &hasParam, "multiStatements", "true") } if cfg.ParseTime { writeDSNParam(&buf, &hasParam, "parseTime", "true") } if cfg.timeTruncate > 0 { writeDSNParam(&buf, &hasParam, "timeTruncate", cfg.timeTruncate.String()) } if cfg.ReadTimeout > 0 { writeDSNParam(&buf, &hasParam, "readTimeout", cfg.ReadTimeout.String()) } if cfg.RejectReadOnly { writeDSNParam(&buf, &hasParam, "rejectReadOnly", "true") } if len(cfg.ServerPubKey) > 0 { writeDSNParam(&buf, &hasParam, "serverPubKey", url.QueryEscape(cfg.ServerPubKey)) } if cfg.Timeout > 0 { writeDSNParam(&buf, &hasParam, "timeout", cfg.Timeout.String()) } if len(cfg.TLSConfig) > 0 { writeDSNParam(&buf, &hasParam, "tls", url.QueryEscape(cfg.TLSConfig)) } if cfg.WriteTimeout > 0 { writeDSNParam(&buf, &hasParam, "writeTimeout", cfg.WriteTimeout.String()) } if cfg.MaxAllowedPacket != defaultMaxAllowedPacket { writeDSNParam(&buf, &hasParam, "maxAllowedPacket", strconv.Itoa(cfg.MaxAllowedPacket)) } // other params if cfg.Params != nil { var params []string for param := range cfg.Params { params = append(params, param) } sort.Strings(params) for _, param := range params { writeDSNParam(&buf, &hasParam, param, url.QueryEscape(cfg.Params[param])) } } return buf.String() } // ParseDSN parses the DSN string to a Config func ParseDSN(dsn string) (cfg *Config, err error) { // New config with some default values cfg = NewConfig() // [user[:password]@][net[(addr)]]/dbname[?param1=value1¶mN=valueN] // Find the last '/' (since the password or the net addr might contain a '/') foundSlash := false for i := len(dsn) - 1; i >= 0; i-- { if dsn[i] == '/' { foundSlash = true var j, k int // left part is empty if i <= 0 if i > 0 { // [username[:password]@][protocol[(address)]] // Find the last '@' in dsn[:i] for j = i; j >= 0; j-- { if dsn[j] == '@' { // username[:password] // Find the first ':' in dsn[:j] for k = 0; k < j; k++ { if dsn[k] == ':' { cfg.Passwd = dsn[k+1 : j] break } } cfg.User = dsn[:k] break } } // [protocol[(address)]] // Find the first '(' in dsn[j+1:i] for k = j + 1; k < i; k++ { if dsn[k] == '(' { // dsn[i-1] must be == ')' if an address is specified if dsn[i-1] != ')' { if strings.ContainsRune(dsn[k+1:i], ')') { return nil, errInvalidDSNUnescaped } return nil, errInvalidDSNAddr } cfg.Addr = dsn[k+1 : i-1] break } } cfg.Net = dsn[j+1 : k] } // dbname[?param1=value1&...¶mN=valueN] // Find the first '?' in dsn[i+1:] for j = i + 1; j < len(dsn); j++ { if dsn[j] == '?' { if err = parseDSNParams(cfg, dsn[j+1:]); err != nil { return } break } } dbname := dsn[i+1 : j] if cfg.DBName, err = url.PathUnescape(dbname); err != nil { return nil, fmt.Errorf("invalid dbname %q: %w", dbname, err) } break } } if !foundSlash && len(dsn) > 0 { return nil, errInvalidDSNNoSlash } if err = cfg.normalize(); err != nil { return nil, err } return } // parseDSNParams parses the DSN "query string" // Values must be url.QueryEscape'ed func parseDSNParams(cfg *Config, params string) (err error) { for _, v := range strings.Split(params, "&") { key, value, found := strings.Cut(v, "=") if !found { continue } // cfg params switch key { // Disable INFILE allowlist / enable all files case "allowAllFiles": var isBool bool cfg.AllowAllFiles, isBool = readBool(value) if !isBool { return errors.New("invalid bool value: " + value) } // Use cleartext authentication mode (MySQL 5.5.10+) case "allowCleartextPasswords": var isBool bool cfg.AllowCleartextPasswords, isBool = readBool(value) if !isBool { return errors.New("invalid bool value: " + value) } // Allow fallback to unencrypted connection if server does not support TLS case "allowFallbackToPlaintext": var isBool bool cfg.AllowFallbackToPlaintext, isBool = readBool(value) if !isBool { return errors.New("invalid bool value: " + value) } // Use native password authentication case "allowNativePasswords": var isBool bool cfg.AllowNativePasswords, isBool = readBool(value) if !isBool { return errors.New("invalid bool value: " + value) } // Use old authentication mode (pre MySQL 4.1) case "allowOldPasswords": var isBool bool cfg.AllowOldPasswords, isBool = readBool(value) if !isBool { return errors.New("invalid bool value: " + value) } // Check connections for Liveness before using them case "checkConnLiveness": var isBool bool cfg.CheckConnLiveness, isBool = readBool(value) if !isBool { return errors.New("invalid bool value: " + value) } // Switch "rowsAffected" mode case "clientFoundRows": var isBool bool cfg.ClientFoundRows, isBool = readBool(value) if !isBool { return errors.New("invalid bool value: " + value) } // Collation case "collation": cfg.Collation = value case "columnsWithAlias": var isBool bool cfg.ColumnsWithAlias, isBool = readBool(value) if !isBool { return errors.New("invalid bool value: " + value) } // Compression case "compress": return errors.New("compression not implemented yet") // Enable client side placeholder substitution case "interpolateParams": var isBool bool cfg.InterpolateParams, isBool = readBool(value) if !isBool { return errors.New("invalid bool value: " + value) } // Time Location case "loc": if value, err = url.QueryUnescape(value); err != nil { return } cfg.Loc, err = time.LoadLocation(value) if err != nil { return } // multiple statements in one query case "multiStatements": var isBool bool cfg.MultiStatements, isBool = readBool(value) if !isBool { return errors.New("invalid bool value: " + value) } // time.Time parsing case "parseTime": var isBool bool cfg.ParseTime, isBool = readBool(value) if !isBool { return errors.New("invalid bool value: " + value) } // time.Time truncation case "timeTruncate": cfg.timeTruncate, err = time.ParseDuration(value) if err != nil { return fmt.Errorf("invalid timeTruncate value: %v, error: %w", value, err) } // I/O read Timeout case "readTimeout": cfg.ReadTimeout, err = time.ParseDuration(value) if err != nil { return } // Reject read-only connections case "rejectReadOnly": var isBool bool cfg.RejectReadOnly, isBool = readBool(value) if !isBool { return errors.New("invalid bool value: " + value) } // Server public key case "serverPubKey": name, err := url.QueryUnescape(value) if err != nil { return fmt.Errorf("invalid value for server pub key name: %v", err) } cfg.ServerPubKey = name // Strict mode case "strict": panic("strict mode has been removed. See https://github.com/go-sql-driver/mysql/wiki/strict-mode") // Dial Timeout case "timeout": cfg.Timeout, err = time.ParseDuration(value) if err != nil { return } // TLS-Encryption case "tls": boolValue, isBool := readBool(value) if isBool { if boolValue { cfg.TLSConfig = "true" } else { cfg.TLSConfig = "false" } } else if vl := strings.ToLower(value); vl == "skip-verify" || vl == "preferred" { cfg.TLSConfig = vl } else { name, err := url.QueryUnescape(value) if err != nil { return fmt.Errorf("invalid value for TLS config name: %v", err) } cfg.TLSConfig = name } // I/O write Timeout case "writeTimeout": cfg.WriteTimeout, err = time.ParseDuration(value) if err != nil { return } case "maxAllowedPacket": cfg.MaxAllowedPacket, err = strconv.Atoi(value) if err != nil { return } // Connection attributes case "connectionAttributes": connectionAttributes, err := url.QueryUnescape(value) if err != nil { return fmt.Errorf("invalid connectionAttributes value: %v", err) } cfg.ConnectionAttributes = connectionAttributes default: // lazy init if cfg.Params == nil { cfg.Params = make(map[string]string) } if cfg.Params[key], err = url.QueryUnescape(value); err != nil { return } } } return } func ensureHavePort(addr string) string { if _, _, err := net.SplitHostPort(addr); err != nil { return net.JoinHostPort(addr, "3306") } return addr } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/errors.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // // Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. package mysql import ( "errors" "fmt" "log" "os" ) // Various errors the driver might return. Can change between driver versions. var ( ErrInvalidConn = errors.New("invalid connection") ErrMalformPkt = errors.New("malformed packet") ErrNoTLS = errors.New("TLS requested but server does not support TLS") ErrCleartextPassword = errors.New("this user requires clear text authentication. If you still want to use it, please add 'allowCleartextPasswords=1' to your DSN") ErrNativePassword = errors.New("this user requires mysql native password authentication") ErrOldPassword = errors.New("this user requires old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords") ErrUnknownPlugin = errors.New("this authentication plugin is not supported") ErrOldProtocol = errors.New("MySQL server does not support required protocol 41+") ErrPktSync = errors.New("commands out of sync. You can't run this command now") ErrPktSyncMul = errors.New("commands out of sync. Did you run multiple statements at once?") ErrPktTooLarge = errors.New("packet for query is too large. Try adjusting the `Config.MaxAllowedPacket`") ErrBusyBuffer = errors.New("busy buffer") // errBadConnNoWrite is used for connection errors where nothing was sent to the database yet. // If this happens first in a function starting a database interaction, it should be replaced by driver.ErrBadConn // to trigger a resend. // See https://github.com/go-sql-driver/mysql/pull/302 errBadConnNoWrite = errors.New("bad connection") ) var defaultLogger = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile)) // Logger is used to log critical error messages. type Logger interface { Print(v ...any) } // NopLogger is a nop implementation of the Logger interface. type NopLogger struct{} // Print implements Logger interface. func (nl *NopLogger) Print(_ ...any) {} // SetLogger is used to set the default logger for critical errors. // The initial logger is os.Stderr. func SetLogger(logger Logger) error { if logger == nil { return errors.New("logger is nil") } defaultLogger = logger return nil } // MySQLError is an error type which represents a single MySQL error type MySQLError struct { Number uint16 SQLState [5]byte Message string } func (me *MySQLError) Error() string { if me.SQLState != [5]byte{} { return fmt.Sprintf("Error %d (%s): %s", me.Number, me.SQLState, me.Message) } return fmt.Sprintf("Error %d: %s", me.Number, me.Message) } func (me *MySQLError) Is(err error) bool { if merr, ok := err.(*MySQLError); ok { return merr.Number == me.Number } return false } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/fields.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // // Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. package mysql import ( "database/sql" "reflect" ) func (mf *mysqlField) typeDatabaseName() string { switch mf.fieldType { case fieldTypeBit: return "BIT" case fieldTypeBLOB: if mf.charSet != binaryCollationID { return "TEXT" } return "BLOB" case fieldTypeDate: return "DATE" case fieldTypeDateTime: return "DATETIME" case fieldTypeDecimal: return "DECIMAL" case fieldTypeDouble: return "DOUBLE" case fieldTypeEnum: return "ENUM" case fieldTypeFloat: return "FLOAT" case fieldTypeGeometry: return "GEOMETRY" case fieldTypeInt24: if mf.flags&flagUnsigned != 0 { return "UNSIGNED MEDIUMINT" } return "MEDIUMINT" case fieldTypeJSON: return "JSON" case fieldTypeLong: if mf.flags&flagUnsigned != 0 { return "UNSIGNED INT" } return "INT" case fieldTypeLongBLOB: if mf.charSet != binaryCollationID { return "LONGTEXT" } return "LONGBLOB" case fieldTypeLongLong: if mf.flags&flagUnsigned != 0 { return "UNSIGNED BIGINT" } return "BIGINT" case fieldTypeMediumBLOB: if mf.charSet != binaryCollationID { return "MEDIUMTEXT" } return "MEDIUMBLOB" case fieldTypeNewDate: return "DATE" case fieldTypeNewDecimal: return "DECIMAL" case fieldTypeNULL: return "NULL" case fieldTypeSet: return "SET" case fieldTypeShort: if mf.flags&flagUnsigned != 0 { return "UNSIGNED SMALLINT" } return "SMALLINT" case fieldTypeString: if mf.flags&flagEnum != 0 { return "ENUM" } else if mf.flags&flagSet != 0 { return "SET" } if mf.charSet == binaryCollationID { return "BINARY" } return "CHAR" case fieldTypeTime: return "TIME" case fieldTypeTimestamp: return "TIMESTAMP" case fieldTypeTiny: if mf.flags&flagUnsigned != 0 { return "UNSIGNED TINYINT" } return "TINYINT" case fieldTypeTinyBLOB: if mf.charSet != binaryCollationID { return "TINYTEXT" } return "TINYBLOB" case fieldTypeVarChar: if mf.charSet == binaryCollationID { return "VARBINARY" } return "VARCHAR" case fieldTypeVarString: if mf.charSet == binaryCollationID { return "VARBINARY" } return "VARCHAR" case fieldTypeYear: return "YEAR" default: return "" } } var ( scanTypeFloat32 = reflect.TypeOf(float32(0)) scanTypeFloat64 = reflect.TypeOf(float64(0)) scanTypeInt8 = reflect.TypeOf(int8(0)) scanTypeInt16 = reflect.TypeOf(int16(0)) scanTypeInt32 = reflect.TypeOf(int32(0)) scanTypeInt64 = reflect.TypeOf(int64(0)) scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{}) scanTypeNullInt = reflect.TypeOf(sql.NullInt64{}) scanTypeNullTime = reflect.TypeOf(sql.NullTime{}) scanTypeUint8 = reflect.TypeOf(uint8(0)) scanTypeUint16 = reflect.TypeOf(uint16(0)) scanTypeUint32 = reflect.TypeOf(uint32(0)) scanTypeUint64 = reflect.TypeOf(uint64(0)) scanTypeString = reflect.TypeOf("") scanTypeNullString = reflect.TypeOf(sql.NullString{}) scanTypeBytes = reflect.TypeOf([]byte{}) scanTypeUnknown = reflect.TypeOf(new(any)) ) type mysqlField struct { tableName string name string length uint32 flags fieldFlag fieldType fieldType decimals byte charSet uint8 } func (mf *mysqlField) scanType() reflect.Type { switch mf.fieldType { case fieldTypeTiny: if mf.flags&flagNotNULL != 0 { if mf.flags&flagUnsigned != 0 { return scanTypeUint8 } return scanTypeInt8 } return scanTypeNullInt case fieldTypeShort, fieldTypeYear: if mf.flags&flagNotNULL != 0 { if mf.flags&flagUnsigned != 0 { return scanTypeUint16 } return scanTypeInt16 } return scanTypeNullInt case fieldTypeInt24, fieldTypeLong: if mf.flags&flagNotNULL != 0 { if mf.flags&flagUnsigned != 0 { return scanTypeUint32 } return scanTypeInt32 } return scanTypeNullInt case fieldTypeLongLong: if mf.flags&flagNotNULL != 0 { if mf.flags&flagUnsigned != 0 { return scanTypeUint64 } return scanTypeInt64 } return scanTypeNullInt case fieldTypeFloat: if mf.flags&flagNotNULL != 0 { return scanTypeFloat32 } return scanTypeNullFloat case fieldTypeDouble: if mf.flags&flagNotNULL != 0 { return scanTypeFloat64 } return scanTypeNullFloat case fieldTypeBit, fieldTypeTinyBLOB, fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB, fieldTypeVarString, fieldTypeString, fieldTypeGeometry: if mf.charSet == binaryCollationID { return scanTypeBytes } fallthrough case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar, fieldTypeEnum, fieldTypeSet, fieldTypeJSON, fieldTypeTime: if mf.flags&flagNotNULL != 0 { return scanTypeString } return scanTypeNullString case fieldTypeDate, fieldTypeNewDate, fieldTypeTimestamp, fieldTypeDateTime: // NullTime is always returned for more consistent behavior as it can // handle both cases of parseTime regardless if the field is nullable. return scanTypeNullTime default: return scanTypeUnknown } } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/infile.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // // Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. package mysql import ( "fmt" "io" "os" "strings" "sync" ) var ( fileRegister map[string]bool fileRegisterLock sync.RWMutex readerRegister map[string]func() io.Reader readerRegisterLock sync.RWMutex ) // RegisterLocalFile adds the given file to the file allowlist, // so that it can be used by "LOAD DATA LOCAL INFILE ". // Alternatively you can allow the use of all local files with // the DSN parameter 'allowAllFiles=true' // // filePath := "/home/gopher/data.csv" // mysql.RegisterLocalFile(filePath) // err := db.Exec("LOAD DATA LOCAL INFILE '" + filePath + "' INTO TABLE foo") // if err != nil { // ... func RegisterLocalFile(filePath string) { fileRegisterLock.Lock() // lazy map init if fileRegister == nil { fileRegister = make(map[string]bool) } fileRegister[strings.Trim(filePath, `"`)] = true fileRegisterLock.Unlock() } // DeregisterLocalFile removes the given filepath from the allowlist. func DeregisterLocalFile(filePath string) { fileRegisterLock.Lock() delete(fileRegister, strings.Trim(filePath, `"`)) fileRegisterLock.Unlock() } // RegisterReaderHandler registers a handler function which is used // to receive a io.Reader. // The Reader can be used by "LOAD DATA LOCAL INFILE Reader::". // If the handler returns a io.ReadCloser Close() is called when the // request is finished. // // mysql.RegisterReaderHandler("data", func() io.Reader { // var csvReader io.Reader // Some Reader that returns CSV data // ... // Open Reader here // return csvReader // }) // err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE foo") // if err != nil { // ... func RegisterReaderHandler(name string, handler func() io.Reader) { readerRegisterLock.Lock() // lazy map init if readerRegister == nil { readerRegister = make(map[string]func() io.Reader) } readerRegister[name] = handler readerRegisterLock.Unlock() } // DeregisterReaderHandler removes the ReaderHandler function with // the given name from the registry. func DeregisterReaderHandler(name string) { readerRegisterLock.Lock() delete(readerRegister, name) readerRegisterLock.Unlock() } func deferredClose(err *error, closer io.Closer) { closeErr := closer.Close() if *err == nil { *err = closeErr } } const defaultPacketSize = 16 * 1024 // 16KB is small enough for disk readahead and large enough for TCP func (mc *okHandler) handleInFileRequest(name string) (err error) { var rdr io.Reader var data []byte packetSize := defaultPacketSize if mc.maxWriteSize < packetSize { packetSize = mc.maxWriteSize } if idx := strings.Index(name, "Reader::"); idx == 0 || (idx > 0 && name[idx-1] == '/') { // io.Reader // The server might return an an absolute path. See issue #355. name = name[idx+8:] readerRegisterLock.RLock() handler, inMap := readerRegister[name] readerRegisterLock.RUnlock() if inMap { rdr = handler() if rdr != nil { if cl, ok := rdr.(io.Closer); ok { defer deferredClose(&err, cl) } } else { err = fmt.Errorf("reader '%s' is ", name) } } else { err = fmt.Errorf("reader '%s' is not registered", name) } } else { // File name = strings.Trim(name, `"`) fileRegisterLock.RLock() fr := fileRegister[name] fileRegisterLock.RUnlock() if mc.cfg.AllowAllFiles || fr { var file *os.File var fi os.FileInfo if file, err = os.Open(name); err == nil { defer deferredClose(&err, file) // get file size if fi, err = file.Stat(); err == nil { rdr = file if fileSize := int(fi.Size()); fileSize < packetSize { packetSize = fileSize } } } } else { err = fmt.Errorf("local file '%s' is not registered", name) } } // send content packets // if packetSize == 0, the Reader contains no data if err == nil && packetSize > 0 { data := make([]byte, 4+packetSize) var n int for err == nil { n, err = rdr.Read(data[4:]) if n > 0 { if ioErr := mc.conn().writePacket(data[:4+n]); ioErr != nil { return ioErr } } } if err == io.EOF { err = nil } } // send empty packet (termination) if data == nil { data = make([]byte, 4) } if ioErr := mc.conn().writePacket(data[:4]); ioErr != nil { return ioErr } // read OK packet if err == nil { return mc.readResultOK() } mc.conn().readPacket() return err } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/nulltime.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // // Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. package mysql import ( "database/sql" "database/sql/driver" "fmt" "time" ) // NullTime represents a time.Time that may be NULL. // NullTime implements the Scanner interface so // it can be used as a scan destination: // // var nt NullTime // err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt) // ... // if nt.Valid { // // use nt.Time // } else { // // NULL value // } // // # This NullTime implementation is not driver-specific // // Deprecated: NullTime doesn't honor the loc DSN parameter. // NullTime.Scan interprets a time as UTC, not the loc DSN parameter. // Use sql.NullTime instead. type NullTime sql.NullTime // Scan implements the Scanner interface. // The value type must be time.Time or string / []byte (formatted time-string), // otherwise Scan fails. func (nt *NullTime) Scan(value any) (err error) { if value == nil { nt.Time, nt.Valid = time.Time{}, false return } switch v := value.(type) { case time.Time: nt.Time, nt.Valid = v, true return case []byte: nt.Time, err = parseDateTime(v, time.UTC) nt.Valid = (err == nil) return case string: nt.Time, err = parseDateTime([]byte(v), time.UTC) nt.Valid = (err == nil) return } nt.Valid = false return fmt.Errorf("can't convert %T to time.Time", value) } // Value implements the driver Valuer interface. func (nt NullTime) Value() (driver.Value, error) { if !nt.Valid { return nil, nil } return nt.Time, nil } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/packets.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. package mysql import ( "bytes" "crypto/tls" "database/sql/driver" "encoding/binary" "encoding/json" "fmt" "io" "math" "strconv" "time" ) // Packets documentation: // http://dev.mysql.com/doc/internals/en/client-server-protocol.html // Read packet to buffer 'data' func (mc *mysqlConn) readPacket() ([]byte, error) { var prevData []byte for { // read packet header data, err := mc.buf.readNext(4) if err != nil { if cerr := mc.canceled.Value(); cerr != nil { return nil, cerr } mc.log(err) mc.Close() return nil, ErrInvalidConn } // packet length [24 bit] pktLen := int(uint32(data[0]) | uint32(data[1])<<8 | uint32(data[2])<<16) // check packet sync [8 bit] if data[3] != mc.sequence { mc.Close() if data[3] > mc.sequence { return nil, ErrPktSyncMul } return nil, ErrPktSync } mc.sequence++ // packets with length 0 terminate a previous packet which is a // multiple of (2^24)-1 bytes long if pktLen == 0 { // there was no previous packet if prevData == nil { mc.log(ErrMalformPkt) mc.Close() return nil, ErrInvalidConn } return prevData, nil } // read packet body [pktLen bytes] data, err = mc.buf.readNext(pktLen) if err != nil { if cerr := mc.canceled.Value(); cerr != nil { return nil, cerr } mc.log(err) mc.Close() return nil, ErrInvalidConn } // return data if this was the last packet if pktLen < maxPacketSize { // zero allocations for non-split packets if prevData == nil { return data, nil } return append(prevData, data...), nil } prevData = append(prevData, data...) } } // Write packet buffer 'data' func (mc *mysqlConn) writePacket(data []byte) error { pktLen := len(data) - 4 if pktLen > mc.maxAllowedPacket { return ErrPktTooLarge } for { var size int if pktLen >= maxPacketSize { data[0] = 0xff data[1] = 0xff data[2] = 0xff size = maxPacketSize } else { data[0] = byte(pktLen) data[1] = byte(pktLen >> 8) data[2] = byte(pktLen >> 16) size = pktLen } data[3] = mc.sequence // Write packet if mc.writeTimeout > 0 { if err := mc.netConn.SetWriteDeadline(time.Now().Add(mc.writeTimeout)); err != nil { return err } } n, err := mc.netConn.Write(data[:4+size]) if err == nil && n == 4+size { mc.sequence++ if size != maxPacketSize { return nil } pktLen -= size data = data[size:] continue } // Handle error if err == nil { // n != len(data) mc.cleanup() mc.log(ErrMalformPkt) } else { if cerr := mc.canceled.Value(); cerr != nil { return cerr } if n == 0 && pktLen == len(data)-4 { // only for the first loop iteration when nothing was written yet return errBadConnNoWrite } mc.cleanup() mc.log(err) } return ErrInvalidConn } } /****************************************************************************** * Initialization Process * ******************************************************************************/ // Handshake Initialization Packet // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err error) { data, err = mc.readPacket() if err != nil { // for init we can rewrite this to ErrBadConn for sql.Driver to retry, since // in connection initialization we don't risk retrying non-idempotent actions. if err == ErrInvalidConn { return nil, "", driver.ErrBadConn } return } if data[0] == iERR { return nil, "", mc.handleErrorPacket(data) } // protocol version [1 byte] if data[0] < minProtocolVersion { return nil, "", fmt.Errorf( "unsupported protocol version %d. Version %d or higher is required", data[0], minProtocolVersion, ) } // server version [null terminated string] // connection id [4 bytes] pos := 1 + bytes.IndexByte(data[1:], 0x00) + 1 + 4 // first part of the password cipher [8 bytes] authData := data[pos : pos+8] // (filler) always 0x00 [1 byte] pos += 8 + 1 // capability flags (lower 2 bytes) [2 bytes] mc.flags = clientFlag(binary.LittleEndian.Uint16(data[pos : pos+2])) if mc.flags&clientProtocol41 == 0 { return nil, "", ErrOldProtocol } if mc.flags&clientSSL == 0 && mc.cfg.TLS != nil { if mc.cfg.AllowFallbackToPlaintext { mc.cfg.TLS = nil } else { return nil, "", ErrNoTLS } } pos += 2 if len(data) > pos { // character set [1 byte] // status flags [2 bytes] // capability flags (upper 2 bytes) [2 bytes] // length of auth-plugin-data [1 byte] // reserved (all [00]) [10 bytes] pos += 1 + 2 + 2 + 1 + 10 // second part of the password cipher [minimum 13 bytes], // where len=MAX(13, length of auth-plugin-data - 8) // // The web documentation is ambiguous about the length. However, // according to mysql-5.7/sql/auth/sql_authentication.cc line 538, // the 13th byte is "\0 byte, terminating the second part of // a scramble". So the second part of the password cipher is // a NULL terminated string that's at least 13 bytes with the // last byte being NULL. // // The official Python library uses the fixed length 12 // which seems to work but technically could have a hidden bug. authData = append(authData, data[pos:pos+12]...) pos += 13 // EOF if version (>= 5.5.7 and < 5.5.10) or (>= 5.6.0 and < 5.6.2) // \NUL otherwise if end := bytes.IndexByte(data[pos:], 0x00); end != -1 { plugin = string(data[pos : pos+end]) } else { plugin = string(data[pos:]) } // make a memory safe copy of the cipher slice var b [20]byte copy(b[:], authData) return b[:], plugin, nil } // make a memory safe copy of the cipher slice var b [8]byte copy(b[:], authData) return b[:], plugin, nil } // Client Authentication Packet // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string) error { // Adjust client flags based on server support clientFlags := clientProtocol41 | clientSecureConn | clientLongPassword | clientTransactions | clientLocalFiles | clientPluginAuth | clientMultiResults | clientConnectAttrs | mc.flags&clientLongFlag if mc.cfg.ClientFoundRows { clientFlags |= clientFoundRows } // To enable TLS / SSL if mc.cfg.TLS != nil { clientFlags |= clientSSL } if mc.cfg.MultiStatements { clientFlags |= clientMultiStatements } // encode length of the auth plugin data var authRespLEIBuf [9]byte authRespLen := len(authResp) authRespLEI := appendLengthEncodedInteger(authRespLEIBuf[:0], uint64(authRespLen)) if len(authRespLEI) > 1 { // if the length can not be written in 1 byte, it must be written as a // length encoded integer clientFlags |= clientPluginAuthLenEncClientData } pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + len(authRespLEI) + len(authResp) + 21 + 1 // To specify a db name if n := len(mc.cfg.DBName); n > 0 { clientFlags |= clientConnectWithDB pktLen += n + 1 } // encode length of the connection attributes var connAttrsLEIBuf [9]byte connAttrsLen := len(mc.connector.encodedAttributes) connAttrsLEI := appendLengthEncodedInteger(connAttrsLEIBuf[:0], uint64(connAttrsLen)) pktLen += len(connAttrsLEI) + len(mc.connector.encodedAttributes) // Calculate packet length and get buffer with that size data, err := mc.buf.takeBuffer(pktLen + 4) if err != nil { // cannot take the buffer. Something must be wrong with the connection mc.log(err) return errBadConnNoWrite } // ClientFlags [32 bit] data[4] = byte(clientFlags) data[5] = byte(clientFlags >> 8) data[6] = byte(clientFlags >> 16) data[7] = byte(clientFlags >> 24) // MaxPacketSize [32 bit] (none) data[8] = 0x00 data[9] = 0x00 data[10] = 0x00 data[11] = 0x00 // Collation ID [1 byte] cname := mc.cfg.Collation if cname == "" { cname = defaultCollation } var found bool data[12], found = collations[cname] if !found { // Note possibility for false negatives: // could be triggered although the collation is valid if the // collations map does not contain entries the server supports. return fmt.Errorf("unknown collation: %q", cname) } // Filler [23 bytes] (all 0x00) pos := 13 for ; pos < 13+23; pos++ { data[pos] = 0 } // SSL Connection Request Packet // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest if mc.cfg.TLS != nil { // Send TLS / SSL request packet if err := mc.writePacket(data[:(4+4+1+23)+4]); err != nil { return err } // Switch to TLS tlsConn := tls.Client(mc.netConn, mc.cfg.TLS) if err := tlsConn.Handshake(); err != nil { return err } mc.netConn = tlsConn mc.buf.nc = tlsConn } // User [null terminated string] if len(mc.cfg.User) > 0 { pos += copy(data[pos:], mc.cfg.User) } data[pos] = 0x00 pos++ // Auth Data [length encoded integer] pos += copy(data[pos:], authRespLEI) pos += copy(data[pos:], authResp) // Databasename [null terminated string] if len(mc.cfg.DBName) > 0 { pos += copy(data[pos:], mc.cfg.DBName) data[pos] = 0x00 pos++ } pos += copy(data[pos:], plugin) data[pos] = 0x00 pos++ // Connection Attributes pos += copy(data[pos:], connAttrsLEI) pos += copy(data[pos:], []byte(mc.connector.encodedAttributes)) // Send Auth packet return mc.writePacket(data[:pos]) } // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte) error { pktLen := 4 + len(authData) data, err := mc.buf.takeSmallBuffer(pktLen) if err != nil { // cannot take the buffer. Something must be wrong with the connection mc.log(err) return errBadConnNoWrite } // Add the auth data [EOF] copy(data[4:], authData) return mc.writePacket(data) } /****************************************************************************** * Command Packets * ******************************************************************************/ func (mc *mysqlConn) writeCommandPacket(command byte) error { // Reset Packet Sequence mc.sequence = 0 data, err := mc.buf.takeSmallBuffer(4 + 1) if err != nil { // cannot take the buffer. Something must be wrong with the connection mc.log(err) return errBadConnNoWrite } // Add command byte data[4] = command // Send CMD packet return mc.writePacket(data) } func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error { // Reset Packet Sequence mc.sequence = 0 pktLen := 1 + len(arg) data, err := mc.buf.takeBuffer(pktLen + 4) if err != nil { // cannot take the buffer. Something must be wrong with the connection mc.log(err) return errBadConnNoWrite } // Add command byte data[4] = command // Add arg copy(data[5:], arg) // Send CMD packet return mc.writePacket(data) } func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error { // Reset Packet Sequence mc.sequence = 0 data, err := mc.buf.takeSmallBuffer(4 + 1 + 4) if err != nil { // cannot take the buffer. Something must be wrong with the connection mc.log(err) return errBadConnNoWrite } // Add command byte data[4] = command // Add arg [32 bit] data[5] = byte(arg) data[6] = byte(arg >> 8) data[7] = byte(arg >> 16) data[8] = byte(arg >> 24) // Send CMD packet return mc.writePacket(data) } /****************************************************************************** * Result Packets * ******************************************************************************/ func (mc *mysqlConn) readAuthResult() ([]byte, string, error) { data, err := mc.readPacket() if err != nil { return nil, "", err } // packet indicator switch data[0] { case iOK: // resultUnchanged, since auth happens before any queries or // commands have been executed. return nil, "", mc.resultUnchanged().handleOkPacket(data) case iAuthMoreData: return data[1:], "", err case iEOF: if len(data) == 1 { // https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest return nil, "mysql_old_password", nil } pluginEndIndex := bytes.IndexByte(data, 0x00) if pluginEndIndex < 0 { return nil, "", ErrMalformPkt } plugin := string(data[1:pluginEndIndex]) authData := data[pluginEndIndex+1:] return authData, plugin, nil default: // Error otherwise return nil, "", mc.handleErrorPacket(data) } } // Returns error if Packet is not a 'Result OK'-Packet func (mc *okHandler) readResultOK() error { data, err := mc.conn().readPacket() if err != nil { return err } if data[0] == iOK { return mc.handleOkPacket(data) } return mc.conn().handleErrorPacket(data) } // Result Set Header Packet // http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-ProtocolText::Resultset func (mc *okHandler) readResultSetHeaderPacket() (int, error) { // handleOkPacket replaces both values; other cases leave the values unchanged. mc.result.affectedRows = append(mc.result.affectedRows, 0) mc.result.insertIds = append(mc.result.insertIds, 0) data, err := mc.conn().readPacket() if err == nil { switch data[0] { case iOK: return 0, mc.handleOkPacket(data) case iERR: return 0, mc.conn().handleErrorPacket(data) case iLocalInFile: return 0, mc.handleInFileRequest(string(data[1:])) } // column count num, _, _ := readLengthEncodedInteger(data) // ignore remaining data in the packet. see #1478. return int(num), nil } return 0, err } // Error Packet // http://dev.mysql.com/doc/internals/en/generic-response-packets.html#packet-ERR_Packet func (mc *mysqlConn) handleErrorPacket(data []byte) error { if data[0] != iERR { return ErrMalformPkt } // 0xff [1 byte] // Error Number [16 bit uint] errno := binary.LittleEndian.Uint16(data[1:3]) // 1792: ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION // 1290: ER_OPTION_PREVENTS_STATEMENT (returned by Aurora during failover) if (errno == 1792 || errno == 1290) && mc.cfg.RejectReadOnly { // Oops; we are connected to a read-only connection, and won't be able // to issue any write statements. Since RejectReadOnly is configured, // we throw away this connection hoping this one would have write // permission. This is specifically for a possible race condition // during failover (e.g. on AWS Aurora). See README.md for more. // // We explicitly close the connection before returning // driver.ErrBadConn to ensure that `database/sql` purges this // connection and initiates a new one for next statement next time. mc.Close() return driver.ErrBadConn } me := &MySQLError{Number: errno} pos := 3 // SQL State [optional: # + 5bytes string] if data[3] == 0x23 { copy(me.SQLState[:], data[4:4+5]) pos = 9 } // Error Message [string] me.Message = string(data[pos:]) return me } func readStatus(b []byte) statusFlag { return statusFlag(b[0]) | statusFlag(b[1])<<8 } // Returns an instance of okHandler for codepaths where mysqlConn.result doesn't // need to be cleared first (e.g. during authentication, or while additional // resultsets are being fetched.) func (mc *mysqlConn) resultUnchanged() *okHandler { return (*okHandler)(mc) } // okHandler represents the state of the connection when mysqlConn.result has // been prepared for processing of OK packets. // // To correctly populate mysqlConn.result (updated by handleOkPacket()), all // callpaths must either: // // 1. first clear it using clearResult(), or // 2. confirm that they don't need to (by calling resultUnchanged()). // // Both return an instance of type *okHandler. type okHandler mysqlConn // Exposes the underlying type's methods. func (mc *okHandler) conn() *mysqlConn { return (*mysqlConn)(mc) } // clearResult clears the connection's stored affectedRows and insertIds // fields. // // It returns a handler that can process OK responses. func (mc *mysqlConn) clearResult() *okHandler { mc.result = mysqlResult{} return (*okHandler)(mc) } // Ok Packet // http://dev.mysql.com/doc/internals/en/generic-response-packets.html#packet-OK_Packet func (mc *okHandler) handleOkPacket(data []byte) error { var n, m int var affectedRows, insertId uint64 // 0x00 [1 byte] // Affected rows [Length Coded Binary] affectedRows, _, n = readLengthEncodedInteger(data[1:]) // Insert id [Length Coded Binary] insertId, _, m = readLengthEncodedInteger(data[1+n:]) // Update for the current statement result (only used by // readResultSetHeaderPacket). if len(mc.result.affectedRows) > 0 { mc.result.affectedRows[len(mc.result.affectedRows)-1] = int64(affectedRows) } if len(mc.result.insertIds) > 0 { mc.result.insertIds[len(mc.result.insertIds)-1] = int64(insertId) } // server_status [2 bytes] mc.status = readStatus(data[1+n+m : 1+n+m+2]) if mc.status&statusMoreResultsExists != 0 { return nil } // warning count [2 bytes] return nil } // Read Packets as Field Packets until EOF-Packet or an Error appears // http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnDefinition41 func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) { columns := make([]mysqlField, count) for i := 0; ; i++ { data, err := mc.readPacket() if err != nil { return nil, err } // EOF Packet if data[0] == iEOF && (len(data) == 5 || len(data) == 1) { if i == count { return columns, nil } return nil, fmt.Errorf("column count mismatch n:%d len:%d", count, len(columns)) } // Catalog pos, err := skipLengthEncodedString(data) if err != nil { return nil, err } // Database [len coded string] n, err := skipLengthEncodedString(data[pos:]) if err != nil { return nil, err } pos += n // Table [len coded string] if mc.cfg.ColumnsWithAlias { tableName, _, n, err := readLengthEncodedString(data[pos:]) if err != nil { return nil, err } pos += n columns[i].tableName = string(tableName) } else { n, err = skipLengthEncodedString(data[pos:]) if err != nil { return nil, err } pos += n } // Original table [len coded string] n, err = skipLengthEncodedString(data[pos:]) if err != nil { return nil, err } pos += n // Name [len coded string] name, _, n, err := readLengthEncodedString(data[pos:]) if err != nil { return nil, err } columns[i].name = string(name) pos += n // Original name [len coded string] n, err = skipLengthEncodedString(data[pos:]) if err != nil { return nil, err } pos += n // Filler [uint8] pos++ // Charset [charset, collation uint8] columns[i].charSet = data[pos] pos += 2 // Length [uint32] columns[i].length = binary.LittleEndian.Uint32(data[pos : pos+4]) pos += 4 // Field type [uint8] columns[i].fieldType = fieldType(data[pos]) pos++ // Flags [uint16] columns[i].flags = fieldFlag(binary.LittleEndian.Uint16(data[pos : pos+2])) pos += 2 // Decimals [uint8] columns[i].decimals = data[pos] //pos++ // Default value [len coded binary] //if pos < len(data) { // defaultVal, _, err = bytesToLengthCodedBinary(data[pos:]) //} } } // Read Packets as Field Packets until EOF-Packet or an Error appears // http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-ProtocolText::ResultsetRow func (rows *textRows) readRow(dest []driver.Value) error { mc := rows.mc if rows.rs.done { return io.EOF } data, err := mc.readPacket() if err != nil { return err } // EOF Packet if data[0] == iEOF && len(data) == 5 { // server_status [2 bytes] rows.mc.status = readStatus(data[3:]) rows.rs.done = true if !rows.HasNextResultSet() { rows.mc = nil } return io.EOF } if data[0] == iERR { rows.mc = nil return mc.handleErrorPacket(data) } // RowSet Packet var ( n int isNull bool pos int = 0 ) for i := range dest { // Read bytes and convert to string var buf []byte buf, isNull, n, err = readLengthEncodedString(data[pos:]) pos += n if err != nil { return err } if isNull { dest[i] = nil continue } switch rows.rs.columns[i].fieldType { case fieldTypeTimestamp, fieldTypeDateTime, fieldTypeDate, fieldTypeNewDate: if mc.parseTime { dest[i], err = parseDateTime(buf, mc.cfg.Loc) } else { dest[i] = buf } case fieldTypeTiny, fieldTypeShort, fieldTypeInt24, fieldTypeYear, fieldTypeLong: dest[i], err = strconv.ParseInt(string(buf), 10, 64) case fieldTypeLongLong: if rows.rs.columns[i].flags&flagUnsigned != 0 { dest[i], err = strconv.ParseUint(string(buf), 10, 64) } else { dest[i], err = strconv.ParseInt(string(buf), 10, 64) } case fieldTypeFloat: var d float64 d, err = strconv.ParseFloat(string(buf), 32) dest[i] = float32(d) case fieldTypeDouble: dest[i], err = strconv.ParseFloat(string(buf), 64) default: dest[i] = buf } if err != nil { return err } } return nil } // Reads Packets until EOF-Packet or an Error appears. Returns count of Packets read func (mc *mysqlConn) readUntilEOF() error { for { data, err := mc.readPacket() if err != nil { return err } switch data[0] { case iERR: return mc.handleErrorPacket(data) case iEOF: if len(data) == 5 { mc.status = readStatus(data[3:]) } return nil } } } /****************************************************************************** * Prepared Statements * ******************************************************************************/ // Prepare Result Packets // http://dev.mysql.com/doc/internals/en/com-stmt-prepare-response.html func (stmt *mysqlStmt) readPrepareResultPacket() (uint16, error) { data, err := stmt.mc.readPacket() if err == nil { // packet indicator [1 byte] if data[0] != iOK { return 0, stmt.mc.handleErrorPacket(data) } // statement id [4 bytes] stmt.id = binary.LittleEndian.Uint32(data[1:5]) // Column count [16 bit uint] columnCount := binary.LittleEndian.Uint16(data[5:7]) // Param count [16 bit uint] stmt.paramCount = int(binary.LittleEndian.Uint16(data[7:9])) // Reserved [8 bit] // Warning count [16 bit uint] return columnCount, nil } return 0, err } // http://dev.mysql.com/doc/internals/en/com-stmt-send-long-data.html func (stmt *mysqlStmt) writeCommandLongData(paramID int, arg []byte) error { maxLen := stmt.mc.maxAllowedPacket - 1 pktLen := maxLen // After the header (bytes 0-3) follows before the data: // 1 byte command // 4 bytes stmtID // 2 bytes paramID const dataOffset = 1 + 4 + 2 // Cannot use the write buffer since // a) the buffer is too small // b) it is in use data := make([]byte, 4+1+4+2+len(arg)) copy(data[4+dataOffset:], arg) for argLen := len(arg); argLen > 0; argLen -= pktLen - dataOffset { if dataOffset+argLen < maxLen { pktLen = dataOffset + argLen } stmt.mc.sequence = 0 // Add command byte [1 byte] data[4] = comStmtSendLongData // Add stmtID [32 bit] data[5] = byte(stmt.id) data[6] = byte(stmt.id >> 8) data[7] = byte(stmt.id >> 16) data[8] = byte(stmt.id >> 24) // Add paramID [16 bit] data[9] = byte(paramID) data[10] = byte(paramID >> 8) // Send CMD packet err := stmt.mc.writePacket(data[:4+pktLen]) if err == nil { data = data[pktLen-dataOffset:] continue } return err } // Reset Packet Sequence stmt.mc.sequence = 0 return nil } // Execute Prepared Statement // http://dev.mysql.com/doc/internals/en/com-stmt-execute.html func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { if len(args) != stmt.paramCount { return fmt.Errorf( "argument count mismatch (got: %d; has: %d)", len(args), stmt.paramCount, ) } const minPktLen = 4 + 1 + 4 + 1 + 4 mc := stmt.mc // Determine threshold dynamically to avoid packet size shortage. longDataSize := mc.maxAllowedPacket / (stmt.paramCount + 1) if longDataSize < 64 { longDataSize = 64 } // Reset packet-sequence mc.sequence = 0 var data []byte var err error if len(args) == 0 { data, err = mc.buf.takeBuffer(minPktLen) } else { data, err = mc.buf.takeCompleteBuffer() // In this case the len(data) == cap(data) which is used to optimise the flow below. } if err != nil { // cannot take the buffer. Something must be wrong with the connection mc.log(err) return errBadConnNoWrite } // command [1 byte] data[4] = comStmtExecute // statement_id [4 bytes] data[5] = byte(stmt.id) data[6] = byte(stmt.id >> 8) data[7] = byte(stmt.id >> 16) data[8] = byte(stmt.id >> 24) // flags (0: CURSOR_TYPE_NO_CURSOR) [1 byte] data[9] = 0x00 // iteration_count (uint32(1)) [4 bytes] data[10] = 0x01 data[11] = 0x00 data[12] = 0x00 data[13] = 0x00 if len(args) > 0 { pos := minPktLen var nullMask []byte if maskLen, typesLen := (len(args)+7)/8, 1+2*len(args); pos+maskLen+typesLen >= cap(data) { // buffer has to be extended but we don't know by how much so // we depend on append after all data with known sizes fit. // We stop at that because we deal with a lot of columns here // which makes the required allocation size hard to guess. tmp := make([]byte, pos+maskLen+typesLen) copy(tmp[:pos], data[:pos]) data = tmp nullMask = data[pos : pos+maskLen] // No need to clean nullMask as make ensures that. pos += maskLen } else { nullMask = data[pos : pos+maskLen] for i := range nullMask { nullMask[i] = 0 } pos += maskLen } // newParameterBoundFlag 1 [1 byte] data[pos] = 0x01 pos++ // type of each parameter [len(args)*2 bytes] paramTypes := data[pos:] pos += len(args) * 2 // value of each parameter [n bytes] paramValues := data[pos:pos] valuesCap := cap(paramValues) for i, arg := range args { // build NULL-bitmap if arg == nil { nullMask[i/8] |= 1 << (uint(i) & 7) paramTypes[i+i] = byte(fieldTypeNULL) paramTypes[i+i+1] = 0x00 continue } if v, ok := arg.(json.RawMessage); ok { arg = []byte(v) } // cache types and values switch v := arg.(type) { case int64: paramTypes[i+i] = byte(fieldTypeLongLong) paramTypes[i+i+1] = 0x00 if cap(paramValues)-len(paramValues)-8 >= 0 { paramValues = paramValues[:len(paramValues)+8] binary.LittleEndian.PutUint64( paramValues[len(paramValues)-8:], uint64(v), ) } else { paramValues = append(paramValues, uint64ToBytes(uint64(v))..., ) } case uint64: paramTypes[i+i] = byte(fieldTypeLongLong) paramTypes[i+i+1] = 0x80 // type is unsigned if cap(paramValues)-len(paramValues)-8 >= 0 { paramValues = paramValues[:len(paramValues)+8] binary.LittleEndian.PutUint64( paramValues[len(paramValues)-8:], uint64(v), ) } else { paramValues = append(paramValues, uint64ToBytes(uint64(v))..., ) } case float64: paramTypes[i+i] = byte(fieldTypeDouble) paramTypes[i+i+1] = 0x00 if cap(paramValues)-len(paramValues)-8 >= 0 { paramValues = paramValues[:len(paramValues)+8] binary.LittleEndian.PutUint64( paramValues[len(paramValues)-8:], math.Float64bits(v), ) } else { paramValues = append(paramValues, uint64ToBytes(math.Float64bits(v))..., ) } case bool: paramTypes[i+i] = byte(fieldTypeTiny) paramTypes[i+i+1] = 0x00 if v { paramValues = append(paramValues, 0x01) } else { paramValues = append(paramValues, 0x00) } case []byte: // Common case (non-nil value) first if v != nil { paramTypes[i+i] = byte(fieldTypeString) paramTypes[i+i+1] = 0x00 if len(v) < longDataSize { paramValues = appendLengthEncodedInteger(paramValues, uint64(len(v)), ) paramValues = append(paramValues, v...) } else { if err := stmt.writeCommandLongData(i, v); err != nil { return err } } continue } // Handle []byte(nil) as a NULL value nullMask[i/8] |= 1 << (uint(i) & 7) paramTypes[i+i] = byte(fieldTypeNULL) paramTypes[i+i+1] = 0x00 case string: paramTypes[i+i] = byte(fieldTypeString) paramTypes[i+i+1] = 0x00 if len(v) < longDataSize { paramValues = appendLengthEncodedInteger(paramValues, uint64(len(v)), ) paramValues = append(paramValues, v...) } else { if err := stmt.writeCommandLongData(i, []byte(v)); err != nil { return err } } case time.Time: paramTypes[i+i] = byte(fieldTypeString) paramTypes[i+i+1] = 0x00 var a [64]byte var b = a[:0] if v.IsZero() { b = append(b, "0000-00-00"...) } else { b, err = appendDateTime(b, v.In(mc.cfg.Loc), mc.cfg.timeTruncate) if err != nil { return err } } paramValues = appendLengthEncodedInteger(paramValues, uint64(len(b)), ) paramValues = append(paramValues, b...) default: return fmt.Errorf("cannot convert type: %T", arg) } } // Check if param values exceeded the available buffer // In that case we must build the data packet with the new values buffer if valuesCap != cap(paramValues) { data = append(data[:pos], paramValues...) if err = mc.buf.store(data); err != nil { mc.log(err) return errBadConnNoWrite } } pos += len(paramValues) data = data[:pos] } return mc.writePacket(data) } // For each remaining resultset in the stream, discards its rows and updates // mc.affectedRows and mc.insertIds. func (mc *okHandler) discardResults() error { for mc.status&statusMoreResultsExists != 0 { resLen, err := mc.readResultSetHeaderPacket() if err != nil { return err } if resLen > 0 { // columns if err := mc.conn().readUntilEOF(); err != nil { return err } // rows if err := mc.conn().readUntilEOF(); err != nil { return err } } } return nil } // http://dev.mysql.com/doc/internals/en/binary-protocol-resultset-row.html func (rows *binaryRows) readRow(dest []driver.Value) error { data, err := rows.mc.readPacket() if err != nil { return err } // packet indicator [1 byte] if data[0] != iOK { // EOF Packet if data[0] == iEOF && len(data) == 5 { rows.mc.status = readStatus(data[3:]) rows.rs.done = true if !rows.HasNextResultSet() { rows.mc = nil } return io.EOF } mc := rows.mc rows.mc = nil // Error otherwise return mc.handleErrorPacket(data) } // NULL-bitmap, [(column-count + 7 + 2) / 8 bytes] pos := 1 + (len(dest)+7+2)>>3 nullMask := data[1:pos] for i := range dest { // Field is NULL // (byte >> bit-pos) % 2 == 1 if ((nullMask[(i+2)>>3] >> uint((i+2)&7)) & 1) == 1 { dest[i] = nil continue } // Convert to byte-coded string switch rows.rs.columns[i].fieldType { case fieldTypeNULL: dest[i] = nil continue // Numeric Types case fieldTypeTiny: if rows.rs.columns[i].flags&flagUnsigned != 0 { dest[i] = int64(data[pos]) } else { dest[i] = int64(int8(data[pos])) } pos++ continue case fieldTypeShort, fieldTypeYear: if rows.rs.columns[i].flags&flagUnsigned != 0 { dest[i] = int64(binary.LittleEndian.Uint16(data[pos : pos+2])) } else { dest[i] = int64(int16(binary.LittleEndian.Uint16(data[pos : pos+2]))) } pos += 2 continue case fieldTypeInt24, fieldTypeLong: if rows.rs.columns[i].flags&flagUnsigned != 0 { dest[i] = int64(binary.LittleEndian.Uint32(data[pos : pos+4])) } else { dest[i] = int64(int32(binary.LittleEndian.Uint32(data[pos : pos+4]))) } pos += 4 continue case fieldTypeLongLong: if rows.rs.columns[i].flags&flagUnsigned != 0 { val := binary.LittleEndian.Uint64(data[pos : pos+8]) if val > math.MaxInt64 { dest[i] = uint64ToString(val) } else { dest[i] = int64(val) } } else { dest[i] = int64(binary.LittleEndian.Uint64(data[pos : pos+8])) } pos += 8 continue case fieldTypeFloat: dest[i] = math.Float32frombits(binary.LittleEndian.Uint32(data[pos : pos+4])) pos += 4 continue case fieldTypeDouble: dest[i] = math.Float64frombits(binary.LittleEndian.Uint64(data[pos : pos+8])) pos += 8 continue // Length coded Binary Strings case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar, fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB, fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB, fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON: var isNull bool var n int dest[i], isNull, n, err = readLengthEncodedString(data[pos:]) pos += n if err == nil { if !isNull { continue } else { dest[i] = nil continue } } return err case fieldTypeDate, fieldTypeNewDate, // Date YYYY-MM-DD fieldTypeTime, // Time [-][H]HH:MM:SS[.fractal] fieldTypeTimestamp, fieldTypeDateTime: // Timestamp YYYY-MM-DD HH:MM:SS[.fractal] num, isNull, n := readLengthEncodedInteger(data[pos:]) pos += n switch { case isNull: dest[i] = nil continue case rows.rs.columns[i].fieldType == fieldTypeTime: // database/sql does not support an equivalent to TIME, return a string var dstlen uint8 switch decimals := rows.rs.columns[i].decimals; decimals { case 0x00, 0x1f: dstlen = 8 case 1, 2, 3, 4, 5, 6: dstlen = 8 + 1 + decimals default: return fmt.Errorf( "protocol error, illegal decimals value %d", rows.rs.columns[i].decimals, ) } dest[i], err = formatBinaryTime(data[pos:pos+int(num)], dstlen) case rows.mc.parseTime: dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc) default: var dstlen uint8 if rows.rs.columns[i].fieldType == fieldTypeDate { dstlen = 10 } else { switch decimals := rows.rs.columns[i].decimals; decimals { case 0x00, 0x1f: dstlen = 19 case 1, 2, 3, 4, 5, 6: dstlen = 19 + 1 + decimals default: return fmt.Errorf( "protocol error, illegal decimals value %d", rows.rs.columns[i].decimals, ) } } dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen) } if err == nil { pos += int(num) continue } else { return err } // Please report if this happens! default: return fmt.Errorf("unknown field type %d", rows.rs.columns[i].fieldType) } } return nil } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/result.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. package mysql import "database/sql/driver" // Result exposes data not available through *connection.Result. // // This is accessible by executing statements using sql.Conn.Raw() and // downcasting the returned result: // // res, err := rawConn.Exec(...) // res.(mysql.Result).AllRowsAffected() type Result interface { driver.Result // AllRowsAffected returns a slice containing the affected rows for each // executed statement. AllRowsAffected() []int64 // AllLastInsertIds returns a slice containing the last inserted ID for each // executed statement. AllLastInsertIds() []int64 } type mysqlResult struct { // One entry in both slices is created for every executed statement result. affectedRows []int64 insertIds []int64 } func (res *mysqlResult) LastInsertId() (int64, error) { return res.insertIds[len(res.insertIds)-1], nil } func (res *mysqlResult) RowsAffected() (int64, error) { return res.affectedRows[len(res.affectedRows)-1], nil } func (res *mysqlResult) AllLastInsertIds() []int64 { return append([]int64{}, res.insertIds...) // defensive copy } func (res *mysqlResult) AllRowsAffected() []int64 { return append([]int64{}, res.affectedRows...) // defensive copy } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/rows.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. package mysql import ( "database/sql/driver" "io" "math" "reflect" ) type resultSet struct { columns []mysqlField columnNames []string done bool } type mysqlRows struct { mc *mysqlConn rs resultSet finish func() } type binaryRows struct { mysqlRows } type textRows struct { mysqlRows } func (rows *mysqlRows) Columns() []string { if rows.rs.columnNames != nil { return rows.rs.columnNames } columns := make([]string, len(rows.rs.columns)) if rows.mc != nil && rows.mc.cfg.ColumnsWithAlias { for i := range columns { if tableName := rows.rs.columns[i].tableName; len(tableName) > 0 { columns[i] = tableName + "." + rows.rs.columns[i].name } else { columns[i] = rows.rs.columns[i].name } } } else { for i := range columns { columns[i] = rows.rs.columns[i].name } } rows.rs.columnNames = columns return columns } func (rows *mysqlRows) ColumnTypeDatabaseTypeName(i int) string { return rows.rs.columns[i].typeDatabaseName() } // func (rows *mysqlRows) ColumnTypeLength(i int) (length int64, ok bool) { // return int64(rows.rs.columns[i].length), true // } func (rows *mysqlRows) ColumnTypeNullable(i int) (nullable, ok bool) { return rows.rs.columns[i].flags&flagNotNULL == 0, true } func (rows *mysqlRows) ColumnTypePrecisionScale(i int) (int64, int64, bool) { column := rows.rs.columns[i] decimals := int64(column.decimals) switch column.fieldType { case fieldTypeDecimal, fieldTypeNewDecimal: if decimals > 0 { return int64(column.length) - 2, decimals, true } return int64(column.length) - 1, decimals, true case fieldTypeTimestamp, fieldTypeDateTime, fieldTypeTime: return decimals, decimals, true case fieldTypeFloat, fieldTypeDouble: if decimals == 0x1f { return math.MaxInt64, math.MaxInt64, true } return math.MaxInt64, decimals, true } return 0, 0, false } func (rows *mysqlRows) ColumnTypeScanType(i int) reflect.Type { return rows.rs.columns[i].scanType() } func (rows *mysqlRows) Close() (err error) { if f := rows.finish; f != nil { f() rows.finish = nil } mc := rows.mc if mc == nil { return nil } if err := mc.error(); err != nil { return err } // flip the buffer for this connection if we need to drain it. // note that for a successful query (i.e. one where rows.next() // has been called until it returns false), `rows.mc` will be nil // by the time the user calls `(*Rows).Close`, so we won't reach this // see: https://github.com/golang/go/commit/651ddbdb5056ded455f47f9c494c67b389622a47 mc.buf.flip() // Remove unread packets from stream if !rows.rs.done { err = mc.readUntilEOF() } if err == nil { handleOk := mc.clearResult() if err = handleOk.discardResults(); err != nil { return err } } rows.mc = nil return err } func (rows *mysqlRows) HasNextResultSet() (b bool) { if rows.mc == nil { return false } return rows.mc.status&statusMoreResultsExists != 0 } func (rows *mysqlRows) nextResultSet() (int, error) { if rows.mc == nil { return 0, io.EOF } if err := rows.mc.error(); err != nil { return 0, err } // Remove unread packets from stream if !rows.rs.done { if err := rows.mc.readUntilEOF(); err != nil { return 0, err } rows.rs.done = true } if !rows.HasNextResultSet() { rows.mc = nil return 0, io.EOF } rows.rs = resultSet{} // rows.mc.affectedRows and rows.mc.insertIds accumulate on each call to // nextResultSet. resLen, err := rows.mc.resultUnchanged().readResultSetHeaderPacket() if err != nil { // Clean up about multi-results flag rows.rs.done = true rows.mc.status = rows.mc.status & (^statusMoreResultsExists) } return resLen, err } func (rows *mysqlRows) nextNotEmptyResultSet() (int, error) { for { resLen, err := rows.nextResultSet() if err != nil { return 0, err } if resLen > 0 { return resLen, nil } rows.rs.done = true } } func (rows *binaryRows) NextResultSet() error { resLen, err := rows.nextNotEmptyResultSet() if err != nil { return err } rows.rs.columns, err = rows.mc.readColumns(resLen) return err } func (rows *binaryRows) Next(dest []driver.Value) error { if mc := rows.mc; mc != nil { if err := mc.error(); err != nil { return err } // Fetch next row from stream return rows.readRow(dest) } return io.EOF } func (rows *textRows) NextResultSet() (err error) { resLen, err := rows.nextNotEmptyResultSet() if err != nil { return err } rows.rs.columns, err = rows.mc.readColumns(resLen) return err } func (rows *textRows) Next(dest []driver.Value) error { if mc := rows.mc; mc != nil { if err := mc.error(); err != nil { return err } // Fetch next row from stream return rows.readRow(dest) } return io.EOF } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/statement.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. package mysql import ( "database/sql/driver" "encoding/json" "fmt" "io" "reflect" ) type mysqlStmt struct { mc *mysqlConn id uint32 paramCount int } func (stmt *mysqlStmt) Close() error { if stmt.mc == nil || stmt.mc.closed.Load() { // driver.Stmt.Close can be called more than once, thus this function // has to be idempotent. // See also Issue #450 and golang/go#16019. //errLog.Print(ErrInvalidConn) return driver.ErrBadConn } err := stmt.mc.writeCommandPacketUint32(comStmtClose, stmt.id) stmt.mc = nil return err } func (stmt *mysqlStmt) NumInput() int { return stmt.paramCount } func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter { return converter{} } func (stmt *mysqlStmt) CheckNamedValue(nv *driver.NamedValue) (err error) { nv.Value, err = converter{}.ConvertValue(nv.Value) return } func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) { if stmt.mc.closed.Load() { stmt.mc.log(ErrInvalidConn) return nil, driver.ErrBadConn } // Send command err := stmt.writeExecutePacket(args) if err != nil { return nil, stmt.mc.markBadConn(err) } mc := stmt.mc handleOk := stmt.mc.clearResult() // Read Result resLen, err := handleOk.readResultSetHeaderPacket() if err != nil { return nil, err } if resLen > 0 { // Columns if err = mc.readUntilEOF(); err != nil { return nil, err } // Rows if err := mc.readUntilEOF(); err != nil { return nil, err } } if err := handleOk.discardResults(); err != nil { return nil, err } copied := mc.result return &copied, nil } func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) { return stmt.query(args) } func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) { if stmt.mc.closed.Load() { stmt.mc.log(ErrInvalidConn) return nil, driver.ErrBadConn } // Send command err := stmt.writeExecutePacket(args) if err != nil { return nil, stmt.mc.markBadConn(err) } mc := stmt.mc // Read Result handleOk := stmt.mc.clearResult() resLen, err := handleOk.readResultSetHeaderPacket() if err != nil { return nil, err } rows := new(binaryRows) if resLen > 0 { rows.mc = mc rows.rs.columns, err = mc.readColumns(resLen) } else { rows.rs.done = true switch err := rows.NextResultSet(); err { case nil, io.EOF: return rows, nil default: return nil, err } } return rows, err } var jsonType = reflect.TypeOf(json.RawMessage{}) type converter struct{} // ConvertValue mirrors the reference/default converter in database/sql/driver // with _one_ exception. We support uint64 with their high bit and the default // implementation does not. This function should be kept in sync with // database/sql/driver defaultConverter.ConvertValue() except for that // deliberate difference. func (c converter) ConvertValue(v any) (driver.Value, error) { if driver.IsValue(v) { return v, nil } if vr, ok := v.(driver.Valuer); ok { sv, err := callValuerValue(vr) if err != nil { return nil, err } if driver.IsValue(sv) { return sv, nil } // A value returned from the Valuer interface can be "a type handled by // a database driver's NamedValueChecker interface" so we should accept // uint64 here as well. if u, ok := sv.(uint64); ok { return u, nil } return nil, fmt.Errorf("non-Value type %T returned from Value", sv) } rv := reflect.ValueOf(v) switch rv.Kind() { case reflect.Ptr: // indirect pointers if rv.IsNil() { return nil, nil } else { return c.ConvertValue(rv.Elem().Interface()) } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return rv.Int(), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return rv.Uint(), nil case reflect.Float32, reflect.Float64: return rv.Float(), nil case reflect.Bool: return rv.Bool(), nil case reflect.Slice: switch t := rv.Type(); { case t == jsonType: return v, nil case t.Elem().Kind() == reflect.Uint8: return rv.Bytes(), nil default: return nil, fmt.Errorf("unsupported type %T, a slice of %s", v, t.Elem().Kind()) } case reflect.String: return rv.String(), nil } return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind()) } var valuerReflectType = reflect.TypeOf((*driver.Valuer)(nil)).Elem() // callValuerValue returns vr.Value(), with one exception: // If vr.Value is an auto-generated method on a pointer type and the // pointer is nil, it would panic at runtime in the panicwrap // method. Treat it like nil instead. // // This is so people can implement driver.Value on value types and // still use nil pointers to those types to mean nil/NULL, just like // string/*string. // // This is an exact copy of the same-named unexported function from the // database/sql package. func callValuerValue(vr driver.Valuer) (v driver.Value, err error) { if rv := reflect.ValueOf(vr); rv.Kind() == reflect.Ptr && rv.IsNil() && rv.Type().Elem().Implements(valuerReflectType) { return nil, nil } return vr.Value() } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/transaction.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. package mysql type mysqlTx struct { mc *mysqlConn } func (tx *mysqlTx) Commit() (err error) { if tx.mc == nil || tx.mc.closed.Load() { return ErrInvalidConn } err = tx.mc.exec("COMMIT") tx.mc = nil return } func (tx *mysqlTx) Rollback() (err error) { if tx.mc == nil || tx.mc.closed.Load() { return ErrInvalidConn } err = tx.mc.exec("ROLLBACK") tx.mc = nil return } ================================================ FILE: vendor/github.com/go-sql-driver/mysql/utils.go ================================================ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. package mysql import ( "crypto/tls" "database/sql" "database/sql/driver" "encoding/binary" "errors" "fmt" "io" "strconv" "strings" "sync" "sync/atomic" "time" ) // Registry for custom tls.Configs var ( tlsConfigLock sync.RWMutex tlsConfigRegistry map[string]*tls.Config ) // RegisterTLSConfig registers a custom tls.Config to be used with sql.Open. // Use the key as a value in the DSN where tls=value. // // Note: The provided tls.Config is exclusively owned by the driver after // registering it. // // rootCertPool := x509.NewCertPool() // pem, err := os.ReadFile("/path/ca-cert.pem") // if err != nil { // log.Fatal(err) // } // if ok := rootCertPool.AppendCertsFromPEM(pem); !ok { // log.Fatal("Failed to append PEM.") // } // clientCert := make([]tls.Certificate, 0, 1) // certs, err := tls.LoadX509KeyPair("/path/client-cert.pem", "/path/client-key.pem") // if err != nil { // log.Fatal(err) // } // clientCert = append(clientCert, certs) // mysql.RegisterTLSConfig("custom", &tls.Config{ // RootCAs: rootCertPool, // Certificates: clientCert, // }) // db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom") func RegisterTLSConfig(key string, config *tls.Config) error { if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" || strings.ToLower(key) == "preferred" { return fmt.Errorf("key '%s' is reserved", key) } tlsConfigLock.Lock() if tlsConfigRegistry == nil { tlsConfigRegistry = make(map[string]*tls.Config) } tlsConfigRegistry[key] = config tlsConfigLock.Unlock() return nil } // DeregisterTLSConfig removes the tls.Config associated with key. func DeregisterTLSConfig(key string) { tlsConfigLock.Lock() if tlsConfigRegistry != nil { delete(tlsConfigRegistry, key) } tlsConfigLock.Unlock() } func getTLSConfigClone(key string) (config *tls.Config) { tlsConfigLock.RLock() if v, ok := tlsConfigRegistry[key]; ok { config = v.Clone() } tlsConfigLock.RUnlock() return } // Returns the bool value of the input. // The 2nd return value indicates if the input was a valid bool value func readBool(input string) (value bool, valid bool) { switch input { case "1", "true", "TRUE", "True": return true, true case "0", "false", "FALSE", "False": return false, true } // Not a valid bool value return } /****************************************************************************** * Time related utils * ******************************************************************************/ func parseDateTime(b []byte, loc *time.Location) (time.Time, error) { const base = "0000-00-00 00:00:00.000000" switch len(b) { case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM" if string(b) == base[:len(b)] { return time.Time{}, nil } year, err := parseByteYear(b) if err != nil { return time.Time{}, err } if b[4] != '-' { return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[4]) } m, err := parseByte2Digits(b[5], b[6]) if err != nil { return time.Time{}, err } month := time.Month(m) if b[7] != '-' { return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[7]) } day, err := parseByte2Digits(b[8], b[9]) if err != nil { return time.Time{}, err } if len(b) == 10 { return time.Date(year, month, day, 0, 0, 0, 0, loc), nil } if b[10] != ' ' { return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[10]) } hour, err := parseByte2Digits(b[11], b[12]) if err != nil { return time.Time{}, err } if b[13] != ':' { return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[13]) } min, err := parseByte2Digits(b[14], b[15]) if err != nil { return time.Time{}, err } if b[16] != ':' { return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[16]) } sec, err := parseByte2Digits(b[17], b[18]) if err != nil { return time.Time{}, err } if len(b) == 19 { return time.Date(year, month, day, hour, min, sec, 0, loc), nil } if b[19] != '.' { return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[19]) } nsec, err := parseByteNanoSec(b[20:]) if err != nil { return time.Time{}, err } return time.Date(year, month, day, hour, min, sec, nsec, loc), nil default: return time.Time{}, fmt.Errorf("invalid time bytes: %s", b) } } func parseByteYear(b []byte) (int, error) { year, n := 0, 1000 for i := 0; i < 4; i++ { v, err := bToi(b[i]) if err != nil { return 0, err } year += v * n n /= 10 } return year, nil } func parseByte2Digits(b1, b2 byte) (int, error) { d1, err := bToi(b1) if err != nil { return 0, err } d2, err := bToi(b2) if err != nil { return 0, err } return d1*10 + d2, nil } func parseByteNanoSec(b []byte) (int, error) { ns, digit := 0, 100000 // max is 6-digits for i := 0; i < len(b); i++ { v, err := bToi(b[i]) if err != nil { return 0, err } ns += v * digit digit /= 10 } // nanoseconds has 10-digits. (needs to scale digits) // 10 - 6 = 4, so we have to multiple 1000. return ns * 1000, nil } func bToi(b byte) (int, error) { if b < '0' || b > '9' { return 0, errors.New("not [0-9]") } return int(b - '0'), nil } func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) { switch num { case 0: return time.Time{}, nil case 4: return time.Date( int(binary.LittleEndian.Uint16(data[:2])), // year time.Month(data[2]), // month int(data[3]), // day 0, 0, 0, 0, loc, ), nil case 7: return time.Date( int(binary.LittleEndian.Uint16(data[:2])), // year time.Month(data[2]), // month int(data[3]), // day int(data[4]), // hour int(data[5]), // minutes int(data[6]), // seconds 0, loc, ), nil case 11: return time.Date( int(binary.LittleEndian.Uint16(data[:2])), // year time.Month(data[2]), // month int(data[3]), // day int(data[4]), // hour int(data[5]), // minutes int(data[6]), // seconds int(binary.LittleEndian.Uint32(data[7:11]))*1000, // nanoseconds loc, ), nil } return nil, fmt.Errorf("invalid DATETIME packet length %d", num) } func appendDateTime(buf []byte, t time.Time, timeTruncate time.Duration) ([]byte, error) { if timeTruncate > 0 { t = t.Truncate(timeTruncate) } year, month, day := t.Date() hour, min, sec := t.Clock() nsec := t.Nanosecond() if year < 1 || year > 9999 { return buf, errors.New("year is not in the range [1, 9999]: " + strconv.Itoa(year)) // use errors.New instead of fmt.Errorf to avoid year escape to heap } year100 := year / 100 year1 := year % 100 var localBuf [len("2006-01-02T15:04:05.999999999")]byte // does not escape localBuf[0], localBuf[1], localBuf[2], localBuf[3] = digits10[year100], digits01[year100], digits10[year1], digits01[year1] localBuf[4] = '-' localBuf[5], localBuf[6] = digits10[month], digits01[month] localBuf[7] = '-' localBuf[8], localBuf[9] = digits10[day], digits01[day] if hour == 0 && min == 0 && sec == 0 && nsec == 0 { return append(buf, localBuf[:10]...), nil } localBuf[10] = ' ' localBuf[11], localBuf[12] = digits10[hour], digits01[hour] localBuf[13] = ':' localBuf[14], localBuf[15] = digits10[min], digits01[min] localBuf[16] = ':' localBuf[17], localBuf[18] = digits10[sec], digits01[sec] if nsec == 0 { return append(buf, localBuf[:19]...), nil } nsec100000000 := nsec / 100000000 nsec1000000 := (nsec / 1000000) % 100 nsec10000 := (nsec / 10000) % 100 nsec100 := (nsec / 100) % 100 nsec1 := nsec % 100 localBuf[19] = '.' // milli second localBuf[20], localBuf[21], localBuf[22] = digits01[nsec100000000], digits10[nsec1000000], digits01[nsec1000000] // micro second localBuf[23], localBuf[24], localBuf[25] = digits10[nsec10000], digits01[nsec10000], digits10[nsec100] // nano second localBuf[26], localBuf[27], localBuf[28] = digits01[nsec100], digits10[nsec1], digits01[nsec1] // trim trailing zeros n := len(localBuf) for n > 0 && localBuf[n-1] == '0' { n-- } return append(buf, localBuf[:n]...), nil } // zeroDateTime is used in formatBinaryDateTime to avoid an allocation // if the DATE or DATETIME has the zero value. // It must never be changed. // The current behavior depends on database/sql copying the result. var zeroDateTime = []byte("0000-00-00 00:00:00.000000") const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" func appendMicrosecs(dst, src []byte, decimals int) []byte { if decimals <= 0 { return dst } if len(src) == 0 { return append(dst, ".000000"[:decimals+1]...) } microsecs := binary.LittleEndian.Uint32(src[:4]) p1 := byte(microsecs / 10000) microsecs -= 10000 * uint32(p1) p2 := byte(microsecs / 100) microsecs -= 100 * uint32(p2) p3 := byte(microsecs) switch decimals { default: return append(dst, '.', digits10[p1], digits01[p1], digits10[p2], digits01[p2], digits10[p3], digits01[p3], ) case 1: return append(dst, '.', digits10[p1], ) case 2: return append(dst, '.', digits10[p1], digits01[p1], ) case 3: return append(dst, '.', digits10[p1], digits01[p1], digits10[p2], ) case 4: return append(dst, '.', digits10[p1], digits01[p1], digits10[p2], digits01[p2], ) case 5: return append(dst, '.', digits10[p1], digits01[p1], digits10[p2], digits01[p2], digits10[p3], ) } } func formatBinaryDateTime(src []byte, length uint8) (driver.Value, error) { // length expects the deterministic length of the zero value, // negative time and 100+ hours are automatically added if needed if len(src) == 0 { return zeroDateTime[:length], nil } var dst []byte // return value var p1, p2, p3 byte // current digit pair switch length { case 10, 19, 21, 22, 23, 24, 25, 26: default: t := "DATE" if length > 10 { t += "TIME" } return nil, fmt.Errorf("illegal %s length %d", t, length) } switch len(src) { case 4, 7, 11: default: t := "DATE" if length > 10 { t += "TIME" } return nil, fmt.Errorf("illegal %s packet length %d", t, len(src)) } dst = make([]byte, 0, length) // start with the date year := binary.LittleEndian.Uint16(src[:2]) pt := year / 100 p1 = byte(year - 100*uint16(pt)) p2, p3 = src[2], src[3] dst = append(dst, digits10[pt], digits01[pt], digits10[p1], digits01[p1], '-', digits10[p2], digits01[p2], '-', digits10[p3], digits01[p3], ) if length == 10 { return dst, nil } if len(src) == 4 { return append(dst, zeroDateTime[10:length]...), nil } dst = append(dst, ' ') p1 = src[4] // hour src = src[5:] // p1 is 2-digit hour, src is after hour p2, p3 = src[0], src[1] dst = append(dst, digits10[p1], digits01[p1], ':', digits10[p2], digits01[p2], ':', digits10[p3], digits01[p3], ) return appendMicrosecs(dst, src[2:], int(length)-20), nil } func formatBinaryTime(src []byte, length uint8) (driver.Value, error) { // length expects the deterministic length of the zero value, // negative time and 100+ hours are automatically added if needed if len(src) == 0 { return zeroDateTime[11 : 11+length], nil } var dst []byte // return value switch length { case 8, // time (can be up to 10 when negative and 100+ hours) 10, 11, 12, 13, 14, 15: // time with fractional seconds default: return nil, fmt.Errorf("illegal TIME length %d", length) } switch len(src) { case 8, 12: default: return nil, fmt.Errorf("invalid TIME packet length %d", len(src)) } // +2 to enable negative time and 100+ hours dst = make([]byte, 0, length+2) if src[0] == 1 { dst = append(dst, '-') } days := binary.LittleEndian.Uint32(src[1:5]) hours := int64(days)*24 + int64(src[5]) if hours >= 100 { dst = strconv.AppendInt(dst, hours, 10) } else { dst = append(dst, digits10[hours], digits01[hours]) } min, sec := src[6], src[7] dst = append(dst, ':', digits10[min], digits01[min], ':', digits10[sec], digits01[sec], ) return appendMicrosecs(dst, src[8:], int(length)-9), nil } /****************************************************************************** * Convert from and to bytes * ******************************************************************************/ func uint64ToBytes(n uint64) []byte { return []byte{ byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24), byte(n >> 32), byte(n >> 40), byte(n >> 48), byte(n >> 56), } } func uint64ToString(n uint64) []byte { var a [20]byte i := 20 // U+0030 = 0 // ... // U+0039 = 9 var q uint64 for n >= 10 { i-- q = n / 10 a[i] = uint8(n-q*10) + 0x30 n = q } i-- a[i] = uint8(n) + 0x30 return a[i:] } // treats string value as unsigned integer representation func stringToInt(b []byte) int { val := 0 for i := range b { val *= 10 val += int(b[i] - 0x30) } return val } // returns the string read as a bytes slice, whether the value is NULL, // the number of bytes read and an error, in case the string is longer than // the input slice func readLengthEncodedString(b []byte) ([]byte, bool, int, error) { // Get length num, isNull, n := readLengthEncodedInteger(b) if num < 1 { return b[n:n], isNull, n, nil } n += int(num) // Check data length if len(b) >= n { return b[n-int(num) : n : n], false, n, nil } return nil, false, n, io.EOF } // returns the number of bytes skipped and an error, in case the string is // longer than the input slice func skipLengthEncodedString(b []byte) (int, error) { // Get length num, _, n := readLengthEncodedInteger(b) if num < 1 { return n, nil } n += int(num) // Check data length if len(b) >= n { return n, nil } return n, io.EOF } // returns the number read, whether the value is NULL and the number of bytes read func readLengthEncodedInteger(b []byte) (uint64, bool, int) { // See issue #349 if len(b) == 0 { return 0, true, 1 } switch b[0] { // 251: NULL case 0xfb: return 0, true, 1 // 252: value of following 2 case 0xfc: return uint64(b[1]) | uint64(b[2])<<8, false, 3 // 253: value of following 3 case 0xfd: return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16, false, 4 // 254: value of following 8 case 0xfe: return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 | uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 | uint64(b[7])<<48 | uint64(b[8])<<56, false, 9 } // 0-250: value of first byte return uint64(b[0]), false, 1 } // encodes a uint64 value and appends it to the given bytes slice func appendLengthEncodedInteger(b []byte, n uint64) []byte { switch { case n <= 250: return append(b, byte(n)) case n <= 0xffff: return append(b, 0xfc, byte(n), byte(n>>8)) case n <= 0xffffff: return append(b, 0xfd, byte(n), byte(n>>8), byte(n>>16)) } return append(b, 0xfe, byte(n), byte(n>>8), byte(n>>16), byte(n>>24), byte(n>>32), byte(n>>40), byte(n>>48), byte(n>>56)) } func appendLengthEncodedString(b []byte, s string) []byte { b = appendLengthEncodedInteger(b, uint64(len(s))) return append(b, s...) } // reserveBuffer checks cap(buf) and expand buffer to len(buf) + appendSize. // If cap(buf) is not enough, reallocate new buffer. func reserveBuffer(buf []byte, appendSize int) []byte { newSize := len(buf) + appendSize if cap(buf) < newSize { // Grow buffer exponentially newBuf := make([]byte, len(buf)*2+appendSize) copy(newBuf, buf) buf = newBuf } return buf[:newSize] } // escapeBytesBackslash escapes []byte with backslashes (\) // This escapes the contents of a string (provided as []byte) by adding backslashes before special // characters, and turning others into specific escape sequences, such as // turning newlines into \n and null bytes into \0. // https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L823-L932 func escapeBytesBackslash(buf, v []byte) []byte { pos := len(buf) buf = reserveBuffer(buf, len(v)*2) for _, c := range v { switch c { case '\x00': buf[pos+1] = '0' buf[pos] = '\\' pos += 2 case '\n': buf[pos+1] = 'n' buf[pos] = '\\' pos += 2 case '\r': buf[pos+1] = 'r' buf[pos] = '\\' pos += 2 case '\x1a': buf[pos+1] = 'Z' buf[pos] = '\\' pos += 2 case '\'': buf[pos+1] = '\'' buf[pos] = '\\' pos += 2 case '"': buf[pos+1] = '"' buf[pos] = '\\' pos += 2 case '\\': buf[pos+1] = '\\' buf[pos] = '\\' pos += 2 default: buf[pos] = c pos++ } } return buf[:pos] } // escapeStringBackslash is similar to escapeBytesBackslash but for string. func escapeStringBackslash(buf []byte, v string) []byte { pos := len(buf) buf = reserveBuffer(buf, len(v)*2) for i := 0; i < len(v); i++ { c := v[i] switch c { case '\x00': buf[pos+1] = '0' buf[pos] = '\\' pos += 2 case '\n': buf[pos+1] = 'n' buf[pos] = '\\' pos += 2 case '\r': buf[pos+1] = 'r' buf[pos] = '\\' pos += 2 case '\x1a': buf[pos+1] = 'Z' buf[pos] = '\\' pos += 2 case '\'': buf[pos+1] = '\'' buf[pos] = '\\' pos += 2 case '"': buf[pos+1] = '"' buf[pos] = '\\' pos += 2 case '\\': buf[pos+1] = '\\' buf[pos] = '\\' pos += 2 default: buf[pos] = c pos++ } } return buf[:pos] } // escapeBytesQuotes escapes apostrophes in []byte by doubling them up. // This escapes the contents of a string by doubling up any apostrophes that // it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in // effect on the server. // https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L963-L1038 func escapeBytesQuotes(buf, v []byte) []byte { pos := len(buf) buf = reserveBuffer(buf, len(v)*2) for _, c := range v { if c == '\'' { buf[pos+1] = '\'' buf[pos] = '\'' pos += 2 } else { buf[pos] = c pos++ } } return buf[:pos] } // escapeStringQuotes is similar to escapeBytesQuotes but for string. func escapeStringQuotes(buf []byte, v string) []byte { pos := len(buf) buf = reserveBuffer(buf, len(v)*2) for i := 0; i < len(v); i++ { c := v[i] if c == '\'' { buf[pos+1] = '\'' buf[pos] = '\'' pos += 2 } else { buf[pos] = c pos++ } } return buf[:pos] } /****************************************************************************** * Sync utils * ******************************************************************************/ // noCopy may be embedded into structs which must not be copied // after the first use. // // See https://github.com/golang/go/issues/8005#issuecomment-190753527 // for details. type noCopy struct{} // Lock is a no-op used by -copylocks checker from `go vet`. func (*noCopy) Lock() {} // Unlock is a no-op used by -copylocks checker from `go vet`. // noCopy should implement sync.Locker from Go 1.11 // https://github.com/golang/go/commit/c2eba53e7f80df21d51285879d51ab81bcfbf6bc // https://github.com/golang/go/issues/26165 func (*noCopy) Unlock() {} // atomicError is a wrapper for atomically accessed error values type atomicError struct { _ noCopy value atomic.Value } // Set sets the error value regardless of the previous value. // The value must not be nil func (ae *atomicError) Set(value error) { ae.value.Store(value) } // Value returns the current error value func (ae *atomicError) Value() error { if v := ae.value.Load(); v != nil { // this will panic if the value doesn't implement the error interface return v.(error) } return nil } func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) { dargs := make([]driver.Value, len(named)) for n, param := range named { if len(param.Name) > 0 { // TODO: support the use of Named Parameters #561 return nil, errors.New("mysql: driver does not support the use of Named Parameters") } dargs[n] = param.Value } return dargs, nil } func mapIsolationLevel(level driver.IsolationLevel) (string, error) { switch sql.IsolationLevel(level) { case sql.LevelRepeatableRead: return "REPEATABLE READ", nil case sql.LevelReadCommitted: return "READ COMMITTED", nil case sql.LevelReadUncommitted: return "READ UNCOMMITTED", nil case sql.LevelSerializable: return "SERIALIZABLE", nil default: return "", fmt.Errorf("mysql: unsupported isolation level: %v", level) } } ================================================ FILE: vendor/github.com/goccy/go-json/.codecov.yml ================================================ codecov: require_ci_to_pass: yes coverage: precision: 2 round: down range: "70...100" status: project: default: target: 70% threshold: 2% patch: off changes: no parsers: gcov: branch_detection: conditional: yes loop: yes method: no macro: no comment: layout: "header,diff" behavior: default require_changes: no ignore: - internal/encoder/vm_color - internal/encoder/vm_color_indent ================================================ FILE: vendor/github.com/goccy/go-json/.gitignore ================================================ cover.html cover.out ================================================ FILE: vendor/github.com/goccy/go-json/.golangci.yml ================================================ run: skip-files: - encode_optype.go - ".*_test\\.go$" linters-settings: govet: enable-all: true disable: - shadow linters: enable-all: true disable: - dogsled - dupl - exhaustive - exhaustivestruct - errorlint - forbidigo - funlen - gci - gochecknoglobals - gochecknoinits - gocognit - gocritic - gocyclo - godot - godox - goerr113 - gofumpt - gomnd - gosec - ifshort - lll - makezero - nakedret - nestif - nlreturn - paralleltest - testpackage - thelper - wrapcheck - interfacer - lll - nakedret - nestif - nlreturn - testpackage - wsl - varnamelen - nilnil - ireturn - govet - forcetypeassert - cyclop - containedctx - revive issues: exclude-rules: # not needed - path: /*.go text: "ST1003: should not use underscores in package names" linters: - stylecheck - path: /*.go text: "don't use an underscore in package name" linters: - golint - path: rtype.go linters: - golint - stylecheck - path: error.go linters: - staticcheck # Maximum issues count per one linter. Set to 0 to disable. Default is 50. max-issues-per-linter: 0 # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. max-same-issues: 0 ================================================ FILE: vendor/github.com/goccy/go-json/CHANGELOG.md ================================================ # v0.10.2 - 2023/03/20 ### New features * Support DebugDOT option for debugging encoder ( #440 ) ### Fix bugs * Fix combination of embedding structure and omitempty option ( #442 ) # v0.10.1 - 2023/03/13 ### Fix bugs * Fix checkptr error for array decoder ( #415 ) * Fix added buffer size check when decoding key ( #430 ) * Fix handling of anonymous fields other than struct ( #431 ) * Fix to not optimize when lower conversion can't handle byte-by-byte ( #432 ) * Fix a problem that MarshalIndent does not work when UnorderedMap is specified ( #435 ) * Fix mapDecoder.DecodeStream() for empty objects containing whitespace ( #425 ) * Fix an issue that could not set the correct NextField for fields in the embedded structure ( #438 ) # v0.10.0 - 2022/11/29 ### New features * Support JSON Path ( #250 ) ### Fix bugs * Fix marshaler for map's key ( #409 ) # v0.9.11 - 2022/08/18 ### Fix bugs * Fix unexpected behavior when buffer ends with backslash ( #383 ) * Fix stream decoding of escaped character ( #387 ) # v0.9.10 - 2022/07/15 ### Fix bugs * Fix boundary exception of type caching ( #382 ) # v0.9.9 - 2022/07/15 ### Fix bugs * Fix encoding of directed interface with typed nil ( #377 ) * Fix embedded primitive type encoding using alias ( #378 ) * Fix slice/array type encoding with types implementing MarshalJSON ( #379 ) * Fix unicode decoding when the expected buffer state is not met after reading ( #380 ) # v0.9.8 - 2022/06/30 ### Fix bugs * Fix decoding of surrogate-pair ( #365 ) * Fix handling of embedded primitive type ( #366 ) * Add validation of escape sequence for decoder ( #367 ) * Fix stream tokenizing respecting UseNumber ( #369 ) * Fix encoding when struct pointer type that implements Marshal JSON is embedded ( #375 ) ### Improve performance * Improve performance of linkRecursiveCode ( #368 ) # v0.9.7 - 2022/04/22 ### Fix bugs #### Encoder * Add filtering process for encoding on slow path ( #355 ) * Fix encoding of interface{} with pointer type ( #363 ) #### Decoder * Fix map key decoder that implements UnmarshalJSON ( #353 ) * Fix decoding of []uint8 type ( #361 ) ### New features * Add DebugWith option for encoder ( #356 ) # v0.9.6 - 2022/03/22 ### Fix bugs * Correct the handling of the minimum value of int type for decoder ( #344 ) * Fix bugs of stream decoder's bufferSize ( #349 ) * Add a guard to use typeptr more safely ( #351 ) ### Improve decoder performance * Improve escapeString's performance ( #345 ) ### Others * Update go version for CI ( #347 ) # v0.9.5 - 2022/03/04 ### Fix bugs * Fix panic when decoding time.Time with context ( #328 ) * Fix reading the next character in buffer to nul consideration ( #338 ) * Fix incorrect handling on skipValue ( #341 ) ### Improve decoder performance * Improve performance when a payload contains escape sequence ( #334 ) # v0.9.4 - 2022/01/21 * Fix IsNilForMarshaler for string type with omitempty ( #323 ) * Fix the case where the embedded field is at the end ( #326 ) # v0.9.3 - 2022/01/14 * Fix logic of removing struct field for decoder ( #322 ) # v0.9.2 - 2022/01/14 * Add invalid decoder to delay type error judgment at decode ( #321 ) # v0.9.1 - 2022/01/11 * Fix encoding of MarshalText/MarshalJSON operation with head offset ( #319 ) # v0.9.0 - 2022/01/05 ### New feature * Supports dynamic filtering of struct fields ( #314 ) ### Improve encoding performance * Improve map encoding performance ( #310 ) * Optimize encoding path for escaped string ( #311 ) * Add encoding option for performance ( #312 ) ### Fix bugs * Fix panic at encoding map value on 1.18 ( #310 ) * Fix MarshalIndent for interface type ( #317 ) # v0.8.1 - 2021/12/05 * Fix operation conversion from PtrHead to Head in Recursive type ( #305 ) # v0.8.0 - 2021/12/02 * Fix embedded field conflict behavior ( #300 ) * Refactor compiler for encoder ( #301 #302 ) # v0.7.10 - 2021/10/16 * Fix conversion from pointer to uint64 ( #294 ) # v0.7.9 - 2021/09/28 * Fix encoding of nil value about interface type that has method ( #291 ) # v0.7.8 - 2021/09/01 * Fix mapassign_faststr for indirect struct type ( #283 ) * Fix encoding of not empty interface type ( #284 ) * Fix encoding of empty struct interface type ( #286 ) # v0.7.7 - 2021/08/25 * Fix invalid utf8 on stream decoder ( #279 ) * Fix buffer length bug on string stream decoder ( #280 ) Thank you @orisano !! # v0.7.6 - 2021/08/13 * Fix nil slice assignment ( #276 ) * Improve error message ( #277 ) # v0.7.5 - 2021/08/12 * Fix encoding of embedded struct with tags ( #265 ) * Fix encoding of embedded struct that isn't first field ( #272 ) * Fix decoding of binary type with escaped char ( #273 ) # v0.7.4 - 2021/07/06 * Fix encoding of indirect layout structure ( #264 ) # v0.7.3 - 2021/06/29 * Fix encoding of pointer type in empty interface ( #262 ) # v0.7.2 - 2021/06/26 ### Fix decoder * Add decoder for func type to fix decoding of nil function value ( #257 ) * Fix stream decoding of []byte type ( #258 ) ### Performance * Improve decoding performance of map[string]interface{} type ( use `mapassign_faststr` ) ( #256 ) * Improve encoding performance of empty interface type ( remove recursive calling of `vm.Run` ) ( #259 ) ### Benchmark * Add bytedance/sonic as benchmark target ( #254 ) # v0.7.1 - 2021/06/18 ### Fix decoder * Fix error when unmarshal empty array ( #253 ) # v0.7.0 - 2021/06/12 ### Support context for MarshalJSON and UnmarshalJSON ( #248 ) * json.MarshalContext(context.Context, interface{}, ...json.EncodeOption) ([]byte, error) * json.NewEncoder(io.Writer).EncodeContext(context.Context, interface{}, ...json.EncodeOption) error * json.UnmarshalContext(context.Context, []byte, interface{}, ...json.DecodeOption) error * json.NewDecoder(io.Reader).DecodeContext(context.Context, interface{}) error ```go type MarshalerContext interface { MarshalJSON(context.Context) ([]byte, error) } type UnmarshalerContext interface { UnmarshalJSON(context.Context, []byte) error } ``` ### Add DecodeFieldPriorityFirstWin option ( #242 ) In the default behavior, go-json, like encoding/json, will reflect the result of the last evaluation when a field with the same name exists. I've added new options to allow you to change this behavior. `json.DecodeFieldPriorityFirstWin` option reflects the result of the first evaluation if a field with the same name exists. This behavior has a performance advantage as it allows the subsequent strings to be skipped if all fields have been evaluated. ### Fix encoder * Fix indent number contains recursive type ( #249 ) * Fix encoding of using empty interface as map key ( #244 ) ### Fix decoder * Fix decoding fields containing escaped characters ( #237 ) ### Refactor * Move some tests to subdirectory ( #243 ) * Refactor package layout for decoder ( #238 ) # v0.6.1 - 2021/06/02 ### Fix encoder * Fix value of totalLength for encoding ( #236 ) # v0.6.0 - 2021/06/01 ### Support Colorize option for encoding (#233) ```go b, err := json.MarshalWithOption(v, json.Colorize(json.DefaultColorScheme)) if err != nil { ... } fmt.Println(string(b)) // print colored json ``` ### Refactor * Fix opcode layout - Adjust memory layout of the opcode to 128 bytes in a 64-bit environment ( #230 ) * Refactor encode option ( #231 ) * Refactor escape string ( #232 ) # v0.5.1 - 2021/5/20 ### Optimization * Add type addrShift to enable bigger encoder/decoder cache ( #213 ) ### Fix decoder * Keep original reference of slice element ( #229 ) ### Refactor * Refactor Debug mode for encoding ( #226 ) * Generate VM sources for encoding ( #227 ) * Refactor validator for null/true/false for decoding ( #221 ) # v0.5.0 - 2021/5/9 ### Supports using omitempty and string tags at the same time ( #216 ) ### Fix decoder * Fix stream decoder for unicode char ( #215 ) * Fix decoding of slice element ( #219 ) * Fix calculating of buffer length for stream decoder ( #220 ) ### Refactor * replace skipWhiteSpace goto by loop ( #212 ) # v0.4.14 - 2021/5/4 ### Benchmark * Add valyala/fastjson to benchmark ( #193 ) * Add benchmark task for CI ( #211 ) ### Fix decoder * Fix decoding of slice with unmarshal json type ( #198 ) * Fix decoding of null value for interface type that does not implement Unmarshaler ( #205 ) * Fix decoding of null value to []byte by json.Unmarshal ( #206 ) * Fix decoding of backslash char at the end of string ( #207 ) * Fix stream decoder for null/true/false value ( #208 ) * Fix stream decoder for slow reader ( #211 ) ### Performance * If cap of slice is enough, reuse slice data for compatibility with encoding/json ( #200 ) # v0.4.13 - 2021/4/20 ### Fix json.Compact and json.Indent * Support validation the input buffer for json.Compact and json.Indent ( #189 ) * Optimize json.Compact and json.Indent ( improve memory footprint ) ( #190 ) # v0.4.12 - 2021/4/15 ### Fix encoder * Fix unnecessary indent for empty slice type ( #181 ) * Fix encoding of omitempty feature for the slice or interface type ( #183 ) * Fix encoding custom types zero values with omitempty when marshaller exists ( #187 ) ### Fix decoder * Fix decoder for invalid top level value ( #184 ) * Fix decoder for invalid number value ( #185 ) # v0.4.11 - 2021/4/3 * Improve decoder performance for interface type # v0.4.10 - 2021/4/2 ### Fix encoder * Fixed a bug when encoding slice and map containing recursive structures * Fixed a logic to determine if indirect reference # v0.4.9 - 2021/3/29 ### Add debug mode If you use `json.MarshalWithOption(v, json.Debug())` and `panic` occurred in `go-json`, produces debug information to console. ### Support a new feature to compatible with encoding/json - invalid UTF-8 is coerced to valid UTF-8 ( without performance down ) ### Fix encoder - Fixed handling of MarshalJSON of function type ### Fix decoding of slice of pointer type If there is a pointer value, go-json will use it. (This behavior is necessary to achieve the ability to prioritize pre-filled values). However, since slices are reused internally, there was a bug that referred to the previous pointer value. Therefore, it is not necessary to refer to the pointer value in advance for the slice element, so we explicitly initialize slice element by `nil`. # v0.4.8 - 2021/3/21 ### Reduce memory usage at compile time * go-json have used about 2GB of memory at compile time, but now it can compile with about less than 550MB. ### Fix any encoder's bug * Add many test cases for encoder * Fix composite type ( slice/array/map ) * Fix pointer types * Fix encoding of MarshalJSON or MarshalText or json.Number type ### Refactor encoder * Change package layout for reducing memory usage at compile * Remove anonymous and only operation * Remove root property from encodeCompileContext and opcode ### Fix CI * Add Go 1.16 * Remove Go 1.13 * Fix `make cover` task ### Number/Delim/Token/RawMessage use the types defined in encoding/json by type alias # v0.4.7 - 2021/02/22 ### Fix decoder * Fix decoding of deep recursive structure * Fix decoding of embedded unexported pointer field * Fix invalid test case * Fix decoding of invalid value * Fix decoding of prefilled value * Fix not being able to return UnmarshalTypeError when it should be returned * Fix decoding of null value * Fix decoding of type of null string * Use pre allocated pointer if exists it at decoding ### Reduce memory usage at compile * Integrate int/int8/int16/int32/int64 and uint/uint8/uint16/uint32/uint64 operation to reduce memory usage at compile ### Remove unnecessary optype ================================================ FILE: vendor/github.com/goccy/go-json/LICENSE ================================================ MIT License Copyright (c) 2020 Masaaki Goshima Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: vendor/github.com/goccy/go-json/Makefile ================================================ PKG := github.com/goccy/go-json BIN_DIR := $(CURDIR)/bin PKGS := $(shell go list ./... | grep -v internal/cmd|grep -v test) COVER_PKGS := $(foreach pkg,$(PKGS),$(subst $(PKG),.,$(pkg))) COMMA := , EMPTY := SPACE := $(EMPTY) $(EMPTY) COVERPKG_OPT := $(subst $(SPACE),$(COMMA),$(COVER_PKGS)) $(BIN_DIR): @mkdir -p $(BIN_DIR) .PHONY: cover cover: go test -coverpkg=$(COVERPKG_OPT) -coverprofile=cover.out ./... .PHONY: cover-html cover-html: cover go tool cover -html=cover.out .PHONY: lint lint: golangci-lint $(BIN_DIR)/golangci-lint run golangci-lint: | $(BIN_DIR) @{ \ set -e; \ GOLANGCI_LINT_TMP_DIR=$$(mktemp -d); \ cd $$GOLANGCI_LINT_TMP_DIR; \ go mod init tmp; \ GOBIN=$(BIN_DIR) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.48.0; \ rm -rf $$GOLANGCI_LINT_TMP_DIR; \ } .PHONY: generate generate: go generate ./internal/... ================================================ FILE: vendor/github.com/goccy/go-json/README.md ================================================ # go-json ![Go](https://github.com/goccy/go-json/workflows/Go/badge.svg) [![GoDoc](https://godoc.org/github.com/goccy/go-json?status.svg)](https://pkg.go.dev/github.com/goccy/go-json?tab=doc) [![codecov](https://codecov.io/gh/goccy/go-json/branch/master/graph/badge.svg)](https://codecov.io/gh/goccy/go-json) Fast JSON encoder/decoder compatible with encoding/json for Go # Roadmap ``` * version ( expected release date ) * v0.9.0 | | while maintaining compatibility with encoding/json, we will add convenient APIs | v * v1.0.0 ``` We are accepting requests for features that will be implemented between v0.9.0 and v.1.0.0. If you have the API you need, please submit your issue [here](https://github.com/goccy/go-json/issues). # Features - Drop-in replacement of `encoding/json` - Fast ( See [Benchmark section](https://github.com/goccy/go-json#benchmarks) ) - Flexible customization with options - Coloring the encoded string - Can propagate context.Context to `MarshalJSON` or `UnmarshalJSON` - Can dynamically filter the fields of the structure type-safely # Installation ``` go get github.com/goccy/go-json ``` # How to use Replace import statement from `encoding/json` to `github.com/goccy/go-json` ``` -import "encoding/json" +import "github.com/goccy/go-json" ``` # JSON library comparison | name | encoder | decoder | compatible with `encoding/json` | | :----: | :------: | :-----: | :-----------------------------: | | encoding/json | yes | yes | N/A | | [json-iterator/go](https://github.com/json-iterator/go) | yes | yes | partial | | [easyjson](https://github.com/mailru/easyjson) | yes | yes | no | | [gojay](https://github.com/francoispqt/gojay) | yes | yes | no | | [segmentio/encoding/json](https://github.com/segmentio/encoding/tree/master/json) | yes | yes | partial | | [jettison](https://github.com/wI2L/jettison) | yes | no | no | | [simdjson-go](https://github.com/minio/simdjson-go) | no | yes | no | | goccy/go-json | yes | yes | yes | - `json-iterator/go` isn't compatible with `encoding/json` in many ways (e.g. https://github.com/json-iterator/go/issues/229 ), but it hasn't been supported for a long time. - `segmentio/encoding/json` is well supported for encoders, but some are not supported for decoder APIs such as `Token` ( streaming decode ) ## Other libraries - [jingo](https://github.com/bet365/jingo) I tried the benchmark but it didn't work. Also, it seems to panic when it receives an unexpected value because there is no error handling... - [ffjson](https://github.com/pquerna/ffjson) Benchmarking gave very slow results. It seems that it is assumed that the user will use the buffer pool properly. Also, development seems to have already stopped # Benchmarks ``` $ cd benchmarks $ go test -bench . ``` ## Encode ## Decode # Fuzzing [go-json-fuzz](https://github.com/goccy/go-json-fuzz) is the repository for fuzzing tests. If you run the test in this repository and find a bug, please commit to corpus to go-json-fuzz and report the issue to [go-json](https://github.com/goccy/go-json/issues). # How it works `go-json` is very fast in both encoding and decoding compared to other libraries. It's easier to implement by using automatic code generation for performance or by using a dedicated interface, but `go-json` dares to stick to compatibility with `encoding/json` and is the simple interface. Despite this, we are developing with the aim of being the fastest library. Here, we explain the various speed-up techniques implemented by `go-json`. ## Basic technique The techniques listed here are the ones used by most of the libraries listed above. ### Buffer reuse Since the only value required for the result of `json.Marshal(interface{}) ([]byte, error)` is `[]byte`, the only value that must be allocated during encoding is the return value `[]byte` . Also, as the number of allocations increases, the performance will be affected, so the number of allocations should be kept as low as possible when creating `[]byte`. Therefore, there is a technique to reduce the number of times a new buffer must be allocated by reusing the buffer used for the previous encoding by using `sync.Pool`. Finally, you allocate a buffer that is as long as the resulting buffer and copy the contents into it, you only need to allocate the buffer once in theory. ```go type buffer struct { data []byte } var bufPool = sync.Pool{ New: func() interface{} { return &buffer{data: make([]byte, 0, 1024)} }, } buf := bufPool.Get().(*buffer) data := encode(buf.data) // reuse buf.data newBuf := make([]byte, len(data)) copy(newBuf, buf) buf.data = data bufPool.Put(buf) ``` ### Elimination of reflection As you know, the reflection operation is very slow. Therefore, using the fact that the address position where the type information is stored is fixed for each binary ( we call this `typeptr` ), we can use the address in the type information to call a pre-built optimized process. For example, you can get the address to the type information from `interface{}` as follows and you can use that information to call a process that does not have reflection. To process without reflection, pass a pointer (`unsafe.Pointer`) to the value is stored. ```go type emptyInterface struct { typ unsafe.Pointer ptr unsafe.Pointer } var typeToEncoder = map[uintptr]func(unsafe.Pointer)([]byte, error){} func Marshal(v interface{}) ([]byte, error) { iface := (*emptyInterface)(unsafe.Pointer(&v) typeptr := uintptr(iface.typ) if enc, exists := typeToEncoder[typeptr]; exists { return enc(iface.ptr) } ... } ``` ※ In reality, `typeToEncoder` can be referenced by multiple goroutines, so exclusive control is required. ## Unique speed-up technique ## Encoder ### Do not escape arguments of `Marshal` `json.Marshal` and `json.Unmarshal` receive `interface{}` value and they perform type determination dynamically to process. In normal case, you need to use the `reflect` library to determine the type dynamically, but since `reflect.Type` is defined as `interface`, when you call the method of `reflect.Type`, The reflect's argument is escaped. Therefore, the arguments for `Marshal` and `Unmarshal` are always escaped to the heap. However, `go-json` can use the feature of `reflect.Type` while avoiding escaping. `reflect.Type` is defined as `interface`, but in reality `reflect.Type` is implemented only by the structure `rtype` defined in the `reflect` package. For this reason, to date `reflect.Type` is the same as `*reflect.rtype`. Therefore, by directly handling `*reflect.rtype`, which is an implementation of `reflect.Type`, it is possible to avoid escaping because it changes from `interface` to using `struct`. The technique for working with `*reflect.rtype` directly from `go-json` is implemented at [rtype.go](https://github.com/goccy/go-json/blob/master/internal/runtime/rtype.go) Also, the same technique is cut out as a library ( https://github.com/goccy/go-reflect ) Initially this feature was the default behavior of `go-json`. But after careful testing, I found that I passed a large value to `json.Marshal()` and if the argument could not be assigned to the stack, it could not be properly escaped to the heap (a bug in the Go compiler). Therefore, this feature will be provided as an **optional** until this issue is resolved. To use it, add `NoEscape` like `MarshalNoEscape()` ### Encoding using opcode sequence I explained that you can use `typeptr` to call a pre-built process from type information. In other libraries, this dedicated process is processed by making it an function calling like anonymous function, but function calls are inherently slow processes and should be avoided as much as possible. Therefore, `go-json` adopted the Instruction-based execution processing system, which is also used to implement virtual machines for programming language. If it is the first type to encode, create the opcode ( instruction ) sequence required for encoding. From the second time onward, use `typeptr` to get the cached pre-built opcode sequence and encode it based on it. An example of the opcode sequence is shown below. ```go json.Marshal(struct{ X int `json:"x"` Y string `json:"y"` }{X: 1, Y: "hello"}) ``` When encoding a structure like the one above, create a sequence of opcodes like this: ``` - opStructFieldHead ( `{` ) - opStructFieldInt ( `"x": 1,` ) - opStructFieldString ( `"y": "hello"` ) - opStructEnd ( `}` ) - opEnd ``` ※ When processing each operation, write the letters on the right. In addition, each opcode is managed by the following structure ( Pseudo code ). ```go type opType int const ( opStructFieldHead opType = iota opStructFieldInt opStructFieldStirng opStructEnd opEnd ) type opcode struct { op opType key []byte next *opcode } ``` The process of encoding using the opcode sequence is roughly implemented as follows. ```go func encode(code *opcode, b []byte, p unsafe.Pointer) ([]byte, error) { for { switch code.op { case opStructFieldHead: b = append(b, '{') code = code.next case opStructFieldInt: b = append(b, code.key...) b = appendInt((*int)(unsafe.Pointer(uintptr(p)+code.offset))) code = code.next case opStructFieldString: b = append(b, code.key...) b = appendString((*string)(unsafe.Pointer(uintptr(p)+code.offset))) code = code.next case opStructEnd: b = append(b, '}') code = code.next case opEnd: goto END } } END: return b, nil } ``` In this way, the huge `switch-case` is used to encode by manipulating the linked list opcodes to avoid unnecessary function calls. ### Opcode sequence optimization One of the advantages of encoding using the opcode sequence is the ease of optimization. The opcode sequence mentioned above is actually converted into the following optimized operations and used. ``` - opStructFieldHeadInt ( `{"x": 1,` ) - opStructEndString ( `"y": "hello"}` ) - opEnd ``` It has been reduced from 5 opcodes to 3 opcodes ! Reducing the number of opcodees means reducing the number of branches with `switch-case`. In other words, the closer the number of operations is to 1, the faster the processing can be performed. In `go-json`, optimization to reduce the number of opcodes itself like the above and it speeds up by preparing opcodes with optimized paths. ### Change recursive call from CALL to JMP Recursive processing is required during encoding if the type is defined recursively as follows: ```go type T struct { X int U *U } type U struct { T *T } b, err := json.Marshal(&T{ X: 1, U: &U{ T: &T{ X: 2, }, }, }) fmt.Println(string(b)) // {"X":1,"U":{"T":{"X":2,"U":null}}} ``` In `go-json`, recursive processing is processed by the operation type of ` opStructFieldRecursive`. In this operation, after acquiring the opcode sequence used for recursive processing, the function is **not** called recursively as it is, but the necessary values ​​are saved by itself and implemented by moving to the next operation. The technique of implementing recursive processing with the `JMP` operation while avoiding the `CALL` operation is a famous technique for implementing a high-speed virtual machine. For more details, please refer to [the article](https://engineering.mercari.com/blog/entry/1599563768-081104c850) ( but Japanese only ). ### Dispatch by typeptr from map to slice When retrieving the data cached from the type information by `typeptr`, we usually use map. Map requires exclusive control, so use `sync.Map` for a naive implementation. However, this is slow, so it's a good idea to use the `atomic` package for exclusive control as implemented by `segmentio/encoding/json` ( https://github.com/segmentio/encoding/blob/master/json/codec.go#L41-L55 ). This implementation slows down the set instead of speeding up the get, but it works well because of the nature of the library, it encodes much more for the same type. However, as a result of profiling, I noticed that `runtime.mapaccess2` accounts for a significant percentage of the execution time. So I thought if I could change the lookup from map to slice. There is an API named `typelinks` defined in the `runtime` package that the `reflect` package uses internally. This allows you to get all the type information defined in the binary at runtime. The fact that all type information can be acquired means that by constructing slices in advance with the acquired total number of type information, it is possible to look up with the value of `typeptr` without worrying about out-of-range access. However, if there is too much type information, it will use a lot of memory, so by default we will only use this optimization if the slice size fits within **2Mib** . If this approach is not available, it will fall back to the `atomic` based process described above. If you want to know more, please refer to the implementation [here](https://github.com/goccy/go-json/blob/master/internal/runtime/type.go#L36-L100) ## Decoder ### Dispatch by typeptr from map to slice Like the encoder, the decoder also uses typeptr to call the dedicated process. ### Faster termination character inspection using NUL character In order to decode, you have to traverse the input buffer character by position. At that time, if you check whether the buffer has reached the end, it will be very slow. `buf` : `[]byte` type variable. holds the string passed to the decoder `cursor` : `int64` type variable. holds the current read position ```go buflen := len(buf) for ; cursor < buflen; cursor++ { // compare cursor and buflen at all times, it is so slow. switch buf[cursor] { case ' ', '\n', '\r', '\t': } } ``` Therefore, by adding the `NUL` (`\000`) character to the end of the read buffer as shown below, it is possible to check the termination character at the same time as other characters. ```go for { switch buf[cursor] { case ' ', '\n', '\r', '\t': case '\000': return nil } cursor++ } ``` ### Use Boundary Check Elimination Due to the `NUL` character optimization, the Go compiler does a boundary check every time, even though `buf[cursor]` does not cause out-of-range access. Therefore, `go-json` eliminates boundary check by fetching characters for hotspot by pointer operation. For example, the following code. ```go func char(ptr unsafe.Pointer, offset int64) byte { return *(*byte)(unsafe.Pointer(uintptr(ptr) + uintptr(offset))) } p := (*sliceHeader)(&unsafe.Pointer(buf)).data for { switch char(p, cursor) { case ' ', '\n', '\r', '\t': case '\000': return nil } cursor++ } ``` ### Checking the existence of fields of struct using Bitmaps I found by the profiling result, in the struct decode, lookup process for field was taking a long time. For example, consider decoding a string like `{"a":1,"b":2,"c":3}` into the following structure: ```go type T struct { A int `json:"a"` B int `json:"b"` C int `json:"c"` } ``` At this time, it was found that it takes a lot of time to acquire the decoding process corresponding to the field from the field name as shown below during the decoding process. ```go fieldName := decodeKey(buf, cursor) // "a" or "b" or "c" decoder, exists := fieldToDecoderMap[fieldName] // so slow if exists { decoder(buf, cursor) } else { skipValue(buf, cursor) } ``` To improve this process, `json-iterator/go` is optimized so that it can be branched by switch-case when the number of fields in the structure is 10 or less (switch-case is faster than map). However, there is a risk of hash collision because the value hashed by the FNV algorithm is used for conditional branching. Also, `gojay` processes this part at high speed by letting the library user yourself write `switch-case`. `go-json` considers and implements a new approach that is different from these. I call this **bitmap field optimization**. The range of values ​​per character can be represented by `[256]byte`. Also, if the number of fields in the structure is 8 or less, `int8` type can represent the state of each field. In other words, it has the following structure. - Base ( 8bit ): `00000000` - Key "a": `00000001` ( assign key "a" to the first bit ) - Key "b": `00000010` ( assign key "b" to the second bit ) - Key "c": `00000100` ( assign key "c" to the third bit ) Bitmap structure is the following ``` | key index(0) | ------------------------ 0 | 00000000 | 1 | 00000000 | ~~ | | 97 (a) | 00000001 | 98 (b) | 00000010 | 99 (c) | 00000100 | ~~ | | 255 | 00000000 | ``` You can think of this as a Bitmap with a height of `256` and a width of the maximum string length in the field name. In other words, it can be represented by the following type . ```go [maxFieldKeyLength][256]int8 ``` When decoding a field character, check whether the corresponding character exists by referring to the pre-built bitmap like the following. ```go var curBit int8 = math.MaxInt8 // 11111111 c := char(buf, cursor) bit := bitmap[keyIdx][c] curBit &= bit if curBit == 0 { // not found field } ``` If `curBit` is not `0` until the end of the field string, then the string is You may have hit one of the fields. But the possibility is that if the decoded string is shorter than the field string, you will get a false hit. - input: `{"a":1}` ```go type T struct { X int `json:"abc"` } ``` ※ Since `a` is shorter than `abc`, it can decode to the end of the field character without `curBit` being 0. Rest assured. In this case, it doesn't matter because you can tell if you hit by comparing the string length of `a` with the string length of `abc`. Finally, calculate the position of the bit where `1` is set and get the corresponding value, and you're done. Using this technique, field lookups are possible with only bitwise operations and access to slices. `go-json` uses a similar technique for fields with 9 or more and 16 or less fields. At this time, Bitmap is constructed as `[maxKeyLen][256]int16` type. Currently, this optimization is not performed when the maximum length of the field name is long (specifically, 64 bytes or more) in addition to the limitation of the number of fields from the viewpoint of saving memory usage. ### Others I have done a lot of other optimizations. I will find time to write about them. If you have any questions about what's written here or other optimizations, please visit the `#go-json` channel on `gophers.slack.com` . ## Reference Regarding the story of go-json, there are the following articles in Japanese only. - https://speakerdeck.com/goccy/zui-su-falsejsonraiburariwoqiu-mete - https://engineering.mercari.com/blog/entry/1599563768-081104c850/ # Looking for Sponsors I'm looking for sponsors this library. This library is being developed as a personal project in my spare time. If you want a quick response or problem resolution when using this library in your project, please register as a [sponsor](https://github.com/sponsors/goccy). I will cooperate as much as possible. Of course, this library is developed as an MIT license, so you can use it freely for free. # License MIT ================================================ FILE: vendor/github.com/goccy/go-json/color.go ================================================ package json import ( "fmt" "github.com/goccy/go-json/internal/encoder" ) type ( ColorFormat = encoder.ColorFormat ColorScheme = encoder.ColorScheme ) const escape = "\x1b" type colorAttr int //nolint:deadcode,varcheck const ( fgBlackColor colorAttr = iota + 30 fgRedColor fgGreenColor fgYellowColor fgBlueColor fgMagentaColor fgCyanColor fgWhiteColor ) //nolint:deadcode,varcheck const ( fgHiBlackColor colorAttr = iota + 90 fgHiRedColor fgHiGreenColor fgHiYellowColor fgHiBlueColor fgHiMagentaColor fgHiCyanColor fgHiWhiteColor ) func createColorFormat(attr colorAttr) ColorFormat { return ColorFormat{ Header: wrapColor(attr), Footer: resetColor(), } } func wrapColor(attr colorAttr) string { return fmt.Sprintf("%s[%dm", escape, attr) } func resetColor() string { return wrapColor(colorAttr(0)) } var ( DefaultColorScheme = &ColorScheme{ Int: createColorFormat(fgHiMagentaColor), Uint: createColorFormat(fgHiMagentaColor), Float: createColorFormat(fgHiMagentaColor), Bool: createColorFormat(fgHiYellowColor), String: createColorFormat(fgHiGreenColor), Binary: createColorFormat(fgHiRedColor), ObjectKey: createColorFormat(fgHiCyanColor), Null: createColorFormat(fgBlueColor), } ) ================================================ FILE: vendor/github.com/goccy/go-json/decode.go ================================================ package json import ( "context" "fmt" "io" "reflect" "unsafe" "github.com/goccy/go-json/internal/decoder" "github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/runtime" ) type Decoder struct { s *decoder.Stream } const ( nul = '\000' ) type emptyInterface struct { typ *runtime.Type ptr unsafe.Pointer } func unmarshal(data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error { src := make([]byte, len(data)+1) // append nul byte to the end copy(src, data) header := (*emptyInterface)(unsafe.Pointer(&v)) if err := validateType(header.typ, uintptr(header.ptr)); err != nil { return err } dec, err := decoder.CompileToGetDecoder(header.typ) if err != nil { return err } ctx := decoder.TakeRuntimeContext() ctx.Buf = src ctx.Option.Flags = 0 for _, optFunc := range optFuncs { optFunc(ctx.Option) } cursor, err := dec.Decode(ctx, 0, 0, header.ptr) if err != nil { decoder.ReleaseRuntimeContext(ctx) return err } decoder.ReleaseRuntimeContext(ctx) return validateEndBuf(src, cursor) } func unmarshalContext(ctx context.Context, data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error { src := make([]byte, len(data)+1) // append nul byte to the end copy(src, data) header := (*emptyInterface)(unsafe.Pointer(&v)) if err := validateType(header.typ, uintptr(header.ptr)); err != nil { return err } dec, err := decoder.CompileToGetDecoder(header.typ) if err != nil { return err } rctx := decoder.TakeRuntimeContext() rctx.Buf = src rctx.Option.Flags = 0 rctx.Option.Flags |= decoder.ContextOption rctx.Option.Context = ctx for _, optFunc := range optFuncs { optFunc(rctx.Option) } cursor, err := dec.Decode(rctx, 0, 0, header.ptr) if err != nil { decoder.ReleaseRuntimeContext(rctx) return err } decoder.ReleaseRuntimeContext(rctx) return validateEndBuf(src, cursor) } var ( pathDecoder = decoder.NewPathDecoder() ) func extractFromPath(path *Path, data []byte, optFuncs ...DecodeOptionFunc) ([][]byte, error) { if path.path.RootSelectorOnly { return [][]byte{data}, nil } src := make([]byte, len(data)+1) // append nul byte to the end copy(src, data) ctx := decoder.TakeRuntimeContext() ctx.Buf = src ctx.Option.Flags = 0 ctx.Option.Flags |= decoder.PathOption ctx.Option.Path = path.path for _, optFunc := range optFuncs { optFunc(ctx.Option) } paths, cursor, err := pathDecoder.DecodePath(ctx, 0, 0) if err != nil { decoder.ReleaseRuntimeContext(ctx) return nil, err } decoder.ReleaseRuntimeContext(ctx) if err := validateEndBuf(src, cursor); err != nil { return nil, err } return paths, nil } func unmarshalNoEscape(data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error { src := make([]byte, len(data)+1) // append nul byte to the end copy(src, data) header := (*emptyInterface)(unsafe.Pointer(&v)) if err := validateType(header.typ, uintptr(header.ptr)); err != nil { return err } dec, err := decoder.CompileToGetDecoder(header.typ) if err != nil { return err } ctx := decoder.TakeRuntimeContext() ctx.Buf = src ctx.Option.Flags = 0 for _, optFunc := range optFuncs { optFunc(ctx.Option) } cursor, err := dec.Decode(ctx, 0, 0, noescape(header.ptr)) if err != nil { decoder.ReleaseRuntimeContext(ctx) return err } decoder.ReleaseRuntimeContext(ctx) return validateEndBuf(src, cursor) } func validateEndBuf(src []byte, cursor int64) error { for { switch src[cursor] { case ' ', '\t', '\n', '\r': cursor++ continue case nul: return nil } return errors.ErrSyntax( fmt.Sprintf("invalid character '%c' after top-level value", src[cursor]), cursor+1, ) } } //nolint:staticcheck //go:nosplit func noescape(p unsafe.Pointer) unsafe.Pointer { x := uintptr(p) return unsafe.Pointer(x ^ 0) } func validateType(typ *runtime.Type, p uintptr) error { if typ == nil || typ.Kind() != reflect.Ptr || p == 0 { return &InvalidUnmarshalError{Type: runtime.RType2Type(typ)} } return nil } // NewDecoder returns a new decoder that reads from r. // // The decoder introduces its own buffering and may // read data from r beyond the JSON values requested. func NewDecoder(r io.Reader) *Decoder { s := decoder.NewStream(r) return &Decoder{ s: s, } } // Buffered returns a reader of the data remaining in the Decoder's // buffer. The reader is valid until the next call to Decode. func (d *Decoder) Buffered() io.Reader { return d.s.Buffered() } // Decode reads the next JSON-encoded value from its // input and stores it in the value pointed to by v. // // See the documentation for Unmarshal for details about // the conversion of JSON into a Go value. func (d *Decoder) Decode(v interface{}) error { return d.DecodeWithOption(v) } // DecodeContext reads the next JSON-encoded value from its // input and stores it in the value pointed to by v with context.Context. func (d *Decoder) DecodeContext(ctx context.Context, v interface{}) error { d.s.Option.Flags |= decoder.ContextOption d.s.Option.Context = ctx return d.DecodeWithOption(v) } func (d *Decoder) DecodeWithOption(v interface{}, optFuncs ...DecodeOptionFunc) error { header := (*emptyInterface)(unsafe.Pointer(&v)) typ := header.typ ptr := uintptr(header.ptr) typeptr := uintptr(unsafe.Pointer(typ)) // noescape trick for header.typ ( reflect.*rtype ) copiedType := *(**runtime.Type)(unsafe.Pointer(&typeptr)) if err := validateType(copiedType, ptr); err != nil { return err } dec, err := decoder.CompileToGetDecoder(typ) if err != nil { return err } if err := d.s.PrepareForDecode(); err != nil { return err } s := d.s for _, optFunc := range optFuncs { optFunc(s.Option) } if err := dec.DecodeStream(s, 0, header.ptr); err != nil { return err } s.Reset() return nil } func (d *Decoder) More() bool { return d.s.More() } func (d *Decoder) Token() (Token, error) { return d.s.Token() } // DisallowUnknownFields causes the Decoder to return an error when the destination // is a struct and the input contains object keys which do not match any // non-ignored, exported fields in the destination. func (d *Decoder) DisallowUnknownFields() { d.s.DisallowUnknownFields = true } func (d *Decoder) InputOffset() int64 { return d.s.TotalOffset() } // UseNumber causes the Decoder to unmarshal a number into an interface{} as a // Number instead of as a float64. func (d *Decoder) UseNumber() { d.s.UseNumber = true } ================================================ FILE: vendor/github.com/goccy/go-json/docker-compose.yml ================================================ version: '2' services: go-json: image: golang:1.18 volumes: - '.:/go/src/go-json' deploy: resources: limits: memory: 620M working_dir: /go/src/go-json command: | sh -c "go test -c . && ls go-json.test" ================================================ FILE: vendor/github.com/goccy/go-json/encode.go ================================================ package json import ( "context" "io" "os" "unsafe" "github.com/goccy/go-json/internal/encoder" "github.com/goccy/go-json/internal/encoder/vm" "github.com/goccy/go-json/internal/encoder/vm_color" "github.com/goccy/go-json/internal/encoder/vm_color_indent" "github.com/goccy/go-json/internal/encoder/vm_indent" ) // An Encoder writes JSON values to an output stream. type Encoder struct { w io.Writer enabledIndent bool enabledHTMLEscape bool prefix string indentStr string } // NewEncoder returns a new encoder that writes to w. func NewEncoder(w io.Writer) *Encoder { return &Encoder{w: w, enabledHTMLEscape: true} } // Encode writes the JSON encoding of v to the stream, followed by a newline character. // // See the documentation for Marshal for details about the conversion of Go values to JSON. func (e *Encoder) Encode(v interface{}) error { return e.EncodeWithOption(v) } // EncodeWithOption call Encode with EncodeOption. func (e *Encoder) EncodeWithOption(v interface{}, optFuncs ...EncodeOptionFunc) error { ctx := encoder.TakeRuntimeContext() ctx.Option.Flag = 0 err := e.encodeWithOption(ctx, v, optFuncs...) encoder.ReleaseRuntimeContext(ctx) return err } // EncodeContext call Encode with context.Context and EncodeOption. func (e *Encoder) EncodeContext(ctx context.Context, v interface{}, optFuncs ...EncodeOptionFunc) error { rctx := encoder.TakeRuntimeContext() rctx.Option.Flag = 0 rctx.Option.Flag |= encoder.ContextOption rctx.Option.Context = ctx err := e.encodeWithOption(rctx, v, optFuncs...) encoder.ReleaseRuntimeContext(rctx) return err } func (e *Encoder) encodeWithOption(ctx *encoder.RuntimeContext, v interface{}, optFuncs ...EncodeOptionFunc) error { if e.enabledHTMLEscape { ctx.Option.Flag |= encoder.HTMLEscapeOption } ctx.Option.Flag |= encoder.NormalizeUTF8Option ctx.Option.DebugOut = os.Stdout for _, optFunc := range optFuncs { optFunc(ctx.Option) } var ( buf []byte err error ) if e.enabledIndent { buf, err = encodeIndent(ctx, v, e.prefix, e.indentStr) } else { buf, err = encode(ctx, v) } if err != nil { return err } if e.enabledIndent { buf = buf[:len(buf)-2] } else { buf = buf[:len(buf)-1] } buf = append(buf, '\n') if _, err := e.w.Write(buf); err != nil { return err } return nil } // SetEscapeHTML specifies whether problematic HTML characters should be escaped inside JSON quoted strings. // The default behavior is to escape &, <, and > to \u0026, \u003c, and \u003e to avoid certain safety problems that can arise when embedding JSON in HTML. // // In non-HTML settings where the escaping interferes with the readability of the output, SetEscapeHTML(false) disables this behavior. func (e *Encoder) SetEscapeHTML(on bool) { e.enabledHTMLEscape = on } // SetIndent instructs the encoder to format each subsequent encoded value as if indented by the package-level function Indent(dst, src, prefix, indent). // Calling SetIndent("", "") disables indentation. func (e *Encoder) SetIndent(prefix, indent string) { if prefix == "" && indent == "" { e.enabledIndent = false return } e.prefix = prefix e.indentStr = indent e.enabledIndent = true } func marshalContext(ctx context.Context, v interface{}, optFuncs ...EncodeOptionFunc) ([]byte, error) { rctx := encoder.TakeRuntimeContext() rctx.Option.Flag = 0 rctx.Option.Flag = encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option | encoder.ContextOption rctx.Option.Context = ctx for _, optFunc := range optFuncs { optFunc(rctx.Option) } buf, err := encode(rctx, v) if err != nil { encoder.ReleaseRuntimeContext(rctx) return nil, err } // this line exists to escape call of `runtime.makeslicecopy` . // if use `make([]byte, len(buf)-1)` and `copy(copied, buf)`, // dst buffer size and src buffer size are differrent. // in this case, compiler uses `runtime.makeslicecopy`, but it is slow. buf = buf[:len(buf)-1] copied := make([]byte, len(buf)) copy(copied, buf) encoder.ReleaseRuntimeContext(rctx) return copied, nil } func marshal(v interface{}, optFuncs ...EncodeOptionFunc) ([]byte, error) { ctx := encoder.TakeRuntimeContext() ctx.Option.Flag = 0 ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option) for _, optFunc := range optFuncs { optFunc(ctx.Option) } buf, err := encode(ctx, v) if err != nil { encoder.ReleaseRuntimeContext(ctx) return nil, err } // this line exists to escape call of `runtime.makeslicecopy` . // if use `make([]byte, len(buf)-1)` and `copy(copied, buf)`, // dst buffer size and src buffer size are differrent. // in this case, compiler uses `runtime.makeslicecopy`, but it is slow. buf = buf[:len(buf)-1] copied := make([]byte, len(buf)) copy(copied, buf) encoder.ReleaseRuntimeContext(ctx) return copied, nil } func marshalNoEscape(v interface{}) ([]byte, error) { ctx := encoder.TakeRuntimeContext() ctx.Option.Flag = 0 ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option) buf, err := encodeNoEscape(ctx, v) if err != nil { encoder.ReleaseRuntimeContext(ctx) return nil, err } // this line exists to escape call of `runtime.makeslicecopy` . // if use `make([]byte, len(buf)-1)` and `copy(copied, buf)`, // dst buffer size and src buffer size are differrent. // in this case, compiler uses `runtime.makeslicecopy`, but it is slow. buf = buf[:len(buf)-1] copied := make([]byte, len(buf)) copy(copied, buf) encoder.ReleaseRuntimeContext(ctx) return copied, nil } func marshalIndent(v interface{}, prefix, indent string, optFuncs ...EncodeOptionFunc) ([]byte, error) { ctx := encoder.TakeRuntimeContext() ctx.Option.Flag = 0 ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option | encoder.IndentOption) for _, optFunc := range optFuncs { optFunc(ctx.Option) } buf, err := encodeIndent(ctx, v, prefix, indent) if err != nil { encoder.ReleaseRuntimeContext(ctx) return nil, err } buf = buf[:len(buf)-2] copied := make([]byte, len(buf)) copy(copied, buf) encoder.ReleaseRuntimeContext(ctx) return copied, nil } func encode(ctx *encoder.RuntimeContext, v interface{}) ([]byte, error) { b := ctx.Buf[:0] if v == nil { b = encoder.AppendNull(ctx, b) b = encoder.AppendComma(ctx, b) return b, nil } header := (*emptyInterface)(unsafe.Pointer(&v)) typ := header.typ typeptr := uintptr(unsafe.Pointer(typ)) codeSet, err := encoder.CompileToGetCodeSet(ctx, typeptr) if err != nil { return nil, err } p := uintptr(header.ptr) ctx.Init(p, codeSet.CodeLength) ctx.KeepRefs = append(ctx.KeepRefs, header.ptr) buf, err := encodeRunCode(ctx, b, codeSet) if err != nil { return nil, err } ctx.Buf = buf return buf, nil } func encodeNoEscape(ctx *encoder.RuntimeContext, v interface{}) ([]byte, error) { b := ctx.Buf[:0] if v == nil { b = encoder.AppendNull(ctx, b) b = encoder.AppendComma(ctx, b) return b, nil } header := (*emptyInterface)(unsafe.Pointer(&v)) typ := header.typ typeptr := uintptr(unsafe.Pointer(typ)) codeSet, err := encoder.CompileToGetCodeSet(ctx, typeptr) if err != nil { return nil, err } p := uintptr(header.ptr) ctx.Init(p, codeSet.CodeLength) buf, err := encodeRunCode(ctx, b, codeSet) if err != nil { return nil, err } ctx.Buf = buf return buf, nil } func encodeIndent(ctx *encoder.RuntimeContext, v interface{}, prefix, indent string) ([]byte, error) { b := ctx.Buf[:0] if v == nil { b = encoder.AppendNull(ctx, b) b = encoder.AppendCommaIndent(ctx, b) return b, nil } header := (*emptyInterface)(unsafe.Pointer(&v)) typ := header.typ typeptr := uintptr(unsafe.Pointer(typ)) codeSet, err := encoder.CompileToGetCodeSet(ctx, typeptr) if err != nil { return nil, err } p := uintptr(header.ptr) ctx.Init(p, codeSet.CodeLength) buf, err := encodeRunIndentCode(ctx, b, codeSet, prefix, indent) ctx.KeepRefs = append(ctx.KeepRefs, header.ptr) if err != nil { return nil, err } ctx.Buf = buf return buf, nil } func encodeRunCode(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]byte, error) { if (ctx.Option.Flag & encoder.DebugOption) != 0 { if (ctx.Option.Flag & encoder.ColorizeOption) != 0 { return vm_color.DebugRun(ctx, b, codeSet) } return vm.DebugRun(ctx, b, codeSet) } if (ctx.Option.Flag & encoder.ColorizeOption) != 0 { return vm_color.Run(ctx, b, codeSet) } return vm.Run(ctx, b, codeSet) } func encodeRunIndentCode(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, prefix, indent string) ([]byte, error) { ctx.Prefix = []byte(prefix) ctx.IndentStr = []byte(indent) if (ctx.Option.Flag & encoder.DebugOption) != 0 { if (ctx.Option.Flag & encoder.ColorizeOption) != 0 { return vm_color_indent.DebugRun(ctx, b, codeSet) } return vm_indent.DebugRun(ctx, b, codeSet) } if (ctx.Option.Flag & encoder.ColorizeOption) != 0 { return vm_color_indent.Run(ctx, b, codeSet) } return vm_indent.Run(ctx, b, codeSet) } ================================================ FILE: vendor/github.com/goccy/go-json/error.go ================================================ package json import ( "github.com/goccy/go-json/internal/errors" ) // Before Go 1.2, an InvalidUTF8Error was returned by Marshal when // attempting to encode a string value with invalid UTF-8 sequences. // As of Go 1.2, Marshal instead coerces the string to valid UTF-8 by // replacing invalid bytes with the Unicode replacement rune U+FFFD. // // Deprecated: No longer used; kept for compatibility. type InvalidUTF8Error = errors.InvalidUTF8Error // An InvalidUnmarshalError describes an invalid argument passed to Unmarshal. // (The argument to Unmarshal must be a non-nil pointer.) type InvalidUnmarshalError = errors.InvalidUnmarshalError // A MarshalerError represents an error from calling a MarshalJSON or MarshalText method. type MarshalerError = errors.MarshalerError // A SyntaxError is a description of a JSON syntax error. type SyntaxError = errors.SyntaxError // An UnmarshalFieldError describes a JSON object key that // led to an unexported (and therefore unwritable) struct field. // // Deprecated: No longer used; kept for compatibility. type UnmarshalFieldError = errors.UnmarshalFieldError // An UnmarshalTypeError describes a JSON value that was // not appropriate for a value of a specific Go type. type UnmarshalTypeError = errors.UnmarshalTypeError // An UnsupportedTypeError is returned by Marshal when attempting // to encode an unsupported value type. type UnsupportedTypeError = errors.UnsupportedTypeError type UnsupportedValueError = errors.UnsupportedValueError type PathError = errors.PathError ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/anonymous_field.go ================================================ package decoder import ( "unsafe" "github.com/goccy/go-json/internal/runtime" ) type anonymousFieldDecoder struct { structType *runtime.Type offset uintptr dec Decoder } func newAnonymousFieldDecoder(structType *runtime.Type, offset uintptr, dec Decoder) *anonymousFieldDecoder { return &anonymousFieldDecoder{ structType: structType, offset: offset, dec: dec, } } func (d *anonymousFieldDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { if *(*unsafe.Pointer)(p) == nil { *(*unsafe.Pointer)(p) = unsafe_New(d.structType) } p = *(*unsafe.Pointer)(p) return d.dec.DecodeStream(s, depth, unsafe.Pointer(uintptr(p)+d.offset)) } func (d *anonymousFieldDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { if *(*unsafe.Pointer)(p) == nil { *(*unsafe.Pointer)(p) = unsafe_New(d.structType) } p = *(*unsafe.Pointer)(p) return d.dec.Decode(ctx, cursor, depth, unsafe.Pointer(uintptr(p)+d.offset)) } func (d *anonymousFieldDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) { return d.dec.DecodePath(ctx, cursor, depth) } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/array.go ================================================ package decoder import ( "fmt" "unsafe" "github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/runtime" ) type arrayDecoder struct { elemType *runtime.Type size uintptr valueDecoder Decoder alen int structName string fieldName string zeroValue unsafe.Pointer } func newArrayDecoder(dec Decoder, elemType *runtime.Type, alen int, structName, fieldName string) *arrayDecoder { // workaround to avoid checkptr errors. cannot use `*(*unsafe.Pointer)(unsafe_New(elemType))` directly. zeroValuePtr := unsafe_New(elemType) zeroValue := **(**unsafe.Pointer)(unsafe.Pointer(&zeroValuePtr)) return &arrayDecoder{ valueDecoder: dec, elemType: elemType, size: elemType.Size(), alen: alen, structName: structName, fieldName: fieldName, zeroValue: zeroValue, } } func (d *arrayDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { depth++ if depth > maxDecodeNestingDepth { return errors.ErrExceededMaxDepth(s.char(), s.cursor) } for { switch s.char() { case ' ', '\n', '\t', '\r': case 'n': if err := nullBytes(s); err != nil { return err } return nil case '[': idx := 0 s.cursor++ if s.skipWhiteSpace() == ']' { for idx < d.alen { *(*unsafe.Pointer)(unsafe.Pointer(uintptr(p) + uintptr(idx)*d.size)) = d.zeroValue idx++ } s.cursor++ return nil } for { if idx < d.alen { if err := d.valueDecoder.DecodeStream(s, depth, unsafe.Pointer(uintptr(p)+uintptr(idx)*d.size)); err != nil { return err } } else { if err := s.skipValue(depth); err != nil { return err } } idx++ switch s.skipWhiteSpace() { case ']': for idx < d.alen { *(*unsafe.Pointer)(unsafe.Pointer(uintptr(p) + uintptr(idx)*d.size)) = d.zeroValue idx++ } s.cursor++ return nil case ',': s.cursor++ continue case nul: if s.read() { s.cursor++ continue } goto ERROR default: goto ERROR } } case nul: if s.read() { continue } goto ERROR default: goto ERROR } s.cursor++ } ERROR: return errors.ErrUnexpectedEndOfJSON("array", s.totalOffset()) } func (d *arrayDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { buf := ctx.Buf depth++ if depth > maxDecodeNestingDepth { return 0, errors.ErrExceededMaxDepth(buf[cursor], cursor) } for { switch buf[cursor] { case ' ', '\n', '\t', '\r': cursor++ continue case 'n': if err := validateNull(buf, cursor); err != nil { return 0, err } cursor += 4 return cursor, nil case '[': idx := 0 cursor++ cursor = skipWhiteSpace(buf, cursor) if buf[cursor] == ']' { for idx < d.alen { *(*unsafe.Pointer)(unsafe.Pointer(uintptr(p) + uintptr(idx)*d.size)) = d.zeroValue idx++ } cursor++ return cursor, nil } for { if idx < d.alen { c, err := d.valueDecoder.Decode(ctx, cursor, depth, unsafe.Pointer(uintptr(p)+uintptr(idx)*d.size)) if err != nil { return 0, err } cursor = c } else { c, err := skipValue(buf, cursor, depth) if err != nil { return 0, err } cursor = c } idx++ cursor = skipWhiteSpace(buf, cursor) switch buf[cursor] { case ']': for idx < d.alen { *(*unsafe.Pointer)(unsafe.Pointer(uintptr(p) + uintptr(idx)*d.size)) = d.zeroValue idx++ } cursor++ return cursor, nil case ',': cursor++ continue default: return 0, errors.ErrInvalidCharacter(buf[cursor], "array", cursor) } } default: return 0, errors.ErrUnexpectedEndOfJSON("array", cursor) } } } func (d *arrayDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) { return nil, 0, fmt.Errorf("json: array decoder does not support decode path") } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/assign.go ================================================ package decoder import ( "fmt" "reflect" "strconv" ) var ( nilValue = reflect.ValueOf(nil) ) func AssignValue(src, dst reflect.Value) error { if dst.Type().Kind() != reflect.Ptr { return fmt.Errorf("invalid dst type. required pointer type: %T", dst.Type()) } casted, err := castValue(dst.Elem().Type(), src) if err != nil { return err } dst.Elem().Set(casted) return nil } func castValue(t reflect.Type, v reflect.Value) (reflect.Value, error) { switch t.Kind() { case reflect.Int: vv, err := castInt(v) if err != nil { return nilValue, err } return reflect.ValueOf(int(vv.Int())), nil case reflect.Int8: vv, err := castInt(v) if err != nil { return nilValue, err } return reflect.ValueOf(int8(vv.Int())), nil case reflect.Int16: vv, err := castInt(v) if err != nil { return nilValue, err } return reflect.ValueOf(int16(vv.Int())), nil case reflect.Int32: vv, err := castInt(v) if err != nil { return nilValue, err } return reflect.ValueOf(int32(vv.Int())), nil case reflect.Int64: return castInt(v) case reflect.Uint: vv, err := castUint(v) if err != nil { return nilValue, err } return reflect.ValueOf(uint(vv.Uint())), nil case reflect.Uint8: vv, err := castUint(v) if err != nil { return nilValue, err } return reflect.ValueOf(uint8(vv.Uint())), nil case reflect.Uint16: vv, err := castUint(v) if err != nil { return nilValue, err } return reflect.ValueOf(uint16(vv.Uint())), nil case reflect.Uint32: vv, err := castUint(v) if err != nil { return nilValue, err } return reflect.ValueOf(uint32(vv.Uint())), nil case reflect.Uint64: return castUint(v) case reflect.Uintptr: vv, err := castUint(v) if err != nil { return nilValue, err } return reflect.ValueOf(uintptr(vv.Uint())), nil case reflect.String: return castString(v) case reflect.Bool: return castBool(v) case reflect.Float32: vv, err := castFloat(v) if err != nil { return nilValue, err } return reflect.ValueOf(float32(vv.Float())), nil case reflect.Float64: return castFloat(v) case reflect.Array: return castArray(t, v) case reflect.Slice: return castSlice(t, v) case reflect.Map: return castMap(t, v) case reflect.Struct: return castStruct(t, v) } return v, nil } func castInt(v reflect.Value) (reflect.Value, error) { switch v.Type().Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v, nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return reflect.ValueOf(int64(v.Uint())), nil case reflect.String: i64, err := strconv.ParseInt(v.String(), 10, 64) if err != nil { return nilValue, err } return reflect.ValueOf(i64), nil case reflect.Bool: if v.Bool() { return reflect.ValueOf(int64(1)), nil } return reflect.ValueOf(int64(0)), nil case reflect.Float32, reflect.Float64: return reflect.ValueOf(int64(v.Float())), nil case reflect.Array: if v.Len() > 0 { return castInt(v.Index(0)) } return nilValue, fmt.Errorf("failed to cast to int64 from empty array") case reflect.Slice: if v.Len() > 0 { return castInt(v.Index(0)) } return nilValue, fmt.Errorf("failed to cast to int64 from empty slice") case reflect.Interface: return castInt(reflect.ValueOf(v.Interface())) case reflect.Map: return nilValue, fmt.Errorf("failed to cast to int64 from map") case reflect.Struct: return nilValue, fmt.Errorf("failed to cast to int64 from struct") case reflect.Ptr: return castInt(v.Elem()) } return nilValue, fmt.Errorf("failed to cast to int64 from %s", v.Type().Kind()) } func castUint(v reflect.Value) (reflect.Value, error) { switch v.Type().Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return reflect.ValueOf(uint64(v.Int())), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return v, nil case reflect.String: u64, err := strconv.ParseUint(v.String(), 10, 64) if err != nil { return nilValue, err } return reflect.ValueOf(u64), nil case reflect.Bool: if v.Bool() { return reflect.ValueOf(uint64(1)), nil } return reflect.ValueOf(uint64(0)), nil case reflect.Float32, reflect.Float64: return reflect.ValueOf(uint64(v.Float())), nil case reflect.Array: if v.Len() > 0 { return castUint(v.Index(0)) } return nilValue, fmt.Errorf("failed to cast to uint64 from empty array") case reflect.Slice: if v.Len() > 0 { return castUint(v.Index(0)) } return nilValue, fmt.Errorf("failed to cast to uint64 from empty slice") case reflect.Interface: return castUint(reflect.ValueOf(v.Interface())) case reflect.Map: return nilValue, fmt.Errorf("failed to cast to uint64 from map") case reflect.Struct: return nilValue, fmt.Errorf("failed to cast to uint64 from struct") case reflect.Ptr: return castUint(v.Elem()) } return nilValue, fmt.Errorf("failed to cast to uint64 from %s", v.Type().Kind()) } func castString(v reflect.Value) (reflect.Value, error) { switch v.Type().Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return reflect.ValueOf(fmt.Sprint(v.Int())), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return reflect.ValueOf(fmt.Sprint(v.Uint())), nil case reflect.String: return v, nil case reflect.Bool: if v.Bool() { return reflect.ValueOf("true"), nil } return reflect.ValueOf("false"), nil case reflect.Float32, reflect.Float64: return reflect.ValueOf(fmt.Sprint(v.Float())), nil case reflect.Array: if v.Len() > 0 { return castString(v.Index(0)) } return nilValue, fmt.Errorf("failed to cast to string from empty array") case reflect.Slice: if v.Len() > 0 { return castString(v.Index(0)) } return nilValue, fmt.Errorf("failed to cast to string from empty slice") case reflect.Interface: return castString(reflect.ValueOf(v.Interface())) case reflect.Map: return nilValue, fmt.Errorf("failed to cast to string from map") case reflect.Struct: return nilValue, fmt.Errorf("failed to cast to string from struct") case reflect.Ptr: return castString(v.Elem()) } return nilValue, fmt.Errorf("failed to cast to string from %s", v.Type().Kind()) } func castBool(v reflect.Value) (reflect.Value, error) { switch v.Type().Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: switch v.Int() { case 0: return reflect.ValueOf(false), nil case 1: return reflect.ValueOf(true), nil } return nilValue, fmt.Errorf("failed to cast to bool from %d", v.Int()) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: switch v.Uint() { case 0: return reflect.ValueOf(false), nil case 1: return reflect.ValueOf(true), nil } return nilValue, fmt.Errorf("failed to cast to bool from %d", v.Uint()) case reflect.String: b, err := strconv.ParseBool(v.String()) if err != nil { return nilValue, err } return reflect.ValueOf(b), nil case reflect.Bool: return v, nil case reflect.Float32, reflect.Float64: switch v.Float() { case 0: return reflect.ValueOf(false), nil case 1: return reflect.ValueOf(true), nil } return nilValue, fmt.Errorf("failed to cast to bool from %f", v.Float()) case reflect.Array: if v.Len() > 0 { return castBool(v.Index(0)) } return nilValue, fmt.Errorf("failed to cast to string from empty array") case reflect.Slice: if v.Len() > 0 { return castBool(v.Index(0)) } return nilValue, fmt.Errorf("failed to cast to string from empty slice") case reflect.Interface: return castBool(reflect.ValueOf(v.Interface())) case reflect.Map: return nilValue, fmt.Errorf("failed to cast to string from map") case reflect.Struct: return nilValue, fmt.Errorf("failed to cast to string from struct") case reflect.Ptr: return castBool(v.Elem()) } return nilValue, fmt.Errorf("failed to cast to bool from %s", v.Type().Kind()) } func castFloat(v reflect.Value) (reflect.Value, error) { switch v.Type().Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return reflect.ValueOf(float64(v.Int())), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return reflect.ValueOf(float64(v.Uint())), nil case reflect.String: f64, err := strconv.ParseFloat(v.String(), 64) if err != nil { return nilValue, err } return reflect.ValueOf(f64), nil case reflect.Bool: if v.Bool() { return reflect.ValueOf(float64(1)), nil } return reflect.ValueOf(float64(0)), nil case reflect.Float32, reflect.Float64: return v, nil case reflect.Array: if v.Len() > 0 { return castFloat(v.Index(0)) } return nilValue, fmt.Errorf("failed to cast to float64 from empty array") case reflect.Slice: if v.Len() > 0 { return castFloat(v.Index(0)) } return nilValue, fmt.Errorf("failed to cast to float64 from empty slice") case reflect.Interface: return castFloat(reflect.ValueOf(v.Interface())) case reflect.Map: return nilValue, fmt.Errorf("failed to cast to float64 from map") case reflect.Struct: return nilValue, fmt.Errorf("failed to cast to float64 from struct") case reflect.Ptr: return castFloat(v.Elem()) } return nilValue, fmt.Errorf("failed to cast to float64 from %s", v.Type().Kind()) } func castArray(t reflect.Type, v reflect.Value) (reflect.Value, error) { kind := v.Type().Kind() if kind == reflect.Interface { return castArray(t, reflect.ValueOf(v.Interface())) } if kind != reflect.Slice && kind != reflect.Array { return nilValue, fmt.Errorf("failed to cast to array from %s", kind) } if t.Elem() == v.Type().Elem() { return v, nil } if t.Len() != v.Len() { return nilValue, fmt.Errorf("failed to cast [%d]array from slice of %d length", t.Len(), v.Len()) } ret := reflect.New(t).Elem() for i := 0; i < v.Len(); i++ { vv, err := castValue(t.Elem(), v.Index(i)) if err != nil { return nilValue, err } ret.Index(i).Set(vv) } return ret, nil } func castSlice(t reflect.Type, v reflect.Value) (reflect.Value, error) { kind := v.Type().Kind() if kind == reflect.Interface { return castSlice(t, reflect.ValueOf(v.Interface())) } if kind != reflect.Slice && kind != reflect.Array { return nilValue, fmt.Errorf("failed to cast to slice from %s", kind) } if t.Elem() == v.Type().Elem() { return v, nil } ret := reflect.MakeSlice(t, v.Len(), v.Len()) for i := 0; i < v.Len(); i++ { vv, err := castValue(t.Elem(), v.Index(i)) if err != nil { return nilValue, err } ret.Index(i).Set(vv) } return ret, nil } func castMap(t reflect.Type, v reflect.Value) (reflect.Value, error) { ret := reflect.MakeMap(t) switch v.Type().Kind() { case reflect.Map: iter := v.MapRange() for iter.Next() { key, err := castValue(t.Key(), iter.Key()) if err != nil { return nilValue, err } value, err := castValue(t.Elem(), iter.Value()) if err != nil { return nilValue, err } ret.SetMapIndex(key, value) } return ret, nil case reflect.Interface: return castMap(t, reflect.ValueOf(v.Interface())) case reflect.Slice: if v.Len() > 0 { return castMap(t, v.Index(0)) } return nilValue, fmt.Errorf("failed to cast to map from empty slice") } return nilValue, fmt.Errorf("failed to cast to map from %s", v.Type().Kind()) } func castStruct(t reflect.Type, v reflect.Value) (reflect.Value, error) { ret := reflect.New(t).Elem() switch v.Type().Kind() { case reflect.Map: iter := v.MapRange() for iter.Next() { key := iter.Key() k, err := castString(key) if err != nil { return nilValue, err } fieldName := k.String() field, ok := t.FieldByName(fieldName) if ok { value, err := castValue(field.Type, iter.Value()) if err != nil { return nilValue, err } ret.FieldByName(fieldName).Set(value) } } return ret, nil case reflect.Struct: for i := 0; i < v.Type().NumField(); i++ { name := v.Type().Field(i).Name ret.FieldByName(name).Set(v.FieldByName(name)) } return ret, nil case reflect.Interface: return castStruct(t, reflect.ValueOf(v.Interface())) case reflect.Slice: if v.Len() > 0 { return castStruct(t, v.Index(0)) } return nilValue, fmt.Errorf("failed to cast to struct from empty slice") default: return nilValue, fmt.Errorf("failed to cast to struct from %s", v.Type().Kind()) } } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/bool.go ================================================ package decoder import ( "fmt" "unsafe" "github.com/goccy/go-json/internal/errors" ) type boolDecoder struct { structName string fieldName string } func newBoolDecoder(structName, fieldName string) *boolDecoder { return &boolDecoder{structName: structName, fieldName: fieldName} } func (d *boolDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { c := s.skipWhiteSpace() for { switch c { case 't': if err := trueBytes(s); err != nil { return err } **(**bool)(unsafe.Pointer(&p)) = true return nil case 'f': if err := falseBytes(s); err != nil { return err } **(**bool)(unsafe.Pointer(&p)) = false return nil case 'n': if err := nullBytes(s); err != nil { return err } return nil case nul: if s.read() { c = s.char() continue } goto ERROR } break } ERROR: return errors.ErrUnexpectedEndOfJSON("bool", s.totalOffset()) } func (d *boolDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { buf := ctx.Buf cursor = skipWhiteSpace(buf, cursor) switch buf[cursor] { case 't': if err := validateTrue(buf, cursor); err != nil { return 0, err } cursor += 4 **(**bool)(unsafe.Pointer(&p)) = true return cursor, nil case 'f': if err := validateFalse(buf, cursor); err != nil { return 0, err } cursor += 5 **(**bool)(unsafe.Pointer(&p)) = false return cursor, nil case 'n': if err := validateNull(buf, cursor); err != nil { return 0, err } cursor += 4 return cursor, nil } return 0, errors.ErrUnexpectedEndOfJSON("bool", cursor) } func (d *boolDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) { return nil, 0, fmt.Errorf("json: bool decoder does not support decode path") } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/bytes.go ================================================ package decoder import ( "encoding/base64" "fmt" "unsafe" "github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/runtime" ) type bytesDecoder struct { typ *runtime.Type sliceDecoder Decoder stringDecoder *stringDecoder structName string fieldName string } func byteUnmarshalerSliceDecoder(typ *runtime.Type, structName string, fieldName string) Decoder { var unmarshalDecoder Decoder switch { case runtime.PtrTo(typ).Implements(unmarshalJSONType): unmarshalDecoder = newUnmarshalJSONDecoder(runtime.PtrTo(typ), structName, fieldName) case runtime.PtrTo(typ).Implements(unmarshalTextType): unmarshalDecoder = newUnmarshalTextDecoder(runtime.PtrTo(typ), structName, fieldName) default: unmarshalDecoder, _ = compileUint8(typ, structName, fieldName) } return newSliceDecoder(unmarshalDecoder, typ, 1, structName, fieldName) } func newBytesDecoder(typ *runtime.Type, structName string, fieldName string) *bytesDecoder { return &bytesDecoder{ typ: typ, sliceDecoder: byteUnmarshalerSliceDecoder(typ, structName, fieldName), stringDecoder: newStringDecoder(structName, fieldName), structName: structName, fieldName: fieldName, } } func (d *bytesDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { bytes, err := d.decodeStreamBinary(s, depth, p) if err != nil { return err } if bytes == nil { s.reset() return nil } decodedLen := base64.StdEncoding.DecodedLen(len(bytes)) buf := make([]byte, decodedLen) n, err := base64.StdEncoding.Decode(buf, bytes) if err != nil { return err } *(*[]byte)(p) = buf[:n] s.reset() return nil } func (d *bytesDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { bytes, c, err := d.decodeBinary(ctx, cursor, depth, p) if err != nil { return 0, err } if bytes == nil { return c, nil } cursor = c decodedLen := base64.StdEncoding.DecodedLen(len(bytes)) b := make([]byte, decodedLen) n, err := base64.StdEncoding.Decode(b, bytes) if err != nil { return 0, err } *(*[]byte)(p) = b[:n] return cursor, nil } func (d *bytesDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) { return nil, 0, fmt.Errorf("json: []byte decoder does not support decode path") } func (d *bytesDecoder) decodeStreamBinary(s *Stream, depth int64, p unsafe.Pointer) ([]byte, error) { c := s.skipWhiteSpace() if c == '[' { if d.sliceDecoder == nil { return nil, &errors.UnmarshalTypeError{ Type: runtime.RType2Type(d.typ), Offset: s.totalOffset(), } } err := d.sliceDecoder.DecodeStream(s, depth, p) return nil, err } return d.stringDecoder.decodeStreamByte(s) } func (d *bytesDecoder) decodeBinary(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) ([]byte, int64, error) { buf := ctx.Buf cursor = skipWhiteSpace(buf, cursor) if buf[cursor] == '[' { if d.sliceDecoder == nil { return nil, 0, &errors.UnmarshalTypeError{ Type: runtime.RType2Type(d.typ), Offset: cursor, } } c, err := d.sliceDecoder.Decode(ctx, cursor, depth, p) if err != nil { return nil, 0, err } return nil, c, nil } return d.stringDecoder.decodeByte(buf, cursor) } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/compile.go ================================================ package decoder import ( "encoding/json" "fmt" "reflect" "strings" "sync/atomic" "unicode" "unsafe" "github.com/goccy/go-json/internal/runtime" ) var ( jsonNumberType = reflect.TypeOf(json.Number("")) typeAddr *runtime.TypeAddr cachedDecoderMap unsafe.Pointer // map[uintptr]decoder cachedDecoder []Decoder ) func init() { typeAddr = runtime.AnalyzeTypeAddr() if typeAddr == nil { typeAddr = &runtime.TypeAddr{} } cachedDecoder = make([]Decoder, typeAddr.AddrRange>>typeAddr.AddrShift+1) } func loadDecoderMap() map[uintptr]Decoder { p := atomic.LoadPointer(&cachedDecoderMap) return *(*map[uintptr]Decoder)(unsafe.Pointer(&p)) } func storeDecoder(typ uintptr, dec Decoder, m map[uintptr]Decoder) { newDecoderMap := make(map[uintptr]Decoder, len(m)+1) newDecoderMap[typ] = dec for k, v := range m { newDecoderMap[k] = v } atomic.StorePointer(&cachedDecoderMap, *(*unsafe.Pointer)(unsafe.Pointer(&newDecoderMap))) } func compileToGetDecoderSlowPath(typeptr uintptr, typ *runtime.Type) (Decoder, error) { decoderMap := loadDecoderMap() if dec, exists := decoderMap[typeptr]; exists { return dec, nil } dec, err := compileHead(typ, map[uintptr]Decoder{}) if err != nil { return nil, err } storeDecoder(typeptr, dec, decoderMap) return dec, nil } func compileHead(typ *runtime.Type, structTypeToDecoder map[uintptr]Decoder) (Decoder, error) { switch { case implementsUnmarshalJSONType(runtime.PtrTo(typ)): return newUnmarshalJSONDecoder(runtime.PtrTo(typ), "", ""), nil case runtime.PtrTo(typ).Implements(unmarshalTextType): return newUnmarshalTextDecoder(runtime.PtrTo(typ), "", ""), nil } return compile(typ.Elem(), "", "", structTypeToDecoder) } func compile(typ *runtime.Type, structName, fieldName string, structTypeToDecoder map[uintptr]Decoder) (Decoder, error) { switch { case implementsUnmarshalJSONType(runtime.PtrTo(typ)): return newUnmarshalJSONDecoder(runtime.PtrTo(typ), structName, fieldName), nil case runtime.PtrTo(typ).Implements(unmarshalTextType): return newUnmarshalTextDecoder(runtime.PtrTo(typ), structName, fieldName), nil } switch typ.Kind() { case reflect.Ptr: return compilePtr(typ, structName, fieldName, structTypeToDecoder) case reflect.Struct: return compileStruct(typ, structName, fieldName, structTypeToDecoder) case reflect.Slice: elem := typ.Elem() if elem.Kind() == reflect.Uint8 { return compileBytes(elem, structName, fieldName) } return compileSlice(typ, structName, fieldName, structTypeToDecoder) case reflect.Array: return compileArray(typ, structName, fieldName, structTypeToDecoder) case reflect.Map: return compileMap(typ, structName, fieldName, structTypeToDecoder) case reflect.Interface: return compileInterface(typ, structName, fieldName) case reflect.Uintptr: return compileUint(typ, structName, fieldName) case reflect.Int: return compileInt(typ, structName, fieldName) case reflect.Int8: return compileInt8(typ, structName, fieldName) case reflect.Int16: return compileInt16(typ, structName, fieldName) case reflect.Int32: return compileInt32(typ, structName, fieldName) case reflect.Int64: return compileInt64(typ, structName, fieldName) case reflect.Uint: return compileUint(typ, structName, fieldName) case reflect.Uint8: return compileUint8(typ, structName, fieldName) case reflect.Uint16: return compileUint16(typ, structName, fieldName) case reflect.Uint32: return compileUint32(typ, structName, fieldName) case reflect.Uint64: return compileUint64(typ, structName, fieldName) case reflect.String: return compileString(typ, structName, fieldName) case reflect.Bool: return compileBool(structName, fieldName) case reflect.Float32: return compileFloat32(structName, fieldName) case reflect.Float64: return compileFloat64(structName, fieldName) case reflect.Func: return compileFunc(typ, structName, fieldName) } return newInvalidDecoder(typ, structName, fieldName), nil } func isStringTagSupportedType(typ *runtime.Type) bool { switch { case implementsUnmarshalJSONType(runtime.PtrTo(typ)): return false case runtime.PtrTo(typ).Implements(unmarshalTextType): return false } switch typ.Kind() { case reflect.Map: return false case reflect.Slice: return false case reflect.Array: return false case reflect.Struct: return false case reflect.Interface: return false } return true } func compileMapKey(typ *runtime.Type, structName, fieldName string, structTypeToDecoder map[uintptr]Decoder) (Decoder, error) { if runtime.PtrTo(typ).Implements(unmarshalTextType) { return newUnmarshalTextDecoder(runtime.PtrTo(typ), structName, fieldName), nil } if typ.Kind() == reflect.String { return newStringDecoder(structName, fieldName), nil } dec, err := compile(typ, structName, fieldName, structTypeToDecoder) if err != nil { return nil, err } for { switch t := dec.(type) { case *stringDecoder, *interfaceDecoder: return dec, nil case *boolDecoder, *intDecoder, *uintDecoder, *numberDecoder: return newWrappedStringDecoder(typ, dec, structName, fieldName), nil case *ptrDecoder: dec = t.dec default: return newInvalidDecoder(typ, structName, fieldName), nil } } } func compilePtr(typ *runtime.Type, structName, fieldName string, structTypeToDecoder map[uintptr]Decoder) (Decoder, error) { dec, err := compile(typ.Elem(), structName, fieldName, structTypeToDecoder) if err != nil { return nil, err } return newPtrDecoder(dec, typ.Elem(), structName, fieldName), nil } func compileInt(typ *runtime.Type, structName, fieldName string) (Decoder, error) { return newIntDecoder(typ, structName, fieldName, func(p unsafe.Pointer, v int64) { *(*int)(p) = int(v) }), nil } func compileInt8(typ *runtime.Type, structName, fieldName string) (Decoder, error) { return newIntDecoder(typ, structName, fieldName, func(p unsafe.Pointer, v int64) { *(*int8)(p) = int8(v) }), nil } func compileInt16(typ *runtime.Type, structName, fieldName string) (Decoder, error) { return newIntDecoder(typ, structName, fieldName, func(p unsafe.Pointer, v int64) { *(*int16)(p) = int16(v) }), nil } func compileInt32(typ *runtime.Type, structName, fieldName string) (Decoder, error) { return newIntDecoder(typ, structName, fieldName, func(p unsafe.Pointer, v int64) { *(*int32)(p) = int32(v) }), nil } func compileInt64(typ *runtime.Type, structName, fieldName string) (Decoder, error) { return newIntDecoder(typ, structName, fieldName, func(p unsafe.Pointer, v int64) { *(*int64)(p) = v }), nil } func compileUint(typ *runtime.Type, structName, fieldName string) (Decoder, error) { return newUintDecoder(typ, structName, fieldName, func(p unsafe.Pointer, v uint64) { *(*uint)(p) = uint(v) }), nil } func compileUint8(typ *runtime.Type, structName, fieldName string) (Decoder, error) { return newUintDecoder(typ, structName, fieldName, func(p unsafe.Pointer, v uint64) { *(*uint8)(p) = uint8(v) }), nil } func compileUint16(typ *runtime.Type, structName, fieldName string) (Decoder, error) { return newUintDecoder(typ, structName, fieldName, func(p unsafe.Pointer, v uint64) { *(*uint16)(p) = uint16(v) }), nil } func compileUint32(typ *runtime.Type, structName, fieldName string) (Decoder, error) { return newUintDecoder(typ, structName, fieldName, func(p unsafe.Pointer, v uint64) { *(*uint32)(p) = uint32(v) }), nil } func compileUint64(typ *runtime.Type, structName, fieldName string) (Decoder, error) { return newUintDecoder(typ, structName, fieldName, func(p unsafe.Pointer, v uint64) { *(*uint64)(p) = v }), nil } func compileFloat32(structName, fieldName string) (Decoder, error) { return newFloatDecoder(structName, fieldName, func(p unsafe.Pointer, v float64) { *(*float32)(p) = float32(v) }), nil } func compileFloat64(structName, fieldName string) (Decoder, error) { return newFloatDecoder(structName, fieldName, func(p unsafe.Pointer, v float64) { *(*float64)(p) = v }), nil } func compileString(typ *runtime.Type, structName, fieldName string) (Decoder, error) { if typ == runtime.Type2RType(jsonNumberType) { return newNumberDecoder(structName, fieldName, func(p unsafe.Pointer, v json.Number) { *(*json.Number)(p) = v }), nil } return newStringDecoder(structName, fieldName), nil } func compileBool(structName, fieldName string) (Decoder, error) { return newBoolDecoder(structName, fieldName), nil } func compileBytes(typ *runtime.Type, structName, fieldName string) (Decoder, error) { return newBytesDecoder(typ, structName, fieldName), nil } func compileSlice(typ *runtime.Type, structName, fieldName string, structTypeToDecoder map[uintptr]Decoder) (Decoder, error) { elem := typ.Elem() decoder, err := compile(elem, structName, fieldName, structTypeToDecoder) if err != nil { return nil, err } return newSliceDecoder(decoder, elem, elem.Size(), structName, fieldName), nil } func compileArray(typ *runtime.Type, structName, fieldName string, structTypeToDecoder map[uintptr]Decoder) (Decoder, error) { elem := typ.Elem() decoder, err := compile(elem, structName, fieldName, structTypeToDecoder) if err != nil { return nil, err } return newArrayDecoder(decoder, elem, typ.Len(), structName, fieldName), nil } func compileMap(typ *runtime.Type, structName, fieldName string, structTypeToDecoder map[uintptr]Decoder) (Decoder, error) { keyDec, err := compileMapKey(typ.Key(), structName, fieldName, structTypeToDecoder) if err != nil { return nil, err } valueDec, err := compile(typ.Elem(), structName, fieldName, structTypeToDecoder) if err != nil { return nil, err } return newMapDecoder(typ, typ.Key(), keyDec, typ.Elem(), valueDec, structName, fieldName), nil } func compileInterface(typ *runtime.Type, structName, fieldName string) (Decoder, error) { return newInterfaceDecoder(typ, structName, fieldName), nil } func compileFunc(typ *runtime.Type, strutName, fieldName string) (Decoder, error) { return newFuncDecoder(typ, strutName, fieldName), nil } func typeToStructTags(typ *runtime.Type) runtime.StructTags { tags := runtime.StructTags{} fieldNum := typ.NumField() for i := 0; i < fieldNum; i++ { field := typ.Field(i) if runtime.IsIgnoredStructField(field) { continue } tags = append(tags, runtime.StructTagFromField(field)) } return tags } func compileStruct(typ *runtime.Type, structName, fieldName string, structTypeToDecoder map[uintptr]Decoder) (Decoder, error) { fieldNum := typ.NumField() fieldMap := map[string]*structFieldSet{} typeptr := uintptr(unsafe.Pointer(typ)) if dec, exists := structTypeToDecoder[typeptr]; exists { return dec, nil } structDec := newStructDecoder(structName, fieldName, fieldMap) structTypeToDecoder[typeptr] = structDec structName = typ.Name() tags := typeToStructTags(typ) allFields := []*structFieldSet{} for i := 0; i < fieldNum; i++ { field := typ.Field(i) if runtime.IsIgnoredStructField(field) { continue } isUnexportedField := unicode.IsLower([]rune(field.Name)[0]) tag := runtime.StructTagFromField(field) dec, err := compile(runtime.Type2RType(field.Type), structName, field.Name, structTypeToDecoder) if err != nil { return nil, err } if field.Anonymous && !tag.IsTaggedKey { if stDec, ok := dec.(*structDecoder); ok { if runtime.Type2RType(field.Type) == typ { // recursive definition continue } for k, v := range stDec.fieldMap { if tags.ExistsKey(k) { continue } fieldSet := &structFieldSet{ dec: v.dec, offset: field.Offset + v.offset, isTaggedKey: v.isTaggedKey, key: k, keyLen: int64(len(k)), } allFields = append(allFields, fieldSet) } } else if pdec, ok := dec.(*ptrDecoder); ok { contentDec := pdec.contentDecoder() if pdec.typ == typ { // recursive definition continue } var fieldSetErr error if isUnexportedField { fieldSetErr = fmt.Errorf( "json: cannot set embedded pointer to unexported struct: %v", field.Type.Elem(), ) } if dec, ok := contentDec.(*structDecoder); ok { for k, v := range dec.fieldMap { if tags.ExistsKey(k) { continue } fieldSet := &structFieldSet{ dec: newAnonymousFieldDecoder(pdec.typ, v.offset, v.dec), offset: field.Offset, isTaggedKey: v.isTaggedKey, key: k, keyLen: int64(len(k)), err: fieldSetErr, } allFields = append(allFields, fieldSet) } } else { fieldSet := &structFieldSet{ dec: pdec, offset: field.Offset, isTaggedKey: tag.IsTaggedKey, key: field.Name, keyLen: int64(len(field.Name)), } allFields = append(allFields, fieldSet) } } else { fieldSet := &structFieldSet{ dec: dec, offset: field.Offset, isTaggedKey: tag.IsTaggedKey, key: field.Name, keyLen: int64(len(field.Name)), } allFields = append(allFields, fieldSet) } } else { if tag.IsString && isStringTagSupportedType(runtime.Type2RType(field.Type)) { dec = newWrappedStringDecoder(runtime.Type2RType(field.Type), dec, structName, field.Name) } var key string if tag.Key != "" { key = tag.Key } else { key = field.Name } fieldSet := &structFieldSet{ dec: dec, offset: field.Offset, isTaggedKey: tag.IsTaggedKey, key: key, keyLen: int64(len(key)), } allFields = append(allFields, fieldSet) } } for _, set := range filterDuplicatedFields(allFields) { fieldMap[set.key] = set lower := strings.ToLower(set.key) if _, exists := fieldMap[lower]; !exists { // first win fieldMap[lower] = set } } delete(structTypeToDecoder, typeptr) structDec.tryOptimize() return structDec, nil } func filterDuplicatedFields(allFields []*structFieldSet) []*structFieldSet { fieldMap := map[string][]*structFieldSet{} for _, field := range allFields { fieldMap[field.key] = append(fieldMap[field.key], field) } duplicatedFieldMap := map[string]struct{}{} for k, sets := range fieldMap { sets = filterFieldSets(sets) if len(sets) != 1 { duplicatedFieldMap[k] = struct{}{} } } filtered := make([]*structFieldSet, 0, len(allFields)) for _, field := range allFields { if _, exists := duplicatedFieldMap[field.key]; exists { continue } filtered = append(filtered, field) } return filtered } func filterFieldSets(sets []*structFieldSet) []*structFieldSet { if len(sets) == 1 { return sets } filtered := make([]*structFieldSet, 0, len(sets)) for _, set := range sets { if set.isTaggedKey { filtered = append(filtered, set) } } return filtered } func implementsUnmarshalJSONType(typ *runtime.Type) bool { return typ.Implements(unmarshalJSONType) || typ.Implements(unmarshalJSONContextType) } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/compile_norace.go ================================================ //go:build !race // +build !race package decoder import ( "unsafe" "github.com/goccy/go-json/internal/runtime" ) func CompileToGetDecoder(typ *runtime.Type) (Decoder, error) { typeptr := uintptr(unsafe.Pointer(typ)) if typeptr > typeAddr.MaxTypeAddr { return compileToGetDecoderSlowPath(typeptr, typ) } index := (typeptr - typeAddr.BaseTypeAddr) >> typeAddr.AddrShift if dec := cachedDecoder[index]; dec != nil { return dec, nil } dec, err := compileHead(typ, map[uintptr]Decoder{}) if err != nil { return nil, err } cachedDecoder[index] = dec return dec, nil } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/compile_race.go ================================================ //go:build race // +build race package decoder import ( "sync" "unsafe" "github.com/goccy/go-json/internal/runtime" ) var decMu sync.RWMutex func CompileToGetDecoder(typ *runtime.Type) (Decoder, error) { typeptr := uintptr(unsafe.Pointer(typ)) if typeptr > typeAddr.MaxTypeAddr { return compileToGetDecoderSlowPath(typeptr, typ) } index := (typeptr - typeAddr.BaseTypeAddr) >> typeAddr.AddrShift decMu.RLock() if dec := cachedDecoder[index]; dec != nil { decMu.RUnlock() return dec, nil } decMu.RUnlock() dec, err := compileHead(typ, map[uintptr]Decoder{}) if err != nil { return nil, err } decMu.Lock() cachedDecoder[index] = dec decMu.Unlock() return dec, nil } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/context.go ================================================ package decoder import ( "sync" "unsafe" "github.com/goccy/go-json/internal/errors" ) type RuntimeContext struct { Buf []byte Option *Option } var ( runtimeContextPool = sync.Pool{ New: func() interface{} { return &RuntimeContext{ Option: &Option{}, } }, } ) func TakeRuntimeContext() *RuntimeContext { return runtimeContextPool.Get().(*RuntimeContext) } func ReleaseRuntimeContext(ctx *RuntimeContext) { runtimeContextPool.Put(ctx) } var ( isWhiteSpace = [256]bool{} ) func init() { isWhiteSpace[' '] = true isWhiteSpace['\n'] = true isWhiteSpace['\t'] = true isWhiteSpace['\r'] = true } func char(ptr unsafe.Pointer, offset int64) byte { return *(*byte)(unsafe.Pointer(uintptr(ptr) + uintptr(offset))) } func skipWhiteSpace(buf []byte, cursor int64) int64 { for isWhiteSpace[buf[cursor]] { cursor++ } return cursor } func skipObject(buf []byte, cursor, depth int64) (int64, error) { braceCount := 1 for { switch buf[cursor] { case '{': braceCount++ depth++ if depth > maxDecodeNestingDepth { return 0, errors.ErrExceededMaxDepth(buf[cursor], cursor) } case '}': depth-- braceCount-- if braceCount == 0 { return cursor + 1, nil } case '[': depth++ if depth > maxDecodeNestingDepth { return 0, errors.ErrExceededMaxDepth(buf[cursor], cursor) } case ']': depth-- case '"': for { cursor++ switch buf[cursor] { case '\\': cursor++ if buf[cursor] == nul { return 0, errors.ErrUnexpectedEndOfJSON("string of object", cursor) } case '"': goto SWITCH_OUT case nul: return 0, errors.ErrUnexpectedEndOfJSON("string of object", cursor) } } case nul: return 0, errors.ErrUnexpectedEndOfJSON("object of object", cursor) } SWITCH_OUT: cursor++ } } func skipArray(buf []byte, cursor, depth int64) (int64, error) { bracketCount := 1 for { switch buf[cursor] { case '[': bracketCount++ depth++ if depth > maxDecodeNestingDepth { return 0, errors.ErrExceededMaxDepth(buf[cursor], cursor) } case ']': bracketCount-- depth-- if bracketCount == 0 { return cursor + 1, nil } case '{': depth++ if depth > maxDecodeNestingDepth { return 0, errors.ErrExceededMaxDepth(buf[cursor], cursor) } case '}': depth-- case '"': for { cursor++ switch buf[cursor] { case '\\': cursor++ if buf[cursor] == nul { return 0, errors.ErrUnexpectedEndOfJSON("string of object", cursor) } case '"': goto SWITCH_OUT case nul: return 0, errors.ErrUnexpectedEndOfJSON("string of object", cursor) } } case nul: return 0, errors.ErrUnexpectedEndOfJSON("array of object", cursor) } SWITCH_OUT: cursor++ } } func skipValue(buf []byte, cursor, depth int64) (int64, error) { for { switch buf[cursor] { case ' ', '\t', '\n', '\r': cursor++ continue case '{': return skipObject(buf, cursor+1, depth+1) case '[': return skipArray(buf, cursor+1, depth+1) case '"': for { cursor++ switch buf[cursor] { case '\\': cursor++ if buf[cursor] == nul { return 0, errors.ErrUnexpectedEndOfJSON("string of object", cursor) } case '"': return cursor + 1, nil case nul: return 0, errors.ErrUnexpectedEndOfJSON("string of object", cursor) } } case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': for { cursor++ if floatTable[buf[cursor]] { continue } break } return cursor, nil case 't': if err := validateTrue(buf, cursor); err != nil { return 0, err } cursor += 4 return cursor, nil case 'f': if err := validateFalse(buf, cursor); err != nil { return 0, err } cursor += 5 return cursor, nil case 'n': if err := validateNull(buf, cursor); err != nil { return 0, err } cursor += 4 return cursor, nil default: return cursor, errors.ErrUnexpectedEndOfJSON("null", cursor) } } } func validateTrue(buf []byte, cursor int64) error { if cursor+3 >= int64(len(buf)) { return errors.ErrUnexpectedEndOfJSON("true", cursor) } if buf[cursor+1] != 'r' { return errors.ErrInvalidCharacter(buf[cursor+1], "true", cursor) } if buf[cursor+2] != 'u' { return errors.ErrInvalidCharacter(buf[cursor+2], "true", cursor) } if buf[cursor+3] != 'e' { return errors.ErrInvalidCharacter(buf[cursor+3], "true", cursor) } return nil } func validateFalse(buf []byte, cursor int64) error { if cursor+4 >= int64(len(buf)) { return errors.ErrUnexpectedEndOfJSON("false", cursor) } if buf[cursor+1] != 'a' { return errors.ErrInvalidCharacter(buf[cursor+1], "false", cursor) } if buf[cursor+2] != 'l' { return errors.ErrInvalidCharacter(buf[cursor+2], "false", cursor) } if buf[cursor+3] != 's' { return errors.ErrInvalidCharacter(buf[cursor+3], "false", cursor) } if buf[cursor+4] != 'e' { return errors.ErrInvalidCharacter(buf[cursor+4], "false", cursor) } return nil } func validateNull(buf []byte, cursor int64) error { if cursor+3 >= int64(len(buf)) { return errors.ErrUnexpectedEndOfJSON("null", cursor) } if buf[cursor+1] != 'u' { return errors.ErrInvalidCharacter(buf[cursor+1], "null", cursor) } if buf[cursor+2] != 'l' { return errors.ErrInvalidCharacter(buf[cursor+2], "null", cursor) } if buf[cursor+3] != 'l' { return errors.ErrInvalidCharacter(buf[cursor+3], "null", cursor) } return nil } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/float.go ================================================ package decoder import ( "strconv" "unsafe" "github.com/goccy/go-json/internal/errors" ) type floatDecoder struct { op func(unsafe.Pointer, float64) structName string fieldName string } func newFloatDecoder(structName, fieldName string, op func(unsafe.Pointer, float64)) *floatDecoder { return &floatDecoder{op: op, structName: structName, fieldName: fieldName} } var ( floatTable = [256]bool{ '0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true, '7': true, '8': true, '9': true, '.': true, 'e': true, 'E': true, '+': true, '-': true, } validEndNumberChar = [256]bool{ nul: true, ' ': true, '\t': true, '\r': true, '\n': true, ',': true, ':': true, '}': true, ']': true, } ) func floatBytes(s *Stream) []byte { start := s.cursor for { s.cursor++ if floatTable[s.char()] { continue } else if s.char() == nul { if s.read() { s.cursor-- // for retry current character continue } } break } return s.buf[start:s.cursor] } func (d *floatDecoder) decodeStreamByte(s *Stream) ([]byte, error) { for { switch s.char() { case ' ', '\n', '\t', '\r': s.cursor++ continue case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return floatBytes(s), nil case 'n': if err := nullBytes(s); err != nil { return nil, err } return nil, nil case nul: if s.read() { continue } goto ERROR default: goto ERROR } } ERROR: return nil, errors.ErrUnexpectedEndOfJSON("float", s.totalOffset()) } func (d *floatDecoder) decodeByte(buf []byte, cursor int64) ([]byte, int64, error) { for { switch buf[cursor] { case ' ', '\n', '\t', '\r': cursor++ continue case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': start := cursor cursor++ for floatTable[buf[cursor]] { cursor++ } num := buf[start:cursor] return num, cursor, nil case 'n': if err := validateNull(buf, cursor); err != nil { return nil, 0, err } cursor += 4 return nil, cursor, nil default: return nil, 0, errors.ErrUnexpectedEndOfJSON("float", cursor) } } } func (d *floatDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { bytes, err := d.decodeStreamByte(s) if err != nil { return err } if bytes == nil { return nil } str := *(*string)(unsafe.Pointer(&bytes)) f64, err := strconv.ParseFloat(str, 64) if err != nil { return errors.ErrSyntax(err.Error(), s.totalOffset()) } d.op(p, f64) return nil } func (d *floatDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { buf := ctx.Buf bytes, c, err := d.decodeByte(buf, cursor) if err != nil { return 0, err } if bytes == nil { return c, nil } cursor = c if !validEndNumberChar[buf[cursor]] { return 0, errors.ErrUnexpectedEndOfJSON("float", cursor) } s := *(*string)(unsafe.Pointer(&bytes)) f64, err := strconv.ParseFloat(s, 64) if err != nil { return 0, errors.ErrSyntax(err.Error(), cursor) } d.op(p, f64) return cursor, nil } func (d *floatDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) { buf := ctx.Buf bytes, c, err := d.decodeByte(buf, cursor) if err != nil { return nil, 0, err } if bytes == nil { return [][]byte{nullbytes}, c, nil } return [][]byte{bytes}, c, nil } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/func.go ================================================ package decoder import ( "bytes" "fmt" "unsafe" "github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/runtime" ) type funcDecoder struct { typ *runtime.Type structName string fieldName string } func newFuncDecoder(typ *runtime.Type, structName, fieldName string) *funcDecoder { fnDecoder := &funcDecoder{typ, structName, fieldName} return fnDecoder } func (d *funcDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { s.skipWhiteSpace() start := s.cursor if err := s.skipValue(depth); err != nil { return err } src := s.buf[start:s.cursor] if len(src) > 0 { switch src[0] { case '"': return &errors.UnmarshalTypeError{ Value: "string", Type: runtime.RType2Type(d.typ), Offset: s.totalOffset(), } case '[': return &errors.UnmarshalTypeError{ Value: "array", Type: runtime.RType2Type(d.typ), Offset: s.totalOffset(), } case '{': return &errors.UnmarshalTypeError{ Value: "object", Type: runtime.RType2Type(d.typ), Offset: s.totalOffset(), } case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return &errors.UnmarshalTypeError{ Value: "number", Type: runtime.RType2Type(d.typ), Offset: s.totalOffset(), } case 'n': if err := nullBytes(s); err != nil { return err } *(*unsafe.Pointer)(p) = nil return nil case 't': if err := trueBytes(s); err == nil { return &errors.UnmarshalTypeError{ Value: "boolean", Type: runtime.RType2Type(d.typ), Offset: s.totalOffset(), } } case 'f': if err := falseBytes(s); err == nil { return &errors.UnmarshalTypeError{ Value: "boolean", Type: runtime.RType2Type(d.typ), Offset: s.totalOffset(), } } } } return errors.ErrInvalidBeginningOfValue(s.buf[s.cursor], s.totalOffset()) } func (d *funcDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { buf := ctx.Buf cursor = skipWhiteSpace(buf, cursor) start := cursor end, err := skipValue(buf, cursor, depth) if err != nil { return 0, err } src := buf[start:end] if len(src) > 0 { switch src[0] { case '"': return 0, &errors.UnmarshalTypeError{ Value: "string", Type: runtime.RType2Type(d.typ), Offset: start, } case '[': return 0, &errors.UnmarshalTypeError{ Value: "array", Type: runtime.RType2Type(d.typ), Offset: start, } case '{': return 0, &errors.UnmarshalTypeError{ Value: "object", Type: runtime.RType2Type(d.typ), Offset: start, } case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return 0, &errors.UnmarshalTypeError{ Value: "number", Type: runtime.RType2Type(d.typ), Offset: start, } case 'n': if bytes.Equal(src, nullbytes) { *(*unsafe.Pointer)(p) = nil return end, nil } case 't': if err := validateTrue(buf, start); err == nil { return 0, &errors.UnmarshalTypeError{ Value: "boolean", Type: runtime.RType2Type(d.typ), Offset: start, } } case 'f': if err := validateFalse(buf, start); err == nil { return 0, &errors.UnmarshalTypeError{ Value: "boolean", Type: runtime.RType2Type(d.typ), Offset: start, } } } } return cursor, errors.ErrInvalidBeginningOfValue(buf[cursor], cursor) } func (d *funcDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) { return nil, 0, fmt.Errorf("json: func decoder does not support decode path") } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/int.go ================================================ package decoder import ( "fmt" "reflect" "unsafe" "github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/runtime" ) type intDecoder struct { typ *runtime.Type kind reflect.Kind op func(unsafe.Pointer, int64) structName string fieldName string } func newIntDecoder(typ *runtime.Type, structName, fieldName string, op func(unsafe.Pointer, int64)) *intDecoder { return &intDecoder{ typ: typ, kind: typ.Kind(), op: op, structName: structName, fieldName: fieldName, } } func (d *intDecoder) typeError(buf []byte, offset int64) *errors.UnmarshalTypeError { return &errors.UnmarshalTypeError{ Value: fmt.Sprintf("number %s", string(buf)), Type: runtime.RType2Type(d.typ), Struct: d.structName, Field: d.fieldName, Offset: offset, } } var ( pow10i64 = [...]int64{ 1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, } pow10i64Len = len(pow10i64) ) func (d *intDecoder) parseInt(b []byte) (int64, error) { isNegative := false if b[0] == '-' { b = b[1:] isNegative = true } maxDigit := len(b) if maxDigit > pow10i64Len { return 0, fmt.Errorf("invalid length of number") } sum := int64(0) for i := 0; i < maxDigit; i++ { c := int64(b[i]) - 48 digitValue := pow10i64[maxDigit-i-1] sum += c * digitValue } if isNegative { return -1 * sum, nil } return sum, nil } var ( numTable = [256]bool{ '0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true, '7': true, '8': true, '9': true, } ) var ( numZeroBuf = []byte{'0'} ) func (d *intDecoder) decodeStreamByte(s *Stream) ([]byte, error) { for { switch s.char() { case ' ', '\n', '\t', '\r': s.cursor++ continue case '-': start := s.cursor for { s.cursor++ if numTable[s.char()] { continue } else if s.char() == nul { if s.read() { s.cursor-- // for retry current character continue } } break } num := s.buf[start:s.cursor] if len(num) < 2 { goto ERROR } return num, nil case '0': s.cursor++ return numZeroBuf, nil case '1', '2', '3', '4', '5', '6', '7', '8', '9': start := s.cursor for { s.cursor++ if numTable[s.char()] { continue } else if s.char() == nul { if s.read() { s.cursor-- // for retry current character continue } } break } num := s.buf[start:s.cursor] return num, nil case 'n': if err := nullBytes(s); err != nil { return nil, err } return nil, nil case nul: if s.read() { continue } goto ERROR default: return nil, d.typeError([]byte{s.char()}, s.totalOffset()) } } ERROR: return nil, errors.ErrUnexpectedEndOfJSON("number(integer)", s.totalOffset()) } func (d *intDecoder) decodeByte(buf []byte, cursor int64) ([]byte, int64, error) { b := (*sliceHeader)(unsafe.Pointer(&buf)).data for { switch char(b, cursor) { case ' ', '\n', '\t', '\r': cursor++ continue case '0': cursor++ return numZeroBuf, cursor, nil case '-', '1', '2', '3', '4', '5', '6', '7', '8', '9': start := cursor cursor++ for numTable[char(b, cursor)] { cursor++ } num := buf[start:cursor] return num, cursor, nil case 'n': if err := validateNull(buf, cursor); err != nil { return nil, 0, err } cursor += 4 return nil, cursor, nil default: return nil, 0, d.typeError([]byte{char(b, cursor)}, cursor) } } } func (d *intDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { bytes, err := d.decodeStreamByte(s) if err != nil { return err } if bytes == nil { return nil } i64, err := d.parseInt(bytes) if err != nil { return d.typeError(bytes, s.totalOffset()) } switch d.kind { case reflect.Int8: if i64 < -1*(1<<7) || (1<<7) <= i64 { return d.typeError(bytes, s.totalOffset()) } case reflect.Int16: if i64 < -1*(1<<15) || (1<<15) <= i64 { return d.typeError(bytes, s.totalOffset()) } case reflect.Int32: if i64 < -1*(1<<31) || (1<<31) <= i64 { return d.typeError(bytes, s.totalOffset()) } } d.op(p, i64) s.reset() return nil } func (d *intDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { bytes, c, err := d.decodeByte(ctx.Buf, cursor) if err != nil { return 0, err } if bytes == nil { return c, nil } cursor = c i64, err := d.parseInt(bytes) if err != nil { return 0, d.typeError(bytes, cursor) } switch d.kind { case reflect.Int8: if i64 < -1*(1<<7) || (1<<7) <= i64 { return 0, d.typeError(bytes, cursor) } case reflect.Int16: if i64 < -1*(1<<15) || (1<<15) <= i64 { return 0, d.typeError(bytes, cursor) } case reflect.Int32: if i64 < -1*(1<<31) || (1<<31) <= i64 { return 0, d.typeError(bytes, cursor) } } d.op(p, i64) return cursor, nil } func (d *intDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) { return nil, 0, fmt.Errorf("json: int decoder does not support decode path") } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/interface.go ================================================ package decoder import ( "bytes" "encoding" "encoding/json" "reflect" "unsafe" "github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/runtime" ) type interfaceDecoder struct { typ *runtime.Type structName string fieldName string sliceDecoder *sliceDecoder mapDecoder *mapDecoder floatDecoder *floatDecoder numberDecoder *numberDecoder stringDecoder *stringDecoder } func newEmptyInterfaceDecoder(structName, fieldName string) *interfaceDecoder { ifaceDecoder := &interfaceDecoder{ typ: emptyInterfaceType, structName: structName, fieldName: fieldName, floatDecoder: newFloatDecoder(structName, fieldName, func(p unsafe.Pointer, v float64) { *(*interface{})(p) = v }), numberDecoder: newNumberDecoder(structName, fieldName, func(p unsafe.Pointer, v json.Number) { *(*interface{})(p) = v }), stringDecoder: newStringDecoder(structName, fieldName), } ifaceDecoder.sliceDecoder = newSliceDecoder( ifaceDecoder, emptyInterfaceType, emptyInterfaceType.Size(), structName, fieldName, ) ifaceDecoder.mapDecoder = newMapDecoder( interfaceMapType, stringType, ifaceDecoder.stringDecoder, interfaceMapType.Elem(), ifaceDecoder, structName, fieldName, ) return ifaceDecoder } func newInterfaceDecoder(typ *runtime.Type, structName, fieldName string) *interfaceDecoder { emptyIfaceDecoder := newEmptyInterfaceDecoder(structName, fieldName) stringDecoder := newStringDecoder(structName, fieldName) return &interfaceDecoder{ typ: typ, structName: structName, fieldName: fieldName, sliceDecoder: newSliceDecoder( emptyIfaceDecoder, emptyInterfaceType, emptyInterfaceType.Size(), structName, fieldName, ), mapDecoder: newMapDecoder( interfaceMapType, stringType, stringDecoder, interfaceMapType.Elem(), emptyIfaceDecoder, structName, fieldName, ), floatDecoder: newFloatDecoder(structName, fieldName, func(p unsafe.Pointer, v float64) { *(*interface{})(p) = v }), numberDecoder: newNumberDecoder(structName, fieldName, func(p unsafe.Pointer, v json.Number) { *(*interface{})(p) = v }), stringDecoder: stringDecoder, } } func (d *interfaceDecoder) numDecoder(s *Stream) Decoder { if s.UseNumber { return d.numberDecoder } return d.floatDecoder } var ( emptyInterfaceType = runtime.Type2RType(reflect.TypeOf((*interface{})(nil)).Elem()) EmptyInterfaceType = emptyInterfaceType interfaceMapType = runtime.Type2RType( reflect.TypeOf((*map[string]interface{})(nil)).Elem(), ) stringType = runtime.Type2RType( reflect.TypeOf(""), ) ) func decodeStreamUnmarshaler(s *Stream, depth int64, unmarshaler json.Unmarshaler) error { start := s.cursor if err := s.skipValue(depth); err != nil { return err } src := s.buf[start:s.cursor] dst := make([]byte, len(src)) copy(dst, src) if err := unmarshaler.UnmarshalJSON(dst); err != nil { return err } return nil } func decodeStreamUnmarshalerContext(s *Stream, depth int64, unmarshaler unmarshalerContext) error { start := s.cursor if err := s.skipValue(depth); err != nil { return err } src := s.buf[start:s.cursor] dst := make([]byte, len(src)) copy(dst, src) if err := unmarshaler.UnmarshalJSON(s.Option.Context, dst); err != nil { return err } return nil } func decodeUnmarshaler(buf []byte, cursor, depth int64, unmarshaler json.Unmarshaler) (int64, error) { cursor = skipWhiteSpace(buf, cursor) start := cursor end, err := skipValue(buf, cursor, depth) if err != nil { return 0, err } src := buf[start:end] dst := make([]byte, len(src)) copy(dst, src) if err := unmarshaler.UnmarshalJSON(dst); err != nil { return 0, err } return end, nil } func decodeUnmarshalerContext(ctx *RuntimeContext, buf []byte, cursor, depth int64, unmarshaler unmarshalerContext) (int64, error) { cursor = skipWhiteSpace(buf, cursor) start := cursor end, err := skipValue(buf, cursor, depth) if err != nil { return 0, err } src := buf[start:end] dst := make([]byte, len(src)) copy(dst, src) if err := unmarshaler.UnmarshalJSON(ctx.Option.Context, dst); err != nil { return 0, err } return end, nil } func decodeStreamTextUnmarshaler(s *Stream, depth int64, unmarshaler encoding.TextUnmarshaler, p unsafe.Pointer) error { start := s.cursor if err := s.skipValue(depth); err != nil { return err } src := s.buf[start:s.cursor] if bytes.Equal(src, nullbytes) { *(*unsafe.Pointer)(p) = nil return nil } dst := make([]byte, len(src)) copy(dst, src) if err := unmarshaler.UnmarshalText(dst); err != nil { return err } return nil } func decodeTextUnmarshaler(buf []byte, cursor, depth int64, unmarshaler encoding.TextUnmarshaler, p unsafe.Pointer) (int64, error) { cursor = skipWhiteSpace(buf, cursor) start := cursor end, err := skipValue(buf, cursor, depth) if err != nil { return 0, err } src := buf[start:end] if bytes.Equal(src, nullbytes) { *(*unsafe.Pointer)(p) = nil return end, nil } if s, ok := unquoteBytes(src); ok { src = s } if err := unmarshaler.UnmarshalText(src); err != nil { return 0, err } return end, nil } func (d *interfaceDecoder) decodeStreamEmptyInterface(s *Stream, depth int64, p unsafe.Pointer) error { c := s.skipWhiteSpace() for { switch c { case '{': var v map[string]interface{} ptr := unsafe.Pointer(&v) if err := d.mapDecoder.DecodeStream(s, depth, ptr); err != nil { return err } *(*interface{})(p) = v return nil case '[': var v []interface{} ptr := unsafe.Pointer(&v) if err := d.sliceDecoder.DecodeStream(s, depth, ptr); err != nil { return err } *(*interface{})(p) = v return nil case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return d.numDecoder(s).DecodeStream(s, depth, p) case '"': s.cursor++ start := s.cursor for { switch s.char() { case '\\': if _, err := decodeEscapeString(s, nil); err != nil { return err } case '"': literal := s.buf[start:s.cursor] s.cursor++ *(*interface{})(p) = string(literal) return nil case nul: if s.read() { continue } return errors.ErrUnexpectedEndOfJSON("string", s.totalOffset()) } s.cursor++ } case 't': if err := trueBytes(s); err != nil { return err } **(**interface{})(unsafe.Pointer(&p)) = true return nil case 'f': if err := falseBytes(s); err != nil { return err } **(**interface{})(unsafe.Pointer(&p)) = false return nil case 'n': if err := nullBytes(s); err != nil { return err } *(*interface{})(p) = nil return nil case nul: if s.read() { c = s.char() continue } } break } return errors.ErrInvalidBeginningOfValue(c, s.totalOffset()) } type emptyInterface struct { typ *runtime.Type ptr unsafe.Pointer } func (d *interfaceDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { runtimeInterfaceValue := *(*interface{})(unsafe.Pointer(&emptyInterface{ typ: d.typ, ptr: p, })) rv := reflect.ValueOf(runtimeInterfaceValue) if rv.NumMethod() > 0 && rv.CanInterface() { if u, ok := rv.Interface().(unmarshalerContext); ok { return decodeStreamUnmarshalerContext(s, depth, u) } if u, ok := rv.Interface().(json.Unmarshaler); ok { return decodeStreamUnmarshaler(s, depth, u) } if u, ok := rv.Interface().(encoding.TextUnmarshaler); ok { return decodeStreamTextUnmarshaler(s, depth, u, p) } if s.skipWhiteSpace() == 'n' { if err := nullBytes(s); err != nil { return err } *(*interface{})(p) = nil return nil } return d.errUnmarshalType(rv.Type(), s.totalOffset()) } iface := rv.Interface() ifaceHeader := (*emptyInterface)(unsafe.Pointer(&iface)) typ := ifaceHeader.typ if ifaceHeader.ptr == nil || d.typ == typ || typ == nil { // concrete type is empty interface return d.decodeStreamEmptyInterface(s, depth, p) } if typ.Kind() == reflect.Ptr && typ.Elem() == d.typ || typ.Kind() != reflect.Ptr { return d.decodeStreamEmptyInterface(s, depth, p) } if s.skipWhiteSpace() == 'n' { if err := nullBytes(s); err != nil { return err } *(*interface{})(p) = nil return nil } decoder, err := CompileToGetDecoder(typ) if err != nil { return err } return decoder.DecodeStream(s, depth, ifaceHeader.ptr) } func (d *interfaceDecoder) errUnmarshalType(typ reflect.Type, offset int64) *errors.UnmarshalTypeError { return &errors.UnmarshalTypeError{ Value: typ.String(), Type: typ, Offset: offset, Struct: d.structName, Field: d.fieldName, } } func (d *interfaceDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { buf := ctx.Buf runtimeInterfaceValue := *(*interface{})(unsafe.Pointer(&emptyInterface{ typ: d.typ, ptr: p, })) rv := reflect.ValueOf(runtimeInterfaceValue) if rv.NumMethod() > 0 && rv.CanInterface() { if u, ok := rv.Interface().(unmarshalerContext); ok { return decodeUnmarshalerContext(ctx, buf, cursor, depth, u) } if u, ok := rv.Interface().(json.Unmarshaler); ok { return decodeUnmarshaler(buf, cursor, depth, u) } if u, ok := rv.Interface().(encoding.TextUnmarshaler); ok { return decodeTextUnmarshaler(buf, cursor, depth, u, p) } cursor = skipWhiteSpace(buf, cursor) if buf[cursor] == 'n' { if err := validateNull(buf, cursor); err != nil { return 0, err } cursor += 4 **(**interface{})(unsafe.Pointer(&p)) = nil return cursor, nil } return 0, d.errUnmarshalType(rv.Type(), cursor) } iface := rv.Interface() ifaceHeader := (*emptyInterface)(unsafe.Pointer(&iface)) typ := ifaceHeader.typ if ifaceHeader.ptr == nil || d.typ == typ || typ == nil { // concrete type is empty interface return d.decodeEmptyInterface(ctx, cursor, depth, p) } if typ.Kind() == reflect.Ptr && typ.Elem() == d.typ || typ.Kind() != reflect.Ptr { return d.decodeEmptyInterface(ctx, cursor, depth, p) } cursor = skipWhiteSpace(buf, cursor) if buf[cursor] == 'n' { if err := validateNull(buf, cursor); err != nil { return 0, err } cursor += 4 **(**interface{})(unsafe.Pointer(&p)) = nil return cursor, nil } decoder, err := CompileToGetDecoder(typ) if err != nil { return 0, err } return decoder.Decode(ctx, cursor, depth, ifaceHeader.ptr) } func (d *interfaceDecoder) decodeEmptyInterface(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { buf := ctx.Buf cursor = skipWhiteSpace(buf, cursor) switch buf[cursor] { case '{': var v map[string]interface{} ptr := unsafe.Pointer(&v) cursor, err := d.mapDecoder.Decode(ctx, cursor, depth, ptr) if err != nil { return 0, err } **(**interface{})(unsafe.Pointer(&p)) = v return cursor, nil case '[': var v []interface{} ptr := unsafe.Pointer(&v) cursor, err := d.sliceDecoder.Decode(ctx, cursor, depth, ptr) if err != nil { return 0, err } **(**interface{})(unsafe.Pointer(&p)) = v return cursor, nil case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return d.floatDecoder.Decode(ctx, cursor, depth, p) case '"': var v string ptr := unsafe.Pointer(&v) cursor, err := d.stringDecoder.Decode(ctx, cursor, depth, ptr) if err != nil { return 0, err } **(**interface{})(unsafe.Pointer(&p)) = v return cursor, nil case 't': if err := validateTrue(buf, cursor); err != nil { return 0, err } cursor += 4 **(**interface{})(unsafe.Pointer(&p)) = true return cursor, nil case 'f': if err := validateFalse(buf, cursor); err != nil { return 0, err } cursor += 5 **(**interface{})(unsafe.Pointer(&p)) = false return cursor, nil case 'n': if err := validateNull(buf, cursor); err != nil { return 0, err } cursor += 4 **(**interface{})(unsafe.Pointer(&p)) = nil return cursor, nil } return cursor, errors.ErrInvalidBeginningOfValue(buf[cursor], cursor) } func NewPathDecoder() Decoder { ifaceDecoder := &interfaceDecoder{ typ: emptyInterfaceType, structName: "", fieldName: "", floatDecoder: newFloatDecoder("", "", func(p unsafe.Pointer, v float64) { *(*interface{})(p) = v }), numberDecoder: newNumberDecoder("", "", func(p unsafe.Pointer, v json.Number) { *(*interface{})(p) = v }), stringDecoder: newStringDecoder("", ""), } ifaceDecoder.sliceDecoder = newSliceDecoder( ifaceDecoder, emptyInterfaceType, emptyInterfaceType.Size(), "", "", ) ifaceDecoder.mapDecoder = newMapDecoder( interfaceMapType, stringType, ifaceDecoder.stringDecoder, interfaceMapType.Elem(), ifaceDecoder, "", "", ) return ifaceDecoder } var ( truebytes = []byte("true") falsebytes = []byte("false") ) func (d *interfaceDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) { buf := ctx.Buf cursor = skipWhiteSpace(buf, cursor) switch buf[cursor] { case '{': return d.mapDecoder.DecodePath(ctx, cursor, depth) case '[': return d.sliceDecoder.DecodePath(ctx, cursor, depth) case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return d.floatDecoder.DecodePath(ctx, cursor, depth) case '"': return d.stringDecoder.DecodePath(ctx, cursor, depth) case 't': if err := validateTrue(buf, cursor); err != nil { return nil, 0, err } cursor += 4 return [][]byte{truebytes}, cursor, nil case 'f': if err := validateFalse(buf, cursor); err != nil { return nil, 0, err } cursor += 5 return [][]byte{falsebytes}, cursor, nil case 'n': if err := validateNull(buf, cursor); err != nil { return nil, 0, err } cursor += 4 return [][]byte{nullbytes}, cursor, nil } return nil, cursor, errors.ErrInvalidBeginningOfValue(buf[cursor], cursor) } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/invalid.go ================================================ package decoder import ( "reflect" "unsafe" "github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/runtime" ) type invalidDecoder struct { typ *runtime.Type kind reflect.Kind structName string fieldName string } func newInvalidDecoder(typ *runtime.Type, structName, fieldName string) *invalidDecoder { return &invalidDecoder{ typ: typ, kind: typ.Kind(), structName: structName, fieldName: fieldName, } } func (d *invalidDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { return &errors.UnmarshalTypeError{ Value: "object", Type: runtime.RType2Type(d.typ), Offset: s.totalOffset(), Struct: d.structName, Field: d.fieldName, } } func (d *invalidDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { return 0, &errors.UnmarshalTypeError{ Value: "object", Type: runtime.RType2Type(d.typ), Offset: cursor, Struct: d.structName, Field: d.fieldName, } } func (d *invalidDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) { return nil, 0, &errors.UnmarshalTypeError{ Value: "object", Type: runtime.RType2Type(d.typ), Offset: cursor, Struct: d.structName, Field: d.fieldName, } } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/map.go ================================================ package decoder import ( "reflect" "unsafe" "github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/runtime" ) type mapDecoder struct { mapType *runtime.Type keyType *runtime.Type valueType *runtime.Type canUseAssignFaststrType bool keyDecoder Decoder valueDecoder Decoder structName string fieldName string } func newMapDecoder(mapType *runtime.Type, keyType *runtime.Type, keyDec Decoder, valueType *runtime.Type, valueDec Decoder, structName, fieldName string) *mapDecoder { return &mapDecoder{ mapType: mapType, keyDecoder: keyDec, keyType: keyType, canUseAssignFaststrType: canUseAssignFaststrType(keyType, valueType), valueType: valueType, valueDecoder: valueDec, structName: structName, fieldName: fieldName, } } const ( mapMaxElemSize = 128 ) // See detail: https://github.com/goccy/go-json/pull/283 func canUseAssignFaststrType(key *runtime.Type, value *runtime.Type) bool { indirectElem := value.Size() > mapMaxElemSize if indirectElem { return false } return key.Kind() == reflect.String } //go:linkname makemap reflect.makemap func makemap(*runtime.Type, int) unsafe.Pointer //nolint:golint //go:linkname mapassign_faststr runtime.mapassign_faststr //go:noescape func mapassign_faststr(t *runtime.Type, m unsafe.Pointer, s string) unsafe.Pointer //go:linkname mapassign reflect.mapassign //go:noescape func mapassign(t *runtime.Type, m unsafe.Pointer, k, v unsafe.Pointer) func (d *mapDecoder) mapassign(t *runtime.Type, m, k, v unsafe.Pointer) { if d.canUseAssignFaststrType { mapV := mapassign_faststr(t, m, *(*string)(k)) typedmemmove(d.valueType, mapV, v) } else { mapassign(t, m, k, v) } } func (d *mapDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { depth++ if depth > maxDecodeNestingDepth { return errors.ErrExceededMaxDepth(s.char(), s.cursor) } switch s.skipWhiteSpace() { case 'n': if err := nullBytes(s); err != nil { return err } **(**unsafe.Pointer)(unsafe.Pointer(&p)) = nil return nil case '{': default: return errors.ErrExpected("{ character for map value", s.totalOffset()) } mapValue := *(*unsafe.Pointer)(p) if mapValue == nil { mapValue = makemap(d.mapType, 0) } s.cursor++ if s.skipWhiteSpace() == '}' { *(*unsafe.Pointer)(p) = mapValue s.cursor++ return nil } for { k := unsafe_New(d.keyType) if err := d.keyDecoder.DecodeStream(s, depth, k); err != nil { return err } s.skipWhiteSpace() if !s.equalChar(':') { return errors.ErrExpected("colon after object key", s.totalOffset()) } s.cursor++ v := unsafe_New(d.valueType) if err := d.valueDecoder.DecodeStream(s, depth, v); err != nil { return err } d.mapassign(d.mapType, mapValue, k, v) s.skipWhiteSpace() if s.equalChar('}') { **(**unsafe.Pointer)(unsafe.Pointer(&p)) = mapValue s.cursor++ return nil } if !s.equalChar(',') { return errors.ErrExpected("comma after object value", s.totalOffset()) } s.cursor++ } } func (d *mapDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { buf := ctx.Buf depth++ if depth > maxDecodeNestingDepth { return 0, errors.ErrExceededMaxDepth(buf[cursor], cursor) } cursor = skipWhiteSpace(buf, cursor) buflen := int64(len(buf)) if buflen < 2 { return 0, errors.ErrExpected("{} for map", cursor) } switch buf[cursor] { case 'n': if err := validateNull(buf, cursor); err != nil { return 0, err } cursor += 4 **(**unsafe.Pointer)(unsafe.Pointer(&p)) = nil return cursor, nil case '{': default: return 0, errors.ErrExpected("{ character for map value", cursor) } cursor++ cursor = skipWhiteSpace(buf, cursor) mapValue := *(*unsafe.Pointer)(p) if mapValue == nil { mapValue = makemap(d.mapType, 0) } if buf[cursor] == '}' { **(**unsafe.Pointer)(unsafe.Pointer(&p)) = mapValue cursor++ return cursor, nil } for { k := unsafe_New(d.keyType) keyCursor, err := d.keyDecoder.Decode(ctx, cursor, depth, k) if err != nil { return 0, err } cursor = skipWhiteSpace(buf, keyCursor) if buf[cursor] != ':' { return 0, errors.ErrExpected("colon after object key", cursor) } cursor++ v := unsafe_New(d.valueType) valueCursor, err := d.valueDecoder.Decode(ctx, cursor, depth, v) if err != nil { return 0, err } d.mapassign(d.mapType, mapValue, k, v) cursor = skipWhiteSpace(buf, valueCursor) if buf[cursor] == '}' { **(**unsafe.Pointer)(unsafe.Pointer(&p)) = mapValue cursor++ return cursor, nil } if buf[cursor] != ',' { return 0, errors.ErrExpected("comma after object value", cursor) } cursor++ } } func (d *mapDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) { buf := ctx.Buf depth++ if depth > maxDecodeNestingDepth { return nil, 0, errors.ErrExceededMaxDepth(buf[cursor], cursor) } cursor = skipWhiteSpace(buf, cursor) buflen := int64(len(buf)) if buflen < 2 { return nil, 0, errors.ErrExpected("{} for map", cursor) } switch buf[cursor] { case 'n': if err := validateNull(buf, cursor); err != nil { return nil, 0, err } cursor += 4 return [][]byte{nullbytes}, cursor, nil case '{': default: return nil, 0, errors.ErrExpected("{ character for map value", cursor) } cursor++ cursor = skipWhiteSpace(buf, cursor) if buf[cursor] == '}' { cursor++ return nil, cursor, nil } keyDecoder, ok := d.keyDecoder.(*stringDecoder) if !ok { return nil, 0, &errors.UnmarshalTypeError{ Value: "string", Type: reflect.TypeOf(""), Offset: cursor, Struct: d.structName, Field: d.fieldName, } } ret := [][]byte{} for { key, keyCursor, err := keyDecoder.decodeByte(buf, cursor) if err != nil { return nil, 0, err } cursor = skipWhiteSpace(buf, keyCursor) if buf[cursor] != ':' { return nil, 0, errors.ErrExpected("colon after object key", cursor) } cursor++ child, found, err := ctx.Option.Path.Field(string(key)) if err != nil { return nil, 0, err } if found { if child != nil { oldPath := ctx.Option.Path.node ctx.Option.Path.node = child paths, c, err := d.valueDecoder.DecodePath(ctx, cursor, depth) if err != nil { return nil, 0, err } ctx.Option.Path.node = oldPath ret = append(ret, paths...) cursor = c } else { start := cursor end, err := skipValue(buf, cursor, depth) if err != nil { return nil, 0, err } ret = append(ret, buf[start:end]) cursor = end } } else { c, err := skipValue(buf, cursor, depth) if err != nil { return nil, 0, err } cursor = c } cursor = skipWhiteSpace(buf, cursor) if buf[cursor] == '}' { cursor++ return ret, cursor, nil } if buf[cursor] != ',' { return nil, 0, errors.ErrExpected("comma after object value", cursor) } cursor++ } } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/number.go ================================================ package decoder import ( "encoding/json" "strconv" "unsafe" "github.com/goccy/go-json/internal/errors" ) type numberDecoder struct { stringDecoder *stringDecoder op func(unsafe.Pointer, json.Number) structName string fieldName string } func newNumberDecoder(structName, fieldName string, op func(unsafe.Pointer, json.Number)) *numberDecoder { return &numberDecoder{ stringDecoder: newStringDecoder(structName, fieldName), op: op, structName: structName, fieldName: fieldName, } } func (d *numberDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { bytes, err := d.decodeStreamByte(s) if err != nil { return err } if _, err := strconv.ParseFloat(*(*string)(unsafe.Pointer(&bytes)), 64); err != nil { return errors.ErrSyntax(err.Error(), s.totalOffset()) } d.op(p, json.Number(string(bytes))) s.reset() return nil } func (d *numberDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { bytes, c, err := d.decodeByte(ctx.Buf, cursor) if err != nil { return 0, err } if _, err := strconv.ParseFloat(*(*string)(unsafe.Pointer(&bytes)), 64); err != nil { return 0, errors.ErrSyntax(err.Error(), c) } cursor = c s := *(*string)(unsafe.Pointer(&bytes)) d.op(p, json.Number(s)) return cursor, nil } func (d *numberDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) { bytes, c, err := d.decodeByte(ctx.Buf, cursor) if err != nil { return nil, 0, err } if bytes == nil { return [][]byte{nullbytes}, c, nil } return [][]byte{bytes}, c, nil } func (d *numberDecoder) decodeStreamByte(s *Stream) ([]byte, error) { start := s.cursor for { switch s.char() { case ' ', '\n', '\t', '\r': s.cursor++ continue case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return floatBytes(s), nil case 'n': if err := nullBytes(s); err != nil { return nil, err } return nil, nil case '"': return d.stringDecoder.decodeStreamByte(s) case nul: if s.read() { continue } goto ERROR default: goto ERROR } } ERROR: if s.cursor == start { return nil, errors.ErrInvalidBeginningOfValue(s.char(), s.totalOffset()) } return nil, errors.ErrUnexpectedEndOfJSON("json.Number", s.totalOffset()) } func (d *numberDecoder) decodeByte(buf []byte, cursor int64) ([]byte, int64, error) { for { switch buf[cursor] { case ' ', '\n', '\t', '\r': cursor++ continue case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': start := cursor cursor++ for floatTable[buf[cursor]] { cursor++ } num := buf[start:cursor] return num, cursor, nil case 'n': if err := validateNull(buf, cursor); err != nil { return nil, 0, err } cursor += 4 return nil, cursor, nil case '"': return d.stringDecoder.decodeByte(buf, cursor) default: return nil, 0, errors.ErrUnexpectedEndOfJSON("json.Number", cursor) } } } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/option.go ================================================ package decoder import "context" type OptionFlags uint8 const ( FirstWinOption OptionFlags = 1 << iota ContextOption PathOption ) type Option struct { Flags OptionFlags Context context.Context Path *Path } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/path.go ================================================ package decoder import ( "fmt" "reflect" "strconv" "github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/runtime" ) type PathString string func (s PathString) Build() (*Path, error) { builder := new(PathBuilder) return builder.Build([]rune(s)) } type PathBuilder struct { root PathNode node PathNode singleQuotePathSelector bool doubleQuotePathSelector bool } func (b *PathBuilder) Build(buf []rune) (*Path, error) { node, err := b.build(buf) if err != nil { return nil, err } return &Path{ node: node, RootSelectorOnly: node == nil, SingleQuotePathSelector: b.singleQuotePathSelector, DoubleQuotePathSelector: b.doubleQuotePathSelector, }, nil } func (b *PathBuilder) build(buf []rune) (PathNode, error) { if len(buf) == 0 { return nil, errors.ErrEmptyPath() } if buf[0] != '$' { return nil, errors.ErrInvalidPath("JSON Path must start with a $ character") } if len(buf) == 1 { return nil, nil } buf = buf[1:] offset, err := b.buildNext(buf) if err != nil { return nil, err } if len(buf) > offset { return nil, errors.ErrInvalidPath("remain invalid path %q", buf[offset:]) } return b.root, nil } func (b *PathBuilder) buildNextCharIfExists(buf []rune, cursor int) (int, error) { if len(buf) > cursor { offset, err := b.buildNext(buf[cursor:]) if err != nil { return 0, err } return cursor + 1 + offset, nil } return cursor, nil } func (b *PathBuilder) buildNext(buf []rune) (int, error) { switch buf[0] { case '.': if len(buf) == 1 { return 0, errors.ErrInvalidPath("JSON Path ends with dot character") } offset, err := b.buildSelector(buf[1:]) if err != nil { return 0, err } return offset + 1, nil case '[': if len(buf) == 1 { return 0, errors.ErrInvalidPath("JSON Path ends with left bracket character") } offset, err := b.buildIndex(buf[1:]) if err != nil { return 0, err } return offset + 1, nil default: return 0, errors.ErrInvalidPath("expect dot or left bracket character. but found %c character", buf[0]) } } func (b *PathBuilder) buildSelector(buf []rune) (int, error) { switch buf[0] { case '.': if len(buf) == 1 { return 0, errors.ErrInvalidPath("JSON Path ends with double dot character") } offset, err := b.buildPathRecursive(buf[1:]) if err != nil { return 0, err } return 1 + offset, nil case '[', ']', '$', '*': return 0, errors.ErrInvalidPath("found invalid path character %c after dot", buf[0]) } for cursor := 0; cursor < len(buf); cursor++ { switch buf[cursor] { case '$', '*', ']': return 0, errors.ErrInvalidPath("found %c character in field selector context", buf[cursor]) case '.': if cursor+1 >= len(buf) { return 0, errors.ErrInvalidPath("JSON Path ends with dot character") } selector := buf[:cursor] b.addSelectorNode(string(selector)) offset, err := b.buildSelector(buf[cursor+1:]) if err != nil { return 0, err } return cursor + 1 + offset, nil case '[': if cursor+1 >= len(buf) { return 0, errors.ErrInvalidPath("JSON Path ends with left bracket character") } selector := buf[:cursor] b.addSelectorNode(string(selector)) offset, err := b.buildIndex(buf[cursor+1:]) if err != nil { return 0, err } return cursor + 1 + offset, nil case '"': if cursor+1 >= len(buf) { return 0, errors.ErrInvalidPath("JSON Path ends with double quote character") } offset, err := b.buildQuoteSelector(buf[cursor+1:], DoubleQuotePathSelector) if err != nil { return 0, err } return cursor + 1 + offset, nil } } b.addSelectorNode(string(buf)) return len(buf), nil } func (b *PathBuilder) buildQuoteSelector(buf []rune, sel QuotePathSelector) (int, error) { switch buf[0] { case '[', ']', '$', '.', '*', '\'', '"': return 0, errors.ErrInvalidPath("found invalid path character %c after quote", buf[0]) } for cursor := 0; cursor < len(buf); cursor++ { switch buf[cursor] { case '\'': if sel != SingleQuotePathSelector { return 0, errors.ErrInvalidPath("found double quote character in field selector with single quote context") } if len(buf) <= cursor+1 { return 0, errors.ErrInvalidPath("JSON Path ends with single quote character in field selector context") } if buf[cursor+1] != ']' { return 0, errors.ErrInvalidPath("expect right bracket for field selector with single quote but found %c", buf[cursor+1]) } selector := buf[:cursor] b.addSelectorNode(string(selector)) b.singleQuotePathSelector = true return b.buildNextCharIfExists(buf, cursor+2) case '"': if sel != DoubleQuotePathSelector { return 0, errors.ErrInvalidPath("found single quote character in field selector with double quote context") } selector := buf[:cursor] b.addSelectorNode(string(selector)) b.doubleQuotePathSelector = true return b.buildNextCharIfExists(buf, cursor+1) } } return 0, errors.ErrInvalidPath("couldn't find quote character in selector quote path context") } func (b *PathBuilder) buildPathRecursive(buf []rune) (int, error) { switch buf[0] { case '.', '[', ']', '$', '*': return 0, errors.ErrInvalidPath("found invalid path character %c after double dot", buf[0]) } for cursor := 0; cursor < len(buf); cursor++ { switch buf[cursor] { case '$', '*', ']': return 0, errors.ErrInvalidPath("found %c character in field selector context", buf[cursor]) case '.': if cursor+1 >= len(buf) { return 0, errors.ErrInvalidPath("JSON Path ends with dot character") } selector := buf[:cursor] b.addRecursiveNode(string(selector)) offset, err := b.buildSelector(buf[cursor+1:]) if err != nil { return 0, err } return cursor + 1 + offset, nil case '[': if cursor+1 >= len(buf) { return 0, errors.ErrInvalidPath("JSON Path ends with left bracket character") } selector := buf[:cursor] b.addRecursiveNode(string(selector)) offset, err := b.buildIndex(buf[cursor+1:]) if err != nil { return 0, err } return cursor + 1 + offset, nil } } b.addRecursiveNode(string(buf)) return len(buf), nil } func (b *PathBuilder) buildIndex(buf []rune) (int, error) { switch buf[0] { case '.', '[', ']', '$': return 0, errors.ErrInvalidPath("found invalid path character %c after left bracket", buf[0]) case '\'': if len(buf) == 1 { return 0, errors.ErrInvalidPath("JSON Path ends with single quote character") } offset, err := b.buildQuoteSelector(buf[1:], SingleQuotePathSelector) if err != nil { return 0, err } return 1 + offset, nil case '*': if len(buf) == 1 { return 0, errors.ErrInvalidPath("JSON Path ends with star character") } if buf[1] != ']' { return 0, errors.ErrInvalidPath("expect right bracket character for index all path but found %c character", buf[1]) } b.addIndexAllNode() offset := len("*]") if len(buf) > 2 { buildOffset, err := b.buildNext(buf[2:]) if err != nil { return 0, err } return offset + buildOffset, nil } return offset, nil } for cursor := 0; cursor < len(buf); cursor++ { switch buf[cursor] { case ']': index, err := strconv.ParseInt(string(buf[:cursor]), 10, 64) if err != nil { return 0, errors.ErrInvalidPath("%q is unexpected index path", buf[:cursor]) } b.addIndexNode(int(index)) return b.buildNextCharIfExists(buf, cursor+1) } } return 0, errors.ErrInvalidPath("couldn't find right bracket character in index path context") } func (b *PathBuilder) addIndexAllNode() { node := newPathIndexAllNode() if b.root == nil { b.root = node b.node = node } else { b.node = b.node.chain(node) } } func (b *PathBuilder) addRecursiveNode(selector string) { node := newPathRecursiveNode(selector) if b.root == nil { b.root = node b.node = node } else { b.node = b.node.chain(node) } } func (b *PathBuilder) addSelectorNode(name string) { node := newPathSelectorNode(name) if b.root == nil { b.root = node b.node = node } else { b.node = b.node.chain(node) } } func (b *PathBuilder) addIndexNode(idx int) { node := newPathIndexNode(idx) if b.root == nil { b.root = node b.node = node } else { b.node = b.node.chain(node) } } type QuotePathSelector int const ( SingleQuotePathSelector QuotePathSelector = 1 DoubleQuotePathSelector QuotePathSelector = 2 ) type Path struct { node PathNode RootSelectorOnly bool SingleQuotePathSelector bool DoubleQuotePathSelector bool } func (p *Path) Field(sel string) (PathNode, bool, error) { if p.node == nil { return nil, false, nil } return p.node.Field(sel) } func (p *Path) Get(src, dst reflect.Value) error { if p.node == nil { return nil } return p.node.Get(src, dst) } func (p *Path) String() string { if p.node == nil { return "$" } return p.node.String() } type PathNode interface { fmt.Stringer Index(idx int) (PathNode, bool, error) Field(fieldName string) (PathNode, bool, error) Get(src, dst reflect.Value) error chain(PathNode) PathNode target() bool single() bool } type BasePathNode struct { child PathNode } func (n *BasePathNode) chain(node PathNode) PathNode { n.child = node return node } func (n *BasePathNode) target() bool { return n.child == nil } func (n *BasePathNode) single() bool { return true } type PathSelectorNode struct { *BasePathNode selector string } func newPathSelectorNode(selector string) *PathSelectorNode { return &PathSelectorNode{ BasePathNode: &BasePathNode{}, selector: selector, } } func (n *PathSelectorNode) Index(idx int) (PathNode, bool, error) { return nil, false, &errors.PathError{} } func (n *PathSelectorNode) Field(fieldName string) (PathNode, bool, error) { if n.selector == fieldName { return n.child, true, nil } return nil, false, nil } func (n *PathSelectorNode) Get(src, dst reflect.Value) error { switch src.Type().Kind() { case reflect.Map: iter := src.MapRange() for iter.Next() { key, ok := iter.Key().Interface().(string) if !ok { return fmt.Errorf("invalid map key type %T", src.Type().Key()) } child, found, err := n.Field(key) if err != nil { return err } if found { if child != nil { return child.Get(iter.Value(), dst) } return AssignValue(iter.Value(), dst) } } case reflect.Struct: typ := src.Type() for i := 0; i < typ.Len(); i++ { tag := runtime.StructTagFromField(typ.Field(i)) child, found, err := n.Field(tag.Key) if err != nil { return err } if found { if child != nil { return child.Get(src.Field(i), dst) } return AssignValue(src.Field(i), dst) } } case reflect.Ptr: return n.Get(src.Elem(), dst) case reflect.Interface: return n.Get(reflect.ValueOf(src.Interface()), dst) case reflect.Float64, reflect.String, reflect.Bool: return AssignValue(src, dst) } return fmt.Errorf("failed to get %s value from %s", n.selector, src.Type()) } func (n *PathSelectorNode) String() string { s := fmt.Sprintf(".%s", n.selector) if n.child != nil { s += n.child.String() } return s } type PathIndexNode struct { *BasePathNode selector int } func newPathIndexNode(selector int) *PathIndexNode { return &PathIndexNode{ BasePathNode: &BasePathNode{}, selector: selector, } } func (n *PathIndexNode) Index(idx int) (PathNode, bool, error) { if n.selector == idx { return n.child, true, nil } return nil, false, nil } func (n *PathIndexNode) Field(fieldName string) (PathNode, bool, error) { return nil, false, &errors.PathError{} } func (n *PathIndexNode) Get(src, dst reflect.Value) error { switch src.Type().Kind() { case reflect.Array, reflect.Slice: if src.Len() > n.selector { if n.child != nil { return n.child.Get(src.Index(n.selector), dst) } return AssignValue(src.Index(n.selector), dst) } case reflect.Ptr: return n.Get(src.Elem(), dst) case reflect.Interface: return n.Get(reflect.ValueOf(src.Interface()), dst) } return fmt.Errorf("failed to get [%d] value from %s", n.selector, src.Type()) } func (n *PathIndexNode) String() string { s := fmt.Sprintf("[%d]", n.selector) if n.child != nil { s += n.child.String() } return s } type PathIndexAllNode struct { *BasePathNode } func newPathIndexAllNode() *PathIndexAllNode { return &PathIndexAllNode{ BasePathNode: &BasePathNode{}, } } func (n *PathIndexAllNode) Index(idx int) (PathNode, bool, error) { return n.child, true, nil } func (n *PathIndexAllNode) Field(fieldName string) (PathNode, bool, error) { return nil, false, &errors.PathError{} } func (n *PathIndexAllNode) Get(src, dst reflect.Value) error { switch src.Type().Kind() { case reflect.Array, reflect.Slice: var arr []interface{} for i := 0; i < src.Len(); i++ { var v interface{} rv := reflect.ValueOf(&v) if n.child != nil { if err := n.child.Get(src.Index(i), rv); err != nil { return err } } else { if err := AssignValue(src.Index(i), rv); err != nil { return err } } arr = append(arr, v) } if err := AssignValue(reflect.ValueOf(arr), dst); err != nil { return err } return nil case reflect.Ptr: return n.Get(src.Elem(), dst) case reflect.Interface: return n.Get(reflect.ValueOf(src.Interface()), dst) } return fmt.Errorf("failed to get all value from %s", src.Type()) } func (n *PathIndexAllNode) String() string { s := "[*]" if n.child != nil { s += n.child.String() } return s } type PathRecursiveNode struct { *BasePathNode selector string } func newPathRecursiveNode(selector string) *PathRecursiveNode { node := newPathSelectorNode(selector) return &PathRecursiveNode{ BasePathNode: &BasePathNode{ child: node, }, selector: selector, } } func (n *PathRecursiveNode) Field(fieldName string) (PathNode, bool, error) { if n.selector == fieldName { return n.child, true, nil } return nil, false, nil } func (n *PathRecursiveNode) Index(_ int) (PathNode, bool, error) { return n, true, nil } func valueToSliceValue(v interface{}) []interface{} { rv := reflect.ValueOf(v) ret := []interface{}{} if rv.Type().Kind() == reflect.Slice || rv.Type().Kind() == reflect.Array { for i := 0; i < rv.Len(); i++ { ret = append(ret, rv.Index(i).Interface()) } return ret } return []interface{}{v} } func (n *PathRecursiveNode) Get(src, dst reflect.Value) error { if n.child == nil { return fmt.Errorf("failed to get by recursive path ..%s", n.selector) } var arr []interface{} switch src.Type().Kind() { case reflect.Map: iter := src.MapRange() for iter.Next() { key, ok := iter.Key().Interface().(string) if !ok { return fmt.Errorf("invalid map key type %T", src.Type().Key()) } child, found, err := n.Field(key) if err != nil { return err } if found { var v interface{} rv := reflect.ValueOf(&v) _ = child.Get(iter.Value(), rv) arr = append(arr, valueToSliceValue(v)...) } else { var v interface{} rv := reflect.ValueOf(&v) _ = n.Get(iter.Value(), rv) if v != nil { arr = append(arr, valueToSliceValue(v)...) } } } _ = AssignValue(reflect.ValueOf(arr), dst) return nil case reflect.Struct: typ := src.Type() for i := 0; i < typ.Len(); i++ { tag := runtime.StructTagFromField(typ.Field(i)) child, found, err := n.Field(tag.Key) if err != nil { return err } if found { var v interface{} rv := reflect.ValueOf(&v) _ = child.Get(src.Field(i), rv) arr = append(arr, valueToSliceValue(v)...) } else { var v interface{} rv := reflect.ValueOf(&v) _ = n.Get(src.Field(i), rv) if v != nil { arr = append(arr, valueToSliceValue(v)...) } } } _ = AssignValue(reflect.ValueOf(arr), dst) return nil case reflect.Array, reflect.Slice: for i := 0; i < src.Len(); i++ { var v interface{} rv := reflect.ValueOf(&v) _ = n.Get(src.Index(i), rv) if v != nil { arr = append(arr, valueToSliceValue(v)...) } } _ = AssignValue(reflect.ValueOf(arr), dst) return nil case reflect.Ptr: return n.Get(src.Elem(), dst) case reflect.Interface: return n.Get(reflect.ValueOf(src.Interface()), dst) } return fmt.Errorf("failed to get %s value from %s", n.selector, src.Type()) } func (n *PathRecursiveNode) String() string { s := fmt.Sprintf("..%s", n.selector) if n.child != nil { s += n.child.String() } return s } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/ptr.go ================================================ package decoder import ( "fmt" "unsafe" "github.com/goccy/go-json/internal/runtime" ) type ptrDecoder struct { dec Decoder typ *runtime.Type structName string fieldName string } func newPtrDecoder(dec Decoder, typ *runtime.Type, structName, fieldName string) *ptrDecoder { return &ptrDecoder{ dec: dec, typ: typ, structName: structName, fieldName: fieldName, } } func (d *ptrDecoder) contentDecoder() Decoder { dec, ok := d.dec.(*ptrDecoder) if !ok { return d.dec } return dec.contentDecoder() } //nolint:golint //go:linkname unsafe_New reflect.unsafe_New func unsafe_New(*runtime.Type) unsafe.Pointer func UnsafeNew(t *runtime.Type) unsafe.Pointer { return unsafe_New(t) } func (d *ptrDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { if s.skipWhiteSpace() == nul { s.read() } if s.char() == 'n' { if err := nullBytes(s); err != nil { return err } *(*unsafe.Pointer)(p) = nil return nil } var newptr unsafe.Pointer if *(*unsafe.Pointer)(p) == nil { newptr = unsafe_New(d.typ) *(*unsafe.Pointer)(p) = newptr } else { newptr = *(*unsafe.Pointer)(p) } if err := d.dec.DecodeStream(s, depth, newptr); err != nil { return err } return nil } func (d *ptrDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { buf := ctx.Buf cursor = skipWhiteSpace(buf, cursor) if buf[cursor] == 'n' { if err := validateNull(buf, cursor); err != nil { return 0, err } if p != nil { *(*unsafe.Pointer)(p) = nil } cursor += 4 return cursor, nil } var newptr unsafe.Pointer if *(*unsafe.Pointer)(p) == nil { newptr = unsafe_New(d.typ) *(*unsafe.Pointer)(p) = newptr } else { newptr = *(*unsafe.Pointer)(p) } c, err := d.dec.Decode(ctx, cursor, depth, newptr) if err != nil { return 0, err } cursor = c return cursor, nil } func (d *ptrDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) { return nil, 0, fmt.Errorf("json: ptr decoder does not support decode path") } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/slice.go ================================================ package decoder import ( "reflect" "sync" "unsafe" "github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/runtime" ) var ( sliceType = runtime.Type2RType( reflect.TypeOf((*sliceHeader)(nil)).Elem(), ) nilSlice = unsafe.Pointer(&sliceHeader{}) ) type sliceDecoder struct { elemType *runtime.Type isElemPointerType bool valueDecoder Decoder size uintptr arrayPool sync.Pool structName string fieldName string } // If use reflect.SliceHeader, data type is uintptr. // In this case, Go compiler cannot trace reference created by newArray(). // So, define using unsafe.Pointer as data type type sliceHeader struct { data unsafe.Pointer len int cap int } const ( defaultSliceCapacity = 2 ) func newSliceDecoder(dec Decoder, elemType *runtime.Type, size uintptr, structName, fieldName string) *sliceDecoder { return &sliceDecoder{ valueDecoder: dec, elemType: elemType, isElemPointerType: elemType.Kind() == reflect.Ptr || elemType.Kind() == reflect.Map, size: size, arrayPool: sync.Pool{ New: func() interface{} { return &sliceHeader{ data: newArray(elemType, defaultSliceCapacity), len: 0, cap: defaultSliceCapacity, } }, }, structName: structName, fieldName: fieldName, } } func (d *sliceDecoder) newSlice(src *sliceHeader) *sliceHeader { slice := d.arrayPool.Get().(*sliceHeader) if src.len > 0 { // copy original elem if slice.cap < src.cap { data := newArray(d.elemType, src.cap) slice = &sliceHeader{data: data, len: src.len, cap: src.cap} } else { slice.len = src.len } copySlice(d.elemType, *slice, *src) } else { slice.len = 0 } return slice } func (d *sliceDecoder) releaseSlice(p *sliceHeader) { d.arrayPool.Put(p) } //go:linkname copySlice reflect.typedslicecopy func copySlice(elemType *runtime.Type, dst, src sliceHeader) int //go:linkname newArray reflect.unsafe_NewArray func newArray(*runtime.Type, int) unsafe.Pointer //go:linkname typedmemmove reflect.typedmemmove func typedmemmove(t *runtime.Type, dst, src unsafe.Pointer) func (d *sliceDecoder) errNumber(offset int64) *errors.UnmarshalTypeError { return &errors.UnmarshalTypeError{ Value: "number", Type: reflect.SliceOf(runtime.RType2Type(d.elemType)), Struct: d.structName, Field: d.fieldName, Offset: offset, } } func (d *sliceDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { depth++ if depth > maxDecodeNestingDepth { return errors.ErrExceededMaxDepth(s.char(), s.cursor) } for { switch s.char() { case ' ', '\n', '\t', '\r': s.cursor++ continue case 'n': if err := nullBytes(s); err != nil { return err } typedmemmove(sliceType, p, nilSlice) return nil case '[': s.cursor++ if s.skipWhiteSpace() == ']' { dst := (*sliceHeader)(p) if dst.data == nil { dst.data = newArray(d.elemType, 0) } else { dst.len = 0 } s.cursor++ return nil } idx := 0 slice := d.newSlice((*sliceHeader)(p)) srcLen := slice.len capacity := slice.cap data := slice.data for { if capacity <= idx { src := sliceHeader{data: data, len: idx, cap: capacity} capacity *= 2 data = newArray(d.elemType, capacity) dst := sliceHeader{data: data, len: idx, cap: capacity} copySlice(d.elemType, dst, src) } ep := unsafe.Pointer(uintptr(data) + uintptr(idx)*d.size) // if srcLen is greater than idx, keep the original reference if srcLen <= idx { if d.isElemPointerType { **(**unsafe.Pointer)(unsafe.Pointer(&ep)) = nil // initialize elem pointer } else { // assign new element to the slice typedmemmove(d.elemType, ep, unsafe_New(d.elemType)) } } if err := d.valueDecoder.DecodeStream(s, depth, ep); err != nil { return err } s.skipWhiteSpace() RETRY: switch s.char() { case ']': slice.cap = capacity slice.len = idx + 1 slice.data = data dst := (*sliceHeader)(p) dst.len = idx + 1 if dst.len > dst.cap { dst.data = newArray(d.elemType, dst.len) dst.cap = dst.len } copySlice(d.elemType, *dst, *slice) d.releaseSlice(slice) s.cursor++ return nil case ',': idx++ case nul: if s.read() { goto RETRY } slice.cap = capacity slice.data = data d.releaseSlice(slice) goto ERROR default: slice.cap = capacity slice.data = data d.releaseSlice(slice) goto ERROR } s.cursor++ } case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return d.errNumber(s.totalOffset()) case nul: if s.read() { continue } goto ERROR default: goto ERROR } } ERROR: return errors.ErrUnexpectedEndOfJSON("slice", s.totalOffset()) } func (d *sliceDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { buf := ctx.Buf depth++ if depth > maxDecodeNestingDepth { return 0, errors.ErrExceededMaxDepth(buf[cursor], cursor) } for { switch buf[cursor] { case ' ', '\n', '\t', '\r': cursor++ continue case 'n': if err := validateNull(buf, cursor); err != nil { return 0, err } cursor += 4 typedmemmove(sliceType, p, nilSlice) return cursor, nil case '[': cursor++ cursor = skipWhiteSpace(buf, cursor) if buf[cursor] == ']' { dst := (*sliceHeader)(p) if dst.data == nil { dst.data = newArray(d.elemType, 0) } else { dst.len = 0 } cursor++ return cursor, nil } idx := 0 slice := d.newSlice((*sliceHeader)(p)) srcLen := slice.len capacity := slice.cap data := slice.data for { if capacity <= idx { src := sliceHeader{data: data, len: idx, cap: capacity} capacity *= 2 data = newArray(d.elemType, capacity) dst := sliceHeader{data: data, len: idx, cap: capacity} copySlice(d.elemType, dst, src) } ep := unsafe.Pointer(uintptr(data) + uintptr(idx)*d.size) // if srcLen is greater than idx, keep the original reference if srcLen <= idx { if d.isElemPointerType { **(**unsafe.Pointer)(unsafe.Pointer(&ep)) = nil // initialize elem pointer } else { // assign new element to the slice typedmemmove(d.elemType, ep, unsafe_New(d.elemType)) } } c, err := d.valueDecoder.Decode(ctx, cursor, depth, ep) if err != nil { return 0, err } cursor = c cursor = skipWhiteSpace(buf, cursor) switch buf[cursor] { case ']': slice.cap = capacity slice.len = idx + 1 slice.data = data dst := (*sliceHeader)(p) dst.len = idx + 1 if dst.len > dst.cap { dst.data = newArray(d.elemType, dst.len) dst.cap = dst.len } copySlice(d.elemType, *dst, *slice) d.releaseSlice(slice) cursor++ return cursor, nil case ',': idx++ default: slice.cap = capacity slice.data = data d.releaseSlice(slice) return 0, errors.ErrInvalidCharacter(buf[cursor], "slice", cursor) } cursor++ } case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return 0, d.errNumber(cursor) default: return 0, errors.ErrUnexpectedEndOfJSON("slice", cursor) } } } func (d *sliceDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) { buf := ctx.Buf depth++ if depth > maxDecodeNestingDepth { return nil, 0, errors.ErrExceededMaxDepth(buf[cursor], cursor) } ret := [][]byte{} for { switch buf[cursor] { case ' ', '\n', '\t', '\r': cursor++ continue case 'n': if err := validateNull(buf, cursor); err != nil { return nil, 0, err } cursor += 4 return [][]byte{nullbytes}, cursor, nil case '[': cursor++ cursor = skipWhiteSpace(buf, cursor) if buf[cursor] == ']' { cursor++ return ret, cursor, nil } idx := 0 for { child, found, err := ctx.Option.Path.node.Index(idx) if err != nil { return nil, 0, err } if found { if child != nil { oldPath := ctx.Option.Path.node ctx.Option.Path.node = child paths, c, err := d.valueDecoder.DecodePath(ctx, cursor, depth) if err != nil { return nil, 0, err } ctx.Option.Path.node = oldPath ret = append(ret, paths...) cursor = c } else { start := cursor end, err := skipValue(buf, cursor, depth) if err != nil { return nil, 0, err } ret = append(ret, buf[start:end]) cursor = end } } else { c, err := skipValue(buf, cursor, depth) if err != nil { return nil, 0, err } cursor = c } cursor = skipWhiteSpace(buf, cursor) switch buf[cursor] { case ']': cursor++ return ret, cursor, nil case ',': idx++ default: return nil, 0, errors.ErrInvalidCharacter(buf[cursor], "slice", cursor) } cursor++ } case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return nil, 0, d.errNumber(cursor) default: return nil, 0, errors.ErrUnexpectedEndOfJSON("slice", cursor) } } } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/stream.go ================================================ package decoder import ( "bytes" "encoding/json" "io" "strconv" "unsafe" "github.com/goccy/go-json/internal/errors" ) const ( initBufSize = 512 ) type Stream struct { buf []byte bufSize int64 length int64 r io.Reader offset int64 cursor int64 filledBuffer bool allRead bool UseNumber bool DisallowUnknownFields bool Option *Option } func NewStream(r io.Reader) *Stream { return &Stream{ r: r, bufSize: initBufSize, buf: make([]byte, initBufSize), Option: &Option{}, } } func (s *Stream) TotalOffset() int64 { return s.totalOffset() } func (s *Stream) Buffered() io.Reader { buflen := int64(len(s.buf)) for i := s.cursor; i < buflen; i++ { if s.buf[i] == nul { return bytes.NewReader(s.buf[s.cursor:i]) } } return bytes.NewReader(s.buf[s.cursor:]) } func (s *Stream) PrepareForDecode() error { for { switch s.char() { case ' ', '\t', '\r', '\n': s.cursor++ continue case ',', ':': s.cursor++ return nil case nul: if s.read() { continue } return io.EOF } break } return nil } func (s *Stream) totalOffset() int64 { return s.offset + s.cursor } func (s *Stream) char() byte { return s.buf[s.cursor] } func (s *Stream) equalChar(c byte) bool { cur := s.buf[s.cursor] if cur == nul { s.read() cur = s.buf[s.cursor] } return cur == c } func (s *Stream) stat() ([]byte, int64, unsafe.Pointer) { return s.buf, s.cursor, (*sliceHeader)(unsafe.Pointer(&s.buf)).data } func (s *Stream) bufptr() unsafe.Pointer { return (*sliceHeader)(unsafe.Pointer(&s.buf)).data } func (s *Stream) statForRetry() ([]byte, int64, unsafe.Pointer) { s.cursor-- // for retry ( because caller progress cursor position in each loop ) return s.buf, s.cursor, (*sliceHeader)(unsafe.Pointer(&s.buf)).data } func (s *Stream) Reset() { s.reset() s.bufSize = int64(len(s.buf)) } func (s *Stream) More() bool { for { switch s.char() { case ' ', '\n', '\r', '\t': s.cursor++ continue case '}', ']': return false case nul: if s.read() { continue } return false } break } return true } func (s *Stream) Token() (interface{}, error) { for { c := s.char() switch c { case ' ', '\n', '\r', '\t': s.cursor++ case '{', '[', ']', '}': s.cursor++ return json.Delim(c), nil case ',', ':': s.cursor++ case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': bytes := floatBytes(s) str := *(*string)(unsafe.Pointer(&bytes)) if s.UseNumber { return json.Number(str), nil } f64, err := strconv.ParseFloat(str, 64) if err != nil { return nil, err } return f64, nil case '"': bytes, err := stringBytes(s) if err != nil { return nil, err } return string(bytes), nil case 't': if err := trueBytes(s); err != nil { return nil, err } return true, nil case 'f': if err := falseBytes(s); err != nil { return nil, err } return false, nil case 'n': if err := nullBytes(s); err != nil { return nil, err } return nil, nil case nul: if s.read() { continue } goto END default: return nil, errors.ErrInvalidCharacter(s.char(), "token", s.totalOffset()) } } END: return nil, io.EOF } func (s *Stream) reset() { s.offset += s.cursor s.buf = s.buf[s.cursor:] s.length -= s.cursor s.cursor = 0 } func (s *Stream) readBuf() []byte { if s.filledBuffer { s.bufSize *= 2 remainBuf := s.buf s.buf = make([]byte, s.bufSize) copy(s.buf, remainBuf) } remainLen := s.length - s.cursor remainNotNulCharNum := int64(0) for i := int64(0); i < remainLen; i++ { if s.buf[s.cursor+i] == nul { break } remainNotNulCharNum++ } s.length = s.cursor + remainNotNulCharNum return s.buf[s.cursor+remainNotNulCharNum:] } func (s *Stream) read() bool { if s.allRead { return false } buf := s.readBuf() last := len(buf) - 1 buf[last] = nul n, err := s.r.Read(buf[:last]) s.length += int64(n) if n == last { s.filledBuffer = true } else { s.filledBuffer = false } if err == io.EOF { s.allRead = true } else if err != nil { return false } return true } func (s *Stream) skipWhiteSpace() byte { p := s.bufptr() LOOP: c := char(p, s.cursor) switch c { case ' ', '\n', '\t', '\r': s.cursor++ goto LOOP case nul: if s.read() { p = s.bufptr() goto LOOP } } return c } func (s *Stream) skipObject(depth int64) error { braceCount := 1 _, cursor, p := s.stat() for { switch char(p, cursor) { case '{': braceCount++ depth++ if depth > maxDecodeNestingDepth { return errors.ErrExceededMaxDepth(s.char(), s.cursor) } case '}': braceCount-- depth-- if braceCount == 0 { s.cursor = cursor + 1 return nil } case '[': depth++ if depth > maxDecodeNestingDepth { return errors.ErrExceededMaxDepth(s.char(), s.cursor) } case ']': depth-- case '"': for { cursor++ switch char(p, cursor) { case '\\': cursor++ if char(p, cursor) == nul { s.cursor = cursor if s.read() { _, cursor, p = s.stat() continue } return errors.ErrUnexpectedEndOfJSON("string of object", cursor) } case '"': goto SWITCH_OUT case nul: s.cursor = cursor if s.read() { _, cursor, p = s.statForRetry() continue } return errors.ErrUnexpectedEndOfJSON("string of object", cursor) } } case nul: s.cursor = cursor if s.read() { _, cursor, p = s.stat() continue } return errors.ErrUnexpectedEndOfJSON("object of object", cursor) } SWITCH_OUT: cursor++ } } func (s *Stream) skipArray(depth int64) error { bracketCount := 1 _, cursor, p := s.stat() for { switch char(p, cursor) { case '[': bracketCount++ depth++ if depth > maxDecodeNestingDepth { return errors.ErrExceededMaxDepth(s.char(), s.cursor) } case ']': bracketCount-- depth-- if bracketCount == 0 { s.cursor = cursor + 1 return nil } case '{': depth++ if depth > maxDecodeNestingDepth { return errors.ErrExceededMaxDepth(s.char(), s.cursor) } case '}': depth-- case '"': for { cursor++ switch char(p, cursor) { case '\\': cursor++ if char(p, cursor) == nul { s.cursor = cursor if s.read() { _, cursor, p = s.stat() continue } return errors.ErrUnexpectedEndOfJSON("string of object", cursor) } case '"': goto SWITCH_OUT case nul: s.cursor = cursor if s.read() { _, cursor, p = s.statForRetry() continue } return errors.ErrUnexpectedEndOfJSON("string of object", cursor) } } case nul: s.cursor = cursor if s.read() { _, cursor, p = s.stat() continue } return errors.ErrUnexpectedEndOfJSON("array of object", cursor) } SWITCH_OUT: cursor++ } } func (s *Stream) skipValue(depth int64) error { _, cursor, p := s.stat() for { switch char(p, cursor) { case ' ', '\n', '\t', '\r': cursor++ continue case nul: s.cursor = cursor if s.read() { _, cursor, p = s.stat() continue } return errors.ErrUnexpectedEndOfJSON("value of object", s.totalOffset()) case '{': s.cursor = cursor + 1 return s.skipObject(depth + 1) case '[': s.cursor = cursor + 1 return s.skipArray(depth + 1) case '"': for { cursor++ switch char(p, cursor) { case '\\': cursor++ if char(p, cursor) == nul { s.cursor = cursor if s.read() { _, cursor, p = s.stat() continue } return errors.ErrUnexpectedEndOfJSON("value of string", s.totalOffset()) } case '"': s.cursor = cursor + 1 return nil case nul: s.cursor = cursor if s.read() { _, cursor, p = s.statForRetry() continue } return errors.ErrUnexpectedEndOfJSON("value of string", s.totalOffset()) } } case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': for { cursor++ c := char(p, cursor) if floatTable[c] { continue } else if c == nul { if s.read() { _, cursor, p = s.stat() continue } } s.cursor = cursor return nil } case 't': s.cursor = cursor if err := trueBytes(s); err != nil { return err } return nil case 'f': s.cursor = cursor if err := falseBytes(s); err != nil { return err } return nil case 'n': s.cursor = cursor if err := nullBytes(s); err != nil { return err } return nil } cursor++ } } func nullBytes(s *Stream) error { // current cursor's character is 'n' s.cursor++ if s.char() != 'u' { if err := retryReadNull(s); err != nil { return err } } s.cursor++ if s.char() != 'l' { if err := retryReadNull(s); err != nil { return err } } s.cursor++ if s.char() != 'l' { if err := retryReadNull(s); err != nil { return err } } s.cursor++ return nil } func retryReadNull(s *Stream) error { if s.char() == nul && s.read() { return nil } return errors.ErrInvalidCharacter(s.char(), "null", s.totalOffset()) } func trueBytes(s *Stream) error { // current cursor's character is 't' s.cursor++ if s.char() != 'r' { if err := retryReadTrue(s); err != nil { return err } } s.cursor++ if s.char() != 'u' { if err := retryReadTrue(s); err != nil { return err } } s.cursor++ if s.char() != 'e' { if err := retryReadTrue(s); err != nil { return err } } s.cursor++ return nil } func retryReadTrue(s *Stream) error { if s.char() == nul && s.read() { return nil } return errors.ErrInvalidCharacter(s.char(), "bool(true)", s.totalOffset()) } func falseBytes(s *Stream) error { // current cursor's character is 'f' s.cursor++ if s.char() != 'a' { if err := retryReadFalse(s); err != nil { return err } } s.cursor++ if s.char() != 'l' { if err := retryReadFalse(s); err != nil { return err } } s.cursor++ if s.char() != 's' { if err := retryReadFalse(s); err != nil { return err } } s.cursor++ if s.char() != 'e' { if err := retryReadFalse(s); err != nil { return err } } s.cursor++ return nil } func retryReadFalse(s *Stream) error { if s.char() == nul && s.read() { return nil } return errors.ErrInvalidCharacter(s.char(), "bool(false)", s.totalOffset()) } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/string.go ================================================ package decoder import ( "bytes" "fmt" "reflect" "unicode" "unicode/utf16" "unicode/utf8" "unsafe" "github.com/goccy/go-json/internal/errors" ) type stringDecoder struct { structName string fieldName string } func newStringDecoder(structName, fieldName string) *stringDecoder { return &stringDecoder{ structName: structName, fieldName: fieldName, } } func (d *stringDecoder) errUnmarshalType(typeName string, offset int64) *errors.UnmarshalTypeError { return &errors.UnmarshalTypeError{ Value: typeName, Type: reflect.TypeOf(""), Offset: offset, Struct: d.structName, Field: d.fieldName, } } func (d *stringDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { bytes, err := d.decodeStreamByte(s) if err != nil { return err } if bytes == nil { return nil } **(**string)(unsafe.Pointer(&p)) = *(*string)(unsafe.Pointer(&bytes)) s.reset() return nil } func (d *stringDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { bytes, c, err := d.decodeByte(ctx.Buf, cursor) if err != nil { return 0, err } if bytes == nil { return c, nil } cursor = c **(**string)(unsafe.Pointer(&p)) = *(*string)(unsafe.Pointer(&bytes)) return cursor, nil } func (d *stringDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) { bytes, c, err := d.decodeByte(ctx.Buf, cursor) if err != nil { return nil, 0, err } if bytes == nil { return [][]byte{nullbytes}, c, nil } return [][]byte{bytes}, c, nil } var ( hexToInt = [256]int{ '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, 'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15, 'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15, } ) func unicodeToRune(code []byte) rune { var r rune for i := 0; i < len(code); i++ { r = r*16 + rune(hexToInt[code[i]]) } return r } func readAtLeast(s *Stream, n int64, p *unsafe.Pointer) bool { for s.cursor+n >= s.length { if !s.read() { return false } *p = s.bufptr() } return true } func decodeUnicodeRune(s *Stream, p unsafe.Pointer) (rune, int64, unsafe.Pointer, error) { const defaultOffset = 5 const surrogateOffset = 11 if !readAtLeast(s, defaultOffset, &p) { return rune(0), 0, nil, errors.ErrInvalidCharacter(s.char(), "escaped string", s.totalOffset()) } r := unicodeToRune(s.buf[s.cursor+1 : s.cursor+defaultOffset]) if utf16.IsSurrogate(r) { if !readAtLeast(s, surrogateOffset, &p) { return unicode.ReplacementChar, defaultOffset, p, nil } if s.buf[s.cursor+defaultOffset] != '\\' || s.buf[s.cursor+defaultOffset+1] != 'u' { return unicode.ReplacementChar, defaultOffset, p, nil } r2 := unicodeToRune(s.buf[s.cursor+defaultOffset+2 : s.cursor+surrogateOffset]) if r := utf16.DecodeRune(r, r2); r != unicode.ReplacementChar { return r, surrogateOffset, p, nil } } return r, defaultOffset, p, nil } func decodeUnicode(s *Stream, p unsafe.Pointer) (unsafe.Pointer, error) { const backSlashAndULen = 2 // length of \u r, offset, pp, err := decodeUnicodeRune(s, p) if err != nil { return nil, err } unicode := []byte(string(r)) unicodeLen := int64(len(unicode)) s.buf = append(append(s.buf[:s.cursor-1], unicode...), s.buf[s.cursor+offset:]...) unicodeOrgLen := offset - 1 s.length = s.length - (backSlashAndULen + (unicodeOrgLen - unicodeLen)) s.cursor = s.cursor - backSlashAndULen + unicodeLen return pp, nil } func decodeEscapeString(s *Stream, p unsafe.Pointer) (unsafe.Pointer, error) { s.cursor++ RETRY: switch s.buf[s.cursor] { case '"': s.buf[s.cursor] = '"' case '\\': s.buf[s.cursor] = '\\' case '/': s.buf[s.cursor] = '/' case 'b': s.buf[s.cursor] = '\b' case 'f': s.buf[s.cursor] = '\f' case 'n': s.buf[s.cursor] = '\n' case 'r': s.buf[s.cursor] = '\r' case 't': s.buf[s.cursor] = '\t' case 'u': return decodeUnicode(s, p) case nul: if !s.read() { return nil, errors.ErrInvalidCharacter(s.char(), "escaped string", s.totalOffset()) } p = s.bufptr() goto RETRY default: return nil, errors.ErrUnexpectedEndOfJSON("string", s.totalOffset()) } s.buf = append(s.buf[:s.cursor-1], s.buf[s.cursor:]...) s.length-- s.cursor-- p = s.bufptr() return p, nil } var ( runeErrBytes = []byte(string(utf8.RuneError)) runeErrBytesLen = int64(len(runeErrBytes)) ) func stringBytes(s *Stream) ([]byte, error) { _, cursor, p := s.stat() cursor++ // skip double quote char start := cursor for { switch char(p, cursor) { case '\\': s.cursor = cursor pp, err := decodeEscapeString(s, p) if err != nil { return nil, err } p = pp cursor = s.cursor case '"': literal := s.buf[start:cursor] cursor++ s.cursor = cursor return literal, nil case // 0x00 is nul, 0x5c is '\\', 0x22 is '"' . 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, // 0x00-0x0F 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, // 0x10-0x1F 0x20, 0x21 /*0x22,*/, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, // 0x20-0x2F 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, // 0x30-0x3F 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, // 0x40-0x4F 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B /*0x5C,*/, 0x5D, 0x5E, 0x5F, // 0x50-0x5F 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, // 0x60-0x6F 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F: // 0x70-0x7F // character is ASCII. skip to next char case 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, // 0x80-0x8F 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, // 0x90-0x9F 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, // 0xA0-0xAF 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, // 0xB0-0xBF 0xC0, 0xC1, // 0xC0-0xC1 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF: // 0xF5-0xFE // character is invalid s.buf = append(append(append([]byte{}, s.buf[:cursor]...), runeErrBytes...), s.buf[cursor+1:]...) _, _, p = s.stat() cursor += runeErrBytesLen s.length += runeErrBytesLen continue case nul: s.cursor = cursor if s.read() { _, cursor, p = s.stat() continue } goto ERROR case 0xEF: // RuneError is {0xEF, 0xBF, 0xBD} if s.buf[cursor+1] == 0xBF && s.buf[cursor+2] == 0xBD { // found RuneError: skip cursor += 2 break } fallthrough default: // multi bytes character if !utf8.FullRune(s.buf[cursor : len(s.buf)-1]) { s.cursor = cursor if s.read() { _, cursor, p = s.stat() continue } goto ERROR } r, size := utf8.DecodeRune(s.buf[cursor:]) if r == utf8.RuneError { s.buf = append(append(append([]byte{}, s.buf[:cursor]...), runeErrBytes...), s.buf[cursor+1:]...) cursor += runeErrBytesLen s.length += runeErrBytesLen _, _, p = s.stat() } else { cursor += int64(size) } continue } cursor++ } ERROR: return nil, errors.ErrUnexpectedEndOfJSON("string", s.totalOffset()) } func (d *stringDecoder) decodeStreamByte(s *Stream) ([]byte, error) { for { switch s.char() { case ' ', '\n', '\t', '\r': s.cursor++ continue case '[': return nil, d.errUnmarshalType("array", s.totalOffset()) case '{': return nil, d.errUnmarshalType("object", s.totalOffset()) case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return nil, d.errUnmarshalType("number", s.totalOffset()) case '"': return stringBytes(s) case 'n': if err := nullBytes(s); err != nil { return nil, err } return nil, nil case nul: if s.read() { continue } } break } return nil, errors.ErrInvalidBeginningOfValue(s.char(), s.totalOffset()) } func (d *stringDecoder) decodeByte(buf []byte, cursor int64) ([]byte, int64, error) { for { switch buf[cursor] { case ' ', '\n', '\t', '\r': cursor++ case '[': return nil, 0, d.errUnmarshalType("array", cursor) case '{': return nil, 0, d.errUnmarshalType("object", cursor) case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return nil, 0, d.errUnmarshalType("number", cursor) case '"': cursor++ start := cursor b := (*sliceHeader)(unsafe.Pointer(&buf)).data escaped := 0 for { switch char(b, cursor) { case '\\': escaped++ cursor++ switch char(b, cursor) { case '"', '\\', '/', 'b', 'f', 'n', 'r', 't': cursor++ case 'u': buflen := int64(len(buf)) if cursor+5 >= buflen { return nil, 0, errors.ErrUnexpectedEndOfJSON("escaped string", cursor) } for i := int64(1); i <= 4; i++ { c := char(b, cursor+i) if !(('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')) { return nil, 0, errors.ErrSyntax(fmt.Sprintf("json: invalid character %c in \\u hexadecimal character escape", c), cursor+i) } } cursor += 5 default: return nil, 0, errors.ErrUnexpectedEndOfJSON("escaped string", cursor) } continue case '"': literal := buf[start:cursor] if escaped > 0 { literal = literal[:unescapeString(literal)] } cursor++ return literal, cursor, nil case nul: return nil, 0, errors.ErrUnexpectedEndOfJSON("string", cursor) } cursor++ } case 'n': if err := validateNull(buf, cursor); err != nil { return nil, 0, err } cursor += 4 return nil, cursor, nil default: return nil, 0, errors.ErrInvalidBeginningOfValue(buf[cursor], cursor) } } } var unescapeMap = [256]byte{ '"': '"', '\\': '\\', '/': '/', 'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t', } func unsafeAdd(ptr unsafe.Pointer, offset int) unsafe.Pointer { return unsafe.Pointer(uintptr(ptr) + uintptr(offset)) } func unescapeString(buf []byte) int { p := (*sliceHeader)(unsafe.Pointer(&buf)).data end := unsafeAdd(p, len(buf)) src := unsafeAdd(p, bytes.IndexByte(buf, '\\')) dst := src for src != end { c := char(src, 0) if c == '\\' { escapeChar := char(src, 1) if escapeChar != 'u' { *(*byte)(dst) = unescapeMap[escapeChar] src = unsafeAdd(src, 2) dst = unsafeAdd(dst, 1) } else { v1 := hexToInt[char(src, 2)] v2 := hexToInt[char(src, 3)] v3 := hexToInt[char(src, 4)] v4 := hexToInt[char(src, 5)] code := rune((v1 << 12) | (v2 << 8) | (v3 << 4) | v4) if code >= 0xd800 && code < 0xdc00 && uintptr(unsafeAdd(src, 11)) < uintptr(end) { if char(src, 6) == '\\' && char(src, 7) == 'u' { v1 := hexToInt[char(src, 8)] v2 := hexToInt[char(src, 9)] v3 := hexToInt[char(src, 10)] v4 := hexToInt[char(src, 11)] lo := rune((v1 << 12) | (v2 << 8) | (v3 << 4) | v4) if lo >= 0xdc00 && lo < 0xe000 { code = (code-0xd800)<<10 | (lo - 0xdc00) + 0x10000 src = unsafeAdd(src, 6) } } } var b [utf8.UTFMax]byte n := utf8.EncodeRune(b[:], code) switch n { case 4: *(*byte)(unsafeAdd(dst, 3)) = b[3] fallthrough case 3: *(*byte)(unsafeAdd(dst, 2)) = b[2] fallthrough case 2: *(*byte)(unsafeAdd(dst, 1)) = b[1] fallthrough case 1: *(*byte)(unsafeAdd(dst, 0)) = b[0] } src = unsafeAdd(src, 6) dst = unsafeAdd(dst, n) } } else { *(*byte)(dst) = c src = unsafeAdd(src, 1) dst = unsafeAdd(dst, 1) } } return int(uintptr(dst) - uintptr(p)) } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/struct.go ================================================ package decoder import ( "fmt" "math" "math/bits" "sort" "strings" "unicode" "unicode/utf16" "unsafe" "github.com/goccy/go-json/internal/errors" ) type structFieldSet struct { dec Decoder offset uintptr isTaggedKey bool fieldIdx int key string keyLen int64 err error } type structDecoder struct { fieldMap map[string]*structFieldSet fieldUniqueNameNum int stringDecoder *stringDecoder structName string fieldName string isTriedOptimize bool keyBitmapUint8 [][256]uint8 keyBitmapUint16 [][256]uint16 sortedFieldSets []*structFieldSet keyDecoder func(*structDecoder, []byte, int64) (int64, *structFieldSet, error) keyStreamDecoder func(*structDecoder, *Stream) (*structFieldSet, string, error) } var ( largeToSmallTable [256]byte ) func init() { for i := 0; i < 256; i++ { c := i if 'A' <= c && c <= 'Z' { c += 'a' - 'A' } largeToSmallTable[i] = byte(c) } } func toASCIILower(s string) string { b := []byte(s) for i := range b { b[i] = largeToSmallTable[b[i]] } return string(b) } func newStructDecoder(structName, fieldName string, fieldMap map[string]*structFieldSet) *structDecoder { return &structDecoder{ fieldMap: fieldMap, stringDecoder: newStringDecoder(structName, fieldName), structName: structName, fieldName: fieldName, keyDecoder: decodeKey, keyStreamDecoder: decodeKeyStream, } } const ( allowOptimizeMaxKeyLen = 64 allowOptimizeMaxFieldLen = 16 ) func (d *structDecoder) tryOptimize() { fieldUniqueNameMap := map[string]int{} fieldIdx := -1 for k, v := range d.fieldMap { lower := strings.ToLower(k) idx, exists := fieldUniqueNameMap[lower] if exists { v.fieldIdx = idx } else { fieldIdx++ v.fieldIdx = fieldIdx } fieldUniqueNameMap[lower] = fieldIdx } d.fieldUniqueNameNum = len(fieldUniqueNameMap) if d.isTriedOptimize { return } fieldMap := map[string]*structFieldSet{} conflicted := map[string]struct{}{} for k, v := range d.fieldMap { key := strings.ToLower(k) if key != k { if key != toASCIILower(k) { d.isTriedOptimize = true return } // already exists same key (e.g. Hello and HELLO has same lower case key if _, exists := conflicted[key]; exists { d.isTriedOptimize = true return } conflicted[key] = struct{}{} } if field, exists := fieldMap[key]; exists { if field != v { d.isTriedOptimize = true return } } fieldMap[key] = v } if len(fieldMap) > allowOptimizeMaxFieldLen { d.isTriedOptimize = true return } var maxKeyLen int sortedKeys := []string{} for key := range fieldMap { keyLen := len(key) if keyLen > allowOptimizeMaxKeyLen { d.isTriedOptimize = true return } if maxKeyLen < keyLen { maxKeyLen = keyLen } sortedKeys = append(sortedKeys, key) } sort.Strings(sortedKeys) // By allocating one extra capacity than `maxKeyLen`, // it is possible to avoid the process of comparing the index of the key with the length of the bitmap each time. bitmapLen := maxKeyLen + 1 if len(sortedKeys) <= 8 { keyBitmap := make([][256]uint8, bitmapLen) for i, key := range sortedKeys { for j := 0; j < len(key); j++ { c := key[j] keyBitmap[j][c] |= (1 << uint(i)) } d.sortedFieldSets = append(d.sortedFieldSets, fieldMap[key]) } d.keyBitmapUint8 = keyBitmap d.keyDecoder = decodeKeyByBitmapUint8 d.keyStreamDecoder = decodeKeyByBitmapUint8Stream } else { keyBitmap := make([][256]uint16, bitmapLen) for i, key := range sortedKeys { for j := 0; j < len(key); j++ { c := key[j] keyBitmap[j][c] |= (1 << uint(i)) } d.sortedFieldSets = append(d.sortedFieldSets, fieldMap[key]) } d.keyBitmapUint16 = keyBitmap d.keyDecoder = decodeKeyByBitmapUint16 d.keyStreamDecoder = decodeKeyByBitmapUint16Stream } } // decode from '\uXXXX' func decodeKeyCharByUnicodeRune(buf []byte, cursor int64) ([]byte, int64, error) { const defaultOffset = 4 const surrogateOffset = 6 if cursor+defaultOffset >= int64(len(buf)) { return nil, 0, errors.ErrUnexpectedEndOfJSON("escaped string", cursor) } r := unicodeToRune(buf[cursor : cursor+defaultOffset]) if utf16.IsSurrogate(r) { cursor += defaultOffset if cursor+surrogateOffset >= int64(len(buf)) || buf[cursor] != '\\' || buf[cursor+1] != 'u' { return []byte(string(unicode.ReplacementChar)), cursor + defaultOffset - 1, nil } cursor += 2 r2 := unicodeToRune(buf[cursor : cursor+defaultOffset]) if r := utf16.DecodeRune(r, r2); r != unicode.ReplacementChar { return []byte(string(r)), cursor + defaultOffset - 1, nil } } return []byte(string(r)), cursor + defaultOffset - 1, nil } func decodeKeyCharByEscapedChar(buf []byte, cursor int64) ([]byte, int64, error) { c := buf[cursor] cursor++ switch c { case '"': return []byte{'"'}, cursor, nil case '\\': return []byte{'\\'}, cursor, nil case '/': return []byte{'/'}, cursor, nil case 'b': return []byte{'\b'}, cursor, nil case 'f': return []byte{'\f'}, cursor, nil case 'n': return []byte{'\n'}, cursor, nil case 'r': return []byte{'\r'}, cursor, nil case 't': return []byte{'\t'}, cursor, nil case 'u': return decodeKeyCharByUnicodeRune(buf, cursor) } return nil, cursor, nil } func decodeKeyByBitmapUint8(d *structDecoder, buf []byte, cursor int64) (int64, *structFieldSet, error) { var ( curBit uint8 = math.MaxUint8 ) b := (*sliceHeader)(unsafe.Pointer(&buf)).data for { switch char(b, cursor) { case ' ', '\n', '\t', '\r': cursor++ case '"': cursor++ c := char(b, cursor) switch c { case '"': cursor++ return cursor, nil, nil case nul: return 0, nil, errors.ErrUnexpectedEndOfJSON("string", cursor) } keyIdx := 0 bitmap := d.keyBitmapUint8 start := cursor for { c := char(b, cursor) switch c { case '"': fieldSetIndex := bits.TrailingZeros8(curBit) field := d.sortedFieldSets[fieldSetIndex] keyLen := cursor - start cursor++ if keyLen < field.keyLen { // early match return cursor, nil, nil } return cursor, field, nil case nul: return 0, nil, errors.ErrUnexpectedEndOfJSON("string", cursor) case '\\': cursor++ chars, nextCursor, err := decodeKeyCharByEscapedChar(buf, cursor) if err != nil { return 0, nil, err } for _, c := range chars { curBit &= bitmap[keyIdx][largeToSmallTable[c]] if curBit == 0 { return decodeKeyNotFound(b, cursor) } keyIdx++ } cursor = nextCursor default: curBit &= bitmap[keyIdx][largeToSmallTable[c]] if curBit == 0 { return decodeKeyNotFound(b, cursor) } keyIdx++ } cursor++ } default: return cursor, nil, errors.ErrInvalidBeginningOfValue(char(b, cursor), cursor) } } } func decodeKeyByBitmapUint16(d *structDecoder, buf []byte, cursor int64) (int64, *structFieldSet, error) { var ( curBit uint16 = math.MaxUint16 ) b := (*sliceHeader)(unsafe.Pointer(&buf)).data for { switch char(b, cursor) { case ' ', '\n', '\t', '\r': cursor++ case '"': cursor++ c := char(b, cursor) switch c { case '"': cursor++ return cursor, nil, nil case nul: return 0, nil, errors.ErrUnexpectedEndOfJSON("string", cursor) } keyIdx := 0 bitmap := d.keyBitmapUint16 start := cursor for { c := char(b, cursor) switch c { case '"': fieldSetIndex := bits.TrailingZeros16(curBit) field := d.sortedFieldSets[fieldSetIndex] keyLen := cursor - start cursor++ if keyLen < field.keyLen { // early match return cursor, nil, nil } return cursor, field, nil case nul: return 0, nil, errors.ErrUnexpectedEndOfJSON("string", cursor) case '\\': cursor++ chars, nextCursor, err := decodeKeyCharByEscapedChar(buf, cursor) if err != nil { return 0, nil, err } for _, c := range chars { curBit &= bitmap[keyIdx][largeToSmallTable[c]] if curBit == 0 { return decodeKeyNotFound(b, cursor) } keyIdx++ } cursor = nextCursor default: curBit &= bitmap[keyIdx][largeToSmallTable[c]] if curBit == 0 { return decodeKeyNotFound(b, cursor) } keyIdx++ } cursor++ } default: return cursor, nil, errors.ErrInvalidBeginningOfValue(char(b, cursor), cursor) } } } func decodeKeyNotFound(b unsafe.Pointer, cursor int64) (int64, *structFieldSet, error) { for { cursor++ switch char(b, cursor) { case '"': cursor++ return cursor, nil, nil case '\\': cursor++ if char(b, cursor) == nul { return 0, nil, errors.ErrUnexpectedEndOfJSON("string", cursor) } case nul: return 0, nil, errors.ErrUnexpectedEndOfJSON("string", cursor) } } } func decodeKey(d *structDecoder, buf []byte, cursor int64) (int64, *structFieldSet, error) { key, c, err := d.stringDecoder.decodeByte(buf, cursor) if err != nil { return 0, nil, err } cursor = c k := *(*string)(unsafe.Pointer(&key)) field, exists := d.fieldMap[k] if !exists { return cursor, nil, nil } return cursor, field, nil } func decodeKeyByBitmapUint8Stream(d *structDecoder, s *Stream) (*structFieldSet, string, error) { var ( curBit uint8 = math.MaxUint8 ) _, cursor, p := s.stat() for { switch char(p, cursor) { case ' ', '\n', '\t', '\r': cursor++ case nul: s.cursor = cursor if s.read() { _, cursor, p = s.stat() continue } return nil, "", errors.ErrInvalidBeginningOfValue(char(p, cursor), s.totalOffset()) case '"': cursor++ FIRST_CHAR: start := cursor switch char(p, cursor) { case '"': cursor++ s.cursor = cursor return nil, "", nil case nul: s.cursor = cursor if s.read() { _, cursor, p = s.stat() goto FIRST_CHAR } return nil, "", errors.ErrUnexpectedEndOfJSON("string", s.totalOffset()) } keyIdx := 0 bitmap := d.keyBitmapUint8 for { c := char(p, cursor) switch c { case '"': fieldSetIndex := bits.TrailingZeros8(curBit) field := d.sortedFieldSets[fieldSetIndex] keyLen := cursor - start cursor++ s.cursor = cursor if keyLen < field.keyLen { // early match return nil, field.key, nil } return field, field.key, nil case nul: s.cursor = cursor if s.read() { _, cursor, p = s.stat() continue } return nil, "", errors.ErrUnexpectedEndOfJSON("string", s.totalOffset()) case '\\': s.cursor = cursor + 1 // skip '\' char chars, err := decodeKeyCharByEscapeCharStream(s) if err != nil { return nil, "", err } cursor = s.cursor for _, c := range chars { curBit &= bitmap[keyIdx][largeToSmallTable[c]] if curBit == 0 { s.cursor = cursor return decodeKeyNotFoundStream(s, start) } keyIdx++ } default: curBit &= bitmap[keyIdx][largeToSmallTable[c]] if curBit == 0 { s.cursor = cursor return decodeKeyNotFoundStream(s, start) } keyIdx++ } cursor++ } default: return nil, "", errors.ErrInvalidBeginningOfValue(char(p, cursor), s.totalOffset()) } } } func decodeKeyByBitmapUint16Stream(d *structDecoder, s *Stream) (*structFieldSet, string, error) { var ( curBit uint16 = math.MaxUint16 ) _, cursor, p := s.stat() for { switch char(p, cursor) { case ' ', '\n', '\t', '\r': cursor++ case nul: s.cursor = cursor if s.read() { _, cursor, p = s.stat() continue } return nil, "", errors.ErrInvalidBeginningOfValue(char(p, cursor), s.totalOffset()) case '"': cursor++ FIRST_CHAR: start := cursor switch char(p, cursor) { case '"': cursor++ s.cursor = cursor return nil, "", nil case nul: s.cursor = cursor if s.read() { _, cursor, p = s.stat() goto FIRST_CHAR } return nil, "", errors.ErrUnexpectedEndOfJSON("string", s.totalOffset()) } keyIdx := 0 bitmap := d.keyBitmapUint16 for { c := char(p, cursor) switch c { case '"': fieldSetIndex := bits.TrailingZeros16(curBit) field := d.sortedFieldSets[fieldSetIndex] keyLen := cursor - start cursor++ s.cursor = cursor if keyLen < field.keyLen { // early match return nil, field.key, nil } return field, field.key, nil case nul: s.cursor = cursor if s.read() { _, cursor, p = s.stat() continue } return nil, "", errors.ErrUnexpectedEndOfJSON("string", s.totalOffset()) case '\\': s.cursor = cursor + 1 // skip '\' char chars, err := decodeKeyCharByEscapeCharStream(s) if err != nil { return nil, "", err } cursor = s.cursor for _, c := range chars { curBit &= bitmap[keyIdx][largeToSmallTable[c]] if curBit == 0 { s.cursor = cursor return decodeKeyNotFoundStream(s, start) } keyIdx++ } default: curBit &= bitmap[keyIdx][largeToSmallTable[c]] if curBit == 0 { s.cursor = cursor return decodeKeyNotFoundStream(s, start) } keyIdx++ } cursor++ } default: return nil, "", errors.ErrInvalidBeginningOfValue(char(p, cursor), s.totalOffset()) } } } // decode from '\uXXXX' func decodeKeyCharByUnicodeRuneStream(s *Stream) ([]byte, error) { const defaultOffset = 4 const surrogateOffset = 6 if s.cursor+defaultOffset >= s.length { if !s.read() { return nil, errors.ErrInvalidCharacter(s.char(), "escaped unicode char", s.totalOffset()) } } r := unicodeToRune(s.buf[s.cursor : s.cursor+defaultOffset]) if utf16.IsSurrogate(r) { s.cursor += defaultOffset if s.cursor+surrogateOffset >= s.length { s.read() } if s.cursor+surrogateOffset >= s.length || s.buf[s.cursor] != '\\' || s.buf[s.cursor+1] != 'u' { s.cursor += defaultOffset - 1 return []byte(string(unicode.ReplacementChar)), nil } r2 := unicodeToRune(s.buf[s.cursor+defaultOffset+2 : s.cursor+surrogateOffset]) if r := utf16.DecodeRune(r, r2); r != unicode.ReplacementChar { s.cursor += defaultOffset - 1 return []byte(string(r)), nil } } s.cursor += defaultOffset - 1 return []byte(string(r)), nil } func decodeKeyCharByEscapeCharStream(s *Stream) ([]byte, error) { c := s.buf[s.cursor] s.cursor++ RETRY: switch c { case '"': return []byte{'"'}, nil case '\\': return []byte{'\\'}, nil case '/': return []byte{'/'}, nil case 'b': return []byte{'\b'}, nil case 'f': return []byte{'\f'}, nil case 'n': return []byte{'\n'}, nil case 'r': return []byte{'\r'}, nil case 't': return []byte{'\t'}, nil case 'u': return decodeKeyCharByUnicodeRuneStream(s) case nul: if !s.read() { return nil, errors.ErrInvalidCharacter(s.char(), "escaped char", s.totalOffset()) } goto RETRY default: return nil, errors.ErrUnexpectedEndOfJSON("struct field", s.totalOffset()) } } func decodeKeyNotFoundStream(s *Stream, start int64) (*structFieldSet, string, error) { buf, cursor, p := s.stat() for { cursor++ switch char(p, cursor) { case '"': b := buf[start:cursor] key := *(*string)(unsafe.Pointer(&b)) cursor++ s.cursor = cursor return nil, key, nil case '\\': cursor++ if char(p, cursor) == nul { s.cursor = cursor if !s.read() { return nil, "", errors.ErrUnexpectedEndOfJSON("string", s.totalOffset()) } buf, cursor, p = s.statForRetry() } case nul: s.cursor = cursor if !s.read() { return nil, "", errors.ErrUnexpectedEndOfJSON("string", s.totalOffset()) } buf, cursor, p = s.statForRetry() } } } func decodeKeyStream(d *structDecoder, s *Stream) (*structFieldSet, string, error) { key, err := d.stringDecoder.decodeStreamByte(s) if err != nil { return nil, "", err } k := *(*string)(unsafe.Pointer(&key)) return d.fieldMap[k], k, nil } func (d *structDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { depth++ if depth > maxDecodeNestingDepth { return errors.ErrExceededMaxDepth(s.char(), s.cursor) } c := s.skipWhiteSpace() switch c { case 'n': if err := nullBytes(s); err != nil { return err } return nil default: if s.char() != '{' { return errors.ErrInvalidBeginningOfValue(s.char(), s.totalOffset()) } } s.cursor++ if s.skipWhiteSpace() == '}' { s.cursor++ return nil } var ( seenFields map[int]struct{} seenFieldNum int ) firstWin := (s.Option.Flags & FirstWinOption) != 0 if firstWin { seenFields = make(map[int]struct{}, d.fieldUniqueNameNum) } for { s.reset() field, key, err := d.keyStreamDecoder(d, s) if err != nil { return err } if s.skipWhiteSpace() != ':' { return errors.ErrExpected("colon after object key", s.totalOffset()) } s.cursor++ if field != nil { if field.err != nil { return field.err } if firstWin { if _, exists := seenFields[field.fieldIdx]; exists { if err := s.skipValue(depth); err != nil { return err } } else { if err := field.dec.DecodeStream(s, depth, unsafe.Pointer(uintptr(p)+field.offset)); err != nil { return err } seenFieldNum++ if d.fieldUniqueNameNum <= seenFieldNum { return s.skipObject(depth) } seenFields[field.fieldIdx] = struct{}{} } } else { if err := field.dec.DecodeStream(s, depth, unsafe.Pointer(uintptr(p)+field.offset)); err != nil { return err } } } else if s.DisallowUnknownFields { return fmt.Errorf("json: unknown field %q", key) } else { if err := s.skipValue(depth); err != nil { return err } } c := s.skipWhiteSpace() if c == '}' { s.cursor++ return nil } if c != ',' { return errors.ErrExpected("comma after object element", s.totalOffset()) } s.cursor++ } } func (d *structDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { buf := ctx.Buf depth++ if depth > maxDecodeNestingDepth { return 0, errors.ErrExceededMaxDepth(buf[cursor], cursor) } buflen := int64(len(buf)) cursor = skipWhiteSpace(buf, cursor) b := (*sliceHeader)(unsafe.Pointer(&buf)).data switch char(b, cursor) { case 'n': if err := validateNull(buf, cursor); err != nil { return 0, err } cursor += 4 return cursor, nil case '{': default: return 0, errors.ErrInvalidBeginningOfValue(char(b, cursor), cursor) } cursor++ cursor = skipWhiteSpace(buf, cursor) if buf[cursor] == '}' { cursor++ return cursor, nil } var ( seenFields map[int]struct{} seenFieldNum int ) firstWin := (ctx.Option.Flags & FirstWinOption) != 0 if firstWin { seenFields = make(map[int]struct{}, d.fieldUniqueNameNum) } for { c, field, err := d.keyDecoder(d, buf, cursor) if err != nil { return 0, err } cursor = skipWhiteSpace(buf, c) if char(b, cursor) != ':' { return 0, errors.ErrExpected("colon after object key", cursor) } cursor++ if cursor >= buflen { return 0, errors.ErrExpected("object value after colon", cursor) } if field != nil { if field.err != nil { return 0, field.err } if firstWin { if _, exists := seenFields[field.fieldIdx]; exists { c, err := skipValue(buf, cursor, depth) if err != nil { return 0, err } cursor = c } else { c, err := field.dec.Decode(ctx, cursor, depth, unsafe.Pointer(uintptr(p)+field.offset)) if err != nil { return 0, err } cursor = c seenFieldNum++ if d.fieldUniqueNameNum <= seenFieldNum { return skipObject(buf, cursor, depth) } seenFields[field.fieldIdx] = struct{}{} } } else { c, err := field.dec.Decode(ctx, cursor, depth, unsafe.Pointer(uintptr(p)+field.offset)) if err != nil { return 0, err } cursor = c } } else { c, err := skipValue(buf, cursor, depth) if err != nil { return 0, err } cursor = c } cursor = skipWhiteSpace(buf, cursor) if char(b, cursor) == '}' { cursor++ return cursor, nil } if char(b, cursor) != ',' { return 0, errors.ErrExpected("comma after object element", cursor) } cursor++ } } func (d *structDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) { return nil, 0, fmt.Errorf("json: struct decoder does not support decode path") } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/type.go ================================================ package decoder import ( "context" "encoding" "encoding/json" "reflect" "unsafe" ) type Decoder interface { Decode(*RuntimeContext, int64, int64, unsafe.Pointer) (int64, error) DecodePath(*RuntimeContext, int64, int64) ([][]byte, int64, error) DecodeStream(*Stream, int64, unsafe.Pointer) error } const ( nul = '\000' maxDecodeNestingDepth = 10000 ) type unmarshalerContext interface { UnmarshalJSON(context.Context, []byte) error } var ( unmarshalJSONType = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem() unmarshalJSONContextType = reflect.TypeOf((*unmarshalerContext)(nil)).Elem() unmarshalTextType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() ) ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/uint.go ================================================ package decoder import ( "fmt" "reflect" "unsafe" "github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/runtime" ) type uintDecoder struct { typ *runtime.Type kind reflect.Kind op func(unsafe.Pointer, uint64) structName string fieldName string } func newUintDecoder(typ *runtime.Type, structName, fieldName string, op func(unsafe.Pointer, uint64)) *uintDecoder { return &uintDecoder{ typ: typ, kind: typ.Kind(), op: op, structName: structName, fieldName: fieldName, } } func (d *uintDecoder) typeError(buf []byte, offset int64) *errors.UnmarshalTypeError { return &errors.UnmarshalTypeError{ Value: fmt.Sprintf("number %s", string(buf)), Type: runtime.RType2Type(d.typ), Offset: offset, } } var ( pow10u64 = [...]uint64{ 1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, } pow10u64Len = len(pow10u64) ) func (d *uintDecoder) parseUint(b []byte) (uint64, error) { maxDigit := len(b) if maxDigit > pow10u64Len { return 0, fmt.Errorf("invalid length of number") } sum := uint64(0) for i := 0; i < maxDigit; i++ { c := uint64(b[i]) - 48 digitValue := pow10u64[maxDigit-i-1] sum += c * digitValue } return sum, nil } func (d *uintDecoder) decodeStreamByte(s *Stream) ([]byte, error) { for { switch s.char() { case ' ', '\n', '\t', '\r': s.cursor++ continue case '0': s.cursor++ return numZeroBuf, nil case '1', '2', '3', '4', '5', '6', '7', '8', '9': start := s.cursor for { s.cursor++ if numTable[s.char()] { continue } else if s.char() == nul { if s.read() { s.cursor-- // for retry current character continue } } break } num := s.buf[start:s.cursor] return num, nil case 'n': if err := nullBytes(s); err != nil { return nil, err } return nil, nil case nul: if s.read() { continue } default: return nil, d.typeError([]byte{s.char()}, s.totalOffset()) } break } return nil, errors.ErrUnexpectedEndOfJSON("number(unsigned integer)", s.totalOffset()) } func (d *uintDecoder) decodeByte(buf []byte, cursor int64) ([]byte, int64, error) { for { switch buf[cursor] { case ' ', '\n', '\t', '\r': cursor++ continue case '0': cursor++ return numZeroBuf, cursor, nil case '1', '2', '3', '4', '5', '6', '7', '8', '9': start := cursor cursor++ for numTable[buf[cursor]] { cursor++ } num := buf[start:cursor] return num, cursor, nil case 'n': if err := validateNull(buf, cursor); err != nil { return nil, 0, err } cursor += 4 return nil, cursor, nil default: return nil, 0, d.typeError([]byte{buf[cursor]}, cursor) } } } func (d *uintDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { bytes, err := d.decodeStreamByte(s) if err != nil { return err } if bytes == nil { return nil } u64, err := d.parseUint(bytes) if err != nil { return d.typeError(bytes, s.totalOffset()) } switch d.kind { case reflect.Uint8: if (1 << 8) <= u64 { return d.typeError(bytes, s.totalOffset()) } case reflect.Uint16: if (1 << 16) <= u64 { return d.typeError(bytes, s.totalOffset()) } case reflect.Uint32: if (1 << 32) <= u64 { return d.typeError(bytes, s.totalOffset()) } } d.op(p, u64) return nil } func (d *uintDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { bytes, c, err := d.decodeByte(ctx.Buf, cursor) if err != nil { return 0, err } if bytes == nil { return c, nil } cursor = c u64, err := d.parseUint(bytes) if err != nil { return 0, d.typeError(bytes, cursor) } switch d.kind { case reflect.Uint8: if (1 << 8) <= u64 { return 0, d.typeError(bytes, cursor) } case reflect.Uint16: if (1 << 16) <= u64 { return 0, d.typeError(bytes, cursor) } case reflect.Uint32: if (1 << 32) <= u64 { return 0, d.typeError(bytes, cursor) } } d.op(p, u64) return cursor, nil } func (d *uintDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) { return nil, 0, fmt.Errorf("json: uint decoder does not support decode path") } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/unmarshal_json.go ================================================ package decoder import ( "context" "encoding/json" "fmt" "unsafe" "github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/runtime" ) type unmarshalJSONDecoder struct { typ *runtime.Type structName string fieldName string } func newUnmarshalJSONDecoder(typ *runtime.Type, structName, fieldName string) *unmarshalJSONDecoder { return &unmarshalJSONDecoder{ typ: typ, structName: structName, fieldName: fieldName, } } func (d *unmarshalJSONDecoder) annotateError(cursor int64, err error) { switch e := err.(type) { case *errors.UnmarshalTypeError: e.Struct = d.structName e.Field = d.fieldName case *errors.SyntaxError: e.Offset = cursor } } func (d *unmarshalJSONDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { s.skipWhiteSpace() start := s.cursor if err := s.skipValue(depth); err != nil { return err } src := s.buf[start:s.cursor] dst := make([]byte, len(src)) copy(dst, src) v := *(*interface{})(unsafe.Pointer(&emptyInterface{ typ: d.typ, ptr: p, })) switch v := v.(type) { case unmarshalerContext: var ctx context.Context if (s.Option.Flags & ContextOption) != 0 { ctx = s.Option.Context } else { ctx = context.Background() } if err := v.UnmarshalJSON(ctx, dst); err != nil { d.annotateError(s.cursor, err) return err } case json.Unmarshaler: if err := v.UnmarshalJSON(dst); err != nil { d.annotateError(s.cursor, err) return err } } return nil } func (d *unmarshalJSONDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { buf := ctx.Buf cursor = skipWhiteSpace(buf, cursor) start := cursor end, err := skipValue(buf, cursor, depth) if err != nil { return 0, err } src := buf[start:end] dst := make([]byte, len(src)) copy(dst, src) v := *(*interface{})(unsafe.Pointer(&emptyInterface{ typ: d.typ, ptr: p, })) if (ctx.Option.Flags & ContextOption) != 0 { if err := v.(unmarshalerContext).UnmarshalJSON(ctx.Option.Context, dst); err != nil { d.annotateError(cursor, err) return 0, err } } else { if err := v.(json.Unmarshaler).UnmarshalJSON(dst); err != nil { d.annotateError(cursor, err) return 0, err } } return end, nil } func (d *unmarshalJSONDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) { return nil, 0, fmt.Errorf("json: unmarshal json decoder does not support decode path") } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/unmarshal_text.go ================================================ package decoder import ( "bytes" "encoding" "fmt" "unicode" "unicode/utf16" "unicode/utf8" "unsafe" "github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/runtime" ) type unmarshalTextDecoder struct { typ *runtime.Type structName string fieldName string } func newUnmarshalTextDecoder(typ *runtime.Type, structName, fieldName string) *unmarshalTextDecoder { return &unmarshalTextDecoder{ typ: typ, structName: structName, fieldName: fieldName, } } func (d *unmarshalTextDecoder) annotateError(cursor int64, err error) { switch e := err.(type) { case *errors.UnmarshalTypeError: e.Struct = d.structName e.Field = d.fieldName case *errors.SyntaxError: e.Offset = cursor } } var ( nullbytes = []byte(`null`) ) func (d *unmarshalTextDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { s.skipWhiteSpace() start := s.cursor if err := s.skipValue(depth); err != nil { return err } src := s.buf[start:s.cursor] if len(src) > 0 { switch src[0] { case '[': return &errors.UnmarshalTypeError{ Value: "array", Type: runtime.RType2Type(d.typ), Offset: s.totalOffset(), } case '{': return &errors.UnmarshalTypeError{ Value: "object", Type: runtime.RType2Type(d.typ), Offset: s.totalOffset(), } case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return &errors.UnmarshalTypeError{ Value: "number", Type: runtime.RType2Type(d.typ), Offset: s.totalOffset(), } case 'n': if bytes.Equal(src, nullbytes) { *(*unsafe.Pointer)(p) = nil return nil } } } dst := make([]byte, len(src)) copy(dst, src) if b, ok := unquoteBytes(dst); ok { dst = b } v := *(*interface{})(unsafe.Pointer(&emptyInterface{ typ: d.typ, ptr: p, })) if err := v.(encoding.TextUnmarshaler).UnmarshalText(dst); err != nil { d.annotateError(s.cursor, err) return err } return nil } func (d *unmarshalTextDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { buf := ctx.Buf cursor = skipWhiteSpace(buf, cursor) start := cursor end, err := skipValue(buf, cursor, depth) if err != nil { return 0, err } src := buf[start:end] if len(src) > 0 { switch src[0] { case '[': return 0, &errors.UnmarshalTypeError{ Value: "array", Type: runtime.RType2Type(d.typ), Offset: start, } case '{': return 0, &errors.UnmarshalTypeError{ Value: "object", Type: runtime.RType2Type(d.typ), Offset: start, } case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return 0, &errors.UnmarshalTypeError{ Value: "number", Type: runtime.RType2Type(d.typ), Offset: start, } case 'n': if bytes.Equal(src, nullbytes) { *(*unsafe.Pointer)(p) = nil return end, nil } } } if s, ok := unquoteBytes(src); ok { src = s } v := *(*interface{})(unsafe.Pointer(&emptyInterface{ typ: d.typ, ptr: *(*unsafe.Pointer)(unsafe.Pointer(&p)), })) if err := v.(encoding.TextUnmarshaler).UnmarshalText(src); err != nil { d.annotateError(cursor, err) return 0, err } return end, nil } func (d *unmarshalTextDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) { return nil, 0, fmt.Errorf("json: unmarshal text decoder does not support decode path") } func unquoteBytes(s []byte) (t []byte, ok bool) { length := len(s) if length < 2 || s[0] != '"' || s[length-1] != '"' { return } s = s[1 : length-1] length -= 2 // Check for unusual characters. If there are none, // then no unquoting is needed, so return a slice of the // original bytes. r := 0 for r < length { c := s[r] if c == '\\' || c == '"' || c < ' ' { break } if c < utf8.RuneSelf { r++ continue } rr, size := utf8.DecodeRune(s[r:]) if rr == utf8.RuneError && size == 1 { break } r += size } if r == length { return s, true } b := make([]byte, length+2*utf8.UTFMax) w := copy(b, s[0:r]) for r < length { // Out of room? Can only happen if s is full of // malformed UTF-8 and we're replacing each // byte with RuneError. if w >= len(b)-2*utf8.UTFMax { nb := make([]byte, (len(b)+utf8.UTFMax)*2) copy(nb, b[0:w]) b = nb } switch c := s[r]; { case c == '\\': r++ if r >= length { return } switch s[r] { default: return case '"', '\\', '/', '\'': b[w] = s[r] r++ w++ case 'b': b[w] = '\b' r++ w++ case 'f': b[w] = '\f' r++ w++ case 'n': b[w] = '\n' r++ w++ case 'r': b[w] = '\r' r++ w++ case 't': b[w] = '\t' r++ w++ case 'u': r-- rr := getu4(s[r:]) if rr < 0 { return } r += 6 if utf16.IsSurrogate(rr) { rr1 := getu4(s[r:]) if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar { // A valid pair; consume. r += 6 w += utf8.EncodeRune(b[w:], dec) break } // Invalid surrogate; fall back to replacement rune. rr = unicode.ReplacementChar } w += utf8.EncodeRune(b[w:], rr) } // Quote, control characters are invalid. case c == '"', c < ' ': return // ASCII case c < utf8.RuneSelf: b[w] = c r++ w++ // Coerce to well-formed UTF-8. default: rr, size := utf8.DecodeRune(s[r:]) r += size w += utf8.EncodeRune(b[w:], rr) } } return b[0:w], true } func getu4(s []byte) rune { if len(s) < 6 || s[0] != '\\' || s[1] != 'u' { return -1 } var r rune for _, c := range s[2:6] { switch { case '0' <= c && c <= '9': c = c - '0' case 'a' <= c && c <= 'f': c = c - 'a' + 10 case 'A' <= c && c <= 'F': c = c - 'A' + 10 default: return -1 } r = r*16 + rune(c) } return r } ================================================ FILE: vendor/github.com/goccy/go-json/internal/decoder/wrapped_string.go ================================================ package decoder import ( "fmt" "reflect" "unsafe" "github.com/goccy/go-json/internal/runtime" ) type wrappedStringDecoder struct { typ *runtime.Type dec Decoder stringDecoder *stringDecoder structName string fieldName string isPtrType bool } func newWrappedStringDecoder(typ *runtime.Type, dec Decoder, structName, fieldName string) *wrappedStringDecoder { return &wrappedStringDecoder{ typ: typ, dec: dec, stringDecoder: newStringDecoder(structName, fieldName), structName: structName, fieldName: fieldName, isPtrType: typ.Kind() == reflect.Ptr, } } func (d *wrappedStringDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { bytes, err := d.stringDecoder.decodeStreamByte(s) if err != nil { return err } if bytes == nil { if d.isPtrType { *(*unsafe.Pointer)(p) = nil } return nil } b := make([]byte, len(bytes)+1) copy(b, bytes) if _, err := d.dec.Decode(&RuntimeContext{Buf: b}, 0, depth, p); err != nil { return err } return nil } func (d *wrappedStringDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { bytes, c, err := d.stringDecoder.decodeByte(ctx.Buf, cursor) if err != nil { return 0, err } if bytes == nil { if d.isPtrType { *(*unsafe.Pointer)(p) = nil } return c, nil } bytes = append(bytes, nul) oldBuf := ctx.Buf ctx.Buf = bytes if _, err := d.dec.Decode(ctx, 0, depth, p); err != nil { return 0, err } ctx.Buf = oldBuf return c, nil } func (d *wrappedStringDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) { return nil, 0, fmt.Errorf("json: wrapped string decoder does not support decode path") } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/code.go ================================================ package encoder import ( "fmt" "reflect" "unsafe" "github.com/goccy/go-json/internal/runtime" ) type Code interface { Kind() CodeKind ToOpcode(*compileContext) Opcodes Filter(*FieldQuery) Code } type AnonymousCode interface { ToAnonymousOpcode(*compileContext) Opcodes } type Opcodes []*Opcode func (o Opcodes) First() *Opcode { if len(o) == 0 { return nil } return o[0] } func (o Opcodes) Last() *Opcode { if len(o) == 0 { return nil } return o[len(o)-1] } func (o Opcodes) Add(codes ...*Opcode) Opcodes { return append(o, codes...) } type CodeKind int const ( CodeKindInterface CodeKind = iota CodeKindPtr CodeKindInt CodeKindUint CodeKindFloat CodeKindString CodeKindBool CodeKindStruct CodeKindMap CodeKindSlice CodeKindArray CodeKindBytes CodeKindMarshalJSON CodeKindMarshalText CodeKindRecursive ) type IntCode struct { typ *runtime.Type bitSize uint8 isString bool isPtr bool } func (c *IntCode) Kind() CodeKind { return CodeKindInt } func (c *IntCode) ToOpcode(ctx *compileContext) Opcodes { var code *Opcode switch { case c.isPtr: code = newOpCode(ctx, c.typ, OpIntPtr) case c.isString: code = newOpCode(ctx, c.typ, OpIntString) default: code = newOpCode(ctx, c.typ, OpInt) } code.NumBitSize = c.bitSize ctx.incIndex() return Opcodes{code} } func (c *IntCode) Filter(_ *FieldQuery) Code { return c } type UintCode struct { typ *runtime.Type bitSize uint8 isString bool isPtr bool } func (c *UintCode) Kind() CodeKind { return CodeKindUint } func (c *UintCode) ToOpcode(ctx *compileContext) Opcodes { var code *Opcode switch { case c.isPtr: code = newOpCode(ctx, c.typ, OpUintPtr) case c.isString: code = newOpCode(ctx, c.typ, OpUintString) default: code = newOpCode(ctx, c.typ, OpUint) } code.NumBitSize = c.bitSize ctx.incIndex() return Opcodes{code} } func (c *UintCode) Filter(_ *FieldQuery) Code { return c } type FloatCode struct { typ *runtime.Type bitSize uint8 isPtr bool } func (c *FloatCode) Kind() CodeKind { return CodeKindFloat } func (c *FloatCode) ToOpcode(ctx *compileContext) Opcodes { var code *Opcode switch { case c.isPtr: switch c.bitSize { case 32: code = newOpCode(ctx, c.typ, OpFloat32Ptr) default: code = newOpCode(ctx, c.typ, OpFloat64Ptr) } default: switch c.bitSize { case 32: code = newOpCode(ctx, c.typ, OpFloat32) default: code = newOpCode(ctx, c.typ, OpFloat64) } } ctx.incIndex() return Opcodes{code} } func (c *FloatCode) Filter(_ *FieldQuery) Code { return c } type StringCode struct { typ *runtime.Type isPtr bool } func (c *StringCode) Kind() CodeKind { return CodeKindString } func (c *StringCode) ToOpcode(ctx *compileContext) Opcodes { isJSONNumberType := c.typ == runtime.Type2RType(jsonNumberType) var code *Opcode if c.isPtr { if isJSONNumberType { code = newOpCode(ctx, c.typ, OpNumberPtr) } else { code = newOpCode(ctx, c.typ, OpStringPtr) } } else { if isJSONNumberType { code = newOpCode(ctx, c.typ, OpNumber) } else { code = newOpCode(ctx, c.typ, OpString) } } ctx.incIndex() return Opcodes{code} } func (c *StringCode) Filter(_ *FieldQuery) Code { return c } type BoolCode struct { typ *runtime.Type isPtr bool } func (c *BoolCode) Kind() CodeKind { return CodeKindBool } func (c *BoolCode) ToOpcode(ctx *compileContext) Opcodes { var code *Opcode switch { case c.isPtr: code = newOpCode(ctx, c.typ, OpBoolPtr) default: code = newOpCode(ctx, c.typ, OpBool) } ctx.incIndex() return Opcodes{code} } func (c *BoolCode) Filter(_ *FieldQuery) Code { return c } type BytesCode struct { typ *runtime.Type isPtr bool } func (c *BytesCode) Kind() CodeKind { return CodeKindBytes } func (c *BytesCode) ToOpcode(ctx *compileContext) Opcodes { var code *Opcode switch { case c.isPtr: code = newOpCode(ctx, c.typ, OpBytesPtr) default: code = newOpCode(ctx, c.typ, OpBytes) } ctx.incIndex() return Opcodes{code} } func (c *BytesCode) Filter(_ *FieldQuery) Code { return c } type SliceCode struct { typ *runtime.Type value Code } func (c *SliceCode) Kind() CodeKind { return CodeKindSlice } func (c *SliceCode) ToOpcode(ctx *compileContext) Opcodes { // header => opcode => elem => end // ^ | // |________| size := c.typ.Elem().Size() header := newSliceHeaderCode(ctx, c.typ) ctx.incIndex() ctx.incIndent() codes := c.value.ToOpcode(ctx) ctx.decIndent() codes.First().Flags |= IndirectFlags elemCode := newSliceElemCode(ctx, c.typ.Elem(), header, size) ctx.incIndex() end := newOpCode(ctx, c.typ, OpSliceEnd) ctx.incIndex() header.End = end header.Next = codes.First() codes.Last().Next = elemCode elemCode.Next = codes.First() elemCode.End = end return Opcodes{header}.Add(codes...).Add(elemCode).Add(end) } func (c *SliceCode) Filter(_ *FieldQuery) Code { return c } type ArrayCode struct { typ *runtime.Type value Code } func (c *ArrayCode) Kind() CodeKind { return CodeKindArray } func (c *ArrayCode) ToOpcode(ctx *compileContext) Opcodes { // header => opcode => elem => end // ^ | // |________| elem := c.typ.Elem() alen := c.typ.Len() size := elem.Size() header := newArrayHeaderCode(ctx, c.typ, alen) ctx.incIndex() ctx.incIndent() codes := c.value.ToOpcode(ctx) ctx.decIndent() codes.First().Flags |= IndirectFlags elemCode := newArrayElemCode(ctx, elem, header, alen, size) ctx.incIndex() end := newOpCode(ctx, c.typ, OpArrayEnd) ctx.incIndex() header.End = end header.Next = codes.First() codes.Last().Next = elemCode elemCode.Next = codes.First() elemCode.End = end return Opcodes{header}.Add(codes...).Add(elemCode).Add(end) } func (c *ArrayCode) Filter(_ *FieldQuery) Code { return c } type MapCode struct { typ *runtime.Type key Code value Code } func (c *MapCode) Kind() CodeKind { return CodeKindMap } func (c *MapCode) ToOpcode(ctx *compileContext) Opcodes { // header => code => value => code => key => code => value => code => end // ^ | // |_______________________| header := newMapHeaderCode(ctx, c.typ) ctx.incIndex() keyCodes := c.key.ToOpcode(ctx) value := newMapValueCode(ctx, c.typ.Elem(), header) ctx.incIndex() ctx.incIndent() valueCodes := c.value.ToOpcode(ctx) ctx.decIndent() valueCodes.First().Flags |= IndirectFlags key := newMapKeyCode(ctx, c.typ.Key(), header) ctx.incIndex() end := newMapEndCode(ctx, c.typ, header) ctx.incIndex() header.Next = keyCodes.First() keyCodes.Last().Next = value value.Next = valueCodes.First() valueCodes.Last().Next = key key.Next = keyCodes.First() header.End = end key.End = end value.End = end return Opcodes{header}.Add(keyCodes...).Add(value).Add(valueCodes...).Add(key).Add(end) } func (c *MapCode) Filter(_ *FieldQuery) Code { return c } type StructCode struct { typ *runtime.Type fields []*StructFieldCode isPtr bool disableIndirectConversion bool isIndirect bool isRecursive bool } func (c *StructCode) Kind() CodeKind { return CodeKindStruct } func (c *StructCode) lastFieldCode(field *StructFieldCode, firstField *Opcode) *Opcode { if isEmbeddedStruct(field) { return c.lastAnonymousFieldCode(firstField) } lastField := firstField for lastField.NextField != nil { lastField = lastField.NextField } return lastField } func (c *StructCode) lastAnonymousFieldCode(firstField *Opcode) *Opcode { // firstField is special StructHead operation for anonymous structure. // So, StructHead's next operation is truly struct head operation. for firstField.Op == OpStructHead || firstField.Op == OpStructField { firstField = firstField.Next } lastField := firstField for lastField.NextField != nil { lastField = lastField.NextField } return lastField } func (c *StructCode) ToOpcode(ctx *compileContext) Opcodes { // header => code => structField => code => end // ^ | // |__________| if c.isRecursive { recursive := newRecursiveCode(ctx, c.typ, &CompiledCode{}) recursive.Type = c.typ ctx.incIndex() *ctx.recursiveCodes = append(*ctx.recursiveCodes, recursive) return Opcodes{recursive} } codes := Opcodes{} var prevField *Opcode ctx.incIndent() for idx, field := range c.fields { isFirstField := idx == 0 isEndField := idx == len(c.fields)-1 fieldCodes := field.ToOpcode(ctx, isFirstField, isEndField) for _, code := range fieldCodes { if c.isIndirect { code.Flags |= IndirectFlags } } firstField := fieldCodes.First() if len(codes) > 0 { codes.Last().Next = firstField firstField.Idx = codes.First().Idx } if prevField != nil { prevField.NextField = firstField } if isEndField { endField := fieldCodes.Last() if len(codes) > 0 { codes.First().End = endField } else { firstField.End = endField } codes = codes.Add(fieldCodes...) break } prevField = c.lastFieldCode(field, firstField) codes = codes.Add(fieldCodes...) } if len(codes) == 0 { head := &Opcode{ Op: OpStructHead, Idx: opcodeOffset(ctx.ptrIndex), Type: c.typ, DisplayIdx: ctx.opcodeIndex, Indent: ctx.indent, } ctx.incOpcodeIndex() end := &Opcode{ Op: OpStructEnd, Idx: opcodeOffset(ctx.ptrIndex), DisplayIdx: ctx.opcodeIndex, Indent: ctx.indent, } head.NextField = end head.Next = end head.End = end codes = codes.Add(head, end) ctx.incIndex() } ctx.decIndent() ctx.structTypeToCodes[uintptr(unsafe.Pointer(c.typ))] = codes return codes } func (c *StructCode) ToAnonymousOpcode(ctx *compileContext) Opcodes { // header => code => structField => code => end // ^ | // |__________| if c.isRecursive { recursive := newRecursiveCode(ctx, c.typ, &CompiledCode{}) recursive.Type = c.typ ctx.incIndex() *ctx.recursiveCodes = append(*ctx.recursiveCodes, recursive) return Opcodes{recursive} } codes := Opcodes{} var prevField *Opcode for idx, field := range c.fields { isFirstField := idx == 0 isEndField := idx == len(c.fields)-1 fieldCodes := field.ToAnonymousOpcode(ctx, isFirstField, isEndField) for _, code := range fieldCodes { if c.isIndirect { code.Flags |= IndirectFlags } } firstField := fieldCodes.First() if len(codes) > 0 { codes.Last().Next = firstField firstField.Idx = codes.First().Idx } if prevField != nil { prevField.NextField = firstField } if isEndField { lastField := fieldCodes.Last() if len(codes) > 0 { codes.First().End = lastField } else { firstField.End = lastField } } prevField = firstField codes = codes.Add(fieldCodes...) } return codes } func (c *StructCode) removeFieldsByTags(tags runtime.StructTags) { fields := make([]*StructFieldCode, 0, len(c.fields)) for _, field := range c.fields { if field.isAnonymous { structCode := field.getAnonymousStruct() if structCode != nil && !structCode.isRecursive { structCode.removeFieldsByTags(tags) if len(structCode.fields) > 0 { fields = append(fields, field) } continue } } if tags.ExistsKey(field.key) { continue } fields = append(fields, field) } c.fields = fields } func (c *StructCode) enableIndirect() { if c.isIndirect { return } c.isIndirect = true if len(c.fields) == 0 { return } structCode := c.fields[0].getStruct() if structCode == nil { return } structCode.enableIndirect() } func (c *StructCode) Filter(query *FieldQuery) Code { fieldMap := map[string]*FieldQuery{} for _, field := range query.Fields { fieldMap[field.Name] = field } fields := make([]*StructFieldCode, 0, len(c.fields)) for _, field := range c.fields { query, exists := fieldMap[field.key] if !exists { continue } fieldCode := &StructFieldCode{ typ: field.typ, key: field.key, tag: field.tag, value: field.value, offset: field.offset, isAnonymous: field.isAnonymous, isTaggedKey: field.isTaggedKey, isNilableType: field.isNilableType, isNilCheck: field.isNilCheck, isAddrForMarshaler: field.isAddrForMarshaler, isNextOpPtrType: field.isNextOpPtrType, } if len(query.Fields) > 0 { fieldCode.value = fieldCode.value.Filter(query) } fields = append(fields, fieldCode) } return &StructCode{ typ: c.typ, fields: fields, isPtr: c.isPtr, disableIndirectConversion: c.disableIndirectConversion, isIndirect: c.isIndirect, isRecursive: c.isRecursive, } } type StructFieldCode struct { typ *runtime.Type key string tag *runtime.StructTag value Code offset uintptr isAnonymous bool isTaggedKey bool isNilableType bool isNilCheck bool isAddrForMarshaler bool isNextOpPtrType bool isMarshalerContext bool } func (c *StructFieldCode) getStruct() *StructCode { value := c.value ptr, ok := value.(*PtrCode) if ok { value = ptr.value } structCode, ok := value.(*StructCode) if ok { return structCode } return nil } func (c *StructFieldCode) getAnonymousStruct() *StructCode { if !c.isAnonymous { return nil } return c.getStruct() } func optimizeStructHeader(code *Opcode, tag *runtime.StructTag) OpType { headType := code.ToHeaderType(tag.IsString) if tag.IsOmitEmpty { headType = headType.HeadToOmitEmptyHead() } return headType } func optimizeStructField(code *Opcode, tag *runtime.StructTag) OpType { fieldType := code.ToFieldType(tag.IsString) if tag.IsOmitEmpty { fieldType = fieldType.FieldToOmitEmptyField() } return fieldType } func (c *StructFieldCode) headerOpcodes(ctx *compileContext, field *Opcode, valueCodes Opcodes) Opcodes { value := valueCodes.First() op := optimizeStructHeader(value, c.tag) field.Op = op if value.Flags&MarshalerContextFlags != 0 { field.Flags |= MarshalerContextFlags } field.NumBitSize = value.NumBitSize field.PtrNum = value.PtrNum field.FieldQuery = value.FieldQuery fieldCodes := Opcodes{field} if op.IsMultipleOpHead() { field.Next = value fieldCodes = fieldCodes.Add(valueCodes...) } else { ctx.decIndex() } return fieldCodes } func (c *StructFieldCode) fieldOpcodes(ctx *compileContext, field *Opcode, valueCodes Opcodes) Opcodes { value := valueCodes.First() op := optimizeStructField(value, c.tag) field.Op = op if value.Flags&MarshalerContextFlags != 0 { field.Flags |= MarshalerContextFlags } field.NumBitSize = value.NumBitSize field.PtrNum = value.PtrNum field.FieldQuery = value.FieldQuery fieldCodes := Opcodes{field} if op.IsMultipleOpField() { field.Next = value fieldCodes = fieldCodes.Add(valueCodes...) } else { ctx.decIndex() } return fieldCodes } func (c *StructFieldCode) addStructEndCode(ctx *compileContext, codes Opcodes) Opcodes { end := &Opcode{ Op: OpStructEnd, Idx: opcodeOffset(ctx.ptrIndex), DisplayIdx: ctx.opcodeIndex, Indent: ctx.indent, } codes.Last().Next = end code := codes.First() for code.Op == OpStructField || code.Op == OpStructHead { code = code.Next } for code.NextField != nil { code = code.NextField } code.NextField = end codes = codes.Add(end) ctx.incOpcodeIndex() return codes } func (c *StructFieldCode) structKey(ctx *compileContext) string { if ctx.escapeKey { rctx := &RuntimeContext{Option: &Option{Flag: HTMLEscapeOption}} return fmt.Sprintf(`%s:`, string(AppendString(rctx, []byte{}, c.key))) } return fmt.Sprintf(`"%s":`, c.key) } func (c *StructFieldCode) flags() OpFlags { var flags OpFlags if c.isTaggedKey { flags |= IsTaggedKeyFlags } if c.isNilableType { flags |= IsNilableTypeFlags } if c.isNilCheck { flags |= NilCheckFlags } if c.isAddrForMarshaler { flags |= AddrForMarshalerFlags } if c.isNextOpPtrType { flags |= IsNextOpPtrTypeFlags } if c.isAnonymous { flags |= AnonymousKeyFlags } if c.isMarshalerContext { flags |= MarshalerContextFlags } return flags } func (c *StructFieldCode) toValueOpcodes(ctx *compileContext) Opcodes { if c.isAnonymous { anonymCode, ok := c.value.(AnonymousCode) if ok { return anonymCode.ToAnonymousOpcode(ctx) } } return c.value.ToOpcode(ctx) } func (c *StructFieldCode) ToOpcode(ctx *compileContext, isFirstField, isEndField bool) Opcodes { field := &Opcode{ Idx: opcodeOffset(ctx.ptrIndex), Flags: c.flags(), Key: c.structKey(ctx), Offset: uint32(c.offset), Type: c.typ, DisplayIdx: ctx.opcodeIndex, Indent: ctx.indent, DisplayKey: c.key, } ctx.incIndex() valueCodes := c.toValueOpcodes(ctx) if isFirstField { codes := c.headerOpcodes(ctx, field, valueCodes) if isEndField { codes = c.addStructEndCode(ctx, codes) } return codes } codes := c.fieldOpcodes(ctx, field, valueCodes) if isEndField { if isEnableStructEndOptimization(c.value) { field.Op = field.Op.FieldToEnd() } else { codes = c.addStructEndCode(ctx, codes) } } return codes } func (c *StructFieldCode) ToAnonymousOpcode(ctx *compileContext, isFirstField, isEndField bool) Opcodes { field := &Opcode{ Idx: opcodeOffset(ctx.ptrIndex), Flags: c.flags() | AnonymousHeadFlags, Key: c.structKey(ctx), Offset: uint32(c.offset), Type: c.typ, DisplayIdx: ctx.opcodeIndex, Indent: ctx.indent, DisplayKey: c.key, } ctx.incIndex() valueCodes := c.toValueOpcodes(ctx) if isFirstField { return c.headerOpcodes(ctx, field, valueCodes) } return c.fieldOpcodes(ctx, field, valueCodes) } func isEnableStructEndOptimization(value Code) bool { switch value.Kind() { case CodeKindInt, CodeKindUint, CodeKindFloat, CodeKindString, CodeKindBool, CodeKindBytes: return true case CodeKindPtr: return isEnableStructEndOptimization(value.(*PtrCode).value) default: return false } } type InterfaceCode struct { typ *runtime.Type fieldQuery *FieldQuery isPtr bool } func (c *InterfaceCode) Kind() CodeKind { return CodeKindInterface } func (c *InterfaceCode) ToOpcode(ctx *compileContext) Opcodes { var code *Opcode switch { case c.isPtr: code = newOpCode(ctx, c.typ, OpInterfacePtr) default: code = newOpCode(ctx, c.typ, OpInterface) } code.FieldQuery = c.fieldQuery if c.typ.NumMethod() > 0 { code.Flags |= NonEmptyInterfaceFlags } ctx.incIndex() return Opcodes{code} } func (c *InterfaceCode) Filter(query *FieldQuery) Code { return &InterfaceCode{ typ: c.typ, fieldQuery: query, isPtr: c.isPtr, } } type MarshalJSONCode struct { typ *runtime.Type fieldQuery *FieldQuery isAddrForMarshaler bool isNilableType bool isMarshalerContext bool } func (c *MarshalJSONCode) Kind() CodeKind { return CodeKindMarshalJSON } func (c *MarshalJSONCode) ToOpcode(ctx *compileContext) Opcodes { code := newOpCode(ctx, c.typ, OpMarshalJSON) code.FieldQuery = c.fieldQuery if c.isAddrForMarshaler { code.Flags |= AddrForMarshalerFlags } if c.isMarshalerContext { code.Flags |= MarshalerContextFlags } if c.isNilableType { code.Flags |= IsNilableTypeFlags } else { code.Flags &= ^IsNilableTypeFlags } ctx.incIndex() return Opcodes{code} } func (c *MarshalJSONCode) Filter(query *FieldQuery) Code { return &MarshalJSONCode{ typ: c.typ, fieldQuery: query, isAddrForMarshaler: c.isAddrForMarshaler, isNilableType: c.isNilableType, isMarshalerContext: c.isMarshalerContext, } } type MarshalTextCode struct { typ *runtime.Type fieldQuery *FieldQuery isAddrForMarshaler bool isNilableType bool } func (c *MarshalTextCode) Kind() CodeKind { return CodeKindMarshalText } func (c *MarshalTextCode) ToOpcode(ctx *compileContext) Opcodes { code := newOpCode(ctx, c.typ, OpMarshalText) code.FieldQuery = c.fieldQuery if c.isAddrForMarshaler { code.Flags |= AddrForMarshalerFlags } if c.isNilableType { code.Flags |= IsNilableTypeFlags } else { code.Flags &= ^IsNilableTypeFlags } ctx.incIndex() return Opcodes{code} } func (c *MarshalTextCode) Filter(query *FieldQuery) Code { return &MarshalTextCode{ typ: c.typ, fieldQuery: query, isAddrForMarshaler: c.isAddrForMarshaler, isNilableType: c.isNilableType, } } type PtrCode struct { typ *runtime.Type value Code ptrNum uint8 } func (c *PtrCode) Kind() CodeKind { return CodeKindPtr } func (c *PtrCode) ToOpcode(ctx *compileContext) Opcodes { codes := c.value.ToOpcode(ctx) codes.First().Op = convertPtrOp(codes.First()) codes.First().PtrNum = c.ptrNum return codes } func (c *PtrCode) ToAnonymousOpcode(ctx *compileContext) Opcodes { var codes Opcodes anonymCode, ok := c.value.(AnonymousCode) if ok { codes = anonymCode.ToAnonymousOpcode(ctx) } else { codes = c.value.ToOpcode(ctx) } codes.First().Op = convertPtrOp(codes.First()) codes.First().PtrNum = c.ptrNum return codes } func (c *PtrCode) Filter(query *FieldQuery) Code { return &PtrCode{ typ: c.typ, value: c.value.Filter(query), ptrNum: c.ptrNum, } } func convertPtrOp(code *Opcode) OpType { ptrHeadOp := code.Op.HeadToPtrHead() if code.Op != ptrHeadOp { if code.PtrNum > 0 { // ptr field and ptr head code.PtrNum-- } return ptrHeadOp } switch code.Op { case OpInt: return OpIntPtr case OpUint: return OpUintPtr case OpFloat32: return OpFloat32Ptr case OpFloat64: return OpFloat64Ptr case OpString: return OpStringPtr case OpBool: return OpBoolPtr case OpBytes: return OpBytesPtr case OpNumber: return OpNumberPtr case OpArray: return OpArrayPtr case OpSlice: return OpSlicePtr case OpMap: return OpMapPtr case OpMarshalJSON: return OpMarshalJSONPtr case OpMarshalText: return OpMarshalTextPtr case OpInterface: return OpInterfacePtr case OpRecursive: return OpRecursivePtr } return code.Op } func isEmbeddedStruct(field *StructFieldCode) bool { if !field.isAnonymous { return false } t := field.typ if t.Kind() == reflect.Ptr { t = t.Elem() } return t.Kind() == reflect.Struct } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/compact.go ================================================ package encoder import ( "bytes" "fmt" "strconv" "unsafe" "github.com/goccy/go-json/internal/errors" ) var ( isWhiteSpace = [256]bool{ ' ': true, '\n': true, '\t': true, '\r': true, } isHTMLEscapeChar = [256]bool{ '<': true, '>': true, '&': true, } nul = byte('\000') ) func Compact(buf *bytes.Buffer, src []byte, escape bool) error { if len(src) == 0 { return errors.ErrUnexpectedEndOfJSON("", 0) } buf.Grow(len(src)) dst := buf.Bytes() ctx := TakeRuntimeContext() ctxBuf := ctx.Buf[:0] ctxBuf = append(append(ctxBuf, src...), nul) ctx.Buf = ctxBuf if err := compactAndWrite(buf, dst, ctxBuf, escape); err != nil { ReleaseRuntimeContext(ctx) return err } ReleaseRuntimeContext(ctx) return nil } func compactAndWrite(buf *bytes.Buffer, dst []byte, src []byte, escape bool) error { dst, err := compact(dst, src, escape) if err != nil { return err } if _, err := buf.Write(dst); err != nil { return err } return nil } func compact(dst, src []byte, escape bool) ([]byte, error) { buf, cursor, err := compactValue(dst, src, 0, escape) if err != nil { return nil, err } if err := validateEndBuf(src, cursor); err != nil { return nil, err } return buf, nil } func validateEndBuf(src []byte, cursor int64) error { for { switch src[cursor] { case ' ', '\t', '\n', '\r': cursor++ continue case nul: return nil } return errors.ErrSyntax( fmt.Sprintf("invalid character '%c' after top-level value", src[cursor]), cursor+1, ) } } func skipWhiteSpace(buf []byte, cursor int64) int64 { LOOP: if isWhiteSpace[buf[cursor]] { cursor++ goto LOOP } return cursor } func compactValue(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) { for { switch src[cursor] { case ' ', '\t', '\n', '\r': cursor++ continue case '{': return compactObject(dst, src, cursor, escape) case '}': return nil, 0, errors.ErrSyntax("unexpected character '}'", cursor) case '[': return compactArray(dst, src, cursor, escape) case ']': return nil, 0, errors.ErrSyntax("unexpected character ']'", cursor) case '"': return compactString(dst, src, cursor, escape) case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return compactNumber(dst, src, cursor) case 't': return compactTrue(dst, src, cursor) case 'f': return compactFalse(dst, src, cursor) case 'n': return compactNull(dst, src, cursor) default: return nil, 0, errors.ErrSyntax(fmt.Sprintf("unexpected character '%c'", src[cursor]), cursor) } } } func compactObject(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) { if src[cursor] == '{' { dst = append(dst, '{') } else { return nil, 0, errors.ErrExpected("expected { character for object value", cursor) } cursor = skipWhiteSpace(src, cursor+1) if src[cursor] == '}' { dst = append(dst, '}') return dst, cursor + 1, nil } var err error for { cursor = skipWhiteSpace(src, cursor) dst, cursor, err = compactString(dst, src, cursor, escape) if err != nil { return nil, 0, err } cursor = skipWhiteSpace(src, cursor) if src[cursor] != ':' { return nil, 0, errors.ErrExpected("colon after object key", cursor) } dst = append(dst, ':') dst, cursor, err = compactValue(dst, src, cursor+1, escape) if err != nil { return nil, 0, err } cursor = skipWhiteSpace(src, cursor) switch src[cursor] { case '}': dst = append(dst, '}') cursor++ return dst, cursor, nil case ',': dst = append(dst, ',') default: return nil, 0, errors.ErrExpected("comma after object value", cursor) } cursor++ } } func compactArray(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) { if src[cursor] == '[' { dst = append(dst, '[') } else { return nil, 0, errors.ErrExpected("expected [ character for array value", cursor) } cursor = skipWhiteSpace(src, cursor+1) if src[cursor] == ']' { dst = append(dst, ']') return dst, cursor + 1, nil } var err error for { dst, cursor, err = compactValue(dst, src, cursor, escape) if err != nil { return nil, 0, err } cursor = skipWhiteSpace(src, cursor) switch src[cursor] { case ']': dst = append(dst, ']') cursor++ return dst, cursor, nil case ',': dst = append(dst, ',') default: return nil, 0, errors.ErrExpected("comma after array value", cursor) } cursor++ } } func compactString(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) { if src[cursor] != '"' { return nil, 0, errors.ErrInvalidCharacter(src[cursor], "string", cursor) } start := cursor for { cursor++ c := src[cursor] if escape { if isHTMLEscapeChar[c] { dst = append(dst, src[start:cursor]...) dst = append(dst, `\u00`...) dst = append(dst, hex[c>>4], hex[c&0xF]) start = cursor + 1 } else if c == 0xE2 && cursor+2 < int64(len(src)) && src[cursor+1] == 0x80 && src[cursor+2]&^1 == 0xA8 { dst = append(dst, src[start:cursor]...) dst = append(dst, `\u202`...) dst = append(dst, hex[src[cursor+2]&0xF]) cursor += 2 start = cursor + 3 } } switch c { case '\\': cursor++ if src[cursor] == nul { return nil, 0, errors.ErrUnexpectedEndOfJSON("string", int64(len(src))) } case '"': cursor++ return append(dst, src[start:cursor]...), cursor, nil case nul: return nil, 0, errors.ErrUnexpectedEndOfJSON("string", int64(len(src))) } } } func compactNumber(dst, src []byte, cursor int64) ([]byte, int64, error) { start := cursor for { cursor++ if floatTable[src[cursor]] { continue } break } num := src[start:cursor] if _, err := strconv.ParseFloat(*(*string)(unsafe.Pointer(&num)), 64); err != nil { return nil, 0, err } dst = append(dst, num...) return dst, cursor, nil } func compactTrue(dst, src []byte, cursor int64) ([]byte, int64, error) { if cursor+3 >= int64(len(src)) { return nil, 0, errors.ErrUnexpectedEndOfJSON("true", cursor) } if !bytes.Equal(src[cursor:cursor+4], []byte(`true`)) { return nil, 0, errors.ErrInvalidCharacter(src[cursor], "true", cursor) } dst = append(dst, "true"...) cursor += 4 return dst, cursor, nil } func compactFalse(dst, src []byte, cursor int64) ([]byte, int64, error) { if cursor+4 >= int64(len(src)) { return nil, 0, errors.ErrUnexpectedEndOfJSON("false", cursor) } if !bytes.Equal(src[cursor:cursor+5], []byte(`false`)) { return nil, 0, errors.ErrInvalidCharacter(src[cursor], "false", cursor) } dst = append(dst, "false"...) cursor += 5 return dst, cursor, nil } func compactNull(dst, src []byte, cursor int64) ([]byte, int64, error) { if cursor+3 >= int64(len(src)) { return nil, 0, errors.ErrUnexpectedEndOfJSON("null", cursor) } if !bytes.Equal(src[cursor:cursor+4], []byte(`null`)) { return nil, 0, errors.ErrInvalidCharacter(src[cursor], "null", cursor) } dst = append(dst, "null"...) cursor += 4 return dst, cursor, nil } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/compiler.go ================================================ package encoder import ( "context" "encoding" "encoding/json" "reflect" "sync/atomic" "unsafe" "github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/runtime" ) type marshalerContext interface { MarshalJSON(context.Context) ([]byte, error) } var ( marshalJSONType = reflect.TypeOf((*json.Marshaler)(nil)).Elem() marshalJSONContextType = reflect.TypeOf((*marshalerContext)(nil)).Elem() marshalTextType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() jsonNumberType = reflect.TypeOf(json.Number("")) cachedOpcodeSets []*OpcodeSet cachedOpcodeMap unsafe.Pointer // map[uintptr]*OpcodeSet typeAddr *runtime.TypeAddr ) func init() { typeAddr = runtime.AnalyzeTypeAddr() if typeAddr == nil { typeAddr = &runtime.TypeAddr{} } cachedOpcodeSets = make([]*OpcodeSet, typeAddr.AddrRange>>typeAddr.AddrShift+1) } func loadOpcodeMap() map[uintptr]*OpcodeSet { p := atomic.LoadPointer(&cachedOpcodeMap) return *(*map[uintptr]*OpcodeSet)(unsafe.Pointer(&p)) } func storeOpcodeSet(typ uintptr, set *OpcodeSet, m map[uintptr]*OpcodeSet) { newOpcodeMap := make(map[uintptr]*OpcodeSet, len(m)+1) newOpcodeMap[typ] = set for k, v := range m { newOpcodeMap[k] = v } atomic.StorePointer(&cachedOpcodeMap, *(*unsafe.Pointer)(unsafe.Pointer(&newOpcodeMap))) } func compileToGetCodeSetSlowPath(typeptr uintptr) (*OpcodeSet, error) { opcodeMap := loadOpcodeMap() if codeSet, exists := opcodeMap[typeptr]; exists { return codeSet, nil } codeSet, err := newCompiler().compile(typeptr) if err != nil { return nil, err } storeOpcodeSet(typeptr, codeSet, opcodeMap) return codeSet, nil } func getFilteredCodeSetIfNeeded(ctx *RuntimeContext, codeSet *OpcodeSet) (*OpcodeSet, error) { if (ctx.Option.Flag & ContextOption) == 0 { return codeSet, nil } query := FieldQueryFromContext(ctx.Option.Context) if query == nil { return codeSet, nil } ctx.Option.Flag |= FieldQueryOption cacheCodeSet := codeSet.getQueryCache(query.Hash()) if cacheCodeSet != nil { return cacheCodeSet, nil } queryCodeSet, err := newCompiler().codeToOpcodeSet(codeSet.Type, codeSet.Code.Filter(query)) if err != nil { return nil, err } codeSet.setQueryCache(query.Hash(), queryCodeSet) return queryCodeSet, nil } type Compiler struct { structTypeToCode map[uintptr]*StructCode } func newCompiler() *Compiler { return &Compiler{ structTypeToCode: map[uintptr]*StructCode{}, } } func (c *Compiler) compile(typeptr uintptr) (*OpcodeSet, error) { // noescape trick for header.typ ( reflect.*rtype ) typ := *(**runtime.Type)(unsafe.Pointer(&typeptr)) code, err := c.typeToCode(typ) if err != nil { return nil, err } return c.codeToOpcodeSet(typ, code) } func (c *Compiler) codeToOpcodeSet(typ *runtime.Type, code Code) (*OpcodeSet, error) { noescapeKeyCode := c.codeToOpcode(&compileContext{ structTypeToCodes: map[uintptr]Opcodes{}, recursiveCodes: &Opcodes{}, }, typ, code) if err := noescapeKeyCode.Validate(); err != nil { return nil, err } escapeKeyCode := c.codeToOpcode(&compileContext{ structTypeToCodes: map[uintptr]Opcodes{}, recursiveCodes: &Opcodes{}, escapeKey: true, }, typ, code) noescapeKeyCode = copyOpcode(noescapeKeyCode) escapeKeyCode = copyOpcode(escapeKeyCode) setTotalLengthToInterfaceOp(noescapeKeyCode) setTotalLengthToInterfaceOp(escapeKeyCode) interfaceNoescapeKeyCode := copyToInterfaceOpcode(noescapeKeyCode) interfaceEscapeKeyCode := copyToInterfaceOpcode(escapeKeyCode) codeLength := noescapeKeyCode.TotalLength() return &OpcodeSet{ Type: typ, NoescapeKeyCode: noescapeKeyCode, EscapeKeyCode: escapeKeyCode, InterfaceNoescapeKeyCode: interfaceNoescapeKeyCode, InterfaceEscapeKeyCode: interfaceEscapeKeyCode, CodeLength: codeLength, EndCode: ToEndCode(interfaceNoescapeKeyCode), Code: code, QueryCache: map[string]*OpcodeSet{}, }, nil } func (c *Compiler) typeToCode(typ *runtime.Type) (Code, error) { switch { case c.implementsMarshalJSON(typ): return c.marshalJSONCode(typ) case c.implementsMarshalText(typ): return c.marshalTextCode(typ) } isPtr := false orgType := typ if typ.Kind() == reflect.Ptr { typ = typ.Elem() isPtr = true } switch { case c.implementsMarshalJSON(typ): return c.marshalJSONCode(orgType) case c.implementsMarshalText(typ): return c.marshalTextCode(orgType) } switch typ.Kind() { case reflect.Slice: elem := typ.Elem() if elem.Kind() == reflect.Uint8 { p := runtime.PtrTo(elem) if !c.implementsMarshalJSONType(p) && !p.Implements(marshalTextType) { return c.bytesCode(typ, isPtr) } } return c.sliceCode(typ) case reflect.Map: if isPtr { return c.ptrCode(runtime.PtrTo(typ)) } return c.mapCode(typ) case reflect.Struct: return c.structCode(typ, isPtr) case reflect.Int: return c.intCode(typ, isPtr) case reflect.Int8: return c.int8Code(typ, isPtr) case reflect.Int16: return c.int16Code(typ, isPtr) case reflect.Int32: return c.int32Code(typ, isPtr) case reflect.Int64: return c.int64Code(typ, isPtr) case reflect.Uint, reflect.Uintptr: return c.uintCode(typ, isPtr) case reflect.Uint8: return c.uint8Code(typ, isPtr) case reflect.Uint16: return c.uint16Code(typ, isPtr) case reflect.Uint32: return c.uint32Code(typ, isPtr) case reflect.Uint64: return c.uint64Code(typ, isPtr) case reflect.Float32: return c.float32Code(typ, isPtr) case reflect.Float64: return c.float64Code(typ, isPtr) case reflect.String: return c.stringCode(typ, isPtr) case reflect.Bool: return c.boolCode(typ, isPtr) case reflect.Interface: return c.interfaceCode(typ, isPtr) default: if isPtr && typ.Implements(marshalTextType) { typ = orgType } return c.typeToCodeWithPtr(typ, isPtr) } } func (c *Compiler) typeToCodeWithPtr(typ *runtime.Type, isPtr bool) (Code, error) { switch { case c.implementsMarshalJSON(typ): return c.marshalJSONCode(typ) case c.implementsMarshalText(typ): return c.marshalTextCode(typ) } switch typ.Kind() { case reflect.Ptr: return c.ptrCode(typ) case reflect.Slice: elem := typ.Elem() if elem.Kind() == reflect.Uint8 { p := runtime.PtrTo(elem) if !c.implementsMarshalJSONType(p) && !p.Implements(marshalTextType) { return c.bytesCode(typ, false) } } return c.sliceCode(typ) case reflect.Array: return c.arrayCode(typ) case reflect.Map: return c.mapCode(typ) case reflect.Struct: return c.structCode(typ, isPtr) case reflect.Interface: return c.interfaceCode(typ, false) case reflect.Int: return c.intCode(typ, false) case reflect.Int8: return c.int8Code(typ, false) case reflect.Int16: return c.int16Code(typ, false) case reflect.Int32: return c.int32Code(typ, false) case reflect.Int64: return c.int64Code(typ, false) case reflect.Uint: return c.uintCode(typ, false) case reflect.Uint8: return c.uint8Code(typ, false) case reflect.Uint16: return c.uint16Code(typ, false) case reflect.Uint32: return c.uint32Code(typ, false) case reflect.Uint64: return c.uint64Code(typ, false) case reflect.Uintptr: return c.uintCode(typ, false) case reflect.Float32: return c.float32Code(typ, false) case reflect.Float64: return c.float64Code(typ, false) case reflect.String: return c.stringCode(typ, false) case reflect.Bool: return c.boolCode(typ, false) } return nil, &errors.UnsupportedTypeError{Type: runtime.RType2Type(typ)} } const intSize = 32 << (^uint(0) >> 63) //nolint:unparam func (c *Compiler) intCode(typ *runtime.Type, isPtr bool) (*IntCode, error) { return &IntCode{typ: typ, bitSize: intSize, isPtr: isPtr}, nil } //nolint:unparam func (c *Compiler) int8Code(typ *runtime.Type, isPtr bool) (*IntCode, error) { return &IntCode{typ: typ, bitSize: 8, isPtr: isPtr}, nil } //nolint:unparam func (c *Compiler) int16Code(typ *runtime.Type, isPtr bool) (*IntCode, error) { return &IntCode{typ: typ, bitSize: 16, isPtr: isPtr}, nil } //nolint:unparam func (c *Compiler) int32Code(typ *runtime.Type, isPtr bool) (*IntCode, error) { return &IntCode{typ: typ, bitSize: 32, isPtr: isPtr}, nil } //nolint:unparam func (c *Compiler) int64Code(typ *runtime.Type, isPtr bool) (*IntCode, error) { return &IntCode{typ: typ, bitSize: 64, isPtr: isPtr}, nil } //nolint:unparam func (c *Compiler) uintCode(typ *runtime.Type, isPtr bool) (*UintCode, error) { return &UintCode{typ: typ, bitSize: intSize, isPtr: isPtr}, nil } //nolint:unparam func (c *Compiler) uint8Code(typ *runtime.Type, isPtr bool) (*UintCode, error) { return &UintCode{typ: typ, bitSize: 8, isPtr: isPtr}, nil } //nolint:unparam func (c *Compiler) uint16Code(typ *runtime.Type, isPtr bool) (*UintCode, error) { return &UintCode{typ: typ, bitSize: 16, isPtr: isPtr}, nil } //nolint:unparam func (c *Compiler) uint32Code(typ *runtime.Type, isPtr bool) (*UintCode, error) { return &UintCode{typ: typ, bitSize: 32, isPtr: isPtr}, nil } //nolint:unparam func (c *Compiler) uint64Code(typ *runtime.Type, isPtr bool) (*UintCode, error) { return &UintCode{typ: typ, bitSize: 64, isPtr: isPtr}, nil } //nolint:unparam func (c *Compiler) float32Code(typ *runtime.Type, isPtr bool) (*FloatCode, error) { return &FloatCode{typ: typ, bitSize: 32, isPtr: isPtr}, nil } //nolint:unparam func (c *Compiler) float64Code(typ *runtime.Type, isPtr bool) (*FloatCode, error) { return &FloatCode{typ: typ, bitSize: 64, isPtr: isPtr}, nil } //nolint:unparam func (c *Compiler) stringCode(typ *runtime.Type, isPtr bool) (*StringCode, error) { return &StringCode{typ: typ, isPtr: isPtr}, nil } //nolint:unparam func (c *Compiler) boolCode(typ *runtime.Type, isPtr bool) (*BoolCode, error) { return &BoolCode{typ: typ, isPtr: isPtr}, nil } //nolint:unparam func (c *Compiler) intStringCode(typ *runtime.Type) (*IntCode, error) { return &IntCode{typ: typ, bitSize: intSize, isString: true}, nil } //nolint:unparam func (c *Compiler) int8StringCode(typ *runtime.Type) (*IntCode, error) { return &IntCode{typ: typ, bitSize: 8, isString: true}, nil } //nolint:unparam func (c *Compiler) int16StringCode(typ *runtime.Type) (*IntCode, error) { return &IntCode{typ: typ, bitSize: 16, isString: true}, nil } //nolint:unparam func (c *Compiler) int32StringCode(typ *runtime.Type) (*IntCode, error) { return &IntCode{typ: typ, bitSize: 32, isString: true}, nil } //nolint:unparam func (c *Compiler) int64StringCode(typ *runtime.Type) (*IntCode, error) { return &IntCode{typ: typ, bitSize: 64, isString: true}, nil } //nolint:unparam func (c *Compiler) uintStringCode(typ *runtime.Type) (*UintCode, error) { return &UintCode{typ: typ, bitSize: intSize, isString: true}, nil } //nolint:unparam func (c *Compiler) uint8StringCode(typ *runtime.Type) (*UintCode, error) { return &UintCode{typ: typ, bitSize: 8, isString: true}, nil } //nolint:unparam func (c *Compiler) uint16StringCode(typ *runtime.Type) (*UintCode, error) { return &UintCode{typ: typ, bitSize: 16, isString: true}, nil } //nolint:unparam func (c *Compiler) uint32StringCode(typ *runtime.Type) (*UintCode, error) { return &UintCode{typ: typ, bitSize: 32, isString: true}, nil } //nolint:unparam func (c *Compiler) uint64StringCode(typ *runtime.Type) (*UintCode, error) { return &UintCode{typ: typ, bitSize: 64, isString: true}, nil } //nolint:unparam func (c *Compiler) bytesCode(typ *runtime.Type, isPtr bool) (*BytesCode, error) { return &BytesCode{typ: typ, isPtr: isPtr}, nil } //nolint:unparam func (c *Compiler) interfaceCode(typ *runtime.Type, isPtr bool) (*InterfaceCode, error) { return &InterfaceCode{typ: typ, isPtr: isPtr}, nil } //nolint:unparam func (c *Compiler) marshalJSONCode(typ *runtime.Type) (*MarshalJSONCode, error) { return &MarshalJSONCode{ typ: typ, isAddrForMarshaler: c.isPtrMarshalJSONType(typ), isNilableType: c.isNilableType(typ), isMarshalerContext: typ.Implements(marshalJSONContextType) || runtime.PtrTo(typ).Implements(marshalJSONContextType), }, nil } //nolint:unparam func (c *Compiler) marshalTextCode(typ *runtime.Type) (*MarshalTextCode, error) { return &MarshalTextCode{ typ: typ, isAddrForMarshaler: c.isPtrMarshalTextType(typ), isNilableType: c.isNilableType(typ), }, nil } func (c *Compiler) ptrCode(typ *runtime.Type) (*PtrCode, error) { code, err := c.typeToCodeWithPtr(typ.Elem(), true) if err != nil { return nil, err } ptr, ok := code.(*PtrCode) if ok { return &PtrCode{typ: typ, value: ptr.value, ptrNum: ptr.ptrNum + 1}, nil } return &PtrCode{typ: typ, value: code, ptrNum: 1}, nil } func (c *Compiler) sliceCode(typ *runtime.Type) (*SliceCode, error) { elem := typ.Elem() code, err := c.listElemCode(elem) if err != nil { return nil, err } if code.Kind() == CodeKindStruct { structCode := code.(*StructCode) structCode.enableIndirect() } return &SliceCode{typ: typ, value: code}, nil } func (c *Compiler) arrayCode(typ *runtime.Type) (*ArrayCode, error) { elem := typ.Elem() code, err := c.listElemCode(elem) if err != nil { return nil, err } if code.Kind() == CodeKindStruct { structCode := code.(*StructCode) structCode.enableIndirect() } return &ArrayCode{typ: typ, value: code}, nil } func (c *Compiler) mapCode(typ *runtime.Type) (*MapCode, error) { keyCode, err := c.mapKeyCode(typ.Key()) if err != nil { return nil, err } valueCode, err := c.mapValueCode(typ.Elem()) if err != nil { return nil, err } if valueCode.Kind() == CodeKindStruct { structCode := valueCode.(*StructCode) structCode.enableIndirect() } return &MapCode{typ: typ, key: keyCode, value: valueCode}, nil } func (c *Compiler) listElemCode(typ *runtime.Type) (Code, error) { switch { case c.isPtrMarshalJSONType(typ): return c.marshalJSONCode(typ) case !typ.Implements(marshalTextType) && runtime.PtrTo(typ).Implements(marshalTextType): return c.marshalTextCode(typ) case typ.Kind() == reflect.Map: return c.ptrCode(runtime.PtrTo(typ)) default: // isPtr was originally used to indicate whether the type of top level is pointer. // However, since the slice/array element is a specification that can get the pointer address, explicitly set isPtr to true. // See here for related issues: https://github.com/goccy/go-json/issues/370 code, err := c.typeToCodeWithPtr(typ, true) if err != nil { return nil, err } ptr, ok := code.(*PtrCode) if ok { if ptr.value.Kind() == CodeKindMap { ptr.ptrNum++ } } return code, nil } } func (c *Compiler) mapKeyCode(typ *runtime.Type) (Code, error) { switch { case c.implementsMarshalText(typ): return c.marshalTextCode(typ) } switch typ.Kind() { case reflect.Ptr: return c.ptrCode(typ) case reflect.String: return c.stringCode(typ, false) case reflect.Int: return c.intStringCode(typ) case reflect.Int8: return c.int8StringCode(typ) case reflect.Int16: return c.int16StringCode(typ) case reflect.Int32: return c.int32StringCode(typ) case reflect.Int64: return c.int64StringCode(typ) case reflect.Uint: return c.uintStringCode(typ) case reflect.Uint8: return c.uint8StringCode(typ) case reflect.Uint16: return c.uint16StringCode(typ) case reflect.Uint32: return c.uint32StringCode(typ) case reflect.Uint64: return c.uint64StringCode(typ) case reflect.Uintptr: return c.uintStringCode(typ) } return nil, &errors.UnsupportedTypeError{Type: runtime.RType2Type(typ)} } func (c *Compiler) mapValueCode(typ *runtime.Type) (Code, error) { switch typ.Kind() { case reflect.Map: return c.ptrCode(runtime.PtrTo(typ)) default: code, err := c.typeToCodeWithPtr(typ, false) if err != nil { return nil, err } ptr, ok := code.(*PtrCode) if ok { if ptr.value.Kind() == CodeKindMap { ptr.ptrNum++ } } return code, nil } } func (c *Compiler) structCode(typ *runtime.Type, isPtr bool) (*StructCode, error) { typeptr := uintptr(unsafe.Pointer(typ)) if code, exists := c.structTypeToCode[typeptr]; exists { derefCode := *code derefCode.isRecursive = true return &derefCode, nil } indirect := runtime.IfaceIndir(typ) code := &StructCode{typ: typ, isPtr: isPtr, isIndirect: indirect} c.structTypeToCode[typeptr] = code fieldNum := typ.NumField() tags := c.typeToStructTags(typ) fields := []*StructFieldCode{} for i, tag := range tags { isOnlyOneFirstField := i == 0 && fieldNum == 1 field, err := c.structFieldCode(code, tag, isPtr, isOnlyOneFirstField) if err != nil { return nil, err } if field.isAnonymous { structCode := field.getAnonymousStruct() if structCode != nil { structCode.removeFieldsByTags(tags) if c.isAssignableIndirect(field, isPtr) { if indirect { structCode.isIndirect = true } else { structCode.isIndirect = false } } } } else { structCode := field.getStruct() if structCode != nil { if indirect { // if parent is indirect type, set child indirect property to true structCode.isIndirect = true } else { // if parent is not indirect type, set child indirect property to false. // but if parent's indirect is false and isPtr is true, then indirect must be true. // Do this only if indirectConversion is enabled at the end of compileStruct. structCode.isIndirect = false } } } fields = append(fields, field) } fieldMap := c.getFieldMap(fields) duplicatedFieldMap := c.getDuplicatedFieldMap(fieldMap) code.fields = c.filteredDuplicatedFields(fields, duplicatedFieldMap) if !code.disableIndirectConversion && !indirect && isPtr { code.enableIndirect() } delete(c.structTypeToCode, typeptr) return code, nil } func toElemType(t *runtime.Type) *runtime.Type { for t.Kind() == reflect.Ptr { t = t.Elem() } return t } func (c *Compiler) structFieldCode(structCode *StructCode, tag *runtime.StructTag, isPtr, isOnlyOneFirstField bool) (*StructFieldCode, error) { field := tag.Field fieldType := runtime.Type2RType(field.Type) isIndirectSpecialCase := isPtr && isOnlyOneFirstField fieldCode := &StructFieldCode{ typ: fieldType, key: tag.Key, tag: tag, offset: field.Offset, isAnonymous: field.Anonymous && !tag.IsTaggedKey && toElemType(fieldType).Kind() == reflect.Struct, isTaggedKey: tag.IsTaggedKey, isNilableType: c.isNilableType(fieldType), isNilCheck: true, } switch { case c.isMovePointerPositionFromHeadToFirstMarshalJSONFieldCase(fieldType, isIndirectSpecialCase): code, err := c.marshalJSONCode(fieldType) if err != nil { return nil, err } fieldCode.value = code fieldCode.isAddrForMarshaler = true fieldCode.isNilCheck = false structCode.isIndirect = false structCode.disableIndirectConversion = true case c.isMovePointerPositionFromHeadToFirstMarshalTextFieldCase(fieldType, isIndirectSpecialCase): code, err := c.marshalTextCode(fieldType) if err != nil { return nil, err } fieldCode.value = code fieldCode.isAddrForMarshaler = true fieldCode.isNilCheck = false structCode.isIndirect = false structCode.disableIndirectConversion = true case isPtr && c.isPtrMarshalJSONType(fieldType): // *struct{ field T } // func (*T) MarshalJSON() ([]byte, error) code, err := c.marshalJSONCode(fieldType) if err != nil { return nil, err } fieldCode.value = code fieldCode.isAddrForMarshaler = true fieldCode.isNilCheck = false case isPtr && c.isPtrMarshalTextType(fieldType): // *struct{ field T } // func (*T) MarshalText() ([]byte, error) code, err := c.marshalTextCode(fieldType) if err != nil { return nil, err } fieldCode.value = code fieldCode.isAddrForMarshaler = true fieldCode.isNilCheck = false default: code, err := c.typeToCodeWithPtr(fieldType, isPtr) if err != nil { return nil, err } switch code.Kind() { case CodeKindPtr, CodeKindInterface: fieldCode.isNextOpPtrType = true } fieldCode.value = code } return fieldCode, nil } func (c *Compiler) isAssignableIndirect(fieldCode *StructFieldCode, isPtr bool) bool { if isPtr { return false } codeType := fieldCode.value.Kind() if codeType == CodeKindMarshalJSON { return false } if codeType == CodeKindMarshalText { return false } return true } func (c *Compiler) getFieldMap(fields []*StructFieldCode) map[string][]*StructFieldCode { fieldMap := map[string][]*StructFieldCode{} for _, field := range fields { if field.isAnonymous { for k, v := range c.getAnonymousFieldMap(field) { fieldMap[k] = append(fieldMap[k], v...) } continue } fieldMap[field.key] = append(fieldMap[field.key], field) } return fieldMap } func (c *Compiler) getAnonymousFieldMap(field *StructFieldCode) map[string][]*StructFieldCode { fieldMap := map[string][]*StructFieldCode{} structCode := field.getAnonymousStruct() if structCode == nil || structCode.isRecursive { fieldMap[field.key] = append(fieldMap[field.key], field) return fieldMap } for k, v := range c.getFieldMapFromAnonymousParent(structCode.fields) { fieldMap[k] = append(fieldMap[k], v...) } return fieldMap } func (c *Compiler) getFieldMapFromAnonymousParent(fields []*StructFieldCode) map[string][]*StructFieldCode { fieldMap := map[string][]*StructFieldCode{} for _, field := range fields { if field.isAnonymous { for k, v := range c.getAnonymousFieldMap(field) { // Do not handle tagged key when embedding more than once for _, vv := range v { vv.isTaggedKey = false } fieldMap[k] = append(fieldMap[k], v...) } continue } fieldMap[field.key] = append(fieldMap[field.key], field) } return fieldMap } func (c *Compiler) getDuplicatedFieldMap(fieldMap map[string][]*StructFieldCode) map[*StructFieldCode]struct{} { duplicatedFieldMap := map[*StructFieldCode]struct{}{} for _, fields := range fieldMap { if len(fields) == 1 { continue } if c.isTaggedKeyOnly(fields) { for _, field := range fields { if field.isTaggedKey { continue } duplicatedFieldMap[field] = struct{}{} } } else { for _, field := range fields { duplicatedFieldMap[field] = struct{}{} } } } return duplicatedFieldMap } func (c *Compiler) filteredDuplicatedFields(fields []*StructFieldCode, duplicatedFieldMap map[*StructFieldCode]struct{}) []*StructFieldCode { filteredFields := make([]*StructFieldCode, 0, len(fields)) for _, field := range fields { if field.isAnonymous { structCode := field.getAnonymousStruct() if structCode != nil && !structCode.isRecursive { structCode.fields = c.filteredDuplicatedFields(structCode.fields, duplicatedFieldMap) if len(structCode.fields) > 0 { filteredFields = append(filteredFields, field) } continue } } if _, exists := duplicatedFieldMap[field]; exists { continue } filteredFields = append(filteredFields, field) } return filteredFields } func (c *Compiler) isTaggedKeyOnly(fields []*StructFieldCode) bool { var taggedKeyFieldCount int for _, field := range fields { if field.isTaggedKey { taggedKeyFieldCount++ } } return taggedKeyFieldCount == 1 } func (c *Compiler) typeToStructTags(typ *runtime.Type) runtime.StructTags { tags := runtime.StructTags{} fieldNum := typ.NumField() for i := 0; i < fieldNum; i++ { field := typ.Field(i) if runtime.IsIgnoredStructField(field) { continue } tags = append(tags, runtime.StructTagFromField(field)) } return tags } // *struct{ field T } => struct { field *T } // func (*T) MarshalJSON() ([]byte, error) func (c *Compiler) isMovePointerPositionFromHeadToFirstMarshalJSONFieldCase(typ *runtime.Type, isIndirectSpecialCase bool) bool { return isIndirectSpecialCase && !c.isNilableType(typ) && c.isPtrMarshalJSONType(typ) } // *struct{ field T } => struct { field *T } // func (*T) MarshalText() ([]byte, error) func (c *Compiler) isMovePointerPositionFromHeadToFirstMarshalTextFieldCase(typ *runtime.Type, isIndirectSpecialCase bool) bool { return isIndirectSpecialCase && !c.isNilableType(typ) && c.isPtrMarshalTextType(typ) } func (c *Compiler) implementsMarshalJSON(typ *runtime.Type) bool { if !c.implementsMarshalJSONType(typ) { return false } if typ.Kind() != reflect.Ptr { return true } // type kind is reflect.Ptr if !c.implementsMarshalJSONType(typ.Elem()) { return true } // needs to dereference return false } func (c *Compiler) implementsMarshalText(typ *runtime.Type) bool { if !typ.Implements(marshalTextType) { return false } if typ.Kind() != reflect.Ptr { return true } // type kind is reflect.Ptr if !typ.Elem().Implements(marshalTextType) { return true } // needs to dereference return false } func (c *Compiler) isNilableType(typ *runtime.Type) bool { if !runtime.IfaceIndir(typ) { return true } switch typ.Kind() { case reflect.Ptr: return true case reflect.Map: return true case reflect.Func: return true default: return false } } func (c *Compiler) implementsMarshalJSONType(typ *runtime.Type) bool { return typ.Implements(marshalJSONType) || typ.Implements(marshalJSONContextType) } func (c *Compiler) isPtrMarshalJSONType(typ *runtime.Type) bool { return !c.implementsMarshalJSONType(typ) && c.implementsMarshalJSONType(runtime.PtrTo(typ)) } func (c *Compiler) isPtrMarshalTextType(typ *runtime.Type) bool { return !typ.Implements(marshalTextType) && runtime.PtrTo(typ).Implements(marshalTextType) } func (c *Compiler) codeToOpcode(ctx *compileContext, typ *runtime.Type, code Code) *Opcode { codes := code.ToOpcode(ctx) codes.Last().Next = newEndOp(ctx, typ) c.linkRecursiveCode(ctx) return codes.First() } func (c *Compiler) linkRecursiveCode(ctx *compileContext) { recursiveCodes := map[uintptr]*CompiledCode{} for _, recursive := range *ctx.recursiveCodes { typeptr := uintptr(unsafe.Pointer(recursive.Type)) codes := ctx.structTypeToCodes[typeptr] if recursiveCode, ok := recursiveCodes[typeptr]; ok { *recursive.Jmp = *recursiveCode continue } code := copyOpcode(codes.First()) code.Op = code.Op.PtrHeadToHead() lastCode := newEndOp(&compileContext{}, recursive.Type) lastCode.Op = OpRecursiveEnd // OpRecursiveEnd must set before call TotalLength code.End.Next = lastCode totalLength := code.TotalLength() // Idx, ElemIdx, Length must set after call TotalLength lastCode.Idx = uint32((totalLength + 1) * uintptrSize) lastCode.ElemIdx = lastCode.Idx + uintptrSize lastCode.Length = lastCode.Idx + 2*uintptrSize // extend length to alloc slot for elemIdx + length curTotalLength := uintptr(recursive.TotalLength()) + 3 nextTotalLength := uintptr(totalLength) + 3 compiled := recursive.Jmp compiled.Code = code compiled.CurLen = curTotalLength compiled.NextLen = nextTotalLength compiled.Linked = true recursiveCodes[typeptr] = compiled } } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/compiler_norace.go ================================================ //go:build !race // +build !race package encoder func CompileToGetCodeSet(ctx *RuntimeContext, typeptr uintptr) (*OpcodeSet, error) { if typeptr > typeAddr.MaxTypeAddr || typeptr < typeAddr.BaseTypeAddr { codeSet, err := compileToGetCodeSetSlowPath(typeptr) if err != nil { return nil, err } return getFilteredCodeSetIfNeeded(ctx, codeSet) } index := (typeptr - typeAddr.BaseTypeAddr) >> typeAddr.AddrShift if codeSet := cachedOpcodeSets[index]; codeSet != nil { filtered, err := getFilteredCodeSetIfNeeded(ctx, codeSet) if err != nil { return nil, err } return filtered, nil } codeSet, err := newCompiler().compile(typeptr) if err != nil { return nil, err } filtered, err := getFilteredCodeSetIfNeeded(ctx, codeSet) if err != nil { return nil, err } cachedOpcodeSets[index] = codeSet return filtered, nil } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/compiler_race.go ================================================ //go:build race // +build race package encoder import ( "sync" ) var setsMu sync.RWMutex func CompileToGetCodeSet(ctx *RuntimeContext, typeptr uintptr) (*OpcodeSet, error) { if typeptr > typeAddr.MaxTypeAddr || typeptr < typeAddr.BaseTypeAddr { codeSet, err := compileToGetCodeSetSlowPath(typeptr) if err != nil { return nil, err } return getFilteredCodeSetIfNeeded(ctx, codeSet) } index := (typeptr - typeAddr.BaseTypeAddr) >> typeAddr.AddrShift setsMu.RLock() if codeSet := cachedOpcodeSets[index]; codeSet != nil { filtered, err := getFilteredCodeSetIfNeeded(ctx, codeSet) if err != nil { setsMu.RUnlock() return nil, err } setsMu.RUnlock() return filtered, nil } setsMu.RUnlock() codeSet, err := newCompiler().compile(typeptr) if err != nil { return nil, err } filtered, err := getFilteredCodeSetIfNeeded(ctx, codeSet) if err != nil { return nil, err } setsMu.Lock() cachedOpcodeSets[index] = codeSet setsMu.Unlock() return filtered, nil } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/context.go ================================================ package encoder import ( "context" "sync" "unsafe" "github.com/goccy/go-json/internal/runtime" ) type compileContext struct { opcodeIndex uint32 ptrIndex int indent uint32 escapeKey bool structTypeToCodes map[uintptr]Opcodes recursiveCodes *Opcodes } func (c *compileContext) incIndent() { c.indent++ } func (c *compileContext) decIndent() { c.indent-- } func (c *compileContext) incIndex() { c.incOpcodeIndex() c.incPtrIndex() } func (c *compileContext) decIndex() { c.decOpcodeIndex() c.decPtrIndex() } func (c *compileContext) incOpcodeIndex() { c.opcodeIndex++ } func (c *compileContext) decOpcodeIndex() { c.opcodeIndex-- } func (c *compileContext) incPtrIndex() { c.ptrIndex++ } func (c *compileContext) decPtrIndex() { c.ptrIndex-- } const ( bufSize = 1024 ) var ( runtimeContextPool = sync.Pool{ New: func() interface{} { return &RuntimeContext{ Buf: make([]byte, 0, bufSize), Ptrs: make([]uintptr, 128), KeepRefs: make([]unsafe.Pointer, 0, 8), Option: &Option{}, } }, } ) type RuntimeContext struct { Context context.Context Buf []byte MarshalBuf []byte Ptrs []uintptr KeepRefs []unsafe.Pointer SeenPtr []uintptr BaseIndent uint32 Prefix []byte IndentStr []byte Option *Option } func (c *RuntimeContext) Init(p uintptr, codelen int) { if len(c.Ptrs) < codelen { c.Ptrs = make([]uintptr, codelen) } c.Ptrs[0] = p c.KeepRefs = c.KeepRefs[:0] c.SeenPtr = c.SeenPtr[:0] c.BaseIndent = 0 } func (c *RuntimeContext) Ptr() uintptr { header := (*runtime.SliceHeader)(unsafe.Pointer(&c.Ptrs)) return uintptr(header.Data) } func TakeRuntimeContext() *RuntimeContext { return runtimeContextPool.Get().(*RuntimeContext) } func ReleaseRuntimeContext(ctx *RuntimeContext) { runtimeContextPool.Put(ctx) } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/decode_rune.go ================================================ package encoder import "unicode/utf8" const ( // The default lowest and highest continuation byte. locb = 128 //0b10000000 hicb = 191 //0b10111111 // These names of these constants are chosen to give nice alignment in the // table below. The first nibble is an index into acceptRanges or F for // special one-byte cases. The second nibble is the Rune length or the // Status for the special one-byte case. xx = 0xF1 // invalid: size 1 as = 0xF0 // ASCII: size 1 s1 = 0x02 // accept 0, size 2 s2 = 0x13 // accept 1, size 3 s3 = 0x03 // accept 0, size 3 s4 = 0x23 // accept 2, size 3 s5 = 0x34 // accept 3, size 4 s6 = 0x04 // accept 0, size 4 s7 = 0x44 // accept 4, size 4 ) // first is information about the first byte in a UTF-8 sequence. var first = [256]uint8{ // 1 2 3 4 5 6 7 8 9 A B C D E F as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x00-0x0F as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x10-0x1F as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x20-0x2F as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x30-0x3F as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x40-0x4F as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x50-0x5F as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x60-0x6F as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x70-0x7F // 1 2 3 4 5 6 7 8 9 A B C D E F xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0x80-0x8F xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0x90-0x9F xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xA0-0xAF xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xB0-0xBF xx, xx, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, // 0xC0-0xCF s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, // 0xD0-0xDF s2, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s4, s3, s3, // 0xE0-0xEF s5, s6, s6, s6, s7, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xF0-0xFF } const ( lineSep = byte(168) //'\u2028' paragraphSep = byte(169) //'\u2029' ) type decodeRuneState int const ( validUTF8State decodeRuneState = iota runeErrorState lineSepState paragraphSepState ) func decodeRuneInString(s string) (decodeRuneState, int) { n := len(s) s0 := s[0] x := first[s0] if x >= as { // The following code simulates an additional check for x == xx and // handling the ASCII and invalid cases accordingly. This mask-and-or // approach prevents an additional branch. mask := rune(x) << 31 >> 31 // Create 0x0000 or 0xFFFF. if rune(s[0])&^mask|utf8.RuneError&mask == utf8.RuneError { return runeErrorState, 1 } return validUTF8State, 1 } sz := int(x & 7) if n < sz { return runeErrorState, 1 } s1 := s[1] switch x >> 4 { case 0: if s1 < locb || hicb < s1 { return runeErrorState, 1 } case 1: if s1 < 0xA0 || hicb < s1 { return runeErrorState, 1 } case 2: if s1 < locb || 0x9F < s1 { return runeErrorState, 1 } case 3: if s1 < 0x90 || hicb < s1 { return runeErrorState, 1 } case 4: if s1 < locb || 0x8F < s1 { return runeErrorState, 1 } } if sz <= 2 { return validUTF8State, 2 } s2 := s[2] if s2 < locb || hicb < s2 { return runeErrorState, 1 } if sz <= 3 { // separator character prefixes: [2]byte{226, 128} if s0 == 226 && s1 == 128 { switch s2 { case lineSep: return lineSepState, 3 case paragraphSep: return paragraphSepState, 3 } } return validUTF8State, 3 } s3 := s[3] if s3 < locb || hicb < s3 { return runeErrorState, 1 } return validUTF8State, 4 } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/encoder.go ================================================ package encoder import ( "bytes" "encoding" "encoding/base64" "encoding/json" "fmt" "math" "reflect" "strconv" "strings" "sync" "unsafe" "github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/runtime" ) func (t OpType) IsMultipleOpHead() bool { switch t { case OpStructHead: return true case OpStructHeadSlice: return true case OpStructHeadArray: return true case OpStructHeadMap: return true case OpStructHeadStruct: return true case OpStructHeadOmitEmpty: return true case OpStructHeadOmitEmptySlice: return true case OpStructHeadOmitEmptyArray: return true case OpStructHeadOmitEmptyMap: return true case OpStructHeadOmitEmptyStruct: return true case OpStructHeadSlicePtr: return true case OpStructHeadOmitEmptySlicePtr: return true case OpStructHeadArrayPtr: return true case OpStructHeadOmitEmptyArrayPtr: return true case OpStructHeadMapPtr: return true case OpStructHeadOmitEmptyMapPtr: return true } return false } func (t OpType) IsMultipleOpField() bool { switch t { case OpStructField: return true case OpStructFieldSlice: return true case OpStructFieldArray: return true case OpStructFieldMap: return true case OpStructFieldStruct: return true case OpStructFieldOmitEmpty: return true case OpStructFieldOmitEmptySlice: return true case OpStructFieldOmitEmptyArray: return true case OpStructFieldOmitEmptyMap: return true case OpStructFieldOmitEmptyStruct: return true case OpStructFieldSlicePtr: return true case OpStructFieldOmitEmptySlicePtr: return true case OpStructFieldArrayPtr: return true case OpStructFieldOmitEmptyArrayPtr: return true case OpStructFieldMapPtr: return true case OpStructFieldOmitEmptyMapPtr: return true } return false } type OpcodeSet struct { Type *runtime.Type NoescapeKeyCode *Opcode EscapeKeyCode *Opcode InterfaceNoescapeKeyCode *Opcode InterfaceEscapeKeyCode *Opcode CodeLength int EndCode *Opcode Code Code QueryCache map[string]*OpcodeSet cacheMu sync.RWMutex } func (s *OpcodeSet) getQueryCache(hash string) *OpcodeSet { s.cacheMu.RLock() codeSet := s.QueryCache[hash] s.cacheMu.RUnlock() return codeSet } func (s *OpcodeSet) setQueryCache(hash string, codeSet *OpcodeSet) { s.cacheMu.Lock() s.QueryCache[hash] = codeSet s.cacheMu.Unlock() } type CompiledCode struct { Code *Opcode Linked bool // whether recursive code already have linked CurLen uintptr NextLen uintptr } const StartDetectingCyclesAfter = 1000 func Load(base uintptr, idx uintptr) uintptr { addr := base + idx return **(**uintptr)(unsafe.Pointer(&addr)) } func Store(base uintptr, idx uintptr, p uintptr) { addr := base + idx **(**uintptr)(unsafe.Pointer(&addr)) = p } func LoadNPtr(base uintptr, idx uintptr, ptrNum int) uintptr { addr := base + idx p := **(**uintptr)(unsafe.Pointer(&addr)) if p == 0 { return 0 } return PtrToPtr(p) /* for i := 0; i < ptrNum; i++ { if p == 0 { return p } p = PtrToPtr(p) } return p */ } func PtrToUint64(p uintptr) uint64 { return **(**uint64)(unsafe.Pointer(&p)) } func PtrToFloat32(p uintptr) float32 { return **(**float32)(unsafe.Pointer(&p)) } func PtrToFloat64(p uintptr) float64 { return **(**float64)(unsafe.Pointer(&p)) } func PtrToBool(p uintptr) bool { return **(**bool)(unsafe.Pointer(&p)) } func PtrToBytes(p uintptr) []byte { return **(**[]byte)(unsafe.Pointer(&p)) } func PtrToNumber(p uintptr) json.Number { return **(**json.Number)(unsafe.Pointer(&p)) } func PtrToString(p uintptr) string { return **(**string)(unsafe.Pointer(&p)) } func PtrToSlice(p uintptr) *runtime.SliceHeader { return *(**runtime.SliceHeader)(unsafe.Pointer(&p)) } func PtrToPtr(p uintptr) uintptr { return uintptr(**(**unsafe.Pointer)(unsafe.Pointer(&p))) } func PtrToNPtr(p uintptr, ptrNum int) uintptr { for i := 0; i < ptrNum; i++ { if p == 0 { return 0 } p = PtrToPtr(p) } return p } func PtrToUnsafePtr(p uintptr) unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Pointer(&p)) } func PtrToInterface(code *Opcode, p uintptr) interface{} { return *(*interface{})(unsafe.Pointer(&emptyInterface{ typ: code.Type, ptr: *(*unsafe.Pointer)(unsafe.Pointer(&p)), })) } func ErrUnsupportedValue(code *Opcode, ptr uintptr) *errors.UnsupportedValueError { v := *(*interface{})(unsafe.Pointer(&emptyInterface{ typ: code.Type, ptr: *(*unsafe.Pointer)(unsafe.Pointer(&ptr)), })) return &errors.UnsupportedValueError{ Value: reflect.ValueOf(v), Str: fmt.Sprintf("encountered a cycle via %s", code.Type), } } func ErrUnsupportedFloat(v float64) *errors.UnsupportedValueError { return &errors.UnsupportedValueError{ Value: reflect.ValueOf(v), Str: strconv.FormatFloat(v, 'g', -1, 64), } } func ErrMarshalerWithCode(code *Opcode, err error) *errors.MarshalerError { return &errors.MarshalerError{ Type: runtime.RType2Type(code.Type), Err: err, } } type emptyInterface struct { typ *runtime.Type ptr unsafe.Pointer } type MapItem struct { Key []byte Value []byte } type Mapslice struct { Items []MapItem } func (m *Mapslice) Len() int { return len(m.Items) } func (m *Mapslice) Less(i, j int) bool { return bytes.Compare(m.Items[i].Key, m.Items[j].Key) < 0 } func (m *Mapslice) Swap(i, j int) { m.Items[i], m.Items[j] = m.Items[j], m.Items[i] } //nolint:structcheck,unused type mapIter struct { key unsafe.Pointer elem unsafe.Pointer t unsafe.Pointer h unsafe.Pointer buckets unsafe.Pointer bptr unsafe.Pointer overflow unsafe.Pointer oldoverflow unsafe.Pointer startBucket uintptr offset uint8 wrapped bool B uint8 i uint8 bucket uintptr checkBucket uintptr } type MapContext struct { Start int First int Idx int Slice *Mapslice Buf []byte Len int Iter mapIter } var mapContextPool = sync.Pool{ New: func() interface{} { return &MapContext{ Slice: &Mapslice{}, } }, } func NewMapContext(mapLen int, unorderedMap bool) *MapContext { ctx := mapContextPool.Get().(*MapContext) if !unorderedMap { if len(ctx.Slice.Items) < mapLen { ctx.Slice.Items = make([]MapItem, mapLen) } else { ctx.Slice.Items = ctx.Slice.Items[:mapLen] } } ctx.Buf = ctx.Buf[:0] ctx.Iter = mapIter{} ctx.Idx = 0 ctx.Len = mapLen return ctx } func ReleaseMapContext(c *MapContext) { mapContextPool.Put(c) } //go:linkname MapIterInit runtime.mapiterinit //go:noescape func MapIterInit(mapType *runtime.Type, m unsafe.Pointer, it *mapIter) //go:linkname MapIterKey reflect.mapiterkey //go:noescape func MapIterKey(it *mapIter) unsafe.Pointer //go:linkname MapIterNext reflect.mapiternext //go:noescape func MapIterNext(it *mapIter) //go:linkname MapLen reflect.maplen //go:noescape func MapLen(m unsafe.Pointer) int func AppendByteSlice(_ *RuntimeContext, b []byte, src []byte) []byte { if src == nil { return append(b, `null`...) } encodedLen := base64.StdEncoding.EncodedLen(len(src)) b = append(b, '"') pos := len(b) remainLen := cap(b[pos:]) var buf []byte if remainLen > encodedLen { buf = b[pos : pos+encodedLen] } else { buf = make([]byte, encodedLen) } base64.StdEncoding.Encode(buf, src) return append(append(b, buf...), '"') } func AppendFloat32(_ *RuntimeContext, b []byte, v float32) []byte { f64 := float64(v) abs := math.Abs(f64) fmt := byte('f') // Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right. if abs != 0 { f32 := float32(abs) if f32 < 1e-6 || f32 >= 1e21 { fmt = 'e' } } return strconv.AppendFloat(b, f64, fmt, -1, 32) } func AppendFloat64(_ *RuntimeContext, b []byte, v float64) []byte { abs := math.Abs(v) fmt := byte('f') // Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right. if abs != 0 { if abs < 1e-6 || abs >= 1e21 { fmt = 'e' } } return strconv.AppendFloat(b, v, fmt, -1, 64) } func AppendBool(_ *RuntimeContext, b []byte, v bool) []byte { if v { return append(b, "true"...) } return append(b, "false"...) } var ( floatTable = [256]bool{ '0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true, '7': true, '8': true, '9': true, '.': true, 'e': true, 'E': true, '+': true, '-': true, } ) func AppendNumber(_ *RuntimeContext, b []byte, n json.Number) ([]byte, error) { if len(n) == 0 { return append(b, '0'), nil } for i := 0; i < len(n); i++ { if !floatTable[n[i]] { return nil, fmt.Errorf("json: invalid number literal %q", n) } } b = append(b, n...) return b, nil } func AppendMarshalJSON(ctx *RuntimeContext, code *Opcode, b []byte, v interface{}) ([]byte, error) { rv := reflect.ValueOf(v) // convert by dynamic interface type if (code.Flags & AddrForMarshalerFlags) != 0 { if rv.CanAddr() { rv = rv.Addr() } else { newV := reflect.New(rv.Type()) newV.Elem().Set(rv) rv = newV } } v = rv.Interface() var bb []byte if (code.Flags & MarshalerContextFlags) != 0 { marshaler, ok := v.(marshalerContext) if !ok { return AppendNull(ctx, b), nil } stdctx := ctx.Option.Context if ctx.Option.Flag&FieldQueryOption != 0 { stdctx = SetFieldQueryToContext(stdctx, code.FieldQuery) } b, err := marshaler.MarshalJSON(stdctx) if err != nil { return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} } bb = b } else { marshaler, ok := v.(json.Marshaler) if !ok { return AppendNull(ctx, b), nil } b, err := marshaler.MarshalJSON() if err != nil { return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} } bb = b } marshalBuf := ctx.MarshalBuf[:0] marshalBuf = append(append(marshalBuf, bb...), nul) compactedBuf, err := compact(b, marshalBuf, (ctx.Option.Flag&HTMLEscapeOption) != 0) if err != nil { return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} } ctx.MarshalBuf = marshalBuf return compactedBuf, nil } func AppendMarshalJSONIndent(ctx *RuntimeContext, code *Opcode, b []byte, v interface{}) ([]byte, error) { rv := reflect.ValueOf(v) // convert by dynamic interface type if (code.Flags & AddrForMarshalerFlags) != 0 { if rv.CanAddr() { rv = rv.Addr() } else { newV := reflect.New(rv.Type()) newV.Elem().Set(rv) rv = newV } } v = rv.Interface() var bb []byte if (code.Flags & MarshalerContextFlags) != 0 { marshaler, ok := v.(marshalerContext) if !ok { return AppendNull(ctx, b), nil } b, err := marshaler.MarshalJSON(ctx.Option.Context) if err != nil { return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} } bb = b } else { marshaler, ok := v.(json.Marshaler) if !ok { return AppendNull(ctx, b), nil } b, err := marshaler.MarshalJSON() if err != nil { return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} } bb = b } marshalBuf := ctx.MarshalBuf[:0] marshalBuf = append(append(marshalBuf, bb...), nul) indentedBuf, err := doIndent( b, marshalBuf, string(ctx.Prefix)+strings.Repeat(string(ctx.IndentStr), int(ctx.BaseIndent+code.Indent)), string(ctx.IndentStr), (ctx.Option.Flag&HTMLEscapeOption) != 0, ) if err != nil { return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} } ctx.MarshalBuf = marshalBuf return indentedBuf, nil } func AppendMarshalText(ctx *RuntimeContext, code *Opcode, b []byte, v interface{}) ([]byte, error) { rv := reflect.ValueOf(v) // convert by dynamic interface type if (code.Flags & AddrForMarshalerFlags) != 0 { if rv.CanAddr() { rv = rv.Addr() } else { newV := reflect.New(rv.Type()) newV.Elem().Set(rv) rv = newV } } v = rv.Interface() marshaler, ok := v.(encoding.TextMarshaler) if !ok { return AppendNull(ctx, b), nil } bytes, err := marshaler.MarshalText() if err != nil { return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} } return AppendString(ctx, b, *(*string)(unsafe.Pointer(&bytes))), nil } func AppendMarshalTextIndent(ctx *RuntimeContext, code *Opcode, b []byte, v interface{}) ([]byte, error) { rv := reflect.ValueOf(v) // convert by dynamic interface type if (code.Flags & AddrForMarshalerFlags) != 0 { if rv.CanAddr() { rv = rv.Addr() } else { newV := reflect.New(rv.Type()) newV.Elem().Set(rv) rv = newV } } v = rv.Interface() marshaler, ok := v.(encoding.TextMarshaler) if !ok { return AppendNull(ctx, b), nil } bytes, err := marshaler.MarshalText() if err != nil { return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} } return AppendString(ctx, b, *(*string)(unsafe.Pointer(&bytes))), nil } func AppendNull(_ *RuntimeContext, b []byte) []byte { return append(b, "null"...) } func AppendComma(_ *RuntimeContext, b []byte) []byte { return append(b, ',') } func AppendCommaIndent(_ *RuntimeContext, b []byte) []byte { return append(b, ',', '\n') } func AppendStructEnd(_ *RuntimeContext, b []byte) []byte { return append(b, '}', ',') } func AppendStructEndIndent(ctx *RuntimeContext, code *Opcode, b []byte) []byte { b = append(b, '\n') b = append(b, ctx.Prefix...) indentNum := ctx.BaseIndent + code.Indent - 1 for i := uint32(0); i < indentNum; i++ { b = append(b, ctx.IndentStr...) } return append(b, '}', ',', '\n') } func AppendIndent(ctx *RuntimeContext, b []byte, indent uint32) []byte { b = append(b, ctx.Prefix...) indentNum := ctx.BaseIndent + indent for i := uint32(0); i < indentNum; i++ { b = append(b, ctx.IndentStr...) } return b } func IsNilForMarshaler(v interface{}) bool { rv := reflect.ValueOf(v) switch rv.Kind() { case reflect.Bool: return !rv.Bool() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return rv.Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return rv.Uint() == 0 case reflect.Float32, reflect.Float64: return math.Float64bits(rv.Float()) == 0 case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Func: return rv.IsNil() case reflect.Slice: return rv.IsNil() || rv.Len() == 0 case reflect.String: return rv.Len() == 0 } return false } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/indent.go ================================================ package encoder import ( "bytes" "fmt" "github.com/goccy/go-json/internal/errors" ) func takeIndentSrcRuntimeContext(src []byte) (*RuntimeContext, []byte) { ctx := TakeRuntimeContext() buf := ctx.Buf[:0] buf = append(append(buf, src...), nul) ctx.Buf = buf return ctx, buf } func Indent(buf *bytes.Buffer, src []byte, prefix, indentStr string) error { if len(src) == 0 { return errors.ErrUnexpectedEndOfJSON("", 0) } srcCtx, srcBuf := takeIndentSrcRuntimeContext(src) dstCtx := TakeRuntimeContext() dst := dstCtx.Buf[:0] dst, err := indentAndWrite(buf, dst, srcBuf, prefix, indentStr) if err != nil { ReleaseRuntimeContext(srcCtx) ReleaseRuntimeContext(dstCtx) return err } dstCtx.Buf = dst ReleaseRuntimeContext(srcCtx) ReleaseRuntimeContext(dstCtx) return nil } func indentAndWrite(buf *bytes.Buffer, dst []byte, src []byte, prefix, indentStr string) ([]byte, error) { dst, err := doIndent(dst, src, prefix, indentStr, false) if err != nil { return nil, err } if _, err := buf.Write(dst); err != nil { return nil, err } return dst, nil } func doIndent(dst, src []byte, prefix, indentStr string, escape bool) ([]byte, error) { buf, cursor, err := indentValue(dst, src, 0, 0, []byte(prefix), []byte(indentStr), escape) if err != nil { return nil, err } if err := validateEndBuf(src, cursor); err != nil { return nil, err } return buf, nil } func indentValue( dst []byte, src []byte, indentNum int, cursor int64, prefix []byte, indentBytes []byte, escape bool) ([]byte, int64, error) { for { switch src[cursor] { case ' ', '\t', '\n', '\r': cursor++ continue case '{': return indentObject(dst, src, indentNum, cursor, prefix, indentBytes, escape) case '}': return nil, 0, errors.ErrSyntax("unexpected character '}'", cursor) case '[': return indentArray(dst, src, indentNum, cursor, prefix, indentBytes, escape) case ']': return nil, 0, errors.ErrSyntax("unexpected character ']'", cursor) case '"': return compactString(dst, src, cursor, escape) case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return compactNumber(dst, src, cursor) case 't': return compactTrue(dst, src, cursor) case 'f': return compactFalse(dst, src, cursor) case 'n': return compactNull(dst, src, cursor) default: return nil, 0, errors.ErrSyntax(fmt.Sprintf("unexpected character '%c'", src[cursor]), cursor) } } } func indentObject( dst []byte, src []byte, indentNum int, cursor int64, prefix []byte, indentBytes []byte, escape bool) ([]byte, int64, error) { if src[cursor] == '{' { dst = append(dst, '{') } else { return nil, 0, errors.ErrExpected("expected { character for object value", cursor) } cursor = skipWhiteSpace(src, cursor+1) if src[cursor] == '}' { dst = append(dst, '}') return dst, cursor + 1, nil } indentNum++ var err error for { dst = append(append(dst, '\n'), prefix...) for i := 0; i < indentNum; i++ { dst = append(dst, indentBytes...) } cursor = skipWhiteSpace(src, cursor) dst, cursor, err = compactString(dst, src, cursor, escape) if err != nil { return nil, 0, err } cursor = skipWhiteSpace(src, cursor) if src[cursor] != ':' { return nil, 0, errors.ErrSyntax( fmt.Sprintf("invalid character '%c' after object key", src[cursor]), cursor+1, ) } dst = append(dst, ':', ' ') dst, cursor, err = indentValue(dst, src, indentNum, cursor+1, prefix, indentBytes, escape) if err != nil { return nil, 0, err } cursor = skipWhiteSpace(src, cursor) switch src[cursor] { case '}': dst = append(append(dst, '\n'), prefix...) for i := 0; i < indentNum-1; i++ { dst = append(dst, indentBytes...) } dst = append(dst, '}') cursor++ return dst, cursor, nil case ',': dst = append(dst, ',') default: return nil, 0, errors.ErrSyntax( fmt.Sprintf("invalid character '%c' after object key:value pair", src[cursor]), cursor+1, ) } cursor++ } } func indentArray( dst []byte, src []byte, indentNum int, cursor int64, prefix []byte, indentBytes []byte, escape bool) ([]byte, int64, error) { if src[cursor] == '[' { dst = append(dst, '[') } else { return nil, 0, errors.ErrExpected("expected [ character for array value", cursor) } cursor = skipWhiteSpace(src, cursor+1) if src[cursor] == ']' { dst = append(dst, ']') return dst, cursor + 1, nil } indentNum++ var err error for { dst = append(append(dst, '\n'), prefix...) for i := 0; i < indentNum; i++ { dst = append(dst, indentBytes...) } dst, cursor, err = indentValue(dst, src, indentNum, cursor, prefix, indentBytes, escape) if err != nil { return nil, 0, err } cursor = skipWhiteSpace(src, cursor) switch src[cursor] { case ']': dst = append(append(dst, '\n'), prefix...) for i := 0; i < indentNum-1; i++ { dst = append(dst, indentBytes...) } dst = append(dst, ']') cursor++ return dst, cursor, nil case ',': dst = append(dst, ',') default: return nil, 0, errors.ErrSyntax( fmt.Sprintf("invalid character '%c' after array value", src[cursor]), cursor+1, ) } cursor++ } } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/int.go ================================================ package encoder import ( "unsafe" ) var endianness int func init() { var b [2]byte *(*uint16)(unsafe.Pointer(&b)) = uint16(0xABCD) switch b[0] { case 0xCD: endianness = 0 // LE case 0xAB: endianness = 1 // BE default: panic("could not determine endianness") } } // "00010203...96979899" cast to []uint16 var intLELookup = [100]uint16{ 0x3030, 0x3130, 0x3230, 0x3330, 0x3430, 0x3530, 0x3630, 0x3730, 0x3830, 0x3930, 0x3031, 0x3131, 0x3231, 0x3331, 0x3431, 0x3531, 0x3631, 0x3731, 0x3831, 0x3931, 0x3032, 0x3132, 0x3232, 0x3332, 0x3432, 0x3532, 0x3632, 0x3732, 0x3832, 0x3932, 0x3033, 0x3133, 0x3233, 0x3333, 0x3433, 0x3533, 0x3633, 0x3733, 0x3833, 0x3933, 0x3034, 0x3134, 0x3234, 0x3334, 0x3434, 0x3534, 0x3634, 0x3734, 0x3834, 0x3934, 0x3035, 0x3135, 0x3235, 0x3335, 0x3435, 0x3535, 0x3635, 0x3735, 0x3835, 0x3935, 0x3036, 0x3136, 0x3236, 0x3336, 0x3436, 0x3536, 0x3636, 0x3736, 0x3836, 0x3936, 0x3037, 0x3137, 0x3237, 0x3337, 0x3437, 0x3537, 0x3637, 0x3737, 0x3837, 0x3937, 0x3038, 0x3138, 0x3238, 0x3338, 0x3438, 0x3538, 0x3638, 0x3738, 0x3838, 0x3938, 0x3039, 0x3139, 0x3239, 0x3339, 0x3439, 0x3539, 0x3639, 0x3739, 0x3839, 0x3939, } var intBELookup = [100]uint16{ 0x3030, 0x3031, 0x3032, 0x3033, 0x3034, 0x3035, 0x3036, 0x3037, 0x3038, 0x3039, 0x3130, 0x3131, 0x3132, 0x3133, 0x3134, 0x3135, 0x3136, 0x3137, 0x3138, 0x3139, 0x3230, 0x3231, 0x3232, 0x3233, 0x3234, 0x3235, 0x3236, 0x3237, 0x3238, 0x3239, 0x3330, 0x3331, 0x3332, 0x3333, 0x3334, 0x3335, 0x3336, 0x3337, 0x3338, 0x3339, 0x3430, 0x3431, 0x3432, 0x3433, 0x3434, 0x3435, 0x3436, 0x3437, 0x3438, 0x3439, 0x3530, 0x3531, 0x3532, 0x3533, 0x3534, 0x3535, 0x3536, 0x3537, 0x3538, 0x3539, 0x3630, 0x3631, 0x3632, 0x3633, 0x3634, 0x3635, 0x3636, 0x3637, 0x3638, 0x3639, 0x3730, 0x3731, 0x3732, 0x3733, 0x3734, 0x3735, 0x3736, 0x3737, 0x3738, 0x3739, 0x3830, 0x3831, 0x3832, 0x3833, 0x3834, 0x3835, 0x3836, 0x3837, 0x3838, 0x3839, 0x3930, 0x3931, 0x3932, 0x3933, 0x3934, 0x3935, 0x3936, 0x3937, 0x3938, 0x3939, } var intLookup = [2]*[100]uint16{&intLELookup, &intBELookup} func numMask(numBitSize uint8) uint64 { return 1<>(code.NumBitSize-1))&1 == 1 if !negative { if n < 10 { return append(out, byte(n+'0')) } else if n < 100 { u := intLELookup[n] return append(out, byte(u), byte(u>>8)) } } else { n = -n & mask } lookup := intLookup[endianness] var b [22]byte u := (*[11]uint16)(unsafe.Pointer(&b)) i := 11 for n >= 100 { j := n % 100 n /= 100 i-- u[i] = lookup[j] } i-- u[i] = lookup[n] i *= 2 // convert to byte index if n < 10 { i++ // remove leading zero } if negative { i-- b[i] = '-' } return append(out, b[i:]...) } func AppendUint(_ *RuntimeContext, out []byte, p uintptr, code *Opcode) []byte { var u64 uint64 switch code.NumBitSize { case 8: u64 = (uint64)(**(**uint8)(unsafe.Pointer(&p))) case 16: u64 = (uint64)(**(**uint16)(unsafe.Pointer(&p))) case 32: u64 = (uint64)(**(**uint32)(unsafe.Pointer(&p))) case 64: u64 = **(**uint64)(unsafe.Pointer(&p)) } mask := numMask(code.NumBitSize) n := u64 & mask if n < 10 { return append(out, byte(n+'0')) } else if n < 100 { u := intLELookup[n] return append(out, byte(u), byte(u>>8)) } lookup := intLookup[endianness] var b [22]byte u := (*[11]uint16)(unsafe.Pointer(&b)) i := 11 for n >= 100 { j := n % 100 n /= 100 i-- u[i] = lookup[j] } i-- u[i] = lookup[n] i *= 2 // convert to byte index if n < 10 { i++ // remove leading zero } return append(out, b[i:]...) } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/map112.go ================================================ //go:build !go1.13 // +build !go1.13 package encoder import "unsafe" //go:linkname MapIterValue reflect.mapitervalue func MapIterValue(it *mapIter) unsafe.Pointer ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/map113.go ================================================ //go:build go1.13 // +build go1.13 package encoder import "unsafe" //go:linkname MapIterValue reflect.mapiterelem func MapIterValue(it *mapIter) unsafe.Pointer ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/opcode.go ================================================ package encoder import ( "bytes" "fmt" "sort" "strings" "unsafe" "github.com/goccy/go-json/internal/runtime" ) const uintptrSize = 4 << (^uintptr(0) >> 63) type OpFlags uint16 const ( AnonymousHeadFlags OpFlags = 1 << 0 AnonymousKeyFlags OpFlags = 1 << 1 IndirectFlags OpFlags = 1 << 2 IsTaggedKeyFlags OpFlags = 1 << 3 NilCheckFlags OpFlags = 1 << 4 AddrForMarshalerFlags OpFlags = 1 << 5 IsNextOpPtrTypeFlags OpFlags = 1 << 6 IsNilableTypeFlags OpFlags = 1 << 7 MarshalerContextFlags OpFlags = 1 << 8 NonEmptyInterfaceFlags OpFlags = 1 << 9 ) type Opcode struct { Op OpType // operation type Idx uint32 // offset to access ptr Next *Opcode // next opcode End *Opcode // array/slice/struct/map end NextField *Opcode // next struct field Key string // struct field key Offset uint32 // offset size from struct header PtrNum uint8 // pointer number: e.g. double pointer is 2. NumBitSize uint8 Flags OpFlags Type *runtime.Type // go type Jmp *CompiledCode // for recursive call FieldQuery *FieldQuery // field query for Interface / MarshalJSON / MarshalText ElemIdx uint32 // offset to access array/slice elem Length uint32 // offset to access slice length or array length Indent uint32 // indent number Size uint32 // array/slice elem size DisplayIdx uint32 // opcode index DisplayKey string // key text to display } func (c *Opcode) Validate() error { var prevIdx uint32 for code := c; !code.IsEnd(); { if prevIdx != 0 { if code.DisplayIdx != prevIdx+1 { return fmt.Errorf( "invalid index. previous display index is %d but next is %d. dump = %s", prevIdx, code.DisplayIdx, c.Dump(), ) } } prevIdx = code.DisplayIdx code = code.IterNext() } return nil } func (c *Opcode) IterNext() *Opcode { if c == nil { return nil } switch c.Op.CodeType() { case CodeArrayElem, CodeSliceElem, CodeMapKey: return c.End default: return c.Next } } func (c *Opcode) IsEnd() bool { if c == nil { return true } return c.Op == OpEnd || c.Op == OpInterfaceEnd || c.Op == OpRecursiveEnd } func (c *Opcode) MaxIdx() uint32 { max := uint32(0) for _, value := range []uint32{ c.Idx, c.ElemIdx, c.Length, c.Size, } { if max < value { max = value } } return max } func (c *Opcode) ToHeaderType(isString bool) OpType { switch c.Op { case OpInt: if isString { return OpStructHeadIntString } return OpStructHeadInt case OpIntPtr: if isString { return OpStructHeadIntPtrString } return OpStructHeadIntPtr case OpUint: if isString { return OpStructHeadUintString } return OpStructHeadUint case OpUintPtr: if isString { return OpStructHeadUintPtrString } return OpStructHeadUintPtr case OpFloat32: if isString { return OpStructHeadFloat32String } return OpStructHeadFloat32 case OpFloat32Ptr: if isString { return OpStructHeadFloat32PtrString } return OpStructHeadFloat32Ptr case OpFloat64: if isString { return OpStructHeadFloat64String } return OpStructHeadFloat64 case OpFloat64Ptr: if isString { return OpStructHeadFloat64PtrString } return OpStructHeadFloat64Ptr case OpString: if isString { return OpStructHeadStringString } return OpStructHeadString case OpStringPtr: if isString { return OpStructHeadStringPtrString } return OpStructHeadStringPtr case OpNumber: if isString { return OpStructHeadNumberString } return OpStructHeadNumber case OpNumberPtr: if isString { return OpStructHeadNumberPtrString } return OpStructHeadNumberPtr case OpBool: if isString { return OpStructHeadBoolString } return OpStructHeadBool case OpBoolPtr: if isString { return OpStructHeadBoolPtrString } return OpStructHeadBoolPtr case OpBytes: return OpStructHeadBytes case OpBytesPtr: return OpStructHeadBytesPtr case OpMap: return OpStructHeadMap case OpMapPtr: c.Op = OpMap return OpStructHeadMapPtr case OpArray: return OpStructHeadArray case OpArrayPtr: c.Op = OpArray return OpStructHeadArrayPtr case OpSlice: return OpStructHeadSlice case OpSlicePtr: c.Op = OpSlice return OpStructHeadSlicePtr case OpMarshalJSON: return OpStructHeadMarshalJSON case OpMarshalJSONPtr: return OpStructHeadMarshalJSONPtr case OpMarshalText: return OpStructHeadMarshalText case OpMarshalTextPtr: return OpStructHeadMarshalTextPtr } return OpStructHead } func (c *Opcode) ToFieldType(isString bool) OpType { switch c.Op { case OpInt: if isString { return OpStructFieldIntString } return OpStructFieldInt case OpIntPtr: if isString { return OpStructFieldIntPtrString } return OpStructFieldIntPtr case OpUint: if isString { return OpStructFieldUintString } return OpStructFieldUint case OpUintPtr: if isString { return OpStructFieldUintPtrString } return OpStructFieldUintPtr case OpFloat32: if isString { return OpStructFieldFloat32String } return OpStructFieldFloat32 case OpFloat32Ptr: if isString { return OpStructFieldFloat32PtrString } return OpStructFieldFloat32Ptr case OpFloat64: if isString { return OpStructFieldFloat64String } return OpStructFieldFloat64 case OpFloat64Ptr: if isString { return OpStructFieldFloat64PtrString } return OpStructFieldFloat64Ptr case OpString: if isString { return OpStructFieldStringString } return OpStructFieldString case OpStringPtr: if isString { return OpStructFieldStringPtrString } return OpStructFieldStringPtr case OpNumber: if isString { return OpStructFieldNumberString } return OpStructFieldNumber case OpNumberPtr: if isString { return OpStructFieldNumberPtrString } return OpStructFieldNumberPtr case OpBool: if isString { return OpStructFieldBoolString } return OpStructFieldBool case OpBoolPtr: if isString { return OpStructFieldBoolPtrString } return OpStructFieldBoolPtr case OpBytes: return OpStructFieldBytes case OpBytesPtr: return OpStructFieldBytesPtr case OpMap: return OpStructFieldMap case OpMapPtr: c.Op = OpMap return OpStructFieldMapPtr case OpArray: return OpStructFieldArray case OpArrayPtr: c.Op = OpArray return OpStructFieldArrayPtr case OpSlice: return OpStructFieldSlice case OpSlicePtr: c.Op = OpSlice return OpStructFieldSlicePtr case OpMarshalJSON: return OpStructFieldMarshalJSON case OpMarshalJSONPtr: return OpStructFieldMarshalJSONPtr case OpMarshalText: return OpStructFieldMarshalText case OpMarshalTextPtr: return OpStructFieldMarshalTextPtr } return OpStructField } func newOpCode(ctx *compileContext, typ *runtime.Type, op OpType) *Opcode { return newOpCodeWithNext(ctx, typ, op, newEndOp(ctx, typ)) } func opcodeOffset(idx int) uint32 { return uint32(idx) * uintptrSize } func getCodeAddrByIdx(head *Opcode, idx uint32) *Opcode { addr := uintptr(unsafe.Pointer(head)) + uintptr(idx)*unsafe.Sizeof(Opcode{}) return *(**Opcode)(unsafe.Pointer(&addr)) } func copyOpcode(code *Opcode) *Opcode { codeNum := ToEndCode(code).DisplayIdx + 1 codeSlice := make([]Opcode, codeNum) head := (*Opcode)((*runtime.SliceHeader)(unsafe.Pointer(&codeSlice)).Data) ptr := head c := code for { *ptr = Opcode{ Op: c.Op, Key: c.Key, PtrNum: c.PtrNum, NumBitSize: c.NumBitSize, Flags: c.Flags, Idx: c.Idx, Offset: c.Offset, Type: c.Type, FieldQuery: c.FieldQuery, DisplayIdx: c.DisplayIdx, DisplayKey: c.DisplayKey, ElemIdx: c.ElemIdx, Length: c.Length, Size: c.Size, Indent: c.Indent, Jmp: c.Jmp, } if c.End != nil { ptr.End = getCodeAddrByIdx(head, c.End.DisplayIdx) } if c.NextField != nil { ptr.NextField = getCodeAddrByIdx(head, c.NextField.DisplayIdx) } if c.Next != nil { ptr.Next = getCodeAddrByIdx(head, c.Next.DisplayIdx) } if c.IsEnd() { break } ptr = getCodeAddrByIdx(head, c.DisplayIdx+1) c = c.IterNext() } return head } func setTotalLengthToInterfaceOp(code *Opcode) { for c := code; !c.IsEnd(); { if c.Op == OpInterface || c.Op == OpInterfacePtr { c.Length = uint32(code.TotalLength()) } c = c.IterNext() } } func ToEndCode(code *Opcode) *Opcode { c := code for !c.IsEnd() { c = c.IterNext() } return c } func copyToInterfaceOpcode(code *Opcode) *Opcode { copied := copyOpcode(code) c := copied c = ToEndCode(c) c.Idx += uintptrSize c.ElemIdx = c.Idx + uintptrSize c.Length = c.Idx + 2*uintptrSize c.Op = OpInterfaceEnd return copied } func newOpCodeWithNext(ctx *compileContext, typ *runtime.Type, op OpType, next *Opcode) *Opcode { return &Opcode{ Op: op, Idx: opcodeOffset(ctx.ptrIndex), Next: next, Type: typ, DisplayIdx: ctx.opcodeIndex, Indent: ctx.indent, } } func newEndOp(ctx *compileContext, typ *runtime.Type) *Opcode { return newOpCodeWithNext(ctx, typ, OpEnd, nil) } func (c *Opcode) TotalLength() int { var idx int code := c for !code.IsEnd() { maxIdx := int(code.MaxIdx() / uintptrSize) if idx < maxIdx { idx = maxIdx } if code.Op == OpRecursiveEnd { break } code = code.IterNext() } maxIdx := int(code.MaxIdx() / uintptrSize) if idx < maxIdx { idx = maxIdx } return idx + 1 } func (c *Opcode) dumpHead(code *Opcode) string { var length uint32 if code.Op.CodeType() == CodeArrayHead { length = code.Length } else { length = code.Length / uintptrSize } return fmt.Sprintf( `[%03d]%s%s ([idx:%d][elemIdx:%d][length:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, code.ElemIdx/uintptrSize, length, ) } func (c *Opcode) dumpMapHead(code *Opcode) string { return fmt.Sprintf( `[%03d]%s%s ([idx:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, ) } func (c *Opcode) dumpMapEnd(code *Opcode) string { return fmt.Sprintf( `[%03d]%s%s ([idx:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, ) } func (c *Opcode) dumpElem(code *Opcode) string { var length uint32 if code.Op.CodeType() == CodeArrayElem { length = code.Length } else { length = code.Length / uintptrSize } return fmt.Sprintf( `[%03d]%s%s ([idx:%d][elemIdx:%d][length:%d][size:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, code.ElemIdx/uintptrSize, length, code.Size, ) } func (c *Opcode) dumpField(code *Opcode) string { return fmt.Sprintf( `[%03d]%s%s ([idx:%d][key:%s][offset:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, code.DisplayKey, code.Offset, ) } func (c *Opcode) dumpKey(code *Opcode) string { return fmt.Sprintf( `[%03d]%s%s ([idx:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, ) } func (c *Opcode) dumpValue(code *Opcode) string { return fmt.Sprintf( `[%03d]%s%s ([idx:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, ) } func (c *Opcode) Dump() string { codes := []string{} for code := c; !code.IsEnd(); { switch code.Op.CodeType() { case CodeSliceHead: codes = append(codes, c.dumpHead(code)) code = code.Next case CodeMapHead: codes = append(codes, c.dumpMapHead(code)) code = code.Next case CodeArrayElem, CodeSliceElem: codes = append(codes, c.dumpElem(code)) code = code.End case CodeMapKey: codes = append(codes, c.dumpKey(code)) code = code.End case CodeMapValue: codes = append(codes, c.dumpValue(code)) code = code.Next case CodeMapEnd: codes = append(codes, c.dumpMapEnd(code)) code = code.Next case CodeStructField: codes = append(codes, c.dumpField(code)) code = code.Next case CodeStructEnd: codes = append(codes, c.dumpField(code)) code = code.Next default: codes = append(codes, fmt.Sprintf( "[%03d]%s%s ([idx:%d])", code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, )) code = code.Next } } return strings.Join(codes, "\n") } func (c *Opcode) DumpDOT() string { type edge struct { from, to *Opcode label string weight int } var edges []edge b := &bytes.Buffer{} fmt.Fprintf(b, "digraph \"%p\" {\n", c.Type) fmt.Fprintln(b, "mclimit=1.5;\nrankdir=TD;\nordering=out;\nnode[shape=box];") for code := c; !code.IsEnd(); { label := code.Op.String() fmt.Fprintf(b, "\"%p\" [label=%q];\n", code, label) if p := code.Next; p != nil { edges = append(edges, edge{ from: code, to: p, label: "Next", weight: 10, }) } if p := code.NextField; p != nil { edges = append(edges, edge{ from: code, to: p, label: "NextField", weight: 2, }) } if p := code.End; p != nil { edges = append(edges, edge{ from: code, to: p, label: "End", weight: 1, }) } if p := code.Jmp; p != nil { edges = append(edges, edge{ from: code, to: p.Code, label: "Jmp", weight: 1, }) } switch code.Op.CodeType() { case CodeSliceHead: code = code.Next case CodeMapHead: code = code.Next case CodeArrayElem, CodeSliceElem: code = code.End case CodeMapKey: code = code.End case CodeMapValue: code = code.Next case CodeMapEnd: code = code.Next case CodeStructField: code = code.Next case CodeStructEnd: code = code.Next default: code = code.Next } if code.IsEnd() { fmt.Fprintf(b, "\"%p\" [label=%q];\n", code, code.Op.String()) } } sort.Slice(edges, func(i, j int) bool { return edges[i].to.DisplayIdx < edges[j].to.DisplayIdx }) for _, e := range edges { fmt.Fprintf(b, "\"%p\" -> \"%p\" [label=%q][weight=%d];\n", e.from, e.to, e.label, e.weight) } fmt.Fprint(b, "}") return b.String() } func newSliceHeaderCode(ctx *compileContext, typ *runtime.Type) *Opcode { idx := opcodeOffset(ctx.ptrIndex) ctx.incPtrIndex() elemIdx := opcodeOffset(ctx.ptrIndex) ctx.incPtrIndex() length := opcodeOffset(ctx.ptrIndex) return &Opcode{ Op: OpSlice, Type: typ, Idx: idx, DisplayIdx: ctx.opcodeIndex, ElemIdx: elemIdx, Length: length, Indent: ctx.indent, } } func newSliceElemCode(ctx *compileContext, typ *runtime.Type, head *Opcode, size uintptr) *Opcode { return &Opcode{ Op: OpSliceElem, Type: typ, Idx: head.Idx, DisplayIdx: ctx.opcodeIndex, ElemIdx: head.ElemIdx, Length: head.Length, Indent: ctx.indent, Size: uint32(size), } } func newArrayHeaderCode(ctx *compileContext, typ *runtime.Type, alen int) *Opcode { idx := opcodeOffset(ctx.ptrIndex) ctx.incPtrIndex() elemIdx := opcodeOffset(ctx.ptrIndex) return &Opcode{ Op: OpArray, Type: typ, Idx: idx, DisplayIdx: ctx.opcodeIndex, ElemIdx: elemIdx, Indent: ctx.indent, Length: uint32(alen), } } func newArrayElemCode(ctx *compileContext, typ *runtime.Type, head *Opcode, length int, size uintptr) *Opcode { return &Opcode{ Op: OpArrayElem, Type: typ, Idx: head.Idx, DisplayIdx: ctx.opcodeIndex, ElemIdx: head.ElemIdx, Length: uint32(length), Indent: ctx.indent, Size: uint32(size), } } func newMapHeaderCode(ctx *compileContext, typ *runtime.Type) *Opcode { idx := opcodeOffset(ctx.ptrIndex) ctx.incPtrIndex() return &Opcode{ Op: OpMap, Type: typ, Idx: idx, DisplayIdx: ctx.opcodeIndex, Indent: ctx.indent, } } func newMapKeyCode(ctx *compileContext, typ *runtime.Type, head *Opcode) *Opcode { return &Opcode{ Op: OpMapKey, Type: typ, Idx: head.Idx, DisplayIdx: ctx.opcodeIndex, Indent: ctx.indent, } } func newMapValueCode(ctx *compileContext, typ *runtime.Type, head *Opcode) *Opcode { return &Opcode{ Op: OpMapValue, Type: typ, Idx: head.Idx, DisplayIdx: ctx.opcodeIndex, Indent: ctx.indent, } } func newMapEndCode(ctx *compileContext, typ *runtime.Type, head *Opcode) *Opcode { return &Opcode{ Op: OpMapEnd, Type: typ, Idx: head.Idx, DisplayIdx: ctx.opcodeIndex, Indent: ctx.indent, Next: newEndOp(ctx, typ), } } func newRecursiveCode(ctx *compileContext, typ *runtime.Type, jmp *CompiledCode) *Opcode { return &Opcode{ Op: OpRecursive, Type: typ, Idx: opcodeOffset(ctx.ptrIndex), Next: newEndOp(ctx, typ), DisplayIdx: ctx.opcodeIndex, Indent: ctx.indent, Jmp: jmp, } } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/option.go ================================================ package encoder import ( "context" "io" ) type OptionFlag uint8 const ( HTMLEscapeOption OptionFlag = 1 << iota IndentOption UnorderedMapOption DebugOption ColorizeOption ContextOption NormalizeUTF8Option FieldQueryOption ) type Option struct { Flag OptionFlag ColorScheme *ColorScheme Context context.Context DebugOut io.Writer DebugDOTOut io.WriteCloser } type EncodeFormat struct { Header string Footer string } type EncodeFormatScheme struct { Int EncodeFormat Uint EncodeFormat Float EncodeFormat Bool EncodeFormat String EncodeFormat Binary EncodeFormat ObjectKey EncodeFormat Null EncodeFormat } type ( ColorScheme = EncodeFormatScheme ColorFormat = EncodeFormat ) ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/optype.go ================================================ // Code generated by internal/cmd/generator. DO NOT EDIT! package encoder import ( "strings" ) type CodeType int const ( CodeOp CodeType = 0 CodeArrayHead CodeType = 1 CodeArrayElem CodeType = 2 CodeSliceHead CodeType = 3 CodeSliceElem CodeType = 4 CodeMapHead CodeType = 5 CodeMapKey CodeType = 6 CodeMapValue CodeType = 7 CodeMapEnd CodeType = 8 CodeRecursive CodeType = 9 CodeStructField CodeType = 10 CodeStructEnd CodeType = 11 ) var opTypeStrings = [400]string{ "End", "Interface", "Ptr", "SliceElem", "SliceEnd", "ArrayElem", "ArrayEnd", "MapKey", "MapValue", "MapEnd", "Recursive", "RecursivePtr", "RecursiveEnd", "InterfaceEnd", "Int", "Uint", "Float32", "Float64", "Bool", "String", "Bytes", "Number", "Array", "Map", "Slice", "Struct", "MarshalJSON", "MarshalText", "IntString", "UintString", "Float32String", "Float64String", "BoolString", "StringString", "NumberString", "IntPtr", "UintPtr", "Float32Ptr", "Float64Ptr", "BoolPtr", "StringPtr", "BytesPtr", "NumberPtr", "ArrayPtr", "MapPtr", "SlicePtr", "MarshalJSONPtr", "MarshalTextPtr", "InterfacePtr", "IntPtrString", "UintPtrString", "Float32PtrString", "Float64PtrString", "BoolPtrString", "StringPtrString", "NumberPtrString", "StructHeadInt", "StructHeadOmitEmptyInt", "StructPtrHeadInt", "StructPtrHeadOmitEmptyInt", "StructHeadUint", "StructHeadOmitEmptyUint", "StructPtrHeadUint", "StructPtrHeadOmitEmptyUint", "StructHeadFloat32", "StructHeadOmitEmptyFloat32", "StructPtrHeadFloat32", "StructPtrHeadOmitEmptyFloat32", "StructHeadFloat64", "StructHeadOmitEmptyFloat64", "StructPtrHeadFloat64", "StructPtrHeadOmitEmptyFloat64", "StructHeadBool", "StructHeadOmitEmptyBool", "StructPtrHeadBool", "StructPtrHeadOmitEmptyBool", "StructHeadString", "StructHeadOmitEmptyString", "StructPtrHeadString", "StructPtrHeadOmitEmptyString", "StructHeadBytes", "StructHeadOmitEmptyBytes", "StructPtrHeadBytes", "StructPtrHeadOmitEmptyBytes", "StructHeadNumber", "StructHeadOmitEmptyNumber", "StructPtrHeadNumber", "StructPtrHeadOmitEmptyNumber", "StructHeadArray", "StructHeadOmitEmptyArray", "StructPtrHeadArray", "StructPtrHeadOmitEmptyArray", "StructHeadMap", "StructHeadOmitEmptyMap", "StructPtrHeadMap", "StructPtrHeadOmitEmptyMap", "StructHeadSlice", "StructHeadOmitEmptySlice", "StructPtrHeadSlice", "StructPtrHeadOmitEmptySlice", "StructHeadStruct", "StructHeadOmitEmptyStruct", "StructPtrHeadStruct", "StructPtrHeadOmitEmptyStruct", "StructHeadMarshalJSON", "StructHeadOmitEmptyMarshalJSON", "StructPtrHeadMarshalJSON", "StructPtrHeadOmitEmptyMarshalJSON", "StructHeadMarshalText", "StructHeadOmitEmptyMarshalText", "StructPtrHeadMarshalText", "StructPtrHeadOmitEmptyMarshalText", "StructHeadIntString", "StructHeadOmitEmptyIntString", "StructPtrHeadIntString", "StructPtrHeadOmitEmptyIntString", "StructHeadUintString", "StructHeadOmitEmptyUintString", "StructPtrHeadUintString", "StructPtrHeadOmitEmptyUintString", "StructHeadFloat32String", "StructHeadOmitEmptyFloat32String", "StructPtrHeadFloat32String", "StructPtrHeadOmitEmptyFloat32String", "StructHeadFloat64String", "StructHeadOmitEmptyFloat64String", "StructPtrHeadFloat64String", "StructPtrHeadOmitEmptyFloat64String", "StructHeadBoolString", "StructHeadOmitEmptyBoolString", "StructPtrHeadBoolString", "StructPtrHeadOmitEmptyBoolString", "StructHeadStringString", "StructHeadOmitEmptyStringString", "StructPtrHeadStringString", "StructPtrHeadOmitEmptyStringString", "StructHeadNumberString", "StructHeadOmitEmptyNumberString", "StructPtrHeadNumberString", "StructPtrHeadOmitEmptyNumberString", "StructHeadIntPtr", "StructHeadOmitEmptyIntPtr", "StructPtrHeadIntPtr", "StructPtrHeadOmitEmptyIntPtr", "StructHeadUintPtr", "StructHeadOmitEmptyUintPtr", "StructPtrHeadUintPtr", "StructPtrHeadOmitEmptyUintPtr", "StructHeadFloat32Ptr", "StructHeadOmitEmptyFloat32Ptr", "StructPtrHeadFloat32Ptr", "StructPtrHeadOmitEmptyFloat32Ptr", "StructHeadFloat64Ptr", "StructHeadOmitEmptyFloat64Ptr", "StructPtrHeadFloat64Ptr", "StructPtrHeadOmitEmptyFloat64Ptr", "StructHeadBoolPtr", "StructHeadOmitEmptyBoolPtr", "StructPtrHeadBoolPtr", "StructPtrHeadOmitEmptyBoolPtr", "StructHeadStringPtr", "StructHeadOmitEmptyStringPtr", "StructPtrHeadStringPtr", "StructPtrHeadOmitEmptyStringPtr", "StructHeadBytesPtr", "StructHeadOmitEmptyBytesPtr", "StructPtrHeadBytesPtr", "StructPtrHeadOmitEmptyBytesPtr", "StructHeadNumberPtr", "StructHeadOmitEmptyNumberPtr", "StructPtrHeadNumberPtr", "StructPtrHeadOmitEmptyNumberPtr", "StructHeadArrayPtr", "StructHeadOmitEmptyArrayPtr", "StructPtrHeadArrayPtr", "StructPtrHeadOmitEmptyArrayPtr", "StructHeadMapPtr", "StructHeadOmitEmptyMapPtr", "StructPtrHeadMapPtr", "StructPtrHeadOmitEmptyMapPtr", "StructHeadSlicePtr", "StructHeadOmitEmptySlicePtr", "StructPtrHeadSlicePtr", "StructPtrHeadOmitEmptySlicePtr", "StructHeadMarshalJSONPtr", "StructHeadOmitEmptyMarshalJSONPtr", "StructPtrHeadMarshalJSONPtr", "StructPtrHeadOmitEmptyMarshalJSONPtr", "StructHeadMarshalTextPtr", "StructHeadOmitEmptyMarshalTextPtr", "StructPtrHeadMarshalTextPtr", "StructPtrHeadOmitEmptyMarshalTextPtr", "StructHeadInterfacePtr", "StructHeadOmitEmptyInterfacePtr", "StructPtrHeadInterfacePtr", "StructPtrHeadOmitEmptyInterfacePtr", "StructHeadIntPtrString", "StructHeadOmitEmptyIntPtrString", "StructPtrHeadIntPtrString", "StructPtrHeadOmitEmptyIntPtrString", "StructHeadUintPtrString", "StructHeadOmitEmptyUintPtrString", "StructPtrHeadUintPtrString", "StructPtrHeadOmitEmptyUintPtrString", "StructHeadFloat32PtrString", "StructHeadOmitEmptyFloat32PtrString", "StructPtrHeadFloat32PtrString", "StructPtrHeadOmitEmptyFloat32PtrString", "StructHeadFloat64PtrString", "StructHeadOmitEmptyFloat64PtrString", "StructPtrHeadFloat64PtrString", "StructPtrHeadOmitEmptyFloat64PtrString", "StructHeadBoolPtrString", "StructHeadOmitEmptyBoolPtrString", "StructPtrHeadBoolPtrString", "StructPtrHeadOmitEmptyBoolPtrString", "StructHeadStringPtrString", "StructHeadOmitEmptyStringPtrString", "StructPtrHeadStringPtrString", "StructPtrHeadOmitEmptyStringPtrString", "StructHeadNumberPtrString", "StructHeadOmitEmptyNumberPtrString", "StructPtrHeadNumberPtrString", "StructPtrHeadOmitEmptyNumberPtrString", "StructHead", "StructHeadOmitEmpty", "StructPtrHead", "StructPtrHeadOmitEmpty", "StructFieldInt", "StructFieldOmitEmptyInt", "StructEndInt", "StructEndOmitEmptyInt", "StructFieldUint", "StructFieldOmitEmptyUint", "StructEndUint", "StructEndOmitEmptyUint", "StructFieldFloat32", "StructFieldOmitEmptyFloat32", "StructEndFloat32", "StructEndOmitEmptyFloat32", "StructFieldFloat64", "StructFieldOmitEmptyFloat64", "StructEndFloat64", "StructEndOmitEmptyFloat64", "StructFieldBool", "StructFieldOmitEmptyBool", "StructEndBool", "StructEndOmitEmptyBool", "StructFieldString", "StructFieldOmitEmptyString", "StructEndString", "StructEndOmitEmptyString", "StructFieldBytes", "StructFieldOmitEmptyBytes", "StructEndBytes", "StructEndOmitEmptyBytes", "StructFieldNumber", "StructFieldOmitEmptyNumber", "StructEndNumber", "StructEndOmitEmptyNumber", "StructFieldArray", "StructFieldOmitEmptyArray", "StructEndArray", "StructEndOmitEmptyArray", "StructFieldMap", "StructFieldOmitEmptyMap", "StructEndMap", "StructEndOmitEmptyMap", "StructFieldSlice", "StructFieldOmitEmptySlice", "StructEndSlice", "StructEndOmitEmptySlice", "StructFieldStruct", "StructFieldOmitEmptyStruct", "StructEndStruct", "StructEndOmitEmptyStruct", "StructFieldMarshalJSON", "StructFieldOmitEmptyMarshalJSON", "StructEndMarshalJSON", "StructEndOmitEmptyMarshalJSON", "StructFieldMarshalText", "StructFieldOmitEmptyMarshalText", "StructEndMarshalText", "StructEndOmitEmptyMarshalText", "StructFieldIntString", "StructFieldOmitEmptyIntString", "StructEndIntString", "StructEndOmitEmptyIntString", "StructFieldUintString", "StructFieldOmitEmptyUintString", "StructEndUintString", "StructEndOmitEmptyUintString", "StructFieldFloat32String", "StructFieldOmitEmptyFloat32String", "StructEndFloat32String", "StructEndOmitEmptyFloat32String", "StructFieldFloat64String", "StructFieldOmitEmptyFloat64String", "StructEndFloat64String", "StructEndOmitEmptyFloat64String", "StructFieldBoolString", "StructFieldOmitEmptyBoolString", "StructEndBoolString", "StructEndOmitEmptyBoolString", "StructFieldStringString", "StructFieldOmitEmptyStringString", "StructEndStringString", "StructEndOmitEmptyStringString", "StructFieldNumberString", "StructFieldOmitEmptyNumberString", "StructEndNumberString", "StructEndOmitEmptyNumberString", "StructFieldIntPtr", "StructFieldOmitEmptyIntPtr", "StructEndIntPtr", "StructEndOmitEmptyIntPtr", "StructFieldUintPtr", "StructFieldOmitEmptyUintPtr", "StructEndUintPtr", "StructEndOmitEmptyUintPtr", "StructFieldFloat32Ptr", "StructFieldOmitEmptyFloat32Ptr", "StructEndFloat32Ptr", "StructEndOmitEmptyFloat32Ptr", "StructFieldFloat64Ptr", "StructFieldOmitEmptyFloat64Ptr", "StructEndFloat64Ptr", "StructEndOmitEmptyFloat64Ptr", "StructFieldBoolPtr", "StructFieldOmitEmptyBoolPtr", "StructEndBoolPtr", "StructEndOmitEmptyBoolPtr", "StructFieldStringPtr", "StructFieldOmitEmptyStringPtr", "StructEndStringPtr", "StructEndOmitEmptyStringPtr", "StructFieldBytesPtr", "StructFieldOmitEmptyBytesPtr", "StructEndBytesPtr", "StructEndOmitEmptyBytesPtr", "StructFieldNumberPtr", "StructFieldOmitEmptyNumberPtr", "StructEndNumberPtr", "StructEndOmitEmptyNumberPtr", "StructFieldArrayPtr", "StructFieldOmitEmptyArrayPtr", "StructEndArrayPtr", "StructEndOmitEmptyArrayPtr", "StructFieldMapPtr", "StructFieldOmitEmptyMapPtr", "StructEndMapPtr", "StructEndOmitEmptyMapPtr", "StructFieldSlicePtr", "StructFieldOmitEmptySlicePtr", "StructEndSlicePtr", "StructEndOmitEmptySlicePtr", "StructFieldMarshalJSONPtr", "StructFieldOmitEmptyMarshalJSONPtr", "StructEndMarshalJSONPtr", "StructEndOmitEmptyMarshalJSONPtr", "StructFieldMarshalTextPtr", "StructFieldOmitEmptyMarshalTextPtr", "StructEndMarshalTextPtr", "StructEndOmitEmptyMarshalTextPtr", "StructFieldInterfacePtr", "StructFieldOmitEmptyInterfacePtr", "StructEndInterfacePtr", "StructEndOmitEmptyInterfacePtr", "StructFieldIntPtrString", "StructFieldOmitEmptyIntPtrString", "StructEndIntPtrString", "StructEndOmitEmptyIntPtrString", "StructFieldUintPtrString", "StructFieldOmitEmptyUintPtrString", "StructEndUintPtrString", "StructEndOmitEmptyUintPtrString", "StructFieldFloat32PtrString", "StructFieldOmitEmptyFloat32PtrString", "StructEndFloat32PtrString", "StructEndOmitEmptyFloat32PtrString", "StructFieldFloat64PtrString", "StructFieldOmitEmptyFloat64PtrString", "StructEndFloat64PtrString", "StructEndOmitEmptyFloat64PtrString", "StructFieldBoolPtrString", "StructFieldOmitEmptyBoolPtrString", "StructEndBoolPtrString", "StructEndOmitEmptyBoolPtrString", "StructFieldStringPtrString", "StructFieldOmitEmptyStringPtrString", "StructEndStringPtrString", "StructEndOmitEmptyStringPtrString", "StructFieldNumberPtrString", "StructFieldOmitEmptyNumberPtrString", "StructEndNumberPtrString", "StructEndOmitEmptyNumberPtrString", "StructField", "StructFieldOmitEmpty", "StructEnd", "StructEndOmitEmpty", } type OpType uint16 const ( OpEnd OpType = 0 OpInterface OpType = 1 OpPtr OpType = 2 OpSliceElem OpType = 3 OpSliceEnd OpType = 4 OpArrayElem OpType = 5 OpArrayEnd OpType = 6 OpMapKey OpType = 7 OpMapValue OpType = 8 OpMapEnd OpType = 9 OpRecursive OpType = 10 OpRecursivePtr OpType = 11 OpRecursiveEnd OpType = 12 OpInterfaceEnd OpType = 13 OpInt OpType = 14 OpUint OpType = 15 OpFloat32 OpType = 16 OpFloat64 OpType = 17 OpBool OpType = 18 OpString OpType = 19 OpBytes OpType = 20 OpNumber OpType = 21 OpArray OpType = 22 OpMap OpType = 23 OpSlice OpType = 24 OpStruct OpType = 25 OpMarshalJSON OpType = 26 OpMarshalText OpType = 27 OpIntString OpType = 28 OpUintString OpType = 29 OpFloat32String OpType = 30 OpFloat64String OpType = 31 OpBoolString OpType = 32 OpStringString OpType = 33 OpNumberString OpType = 34 OpIntPtr OpType = 35 OpUintPtr OpType = 36 OpFloat32Ptr OpType = 37 OpFloat64Ptr OpType = 38 OpBoolPtr OpType = 39 OpStringPtr OpType = 40 OpBytesPtr OpType = 41 OpNumberPtr OpType = 42 OpArrayPtr OpType = 43 OpMapPtr OpType = 44 OpSlicePtr OpType = 45 OpMarshalJSONPtr OpType = 46 OpMarshalTextPtr OpType = 47 OpInterfacePtr OpType = 48 OpIntPtrString OpType = 49 OpUintPtrString OpType = 50 OpFloat32PtrString OpType = 51 OpFloat64PtrString OpType = 52 OpBoolPtrString OpType = 53 OpStringPtrString OpType = 54 OpNumberPtrString OpType = 55 OpStructHeadInt OpType = 56 OpStructHeadOmitEmptyInt OpType = 57 OpStructPtrHeadInt OpType = 58 OpStructPtrHeadOmitEmptyInt OpType = 59 OpStructHeadUint OpType = 60 OpStructHeadOmitEmptyUint OpType = 61 OpStructPtrHeadUint OpType = 62 OpStructPtrHeadOmitEmptyUint OpType = 63 OpStructHeadFloat32 OpType = 64 OpStructHeadOmitEmptyFloat32 OpType = 65 OpStructPtrHeadFloat32 OpType = 66 OpStructPtrHeadOmitEmptyFloat32 OpType = 67 OpStructHeadFloat64 OpType = 68 OpStructHeadOmitEmptyFloat64 OpType = 69 OpStructPtrHeadFloat64 OpType = 70 OpStructPtrHeadOmitEmptyFloat64 OpType = 71 OpStructHeadBool OpType = 72 OpStructHeadOmitEmptyBool OpType = 73 OpStructPtrHeadBool OpType = 74 OpStructPtrHeadOmitEmptyBool OpType = 75 OpStructHeadString OpType = 76 OpStructHeadOmitEmptyString OpType = 77 OpStructPtrHeadString OpType = 78 OpStructPtrHeadOmitEmptyString OpType = 79 OpStructHeadBytes OpType = 80 OpStructHeadOmitEmptyBytes OpType = 81 OpStructPtrHeadBytes OpType = 82 OpStructPtrHeadOmitEmptyBytes OpType = 83 OpStructHeadNumber OpType = 84 OpStructHeadOmitEmptyNumber OpType = 85 OpStructPtrHeadNumber OpType = 86 OpStructPtrHeadOmitEmptyNumber OpType = 87 OpStructHeadArray OpType = 88 OpStructHeadOmitEmptyArray OpType = 89 OpStructPtrHeadArray OpType = 90 OpStructPtrHeadOmitEmptyArray OpType = 91 OpStructHeadMap OpType = 92 OpStructHeadOmitEmptyMap OpType = 93 OpStructPtrHeadMap OpType = 94 OpStructPtrHeadOmitEmptyMap OpType = 95 OpStructHeadSlice OpType = 96 OpStructHeadOmitEmptySlice OpType = 97 OpStructPtrHeadSlice OpType = 98 OpStructPtrHeadOmitEmptySlice OpType = 99 OpStructHeadStruct OpType = 100 OpStructHeadOmitEmptyStruct OpType = 101 OpStructPtrHeadStruct OpType = 102 OpStructPtrHeadOmitEmptyStruct OpType = 103 OpStructHeadMarshalJSON OpType = 104 OpStructHeadOmitEmptyMarshalJSON OpType = 105 OpStructPtrHeadMarshalJSON OpType = 106 OpStructPtrHeadOmitEmptyMarshalJSON OpType = 107 OpStructHeadMarshalText OpType = 108 OpStructHeadOmitEmptyMarshalText OpType = 109 OpStructPtrHeadMarshalText OpType = 110 OpStructPtrHeadOmitEmptyMarshalText OpType = 111 OpStructHeadIntString OpType = 112 OpStructHeadOmitEmptyIntString OpType = 113 OpStructPtrHeadIntString OpType = 114 OpStructPtrHeadOmitEmptyIntString OpType = 115 OpStructHeadUintString OpType = 116 OpStructHeadOmitEmptyUintString OpType = 117 OpStructPtrHeadUintString OpType = 118 OpStructPtrHeadOmitEmptyUintString OpType = 119 OpStructHeadFloat32String OpType = 120 OpStructHeadOmitEmptyFloat32String OpType = 121 OpStructPtrHeadFloat32String OpType = 122 OpStructPtrHeadOmitEmptyFloat32String OpType = 123 OpStructHeadFloat64String OpType = 124 OpStructHeadOmitEmptyFloat64String OpType = 125 OpStructPtrHeadFloat64String OpType = 126 OpStructPtrHeadOmitEmptyFloat64String OpType = 127 OpStructHeadBoolString OpType = 128 OpStructHeadOmitEmptyBoolString OpType = 129 OpStructPtrHeadBoolString OpType = 130 OpStructPtrHeadOmitEmptyBoolString OpType = 131 OpStructHeadStringString OpType = 132 OpStructHeadOmitEmptyStringString OpType = 133 OpStructPtrHeadStringString OpType = 134 OpStructPtrHeadOmitEmptyStringString OpType = 135 OpStructHeadNumberString OpType = 136 OpStructHeadOmitEmptyNumberString OpType = 137 OpStructPtrHeadNumberString OpType = 138 OpStructPtrHeadOmitEmptyNumberString OpType = 139 OpStructHeadIntPtr OpType = 140 OpStructHeadOmitEmptyIntPtr OpType = 141 OpStructPtrHeadIntPtr OpType = 142 OpStructPtrHeadOmitEmptyIntPtr OpType = 143 OpStructHeadUintPtr OpType = 144 OpStructHeadOmitEmptyUintPtr OpType = 145 OpStructPtrHeadUintPtr OpType = 146 OpStructPtrHeadOmitEmptyUintPtr OpType = 147 OpStructHeadFloat32Ptr OpType = 148 OpStructHeadOmitEmptyFloat32Ptr OpType = 149 OpStructPtrHeadFloat32Ptr OpType = 150 OpStructPtrHeadOmitEmptyFloat32Ptr OpType = 151 OpStructHeadFloat64Ptr OpType = 152 OpStructHeadOmitEmptyFloat64Ptr OpType = 153 OpStructPtrHeadFloat64Ptr OpType = 154 OpStructPtrHeadOmitEmptyFloat64Ptr OpType = 155 OpStructHeadBoolPtr OpType = 156 OpStructHeadOmitEmptyBoolPtr OpType = 157 OpStructPtrHeadBoolPtr OpType = 158 OpStructPtrHeadOmitEmptyBoolPtr OpType = 159 OpStructHeadStringPtr OpType = 160 OpStructHeadOmitEmptyStringPtr OpType = 161 OpStructPtrHeadStringPtr OpType = 162 OpStructPtrHeadOmitEmptyStringPtr OpType = 163 OpStructHeadBytesPtr OpType = 164 OpStructHeadOmitEmptyBytesPtr OpType = 165 OpStructPtrHeadBytesPtr OpType = 166 OpStructPtrHeadOmitEmptyBytesPtr OpType = 167 OpStructHeadNumberPtr OpType = 168 OpStructHeadOmitEmptyNumberPtr OpType = 169 OpStructPtrHeadNumberPtr OpType = 170 OpStructPtrHeadOmitEmptyNumberPtr OpType = 171 OpStructHeadArrayPtr OpType = 172 OpStructHeadOmitEmptyArrayPtr OpType = 173 OpStructPtrHeadArrayPtr OpType = 174 OpStructPtrHeadOmitEmptyArrayPtr OpType = 175 OpStructHeadMapPtr OpType = 176 OpStructHeadOmitEmptyMapPtr OpType = 177 OpStructPtrHeadMapPtr OpType = 178 OpStructPtrHeadOmitEmptyMapPtr OpType = 179 OpStructHeadSlicePtr OpType = 180 OpStructHeadOmitEmptySlicePtr OpType = 181 OpStructPtrHeadSlicePtr OpType = 182 OpStructPtrHeadOmitEmptySlicePtr OpType = 183 OpStructHeadMarshalJSONPtr OpType = 184 OpStructHeadOmitEmptyMarshalJSONPtr OpType = 185 OpStructPtrHeadMarshalJSONPtr OpType = 186 OpStructPtrHeadOmitEmptyMarshalJSONPtr OpType = 187 OpStructHeadMarshalTextPtr OpType = 188 OpStructHeadOmitEmptyMarshalTextPtr OpType = 189 OpStructPtrHeadMarshalTextPtr OpType = 190 OpStructPtrHeadOmitEmptyMarshalTextPtr OpType = 191 OpStructHeadInterfacePtr OpType = 192 OpStructHeadOmitEmptyInterfacePtr OpType = 193 OpStructPtrHeadInterfacePtr OpType = 194 OpStructPtrHeadOmitEmptyInterfacePtr OpType = 195 OpStructHeadIntPtrString OpType = 196 OpStructHeadOmitEmptyIntPtrString OpType = 197 OpStructPtrHeadIntPtrString OpType = 198 OpStructPtrHeadOmitEmptyIntPtrString OpType = 199 OpStructHeadUintPtrString OpType = 200 OpStructHeadOmitEmptyUintPtrString OpType = 201 OpStructPtrHeadUintPtrString OpType = 202 OpStructPtrHeadOmitEmptyUintPtrString OpType = 203 OpStructHeadFloat32PtrString OpType = 204 OpStructHeadOmitEmptyFloat32PtrString OpType = 205 OpStructPtrHeadFloat32PtrString OpType = 206 OpStructPtrHeadOmitEmptyFloat32PtrString OpType = 207 OpStructHeadFloat64PtrString OpType = 208 OpStructHeadOmitEmptyFloat64PtrString OpType = 209 OpStructPtrHeadFloat64PtrString OpType = 210 OpStructPtrHeadOmitEmptyFloat64PtrString OpType = 211 OpStructHeadBoolPtrString OpType = 212 OpStructHeadOmitEmptyBoolPtrString OpType = 213 OpStructPtrHeadBoolPtrString OpType = 214 OpStructPtrHeadOmitEmptyBoolPtrString OpType = 215 OpStructHeadStringPtrString OpType = 216 OpStructHeadOmitEmptyStringPtrString OpType = 217 OpStructPtrHeadStringPtrString OpType = 218 OpStructPtrHeadOmitEmptyStringPtrString OpType = 219 OpStructHeadNumberPtrString OpType = 220 OpStructHeadOmitEmptyNumberPtrString OpType = 221 OpStructPtrHeadNumberPtrString OpType = 222 OpStructPtrHeadOmitEmptyNumberPtrString OpType = 223 OpStructHead OpType = 224 OpStructHeadOmitEmpty OpType = 225 OpStructPtrHead OpType = 226 OpStructPtrHeadOmitEmpty OpType = 227 OpStructFieldInt OpType = 228 OpStructFieldOmitEmptyInt OpType = 229 OpStructEndInt OpType = 230 OpStructEndOmitEmptyInt OpType = 231 OpStructFieldUint OpType = 232 OpStructFieldOmitEmptyUint OpType = 233 OpStructEndUint OpType = 234 OpStructEndOmitEmptyUint OpType = 235 OpStructFieldFloat32 OpType = 236 OpStructFieldOmitEmptyFloat32 OpType = 237 OpStructEndFloat32 OpType = 238 OpStructEndOmitEmptyFloat32 OpType = 239 OpStructFieldFloat64 OpType = 240 OpStructFieldOmitEmptyFloat64 OpType = 241 OpStructEndFloat64 OpType = 242 OpStructEndOmitEmptyFloat64 OpType = 243 OpStructFieldBool OpType = 244 OpStructFieldOmitEmptyBool OpType = 245 OpStructEndBool OpType = 246 OpStructEndOmitEmptyBool OpType = 247 OpStructFieldString OpType = 248 OpStructFieldOmitEmptyString OpType = 249 OpStructEndString OpType = 250 OpStructEndOmitEmptyString OpType = 251 OpStructFieldBytes OpType = 252 OpStructFieldOmitEmptyBytes OpType = 253 OpStructEndBytes OpType = 254 OpStructEndOmitEmptyBytes OpType = 255 OpStructFieldNumber OpType = 256 OpStructFieldOmitEmptyNumber OpType = 257 OpStructEndNumber OpType = 258 OpStructEndOmitEmptyNumber OpType = 259 OpStructFieldArray OpType = 260 OpStructFieldOmitEmptyArray OpType = 261 OpStructEndArray OpType = 262 OpStructEndOmitEmptyArray OpType = 263 OpStructFieldMap OpType = 264 OpStructFieldOmitEmptyMap OpType = 265 OpStructEndMap OpType = 266 OpStructEndOmitEmptyMap OpType = 267 OpStructFieldSlice OpType = 268 OpStructFieldOmitEmptySlice OpType = 269 OpStructEndSlice OpType = 270 OpStructEndOmitEmptySlice OpType = 271 OpStructFieldStruct OpType = 272 OpStructFieldOmitEmptyStruct OpType = 273 OpStructEndStruct OpType = 274 OpStructEndOmitEmptyStruct OpType = 275 OpStructFieldMarshalJSON OpType = 276 OpStructFieldOmitEmptyMarshalJSON OpType = 277 OpStructEndMarshalJSON OpType = 278 OpStructEndOmitEmptyMarshalJSON OpType = 279 OpStructFieldMarshalText OpType = 280 OpStructFieldOmitEmptyMarshalText OpType = 281 OpStructEndMarshalText OpType = 282 OpStructEndOmitEmptyMarshalText OpType = 283 OpStructFieldIntString OpType = 284 OpStructFieldOmitEmptyIntString OpType = 285 OpStructEndIntString OpType = 286 OpStructEndOmitEmptyIntString OpType = 287 OpStructFieldUintString OpType = 288 OpStructFieldOmitEmptyUintString OpType = 289 OpStructEndUintString OpType = 290 OpStructEndOmitEmptyUintString OpType = 291 OpStructFieldFloat32String OpType = 292 OpStructFieldOmitEmptyFloat32String OpType = 293 OpStructEndFloat32String OpType = 294 OpStructEndOmitEmptyFloat32String OpType = 295 OpStructFieldFloat64String OpType = 296 OpStructFieldOmitEmptyFloat64String OpType = 297 OpStructEndFloat64String OpType = 298 OpStructEndOmitEmptyFloat64String OpType = 299 OpStructFieldBoolString OpType = 300 OpStructFieldOmitEmptyBoolString OpType = 301 OpStructEndBoolString OpType = 302 OpStructEndOmitEmptyBoolString OpType = 303 OpStructFieldStringString OpType = 304 OpStructFieldOmitEmptyStringString OpType = 305 OpStructEndStringString OpType = 306 OpStructEndOmitEmptyStringString OpType = 307 OpStructFieldNumberString OpType = 308 OpStructFieldOmitEmptyNumberString OpType = 309 OpStructEndNumberString OpType = 310 OpStructEndOmitEmptyNumberString OpType = 311 OpStructFieldIntPtr OpType = 312 OpStructFieldOmitEmptyIntPtr OpType = 313 OpStructEndIntPtr OpType = 314 OpStructEndOmitEmptyIntPtr OpType = 315 OpStructFieldUintPtr OpType = 316 OpStructFieldOmitEmptyUintPtr OpType = 317 OpStructEndUintPtr OpType = 318 OpStructEndOmitEmptyUintPtr OpType = 319 OpStructFieldFloat32Ptr OpType = 320 OpStructFieldOmitEmptyFloat32Ptr OpType = 321 OpStructEndFloat32Ptr OpType = 322 OpStructEndOmitEmptyFloat32Ptr OpType = 323 OpStructFieldFloat64Ptr OpType = 324 OpStructFieldOmitEmptyFloat64Ptr OpType = 325 OpStructEndFloat64Ptr OpType = 326 OpStructEndOmitEmptyFloat64Ptr OpType = 327 OpStructFieldBoolPtr OpType = 328 OpStructFieldOmitEmptyBoolPtr OpType = 329 OpStructEndBoolPtr OpType = 330 OpStructEndOmitEmptyBoolPtr OpType = 331 OpStructFieldStringPtr OpType = 332 OpStructFieldOmitEmptyStringPtr OpType = 333 OpStructEndStringPtr OpType = 334 OpStructEndOmitEmptyStringPtr OpType = 335 OpStructFieldBytesPtr OpType = 336 OpStructFieldOmitEmptyBytesPtr OpType = 337 OpStructEndBytesPtr OpType = 338 OpStructEndOmitEmptyBytesPtr OpType = 339 OpStructFieldNumberPtr OpType = 340 OpStructFieldOmitEmptyNumberPtr OpType = 341 OpStructEndNumberPtr OpType = 342 OpStructEndOmitEmptyNumberPtr OpType = 343 OpStructFieldArrayPtr OpType = 344 OpStructFieldOmitEmptyArrayPtr OpType = 345 OpStructEndArrayPtr OpType = 346 OpStructEndOmitEmptyArrayPtr OpType = 347 OpStructFieldMapPtr OpType = 348 OpStructFieldOmitEmptyMapPtr OpType = 349 OpStructEndMapPtr OpType = 350 OpStructEndOmitEmptyMapPtr OpType = 351 OpStructFieldSlicePtr OpType = 352 OpStructFieldOmitEmptySlicePtr OpType = 353 OpStructEndSlicePtr OpType = 354 OpStructEndOmitEmptySlicePtr OpType = 355 OpStructFieldMarshalJSONPtr OpType = 356 OpStructFieldOmitEmptyMarshalJSONPtr OpType = 357 OpStructEndMarshalJSONPtr OpType = 358 OpStructEndOmitEmptyMarshalJSONPtr OpType = 359 OpStructFieldMarshalTextPtr OpType = 360 OpStructFieldOmitEmptyMarshalTextPtr OpType = 361 OpStructEndMarshalTextPtr OpType = 362 OpStructEndOmitEmptyMarshalTextPtr OpType = 363 OpStructFieldInterfacePtr OpType = 364 OpStructFieldOmitEmptyInterfacePtr OpType = 365 OpStructEndInterfacePtr OpType = 366 OpStructEndOmitEmptyInterfacePtr OpType = 367 OpStructFieldIntPtrString OpType = 368 OpStructFieldOmitEmptyIntPtrString OpType = 369 OpStructEndIntPtrString OpType = 370 OpStructEndOmitEmptyIntPtrString OpType = 371 OpStructFieldUintPtrString OpType = 372 OpStructFieldOmitEmptyUintPtrString OpType = 373 OpStructEndUintPtrString OpType = 374 OpStructEndOmitEmptyUintPtrString OpType = 375 OpStructFieldFloat32PtrString OpType = 376 OpStructFieldOmitEmptyFloat32PtrString OpType = 377 OpStructEndFloat32PtrString OpType = 378 OpStructEndOmitEmptyFloat32PtrString OpType = 379 OpStructFieldFloat64PtrString OpType = 380 OpStructFieldOmitEmptyFloat64PtrString OpType = 381 OpStructEndFloat64PtrString OpType = 382 OpStructEndOmitEmptyFloat64PtrString OpType = 383 OpStructFieldBoolPtrString OpType = 384 OpStructFieldOmitEmptyBoolPtrString OpType = 385 OpStructEndBoolPtrString OpType = 386 OpStructEndOmitEmptyBoolPtrString OpType = 387 OpStructFieldStringPtrString OpType = 388 OpStructFieldOmitEmptyStringPtrString OpType = 389 OpStructEndStringPtrString OpType = 390 OpStructEndOmitEmptyStringPtrString OpType = 391 OpStructFieldNumberPtrString OpType = 392 OpStructFieldOmitEmptyNumberPtrString OpType = 393 OpStructEndNumberPtrString OpType = 394 OpStructEndOmitEmptyNumberPtrString OpType = 395 OpStructField OpType = 396 OpStructFieldOmitEmpty OpType = 397 OpStructEnd OpType = 398 OpStructEndOmitEmpty OpType = 399 ) func (t OpType) String() string { if int(t) >= 400 { return "" } return opTypeStrings[int(t)] } func (t OpType) CodeType() CodeType { if strings.Contains(t.String(), "Struct") { if strings.Contains(t.String(), "End") { return CodeStructEnd } return CodeStructField } switch t { case OpArray, OpArrayPtr: return CodeArrayHead case OpArrayElem: return CodeArrayElem case OpSlice, OpSlicePtr: return CodeSliceHead case OpSliceElem: return CodeSliceElem case OpMap, OpMapPtr: return CodeMapHead case OpMapKey: return CodeMapKey case OpMapValue: return CodeMapValue case OpMapEnd: return CodeMapEnd } return CodeOp } func (t OpType) HeadToPtrHead() OpType { if strings.Index(t.String(), "PtrHead") > 0 { return t } idx := strings.Index(t.String(), "Head") if idx == -1 { return t } suffix := "PtrHead" + t.String()[idx+len("Head"):] const toPtrOffset = 2 if strings.Contains(OpType(int(t)+toPtrOffset).String(), suffix) { return OpType(int(t) + toPtrOffset) } return t } func (t OpType) HeadToOmitEmptyHead() OpType { const toOmitEmptyOffset = 1 if strings.Contains(OpType(int(t)+toOmitEmptyOffset).String(), "OmitEmpty") { return OpType(int(t) + toOmitEmptyOffset) } return t } func (t OpType) PtrHeadToHead() OpType { idx := strings.Index(t.String(), "PtrHead") if idx == -1 { return t } suffix := t.String()[idx+len("Ptr"):] const toPtrOffset = 2 if strings.Contains(OpType(int(t)-toPtrOffset).String(), suffix) { return OpType(int(t) - toPtrOffset) } return t } func (t OpType) FieldToEnd() OpType { idx := strings.Index(t.String(), "Field") if idx == -1 { return t } suffix := t.String()[idx+len("Field"):] if suffix == "" || suffix == "OmitEmpty" { return t } const toEndOffset = 2 if strings.Contains(OpType(int(t)+toEndOffset).String(), "End"+suffix) { return OpType(int(t) + toEndOffset) } return t } func (t OpType) FieldToOmitEmptyField() OpType { const toOmitEmptyOffset = 1 if strings.Contains(OpType(int(t)+toOmitEmptyOffset).String(), "OmitEmpty") { return OpType(int(t) + toOmitEmptyOffset) } return t } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/query.go ================================================ package encoder import ( "context" "fmt" "reflect" ) var ( Marshal func(interface{}) ([]byte, error) Unmarshal func([]byte, interface{}) error ) type FieldQuery struct { Name string Fields []*FieldQuery hash string } func (q *FieldQuery) Hash() string { if q.hash != "" { return q.hash } b, _ := Marshal(q) q.hash = string(b) return q.hash } func (q *FieldQuery) MarshalJSON() ([]byte, error) { if q.Name != "" { if len(q.Fields) > 0 { return Marshal(map[string][]*FieldQuery{q.Name: q.Fields}) } return Marshal(q.Name) } return Marshal(q.Fields) } func (q *FieldQuery) QueryString() (FieldQueryString, error) { b, err := Marshal(q) if err != nil { return "", err } return FieldQueryString(b), nil } type FieldQueryString string func (s FieldQueryString) Build() (*FieldQuery, error) { var query interface{} if err := Unmarshal([]byte(s), &query); err != nil { return nil, err } return s.build(reflect.ValueOf(query)) } func (s FieldQueryString) build(v reflect.Value) (*FieldQuery, error) { switch v.Type().Kind() { case reflect.String: return s.buildString(v) case reflect.Map: return s.buildMap(v) case reflect.Slice: return s.buildSlice(v) case reflect.Interface: return s.build(reflect.ValueOf(v.Interface())) } return nil, fmt.Errorf("failed to build field query") } func (s FieldQueryString) buildString(v reflect.Value) (*FieldQuery, error) { b := []byte(v.String()) switch b[0] { case '[', '{': var query interface{} if err := Unmarshal(b, &query); err != nil { return nil, err } if str, ok := query.(string); ok { return &FieldQuery{Name: str}, nil } return s.build(reflect.ValueOf(query)) } return &FieldQuery{Name: string(b)}, nil } func (s FieldQueryString) buildSlice(v reflect.Value) (*FieldQuery, error) { fields := make([]*FieldQuery, 0, v.Len()) for i := 0; i < v.Len(); i++ { def, err := s.build(v.Index(i)) if err != nil { return nil, err } fields = append(fields, def) } return &FieldQuery{Fields: fields}, nil } func (s FieldQueryString) buildMap(v reflect.Value) (*FieldQuery, error) { keys := v.MapKeys() if len(keys) != 1 { return nil, fmt.Errorf("failed to build field query object") } key := keys[0] if key.Type().Kind() != reflect.String { return nil, fmt.Errorf("failed to build field query. invalid object key type") } name := key.String() def, err := s.build(v.MapIndex(key)) if err != nil { return nil, err } return &FieldQuery{ Name: name, Fields: def.Fields, }, nil } type queryKey struct{} func FieldQueryFromContext(ctx context.Context) *FieldQuery { query := ctx.Value(queryKey{}) if query == nil { return nil } q, ok := query.(*FieldQuery) if !ok { return nil } return q } func SetFieldQueryToContext(ctx context.Context, query *FieldQuery) context.Context { return context.WithValue(ctx, queryKey{}, query) } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/string.go ================================================ package encoder import ( "math/bits" "reflect" "unsafe" ) const ( lsb = 0x0101010101010101 msb = 0x8080808080808080 ) var hex = "0123456789abcdef" //nolint:govet func stringToUint64Slice(s string) []uint64 { return *(*[]uint64)(unsafe.Pointer(&reflect.SliceHeader{ Data: ((*reflect.StringHeader)(unsafe.Pointer(&s))).Data, Len: len(s) / 8, Cap: len(s) / 8, })) } func AppendString(ctx *RuntimeContext, buf []byte, s string) []byte { if ctx.Option.Flag&HTMLEscapeOption != 0 { if ctx.Option.Flag&NormalizeUTF8Option != 0 { return appendNormalizedHTMLString(buf, s) } return appendHTMLString(buf, s) } if ctx.Option.Flag&NormalizeUTF8Option != 0 { return appendNormalizedString(buf, s) } return appendString(buf, s) } func appendNormalizedHTMLString(buf []byte, s string) []byte { valLen := len(s) if valLen == 0 { return append(buf, `""`...) } buf = append(buf, '"') var ( i, j int ) if valLen >= 8 { chunks := stringToUint64Slice(s) for _, n := range chunks { // combine masks before checking for the MSB of each byte. We include // `n` in the mask to check whether any of the *input* byte MSBs were // set (i.e. the byte was outside the ASCII range). mask := n | (n - (lsb * 0x20)) | ((n ^ (lsb * '"')) - lsb) | ((n ^ (lsb * '\\')) - lsb) | ((n ^ (lsb * '<')) - lsb) | ((n ^ (lsb * '>')) - lsb) | ((n ^ (lsb * '&')) - lsb) if (mask & msb) != 0 { j = bits.TrailingZeros64(mask&msb) / 8 goto ESCAPE_END } } for i := len(chunks) * 8; i < valLen; i++ { if needEscapeHTMLNormalizeUTF8[s[i]] { j = i goto ESCAPE_END } } // no found any escape characters. return append(append(buf, s...), '"') } ESCAPE_END: for j < valLen { c := s[j] if !needEscapeHTMLNormalizeUTF8[c] { // fast path: most of the time, printable ascii characters are used j++ continue } switch c { case '\\', '"': buf = append(buf, s[i:j]...) buf = append(buf, '\\', c) i = j + 1 j = j + 1 continue case '\n': buf = append(buf, s[i:j]...) buf = append(buf, '\\', 'n') i = j + 1 j = j + 1 continue case '\r': buf = append(buf, s[i:j]...) buf = append(buf, '\\', 'r') i = j + 1 j = j + 1 continue case '\t': buf = append(buf, s[i:j]...) buf = append(buf, '\\', 't') i = j + 1 j = j + 1 continue case '<', '>', '&': buf = append(buf, s[i:j]...) buf = append(buf, `\u00`...) buf = append(buf, hex[c>>4], hex[c&0xF]) i = j + 1 j = j + 1 continue case 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0B, 0x0C, 0x0E, 0x0F, // 0x00-0x0F 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F: // 0x10-0x1F buf = append(buf, s[i:j]...) buf = append(buf, `\u00`...) buf = append(buf, hex[c>>4], hex[c&0xF]) i = j + 1 j = j + 1 continue } state, size := decodeRuneInString(s[j:]) switch state { case runeErrorState: buf = append(buf, s[i:j]...) buf = append(buf, `\ufffd`...) i = j + 1 j = j + 1 continue // U+2028 is LINE SEPARATOR. // U+2029 is PARAGRAPH SEPARATOR. // They are both technically valid characters in JSON strings, // but don't work in JSONP, which has to be evaluated as JavaScript, // and can lead to security holes there. It is valid JSON to // escape them, so we do so unconditionally. // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. case lineSepState: buf = append(buf, s[i:j]...) buf = append(buf, `\u2028`...) i = j + 3 j = j + 3 continue case paragraphSepState: buf = append(buf, s[i:j]...) buf = append(buf, `\u2029`...) i = j + 3 j = j + 3 continue } j += size } return append(append(buf, s[i:]...), '"') } func appendHTMLString(buf []byte, s string) []byte { valLen := len(s) if valLen == 0 { return append(buf, `""`...) } buf = append(buf, '"') var ( i, j int ) if valLen >= 8 { chunks := stringToUint64Slice(s) for _, n := range chunks { // combine masks before checking for the MSB of each byte. We include // `n` in the mask to check whether any of the *input* byte MSBs were // set (i.e. the byte was outside the ASCII range). mask := n | (n - (lsb * 0x20)) | ((n ^ (lsb * '"')) - lsb) | ((n ^ (lsb * '\\')) - lsb) | ((n ^ (lsb * '<')) - lsb) | ((n ^ (lsb * '>')) - lsb) | ((n ^ (lsb * '&')) - lsb) if (mask & msb) != 0 { j = bits.TrailingZeros64(mask&msb) / 8 goto ESCAPE_END } } for i := len(chunks) * 8; i < valLen; i++ { if needEscapeHTML[s[i]] { j = i goto ESCAPE_END } } // no found any escape characters. return append(append(buf, s...), '"') } ESCAPE_END: for j < valLen { c := s[j] if !needEscapeHTML[c] { // fast path: most of the time, printable ascii characters are used j++ continue } switch c { case '\\', '"': buf = append(buf, s[i:j]...) buf = append(buf, '\\', c) i = j + 1 j = j + 1 continue case '\n': buf = append(buf, s[i:j]...) buf = append(buf, '\\', 'n') i = j + 1 j = j + 1 continue case '\r': buf = append(buf, s[i:j]...) buf = append(buf, '\\', 'r') i = j + 1 j = j + 1 continue case '\t': buf = append(buf, s[i:j]...) buf = append(buf, '\\', 't') i = j + 1 j = j + 1 continue case '<', '>', '&': buf = append(buf, s[i:j]...) buf = append(buf, `\u00`...) buf = append(buf, hex[c>>4], hex[c&0xF]) i = j + 1 j = j + 1 continue case 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0B, 0x0C, 0x0E, 0x0F, // 0x00-0x0F 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F: // 0x10-0x1F buf = append(buf, s[i:j]...) buf = append(buf, `\u00`...) buf = append(buf, hex[c>>4], hex[c&0xF]) i = j + 1 j = j + 1 continue } j++ } return append(append(buf, s[i:]...), '"') } func appendNormalizedString(buf []byte, s string) []byte { valLen := len(s) if valLen == 0 { return append(buf, `""`...) } buf = append(buf, '"') var ( i, j int ) if valLen >= 8 { chunks := stringToUint64Slice(s) for _, n := range chunks { // combine masks before checking for the MSB of each byte. We include // `n` in the mask to check whether any of the *input* byte MSBs were // set (i.e. the byte was outside the ASCII range). mask := n | (n - (lsb * 0x20)) | ((n ^ (lsb * '"')) - lsb) | ((n ^ (lsb * '\\')) - lsb) if (mask & msb) != 0 { j = bits.TrailingZeros64(mask&msb) / 8 goto ESCAPE_END } } valLen := len(s) for i := len(chunks) * 8; i < valLen; i++ { if needEscapeNormalizeUTF8[s[i]] { j = i goto ESCAPE_END } } return append(append(buf, s...), '"') } ESCAPE_END: for j < valLen { c := s[j] if !needEscapeNormalizeUTF8[c] { // fast path: most of the time, printable ascii characters are used j++ continue } switch c { case '\\', '"': buf = append(buf, s[i:j]...) buf = append(buf, '\\', c) i = j + 1 j = j + 1 continue case '\n': buf = append(buf, s[i:j]...) buf = append(buf, '\\', 'n') i = j + 1 j = j + 1 continue case '\r': buf = append(buf, s[i:j]...) buf = append(buf, '\\', 'r') i = j + 1 j = j + 1 continue case '\t': buf = append(buf, s[i:j]...) buf = append(buf, '\\', 't') i = j + 1 j = j + 1 continue case 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0B, 0x0C, 0x0E, 0x0F, // 0x00-0x0F 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F: // 0x10-0x1F buf = append(buf, s[i:j]...) buf = append(buf, `\u00`...) buf = append(buf, hex[c>>4], hex[c&0xF]) i = j + 1 j = j + 1 continue } state, size := decodeRuneInString(s[j:]) switch state { case runeErrorState: buf = append(buf, s[i:j]...) buf = append(buf, `\ufffd`...) i = j + 1 j = j + 1 continue // U+2028 is LINE SEPARATOR. // U+2029 is PARAGRAPH SEPARATOR. // They are both technically valid characters in JSON strings, // but don't work in JSONP, which has to be evaluated as JavaScript, // and can lead to security holes there. It is valid JSON to // escape them, so we do so unconditionally. // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. case lineSepState: buf = append(buf, s[i:j]...) buf = append(buf, `\u2028`...) i = j + 3 j = j + 3 continue case paragraphSepState: buf = append(buf, s[i:j]...) buf = append(buf, `\u2029`...) i = j + 3 j = j + 3 continue } j += size } return append(append(buf, s[i:]...), '"') } func appendString(buf []byte, s string) []byte { valLen := len(s) if valLen == 0 { return append(buf, `""`...) } buf = append(buf, '"') var ( i, j int ) if valLen >= 8 { chunks := stringToUint64Slice(s) for _, n := range chunks { // combine masks before checking for the MSB of each byte. We include // `n` in the mask to check whether any of the *input* byte MSBs were // set (i.e. the byte was outside the ASCII range). mask := n | (n - (lsb * 0x20)) | ((n ^ (lsb * '"')) - lsb) | ((n ^ (lsb * '\\')) - lsb) if (mask & msb) != 0 { j = bits.TrailingZeros64(mask&msb) / 8 goto ESCAPE_END } } valLen := len(s) for i := len(chunks) * 8; i < valLen; i++ { if needEscape[s[i]] { j = i goto ESCAPE_END } } return append(append(buf, s...), '"') } ESCAPE_END: for j < valLen { c := s[j] if !needEscape[c] { // fast path: most of the time, printable ascii characters are used j++ continue } switch c { case '\\', '"': buf = append(buf, s[i:j]...) buf = append(buf, '\\', c) i = j + 1 j = j + 1 continue case '\n': buf = append(buf, s[i:j]...) buf = append(buf, '\\', 'n') i = j + 1 j = j + 1 continue case '\r': buf = append(buf, s[i:j]...) buf = append(buf, '\\', 'r') i = j + 1 j = j + 1 continue case '\t': buf = append(buf, s[i:j]...) buf = append(buf, '\\', 't') i = j + 1 j = j + 1 continue case 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0B, 0x0C, 0x0E, 0x0F, // 0x00-0x0F 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F: // 0x10-0x1F buf = append(buf, s[i:j]...) buf = append(buf, `\u00`...) buf = append(buf, hex[c>>4], hex[c&0xF]) i = j + 1 j = j + 1 continue } j++ } return append(append(buf, s[i:]...), '"') } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/string_table.go ================================================ package encoder var needEscapeHTMLNormalizeUTF8 = [256]bool{ '"': true, '&': true, '<': true, '>': true, '\\': true, 0x00: true, 0x01: true, 0x02: true, 0x03: true, 0x04: true, 0x05: true, 0x06: true, 0x07: true, 0x08: true, 0x09: true, 0x0a: true, 0x0b: true, 0x0c: true, 0x0d: true, 0x0e: true, 0x0f: true, 0x10: true, 0x11: true, 0x12: true, 0x13: true, 0x14: true, 0x15: true, 0x16: true, 0x17: true, 0x18: true, 0x19: true, 0x1a: true, 0x1b: true, 0x1c: true, 0x1d: true, 0x1e: true, 0x1f: true, /* 0x20 - 0x7f */ 0x80: true, 0x81: true, 0x82: true, 0x83: true, 0x84: true, 0x85: true, 0x86: true, 0x87: true, 0x88: true, 0x89: true, 0x8a: true, 0x8b: true, 0x8c: true, 0x8d: true, 0x8e: true, 0x8f: true, 0x90: true, 0x91: true, 0x92: true, 0x93: true, 0x94: true, 0x95: true, 0x96: true, 0x97: true, 0x98: true, 0x99: true, 0x9a: true, 0x9b: true, 0x9c: true, 0x9d: true, 0x9e: true, 0x9f: true, 0xa0: true, 0xa1: true, 0xa2: true, 0xa3: true, 0xa4: true, 0xa5: true, 0xa6: true, 0xa7: true, 0xa8: true, 0xa9: true, 0xaa: true, 0xab: true, 0xac: true, 0xad: true, 0xae: true, 0xaf: true, 0xb0: true, 0xb1: true, 0xb2: true, 0xb3: true, 0xb4: true, 0xb5: true, 0xb6: true, 0xb7: true, 0xb8: true, 0xb9: true, 0xba: true, 0xbb: true, 0xbc: true, 0xbd: true, 0xbe: true, 0xbf: true, 0xc0: true, 0xc1: true, 0xc2: true, 0xc3: true, 0xc4: true, 0xc5: true, 0xc6: true, 0xc7: true, 0xc8: true, 0xc9: true, 0xca: true, 0xcb: true, 0xcc: true, 0xcd: true, 0xce: true, 0xcf: true, 0xd0: true, 0xd1: true, 0xd2: true, 0xd3: true, 0xd4: true, 0xd5: true, 0xd6: true, 0xd7: true, 0xd8: true, 0xd9: true, 0xda: true, 0xdb: true, 0xdc: true, 0xdd: true, 0xde: true, 0xdf: true, 0xe0: true, 0xe1: true, 0xe2: true, 0xe3: true, 0xe4: true, 0xe5: true, 0xe6: true, 0xe7: true, 0xe8: true, 0xe9: true, 0xea: true, 0xeb: true, 0xec: true, 0xed: true, 0xee: true, 0xef: true, 0xf0: true, 0xf1: true, 0xf2: true, 0xf3: true, 0xf4: true, 0xf5: true, 0xf6: true, 0xf7: true, 0xf8: true, 0xf9: true, 0xfa: true, 0xfb: true, 0xfc: true, 0xfd: true, 0xfe: true, 0xff: true, } var needEscapeNormalizeUTF8 = [256]bool{ '"': true, '\\': true, 0x00: true, 0x01: true, 0x02: true, 0x03: true, 0x04: true, 0x05: true, 0x06: true, 0x07: true, 0x08: true, 0x09: true, 0x0a: true, 0x0b: true, 0x0c: true, 0x0d: true, 0x0e: true, 0x0f: true, 0x10: true, 0x11: true, 0x12: true, 0x13: true, 0x14: true, 0x15: true, 0x16: true, 0x17: true, 0x18: true, 0x19: true, 0x1a: true, 0x1b: true, 0x1c: true, 0x1d: true, 0x1e: true, 0x1f: true, /* 0x20 - 0x7f */ 0x80: true, 0x81: true, 0x82: true, 0x83: true, 0x84: true, 0x85: true, 0x86: true, 0x87: true, 0x88: true, 0x89: true, 0x8a: true, 0x8b: true, 0x8c: true, 0x8d: true, 0x8e: true, 0x8f: true, 0x90: true, 0x91: true, 0x92: true, 0x93: true, 0x94: true, 0x95: true, 0x96: true, 0x97: true, 0x98: true, 0x99: true, 0x9a: true, 0x9b: true, 0x9c: true, 0x9d: true, 0x9e: true, 0x9f: true, 0xa0: true, 0xa1: true, 0xa2: true, 0xa3: true, 0xa4: true, 0xa5: true, 0xa6: true, 0xa7: true, 0xa8: true, 0xa9: true, 0xaa: true, 0xab: true, 0xac: true, 0xad: true, 0xae: true, 0xaf: true, 0xb0: true, 0xb1: true, 0xb2: true, 0xb3: true, 0xb4: true, 0xb5: true, 0xb6: true, 0xb7: true, 0xb8: true, 0xb9: true, 0xba: true, 0xbb: true, 0xbc: true, 0xbd: true, 0xbe: true, 0xbf: true, 0xc0: true, 0xc1: true, 0xc2: true, 0xc3: true, 0xc4: true, 0xc5: true, 0xc6: true, 0xc7: true, 0xc8: true, 0xc9: true, 0xca: true, 0xcb: true, 0xcc: true, 0xcd: true, 0xce: true, 0xcf: true, 0xd0: true, 0xd1: true, 0xd2: true, 0xd3: true, 0xd4: true, 0xd5: true, 0xd6: true, 0xd7: true, 0xd8: true, 0xd9: true, 0xda: true, 0xdb: true, 0xdc: true, 0xdd: true, 0xde: true, 0xdf: true, 0xe0: true, 0xe1: true, 0xe2: true, 0xe3: true, 0xe4: true, 0xe5: true, 0xe6: true, 0xe7: true, 0xe8: true, 0xe9: true, 0xea: true, 0xeb: true, 0xec: true, 0xed: true, 0xee: true, 0xef: true, 0xf0: true, 0xf1: true, 0xf2: true, 0xf3: true, 0xf4: true, 0xf5: true, 0xf6: true, 0xf7: true, 0xf8: true, 0xf9: true, 0xfa: true, 0xfb: true, 0xfc: true, 0xfd: true, 0xfe: true, 0xff: true, } var needEscapeHTML = [256]bool{ '"': true, '&': true, '<': true, '>': true, '\\': true, 0x00: true, 0x01: true, 0x02: true, 0x03: true, 0x04: true, 0x05: true, 0x06: true, 0x07: true, 0x08: true, 0x09: true, 0x0a: true, 0x0b: true, 0x0c: true, 0x0d: true, 0x0e: true, 0x0f: true, 0x10: true, 0x11: true, 0x12: true, 0x13: true, 0x14: true, 0x15: true, 0x16: true, 0x17: true, 0x18: true, 0x19: true, 0x1a: true, 0x1b: true, 0x1c: true, 0x1d: true, 0x1e: true, 0x1f: true, /* 0x20 - 0xff */ } var needEscape = [256]bool{ '"': true, '\\': true, 0x00: true, 0x01: true, 0x02: true, 0x03: true, 0x04: true, 0x05: true, 0x06: true, 0x07: true, 0x08: true, 0x09: true, 0x0a: true, 0x0b: true, 0x0c: true, 0x0d: true, 0x0e: true, 0x0f: true, 0x10: true, 0x11: true, 0x12: true, 0x13: true, 0x14: true, 0x15: true, 0x16: true, 0x17: true, 0x18: true, 0x19: true, 0x1a: true, 0x1b: true, 0x1c: true, 0x1d: true, 0x1e: true, 0x1f: true, /* 0x20 - 0xff */ } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/vm/debug_vm.go ================================================ package vm import ( "fmt" "io" "github.com/goccy/go-json/internal/encoder" ) func DebugRun(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]byte, error) { defer func() { var code *encoder.Opcode if (ctx.Option.Flag & encoder.HTMLEscapeOption) != 0 { code = codeSet.EscapeKeyCode } else { code = codeSet.NoescapeKeyCode } if wc := ctx.Option.DebugDOTOut; wc != nil { _, _ = io.WriteString(wc, code.DumpDOT()) wc.Close() ctx.Option.DebugDOTOut = nil } if err := recover(); err != nil { w := ctx.Option.DebugOut fmt.Fprintln(w, "=============[DEBUG]===============") fmt.Fprintln(w, "* [TYPE]") fmt.Fprintln(w, codeSet.Type) fmt.Fprintf(w, "\n") fmt.Fprintln(w, "* [ALL OPCODE]") fmt.Fprintln(w, code.Dump()) fmt.Fprintf(w, "\n") fmt.Fprintln(w, "* [CONTEXT]") fmt.Fprintf(w, "%+v\n", ctx) fmt.Fprintln(w, "===================================") panic(err) } }() return Run(ctx, b, codeSet) } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/vm/hack.go ================================================ package vm import ( // HACK: compile order // `vm`, `vm_indent`, `vm_color`, `vm_color_indent` packages uses a lot of memory to compile, // so forcibly make dependencies and avoid compiling in concurrent. // dependency order: vm => vm_indent => vm_color => vm_color_indent _ "github.com/goccy/go-json/internal/encoder/vm_indent" ) ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/vm/util.go ================================================ package vm import ( "encoding/json" "fmt" "unsafe" "github.com/goccy/go-json/internal/encoder" "github.com/goccy/go-json/internal/runtime" ) const uintptrSize = 4 << (^uintptr(0) >> 63) var ( appendInt = encoder.AppendInt appendUint = encoder.AppendUint appendFloat32 = encoder.AppendFloat32 appendFloat64 = encoder.AppendFloat64 appendString = encoder.AppendString appendByteSlice = encoder.AppendByteSlice appendNumber = encoder.AppendNumber errUnsupportedValue = encoder.ErrUnsupportedValue errUnsupportedFloat = encoder.ErrUnsupportedFloat mapiterinit = encoder.MapIterInit mapiterkey = encoder.MapIterKey mapitervalue = encoder.MapIterValue mapiternext = encoder.MapIterNext maplen = encoder.MapLen ) type emptyInterface struct { typ *runtime.Type ptr unsafe.Pointer } type nonEmptyInterface struct { itab *struct { ityp *runtime.Type // static interface type typ *runtime.Type // dynamic concrete type // unused fields... } ptr unsafe.Pointer } func errUnimplementedOp(op encoder.OpType) error { return fmt.Errorf("encoder: opcode %s has not been implemented", op) } func load(base uintptr, idx uint32) uintptr { addr := base + uintptr(idx) return **(**uintptr)(unsafe.Pointer(&addr)) } func store(base uintptr, idx uint32, p uintptr) { addr := base + uintptr(idx) **(**uintptr)(unsafe.Pointer(&addr)) = p } func loadNPtr(base uintptr, idx uint32, ptrNum uint8) uintptr { addr := base + uintptr(idx) p := **(**uintptr)(unsafe.Pointer(&addr)) for i := uint8(0); i < ptrNum; i++ { if p == 0 { return 0 } p = ptrToPtr(p) } return p } func ptrToUint64(p uintptr, bitSize uint8) uint64 { switch bitSize { case 8: return (uint64)(**(**uint8)(unsafe.Pointer(&p))) case 16: return (uint64)(**(**uint16)(unsafe.Pointer(&p))) case 32: return (uint64)(**(**uint32)(unsafe.Pointer(&p))) case 64: return **(**uint64)(unsafe.Pointer(&p)) } return 0 } func ptrToFloat32(p uintptr) float32 { return **(**float32)(unsafe.Pointer(&p)) } func ptrToFloat64(p uintptr) float64 { return **(**float64)(unsafe.Pointer(&p)) } func ptrToBool(p uintptr) bool { return **(**bool)(unsafe.Pointer(&p)) } func ptrToBytes(p uintptr) []byte { return **(**[]byte)(unsafe.Pointer(&p)) } func ptrToNumber(p uintptr) json.Number { return **(**json.Number)(unsafe.Pointer(&p)) } func ptrToString(p uintptr) string { return **(**string)(unsafe.Pointer(&p)) } func ptrToSlice(p uintptr) *runtime.SliceHeader { return *(**runtime.SliceHeader)(unsafe.Pointer(&p)) } func ptrToPtr(p uintptr) uintptr { return uintptr(**(**unsafe.Pointer)(unsafe.Pointer(&p))) } func ptrToNPtr(p uintptr, ptrNum uint8) uintptr { for i := uint8(0); i < ptrNum; i++ { if p == 0 { return 0 } p = ptrToPtr(p) } return p } func ptrToUnsafePtr(p uintptr) unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Pointer(&p)) } func ptrToInterface(code *encoder.Opcode, p uintptr) interface{} { return *(*interface{})(unsafe.Pointer(&emptyInterface{ typ: code.Type, ptr: *(*unsafe.Pointer)(unsafe.Pointer(&p)), })) } func appendBool(_ *encoder.RuntimeContext, b []byte, v bool) []byte { if v { return append(b, "true"...) } return append(b, "false"...) } func appendNull(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, "null"...) } func appendComma(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, ',') } func appendNullComma(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, "null,"...) } func appendColon(_ *encoder.RuntimeContext, b []byte) []byte { last := len(b) - 1 b[last] = ':' return b } func appendMapKeyValue(_ *encoder.RuntimeContext, _ *encoder.Opcode, b, key, value []byte) []byte { b = append(b, key...) b[len(b)-1] = ':' return append(b, value...) } func appendMapEnd(_ *encoder.RuntimeContext, _ *encoder.Opcode, b []byte) []byte { b[len(b)-1] = '}' b = append(b, ',') return b } func appendMarshalJSON(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte, v interface{}) ([]byte, error) { return encoder.AppendMarshalJSON(ctx, code, b, v) } func appendMarshalText(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte, v interface{}) ([]byte, error) { return encoder.AppendMarshalText(ctx, code, b, v) } func appendArrayHead(_ *encoder.RuntimeContext, _ *encoder.Opcode, b []byte) []byte { return append(b, '[') } func appendArrayEnd(_ *encoder.RuntimeContext, _ *encoder.Opcode, b []byte) []byte { last := len(b) - 1 b[last] = ']' return append(b, ',') } func appendEmptyArray(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, '[', ']', ',') } func appendEmptyObject(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, '{', '}', ',') } func appendObjectEnd(_ *encoder.RuntimeContext, _ *encoder.Opcode, b []byte) []byte { last := len(b) - 1 b[last] = '}' return append(b, ',') } func appendStructHead(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, '{') } func appendStructKey(_ *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { return append(b, code.Key...) } func appendStructEnd(_ *encoder.RuntimeContext, _ *encoder.Opcode, b []byte) []byte { return append(b, '}', ',') } func appendStructEndSkipLast(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { last := len(b) - 1 if b[last] == ',' { b[last] = '}' return appendComma(ctx, b) } return appendStructEnd(ctx, code, b) } func restoreIndent(_ *encoder.RuntimeContext, _ *encoder.Opcode, _ uintptr) {} func storeIndent(_ uintptr, _ *encoder.Opcode, _ uintptr) {} func appendMapKeyIndent(_ *encoder.RuntimeContext, _ *encoder.Opcode, b []byte) []byte { return b } func appendArrayElemIndent(_ *encoder.RuntimeContext, _ *encoder.Opcode, b []byte) []byte { return b } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/vm/vm.go ================================================ // Code generated by internal/cmd/generator. DO NOT EDIT! package vm import ( "math" "reflect" "sort" "unsafe" "github.com/goccy/go-json/internal/encoder" "github.com/goccy/go-json/internal/runtime" ) func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]byte, error) { recursiveLevel := 0 ptrOffset := uintptr(0) ctxptr := ctx.Ptr() var code *encoder.Opcode if (ctx.Option.Flag & encoder.HTMLEscapeOption) != 0 { code = codeSet.EscapeKeyCode } else { code = codeSet.NoescapeKeyCode } for { switch code.Op { default: return nil, errUnimplementedOp(code.Op) case encoder.OpPtr: p := load(ctxptr, code.Idx) code = code.Next store(ctxptr, code.Idx, ptrToPtr(p)) case encoder.OpIntPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpInt: b = appendInt(ctx, b, load(ctxptr, code.Idx), code) b = appendComma(ctx, b) code = code.Next case encoder.OpUintPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpUint: b = appendUint(ctx, b, load(ctxptr, code.Idx), code) b = appendComma(ctx, b) code = code.Next case encoder.OpIntString: b = append(b, '"') b = appendInt(ctx, b, load(ctxptr, code.Idx), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpUintString: b = append(b, '"') b = appendUint(ctx, b, load(ctxptr, code.Idx), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpFloat32Ptr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNull(ctx, b) b = appendComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpFloat32: b = appendFloat32(ctx, b, ptrToFloat32(load(ctxptr, code.Idx))) b = appendComma(ctx, b) code = code.Next case encoder.OpFloat64Ptr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpFloat64: v := ptrToFloat64(load(ctxptr, code.Idx)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next case encoder.OpStringPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpString: b = appendString(ctx, b, ptrToString(load(ctxptr, code.Idx))) b = appendComma(ctx, b) code = code.Next case encoder.OpBoolPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpBool: b = appendBool(ctx, b, ptrToBool(load(ctxptr, code.Idx))) b = appendComma(ctx, b) code = code.Next case encoder.OpBytesPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpBytes: b = appendByteSlice(ctx, b, ptrToBytes(load(ctxptr, code.Idx))) b = appendComma(ctx, b) code = code.Next case encoder.OpNumberPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpNumber: bb, err := appendNumber(ctx, b, ptrToNumber(load(ctxptr, code.Idx))) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpInterfacePtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpInterface: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } if recursiveLevel > encoder.StartDetectingCyclesAfter { for _, seen := range ctx.SeenPtr { if p == seen { return nil, errUnsupportedValue(code, p) } } } ctx.SeenPtr = append(ctx.SeenPtr, p) var ( typ *runtime.Type ifacePtr unsafe.Pointer ) up := ptrToUnsafePtr(p) if code.Flags&encoder.NonEmptyInterfaceFlags != 0 { iface := (*nonEmptyInterface)(up) ifacePtr = iface.ptr if iface.itab != nil { typ = iface.itab.typ } } else { iface := (*emptyInterface)(up) ifacePtr = iface.ptr typ = iface.typ } if ifacePtr == nil { isDirectedNil := typ != nil && typ.Kind() == reflect.Struct && !runtime.IfaceIndir(typ) if !isDirectedNil { b = appendNullComma(ctx, b) code = code.Next break } } ctx.KeepRefs = append(ctx.KeepRefs, up) ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ))) if err != nil { return nil, err } totalLength := uintptr(code.Length) + 3 nextTotalLength := uintptr(ifaceCodeSet.CodeLength) + 3 var c *encoder.Opcode if (ctx.Option.Flag & encoder.HTMLEscapeOption) != 0 { c = ifaceCodeSet.InterfaceEscapeKeyCode } else { c = ifaceCodeSet.InterfaceNoescapeKeyCode } curlen := uintptr(len(ctx.Ptrs)) offsetNum := ptrOffset / uintptrSize oldOffset := ptrOffset ptrOffset += totalLength * uintptrSize oldBaseIndent := ctx.BaseIndent ctx.BaseIndent += code.Indent newLen := offsetNum + totalLength + nextTotalLength if curlen < newLen { ctx.Ptrs = append(ctx.Ptrs, make([]uintptr, newLen-curlen)...) } ctxptr = ctx.Ptr() + ptrOffset // assign new ctxptr end := ifaceCodeSet.EndCode store(ctxptr, c.Idx, uintptr(ifacePtr)) store(ctxptr, end.Idx, oldOffset) store(ctxptr, end.ElemIdx, uintptr(unsafe.Pointer(code.Next))) storeIndent(ctxptr, end, uintptr(oldBaseIndent)) code = c recursiveLevel++ case encoder.OpInterfaceEnd: recursiveLevel-- // restore ctxptr offset := load(ctxptr, code.Idx) restoreIndent(ctx, code, ctxptr) ctx.SeenPtr = ctx.SeenPtr[:len(ctx.SeenPtr)-1] codePtr := load(ctxptr, code.ElemIdx) code = (*encoder.Opcode)(ptrToUnsafePtr(codePtr)) ctxptr = ctx.Ptr() + offset ptrOffset = offset case encoder.OpMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, ptrToPtr(p)) fallthrough case encoder.OpMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } if (code.Flags&encoder.IsNilableTypeFlags) != 0 && (code.Flags&encoder.IndirectFlags) != 0 { p = ptrToPtr(p) } bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, ptrToPtr(p)) fallthrough case encoder.OpMarshalText: p := load(ctxptr, code.Idx) if p == 0 { b = append(b, `""`...) b = appendComma(ctx, b) code = code.Next break } if (code.Flags&encoder.IsNilableTypeFlags) != 0 && (code.Flags&encoder.IndirectFlags) != 0 { p = ptrToPtr(p) } bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpSlicePtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpSlice: p := load(ctxptr, code.Idx) slice := ptrToSlice(p) if p == 0 || slice.Data == nil { b = appendNullComma(ctx, b) code = code.End.Next break } store(ctxptr, code.ElemIdx, 0) store(ctxptr, code.Length, uintptr(slice.Len)) store(ctxptr, code.Idx, uintptr(slice.Data)) if slice.Len > 0 { b = appendArrayHead(ctx, code, b) code = code.Next store(ctxptr, code.Idx, uintptr(slice.Data)) } else { b = appendEmptyArray(ctx, b) code = code.End.Next } case encoder.OpSliceElem: idx := load(ctxptr, code.ElemIdx) length := load(ctxptr, code.Length) idx++ if idx < length { b = appendArrayElemIndent(ctx, code, b) store(ctxptr, code.ElemIdx, idx) data := load(ctxptr, code.Idx) size := uintptr(code.Size) code = code.Next store(ctxptr, code.Idx, data+idx*size) } else { b = appendArrayEnd(ctx, code, b) code = code.End.Next } case encoder.OpArrayPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpArray: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } if code.Length > 0 { b = appendArrayHead(ctx, code, b) store(ctxptr, code.ElemIdx, 0) code = code.Next store(ctxptr, code.Idx, p) } else { b = appendEmptyArray(ctx, b) code = code.End.Next } case encoder.OpArrayElem: idx := load(ctxptr, code.ElemIdx) idx++ if idx < uintptr(code.Length) { b = appendArrayElemIndent(ctx, code, b) store(ctxptr, code.ElemIdx, idx) p := load(ctxptr, code.Idx) size := uintptr(code.Size) code = code.Next store(ctxptr, code.Idx, p+idx*size) } else { b = appendArrayEnd(ctx, code, b) code = code.End.Next } case encoder.OpMapPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpMap: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } uptr := ptrToUnsafePtr(p) mlen := maplen(uptr) if mlen <= 0 { b = appendEmptyObject(ctx, b) code = code.End.Next break } b = appendStructHead(ctx, b) unorderedMap := (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 mapCtx := encoder.NewMapContext(mlen, unorderedMap) mapiterinit(code.Type, uptr, &mapCtx.Iter) store(ctxptr, code.Idx, uintptr(unsafe.Pointer(mapCtx))) ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(mapCtx)) if unorderedMap { b = appendMapKeyIndent(ctx, code.Next, b) } else { mapCtx.Start = len(b) mapCtx.First = len(b) } key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next case encoder.OpMapKey: mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) idx := mapCtx.Idx idx++ if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { if idx < mapCtx.Len { b = appendMapKeyIndent(ctx, code, b) mapCtx.Idx = int(idx) key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next } else { b = appendObjectEnd(ctx, code, b) encoder.ReleaseMapContext(mapCtx) code = code.End.Next } } else { mapCtx.Slice.Items[mapCtx.Idx].Value = b[mapCtx.Start:len(b)] if idx < mapCtx.Len { mapCtx.Idx = int(idx) mapCtx.Start = len(b) key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next } else { code = code.End } } case encoder.OpMapValue: mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { b = appendColon(ctx, b) } else { mapCtx.Slice.Items[mapCtx.Idx].Key = b[mapCtx.Start:len(b)] mapCtx.Start = len(b) } value := mapitervalue(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(value)) mapiternext(&mapCtx.Iter) code = code.Next case encoder.OpMapEnd: // this operation only used by sorted map. mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) sort.Sort(mapCtx.Slice) buf := mapCtx.Buf for _, item := range mapCtx.Slice.Items { buf = appendMapKeyValue(ctx, code, buf, item.Key, item.Value) } buf = appendMapEnd(ctx, code, buf) b = b[:mapCtx.First] b = append(b, buf...) mapCtx.Buf = buf encoder.ReleaseMapContext(mapCtx) code = code.Next case encoder.OpRecursivePtr: p := load(ctxptr, code.Idx) if p == 0 { code = code.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpRecursive: ptr := load(ctxptr, code.Idx) if ptr != 0 { if recursiveLevel > encoder.StartDetectingCyclesAfter { for _, seen := range ctx.SeenPtr { if ptr == seen { return nil, errUnsupportedValue(code, ptr) } } } } ctx.SeenPtr = append(ctx.SeenPtr, ptr) c := code.Jmp.Code curlen := uintptr(len(ctx.Ptrs)) offsetNum := ptrOffset / uintptrSize oldOffset := ptrOffset ptrOffset += code.Jmp.CurLen * uintptrSize oldBaseIndent := ctx.BaseIndent indentDiffFromTop := c.Indent - 1 ctx.BaseIndent += code.Indent - indentDiffFromTop newLen := offsetNum + code.Jmp.CurLen + code.Jmp.NextLen if curlen < newLen { ctx.Ptrs = append(ctx.Ptrs, make([]uintptr, newLen-curlen)...) } ctxptr = ctx.Ptr() + ptrOffset // assign new ctxptr store(ctxptr, c.Idx, ptr) store(ctxptr, c.End.Next.Idx, oldOffset) store(ctxptr, c.End.Next.ElemIdx, uintptr(unsafe.Pointer(code.Next))) storeIndent(ctxptr, c.End.Next, uintptr(oldBaseIndent)) code = c recursiveLevel++ case encoder.OpRecursiveEnd: recursiveLevel-- // restore ctxptr restoreIndent(ctx, code, ctxptr) offset := load(ctxptr, code.Idx) ctx.SeenPtr = ctx.SeenPtr[:len(ctx.SeenPtr)-1] codePtr := load(ctxptr, code.ElemIdx) code = (*encoder.Opcode)(ptrToUnsafePtr(codePtr)) ctxptr = ctx.Ptr() + offset ptrOffset = offset case encoder.OpStructPtrHead: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHead: p := load(ctxptr, code.Idx) if p == 0 && ((code.Flags&encoder.IndirectFlags) != 0 || code.Next.Op == encoder.OpStructEnd) { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if len(code.Key) > 0 { if (code.Flags&encoder.IsTaggedKeyFlags) != 0 || code.Flags&encoder.AnonymousKeyFlags == 0 { b = appendStructKey(ctx, code, b) } } p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructPtrHeadOmitEmpty: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmpty: p := load(ctxptr, code.Idx) if p == 0 && ((code.Flags&encoder.IndirectFlags) != 0 || code.Next.Op == encoder.OpStructEnd) { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) if p == 0 || (ptrToPtr(p) == 0 && (code.Flags&encoder.IsNextOpPtrTypeFlags) != 0) { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadInt: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadInt: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyInt: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyInt: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadIntString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadIntString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyIntString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyIntString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) u64 := ptrToUint64(p, code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadIntPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadIntPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendInt(ctx, b, p, code) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyIntPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyIntPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p, code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadIntPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadIntPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyIntPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyIntPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadUint: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadUint: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyUint: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyUint: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadUintString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadUintString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyUintString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyUintString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadUintPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadUintPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendUint(ctx, b, p, code) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyUintPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyUintPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p, code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadUintPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadUintPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyUintPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyUintPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadFloat32: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadFloat32: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat32: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyFloat32: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat32(p + uintptr(code.Offset)) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, v) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadFloat32String: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadFloat32String: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat32String: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyFloat32String: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat32(p + uintptr(code.Offset)) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadFloat32Ptr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadFloat32Ptr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendFloat32(ctx, b, ptrToFloat32(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat32Ptr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyFloat32Ptr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadFloat32PtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadFloat32PtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat32PtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyFloat32PtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadFloat64: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadFloat64: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat64: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyFloat64: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat64(p + uintptr(code.Offset)) if v == 0 { code = code.NextField } else { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadFloat64String: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadFloat64String: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat64String: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyFloat64String: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat64(p + uintptr(code.Offset)) if v == 0 { code = code.NextField } else { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadFloat64Ptr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadFloat64Ptr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat64Ptr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyFloat64Ptr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadFloat64PtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadFloat64PtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat64PtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyFloat64PtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNull(ctx, b) b = appendComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToString(p + uintptr(code.Offset)) if v == "" { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, v) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadStringString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadStringString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p+uintptr(code.Offset))))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyStringString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyStringString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToString(p + uintptr(code.Offset)) if v == "" { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, v))) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadStringPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadStringPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, ptrToString(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyStringPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyStringPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadStringPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadStringPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyStringPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyStringPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadBool: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadBool: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBool: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyBool: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, v) b = appendComma(ctx, b) code = code.Next } else { code = code.NextField } case encoder.OpStructPtrHeadBoolString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadBoolString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBoolString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyBoolString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } else { code = code.NextField } case encoder.OpStructPtrHeadBoolPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadBoolPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendBool(ctx, b, ptrToBool(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBoolPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyBoolPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadBoolPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadBoolPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBoolPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyBoolPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadBytes: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadBytes: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBytes: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyBytes: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToBytes(p + uintptr(code.Offset)) if len(v) == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, v) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadBytesPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadBytesPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendByteSlice(ctx, b, ptrToBytes(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBytesPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyBytesPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadNumber: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadNumber: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpStructPtrHeadOmitEmptyNumber: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyNumber: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToNumber(p + uintptr(code.Offset)) if v == "" { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next } case encoder.OpStructPtrHeadNumberString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadNumberString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyNumberString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyNumberString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToNumber(p + uintptr(code.Offset)) if v == "" { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadNumberPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadNumberPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyNumberPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyNumberPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructPtrHeadNumberPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadNumberPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyNumberPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyNumberPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadArray, encoder.OpStructPtrHeadSlice: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadArray, encoder.OpStructHeadSlice: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructPtrHeadOmitEmptyArray: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyArray: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructPtrHeadOmitEmptySlice: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptySlice: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) slice := ptrToSlice(p) if slice.Len == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadArrayPtr, encoder.OpStructPtrHeadSlicePtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadArrayPtr, encoder.OpStructHeadSlicePtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNullComma(ctx, b) code = code.NextField } else { code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadOmitEmptyArrayPtr, encoder.OpStructPtrHeadOmitEmptySlicePtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyArrayPtr, encoder.OpStructHeadOmitEmptySlicePtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadMap: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadMap: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if p != 0 && (code.Flags&encoder.IndirectFlags) != 0 { p = ptrToPtr(p + uintptr(code.Offset)) } code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructPtrHeadOmitEmptyMap: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyMap: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if p != 0 && (code.Flags&encoder.IndirectFlags) != 0 { p = ptrToPtr(p + uintptr(code.Offset)) } if maplen(ptrToUnsafePtr(p)) == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadMapPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadMapPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if p == 0 { b = appendNullComma(ctx, b) code = code.NextField break } p = ptrToPtr(p + uintptr(code.Offset)) if p == 0 { b = appendNullComma(ctx, b) code = code.NextField } else { if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p, code.PtrNum) } code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadOmitEmptyMapPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyMapPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if p == 0 { code = code.NextField break } p = ptrToPtr(p + uintptr(code.Offset)) if p == 0 { code = code.NextField } else { if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p, code.PtrNum) } b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadMarshalJSON { p = ptrToPtr(p) } } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadOmitEmptyMarshalJSON { p = ptrToPtr(p) } } iface := ptrToInterface(code, p) if (code.Flags&encoder.NilCheckFlags) != 0 && encoder.IsNilForMarshaler(iface) { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendMarshalJSON(ctx, code, b, iface) if err != nil { return nil, err } b = bb b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if p == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadMarshalText: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadMarshalText: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadMarshalText { p = ptrToPtr(p) } } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyMarshalText: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyMarshalText: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadOmitEmptyMarshalText { p = ptrToPtr(p) } } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if p == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb b = appendComma(ctx, b) code = code.Next } case encoder.OpStructField: if code.Flags&encoder.IsTaggedKeyFlags != 0 || code.Flags&encoder.AnonymousKeyFlags == 0 { b = appendStructKey(ctx, code, b) } p := load(ctxptr, code.Idx) + uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmpty: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) if ptrToPtr(p) == 0 && (code.Flags&encoder.IsNextOpPtrTypeFlags) != 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructFieldInt: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyInt: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldIntString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyIntString: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldIntPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendInt(ctx, b, p, code) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyIntPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p, code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldIntPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyIntPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldUint: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyUint: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldUintString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyUintString: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldUintPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendUint(ctx, b, p, code) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyUintPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p, code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldUintPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyUintPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat32: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat32: p := load(ctxptr, code.Idx) v := ptrToFloat32(p + uintptr(code.Offset)) if v != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat32String: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat32String: p := load(ctxptr, code.Idx) v := ptrToFloat32(p + uintptr(code.Offset)) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat32Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendFloat32(ctx, b, ptrToFloat32(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat32Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat32PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat32PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat64: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat64: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if v != 0 { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat64String: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat64String: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if v != 0 { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat64Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat64Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat64PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat64PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyString: p := load(ctxptr, code.Idx) v := ptrToString(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldStringString: p := load(ctxptr, code.Idx) s := ptrToString(p + uintptr(code.Offset)) b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, s))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyStringString: p := load(ctxptr, code.Idx) v := ptrToString(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, v))) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldStringPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, ptrToString(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyStringPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldStringPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyStringPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBool: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBool: p := load(ctxptr, code.Idx) v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBoolString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBoolString: p := load(ctxptr, code.Idx) v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBoolPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendBool(ctx, b, ptrToBool(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBoolPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBoolPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBoolPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBytes: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBytes: p := load(ctxptr, code.Idx) v := ptrToBytes(p + uintptr(code.Offset)) if len(v) > 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBytesPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendByteSlice(ctx, b, ptrToBytes(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBytesPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldNumber: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpStructFieldOmitEmptyNumber: p := load(ctxptr, code.Idx) v := ptrToNumber(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructFieldNumberString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyNumberString: p := load(ctxptr, code.Idx) v := ptrToNumber(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldNumberPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyNumberPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructFieldNumberPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyNumberPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldMarshalJSON: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { p = ptrToPtr(p) } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyMarshalJSON: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { p = ptrToPtr(p) } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { code = code.NextField break } iface := ptrToInterface(code, p) if (code.Flags&encoder.NilCheckFlags) != 0 && encoder.IsNilForMarshaler(iface) { code = code.NextField break } b = appendStructKey(ctx, code, b) bb, err := appendMarshalJSON(ctx, code, b, iface) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpStructFieldMarshalJSONPtr: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyMarshalJSONPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructFieldMarshalText: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { p = ptrToPtr(p) } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyMarshalText: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { p = ptrToPtr(p) } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { code = code.NextField break } b = appendStructKey(ctx, code, b) bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpStructFieldMarshalTextPtr: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyMarshalTextPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructFieldArray: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyArray: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldArrayPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyArrayPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } else { code = code.NextField } case encoder.OpStructFieldSlice: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptySlice: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) slice := ptrToSlice(p) if slice.Len == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructFieldSlicePtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptySlicePtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } else { code = code.NextField } case encoder.OpStructFieldMap: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToPtr(p + uintptr(code.Offset)) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyMap: p := load(ctxptr, code.Idx) p = ptrToPtr(p + uintptr(code.Offset)) if p == 0 || maplen(ptrToUnsafePtr(p)) == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructFieldMapPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToPtr(p + uintptr(code.Offset)) if p != 0 { p = ptrToNPtr(p, code.PtrNum) } code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyMapPtr: p := load(ctxptr, code.Idx) p = ptrToPtr(p + uintptr(code.Offset)) if p != 0 { p = ptrToNPtr(p, code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } else { code = code.NextField } case encoder.OpStructFieldStruct: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyStruct: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) if ptrToPtr(p) == 0 && (code.Flags&encoder.IsNextOpPtrTypeFlags) != 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructEnd: b = appendStructEndSkipLast(ctx, code, b) code = code.Next case encoder.OpStructEndInt: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyInt: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndIntString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyIntString: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndIntPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendInt(ctx, b, p, code) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyIntPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p, code) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndIntPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyIntPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndUint: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyUint: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndUintString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyUintString: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndUintPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendUint(ctx, b, p, code) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyUintPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p, code) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndUintPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyUintPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat32: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat32: p := load(ctxptr, code.Idx) v := ptrToFloat32(p + uintptr(code.Offset)) if v != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat32String: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat32String: p := load(ctxptr, code.Idx) v := ptrToFloat32(p + uintptr(code.Offset)) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat32Ptr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendFloat32(ctx, b, ptrToFloat32(p)) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat32Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p)) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat32PtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat32PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat64: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat64: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if v != 0 { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat64String: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat64String: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if v != 0 { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat64Ptr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) b = appendStructEnd(ctx, code, b) code = code.Next break } v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat64Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat64PtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat64PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p+uintptr(code.Offset))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyString: p := load(ctxptr, code.Idx) v := ptrToString(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndStringString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) s := ptrToString(p + uintptr(code.Offset)) b = appendString(ctx, b, string(appendString(ctx, []byte{}, s))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyStringString: p := load(ctxptr, code.Idx) v := ptrToString(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, v))) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndStringPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, ptrToString(p)) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyStringPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p)) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndStringPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyStringPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBool: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBool: p := load(ctxptr, code.Idx) v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBoolString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBoolString: p := load(ctxptr, code.Idx) v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBoolPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendBool(ctx, b, ptrToBool(p)) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBoolPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p)) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBoolPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBoolPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBytes: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p+uintptr(code.Offset))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBytes: p := load(ctxptr, code.Idx) v := ptrToBytes(p + uintptr(code.Offset)) if len(v) > 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBytesPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendByteSlice(ctx, b, ptrToBytes(p)) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBytesPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p)) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndNumber: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = appendStructEnd(ctx, code, bb) code = code.Next case encoder.OpStructEndOmitEmptyNumber: p := load(ctxptr, code.Idx) v := ptrToNumber(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = appendStructEnd(ctx, code, bb) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndNumberString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = append(bb, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyNumberString: p := load(ctxptr, code.Idx) v := ptrToNumber(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = append(bb, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndNumberPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = bb } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyNumberPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = appendStructEnd(ctx, code, bb) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndNumberPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyNumberPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpEnd: goto END } } END: return b, nil } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/vm_color/debug_vm.go ================================================ package vm_color import ( "fmt" "github.com/goccy/go-json/internal/encoder" ) func DebugRun(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]byte, error) { var code *encoder.Opcode if (ctx.Option.Flag & encoder.HTMLEscapeOption) != 0 { code = codeSet.EscapeKeyCode } else { code = codeSet.NoescapeKeyCode } defer func() { if err := recover(); err != nil { w := ctx.Option.DebugOut fmt.Fprintln(w, "=============[DEBUG]===============") fmt.Fprintln(w, "* [TYPE]") fmt.Fprintln(w, codeSet.Type) fmt.Fprintf(w, "\n") fmt.Fprintln(w, "* [ALL OPCODE]") fmt.Fprintln(w, code.Dump()) fmt.Fprintf(w, "\n") fmt.Fprintln(w, "* [CONTEXT]") fmt.Fprintf(w, "%+v\n", ctx) fmt.Fprintln(w, "===================================") panic(err) } }() return Run(ctx, b, codeSet) } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/vm_color/hack.go ================================================ package vm_color import ( // HACK: compile order // `vm`, `vm_indent`, `vm_color`, `vm_color_indent` packages uses a lot of memory to compile, // so forcibly make dependencies and avoid compiling in concurrent. // dependency order: vm => vm_indent => vm_color => vm_color_indent _ "github.com/goccy/go-json/internal/encoder/vm_color_indent" ) ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/vm_color/util.go ================================================ package vm_color import ( "encoding/json" "fmt" "unsafe" "github.com/goccy/go-json/internal/encoder" "github.com/goccy/go-json/internal/runtime" ) const uintptrSize = 4 << (^uintptr(0) >> 63) var ( errUnsupportedValue = encoder.ErrUnsupportedValue errUnsupportedFloat = encoder.ErrUnsupportedFloat mapiterinit = encoder.MapIterInit mapiterkey = encoder.MapIterKey mapitervalue = encoder.MapIterValue mapiternext = encoder.MapIterNext maplen = encoder.MapLen ) type emptyInterface struct { typ *runtime.Type ptr unsafe.Pointer } type nonEmptyInterface struct { itab *struct { ityp *runtime.Type // static interface type typ *runtime.Type // dynamic concrete type // unused fields... } ptr unsafe.Pointer } func errUnimplementedOp(op encoder.OpType) error { return fmt.Errorf("encoder: opcode %s has not been implemented", op) } func load(base uintptr, idx uint32) uintptr { addr := base + uintptr(idx) return **(**uintptr)(unsafe.Pointer(&addr)) } func store(base uintptr, idx uint32, p uintptr) { addr := base + uintptr(idx) **(**uintptr)(unsafe.Pointer(&addr)) = p } func loadNPtr(base uintptr, idx uint32, ptrNum uint8) uintptr { addr := base + uintptr(idx) p := **(**uintptr)(unsafe.Pointer(&addr)) for i := uint8(0); i < ptrNum; i++ { if p == 0 { return 0 } p = ptrToPtr(p) } return p } func ptrToUint64(p uintptr, bitSize uint8) uint64 { switch bitSize { case 8: return (uint64)(**(**uint8)(unsafe.Pointer(&p))) case 16: return (uint64)(**(**uint16)(unsafe.Pointer(&p))) case 32: return (uint64)(**(**uint32)(unsafe.Pointer(&p))) case 64: return **(**uint64)(unsafe.Pointer(&p)) } return 0 } func ptrToFloat32(p uintptr) float32 { return **(**float32)(unsafe.Pointer(&p)) } func ptrToFloat64(p uintptr) float64 { return **(**float64)(unsafe.Pointer(&p)) } func ptrToBool(p uintptr) bool { return **(**bool)(unsafe.Pointer(&p)) } func ptrToBytes(p uintptr) []byte { return **(**[]byte)(unsafe.Pointer(&p)) } func ptrToNumber(p uintptr) json.Number { return **(**json.Number)(unsafe.Pointer(&p)) } func ptrToString(p uintptr) string { return **(**string)(unsafe.Pointer(&p)) } func ptrToSlice(p uintptr) *runtime.SliceHeader { return *(**runtime.SliceHeader)(unsafe.Pointer(&p)) } func ptrToPtr(p uintptr) uintptr { return uintptr(**(**unsafe.Pointer)(unsafe.Pointer(&p))) } func ptrToNPtr(p uintptr, ptrNum uint8) uintptr { for i := uint8(0); i < ptrNum; i++ { if p == 0 { return 0 } p = ptrToPtr(p) } return p } func ptrToUnsafePtr(p uintptr) unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Pointer(&p)) } func ptrToInterface(code *encoder.Opcode, p uintptr) interface{} { return *(*interface{})(unsafe.Pointer(&emptyInterface{ typ: code.Type, ptr: *(*unsafe.Pointer)(unsafe.Pointer(&p)), })) } func appendInt(ctx *encoder.RuntimeContext, b []byte, p uintptr, code *encoder.Opcode) []byte { format := ctx.Option.ColorScheme.Int b = append(b, format.Header...) b = encoder.AppendInt(ctx, b, p, code) return append(b, format.Footer...) } func appendUint(ctx *encoder.RuntimeContext, b []byte, p uintptr, code *encoder.Opcode) []byte { format := ctx.Option.ColorScheme.Uint b = append(b, format.Header...) b = encoder.AppendUint(ctx, b, p, code) return append(b, format.Footer...) } func appendFloat32(ctx *encoder.RuntimeContext, b []byte, v float32) []byte { format := ctx.Option.ColorScheme.Float b = append(b, format.Header...) b = encoder.AppendFloat32(ctx, b, v) return append(b, format.Footer...) } func appendFloat64(ctx *encoder.RuntimeContext, b []byte, v float64) []byte { format := ctx.Option.ColorScheme.Float b = append(b, format.Header...) b = encoder.AppendFloat64(ctx, b, v) return append(b, format.Footer...) } func appendString(ctx *encoder.RuntimeContext, b []byte, v string) []byte { format := ctx.Option.ColorScheme.String b = append(b, format.Header...) b = encoder.AppendString(ctx, b, v) return append(b, format.Footer...) } func appendByteSlice(ctx *encoder.RuntimeContext, b []byte, src []byte) []byte { format := ctx.Option.ColorScheme.Binary b = append(b, format.Header...) b = encoder.AppendByteSlice(ctx, b, src) return append(b, format.Footer...) } func appendNumber(ctx *encoder.RuntimeContext, b []byte, n json.Number) ([]byte, error) { format := ctx.Option.ColorScheme.Int b = append(b, format.Header...) bb, err := encoder.AppendNumber(ctx, b, n) if err != nil { return nil, err } return append(bb, format.Footer...), nil } func appendBool(ctx *encoder.RuntimeContext, b []byte, v bool) []byte { format := ctx.Option.ColorScheme.Bool b = append(b, format.Header...) if v { b = append(b, "true"...) } else { b = append(b, "false"...) } return append(b, format.Footer...) } func appendNull(ctx *encoder.RuntimeContext, b []byte) []byte { format := ctx.Option.ColorScheme.Null b = append(b, format.Header...) b = append(b, "null"...) return append(b, format.Footer...) } func appendComma(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, ',') } func appendNullComma(ctx *encoder.RuntimeContext, b []byte) []byte { format := ctx.Option.ColorScheme.Null b = append(b, format.Header...) b = append(b, "null"...) return append(append(b, format.Footer...), ',') } func appendColon(_ *encoder.RuntimeContext, b []byte) []byte { last := len(b) - 1 b[last] = ':' return b } func appendMapKeyValue(_ *encoder.RuntimeContext, _ *encoder.Opcode, b, key, value []byte) []byte { b = append(b, key[:len(key)-1]...) b = append(b, ':') return append(b, value...) } func appendMapEnd(_ *encoder.RuntimeContext, _ *encoder.Opcode, b []byte) []byte { last := len(b) - 1 b[last] = '}' b = append(b, ',') return b } func appendMarshalJSON(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte, v interface{}) ([]byte, error) { return encoder.AppendMarshalJSON(ctx, code, b, v) } func appendMarshalText(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte, v interface{}) ([]byte, error) { format := ctx.Option.ColorScheme.String b = append(b, format.Header...) bb, err := encoder.AppendMarshalText(ctx, code, b, v) if err != nil { return nil, err } return append(bb, format.Footer...), nil } func appendArrayHead(_ *encoder.RuntimeContext, _ *encoder.Opcode, b []byte) []byte { return append(b, '[') } func appendArrayEnd(_ *encoder.RuntimeContext, _ *encoder.Opcode, b []byte) []byte { last := len(b) - 1 b[last] = ']' return append(b, ',') } func appendEmptyArray(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, '[', ']', ',') } func appendEmptyObject(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, '{', '}', ',') } func appendObjectEnd(_ *encoder.RuntimeContext, _ *encoder.Opcode, b []byte) []byte { last := len(b) - 1 b[last] = '}' return append(b, ',') } func appendStructHead(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, '{') } func appendStructKey(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { format := ctx.Option.ColorScheme.ObjectKey b = append(b, format.Header...) b = append(b, code.Key[:len(code.Key)-1]...) b = append(b, format.Footer...) return append(b, ':') } func appendStructEnd(_ *encoder.RuntimeContext, _ *encoder.Opcode, b []byte) []byte { return append(b, '}', ',') } func appendStructEndSkipLast(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { last := len(b) - 1 if b[last] == ',' { b[last] = '}' return appendComma(ctx, b) } return appendStructEnd(ctx, code, b) } func restoreIndent(_ *encoder.RuntimeContext, _ *encoder.Opcode, _ uintptr) {} func storeIndent(_ uintptr, _ *encoder.Opcode, _ uintptr) {} func appendMapKeyIndent(_ *encoder.RuntimeContext, _ *encoder.Opcode, b []byte) []byte { return b } func appendArrayElemIndent(_ *encoder.RuntimeContext, _ *encoder.Opcode, b []byte) []byte { return b } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/vm_color/vm.go ================================================ // Code generated by internal/cmd/generator. DO NOT EDIT! package vm_color import ( "math" "reflect" "sort" "unsafe" "github.com/goccy/go-json/internal/encoder" "github.com/goccy/go-json/internal/runtime" ) func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]byte, error) { recursiveLevel := 0 ptrOffset := uintptr(0) ctxptr := ctx.Ptr() var code *encoder.Opcode if (ctx.Option.Flag & encoder.HTMLEscapeOption) != 0 { code = codeSet.EscapeKeyCode } else { code = codeSet.NoescapeKeyCode } for { switch code.Op { default: return nil, errUnimplementedOp(code.Op) case encoder.OpPtr: p := load(ctxptr, code.Idx) code = code.Next store(ctxptr, code.Idx, ptrToPtr(p)) case encoder.OpIntPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpInt: b = appendInt(ctx, b, load(ctxptr, code.Idx), code) b = appendComma(ctx, b) code = code.Next case encoder.OpUintPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpUint: b = appendUint(ctx, b, load(ctxptr, code.Idx), code) b = appendComma(ctx, b) code = code.Next case encoder.OpIntString: b = append(b, '"') b = appendInt(ctx, b, load(ctxptr, code.Idx), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpUintString: b = append(b, '"') b = appendUint(ctx, b, load(ctxptr, code.Idx), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpFloat32Ptr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNull(ctx, b) b = appendComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpFloat32: b = appendFloat32(ctx, b, ptrToFloat32(load(ctxptr, code.Idx))) b = appendComma(ctx, b) code = code.Next case encoder.OpFloat64Ptr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpFloat64: v := ptrToFloat64(load(ctxptr, code.Idx)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next case encoder.OpStringPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpString: b = appendString(ctx, b, ptrToString(load(ctxptr, code.Idx))) b = appendComma(ctx, b) code = code.Next case encoder.OpBoolPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpBool: b = appendBool(ctx, b, ptrToBool(load(ctxptr, code.Idx))) b = appendComma(ctx, b) code = code.Next case encoder.OpBytesPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpBytes: b = appendByteSlice(ctx, b, ptrToBytes(load(ctxptr, code.Idx))) b = appendComma(ctx, b) code = code.Next case encoder.OpNumberPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpNumber: bb, err := appendNumber(ctx, b, ptrToNumber(load(ctxptr, code.Idx))) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpInterfacePtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpInterface: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } if recursiveLevel > encoder.StartDetectingCyclesAfter { for _, seen := range ctx.SeenPtr { if p == seen { return nil, errUnsupportedValue(code, p) } } } ctx.SeenPtr = append(ctx.SeenPtr, p) var ( typ *runtime.Type ifacePtr unsafe.Pointer ) up := ptrToUnsafePtr(p) if code.Flags&encoder.NonEmptyInterfaceFlags != 0 { iface := (*nonEmptyInterface)(up) ifacePtr = iface.ptr if iface.itab != nil { typ = iface.itab.typ } } else { iface := (*emptyInterface)(up) ifacePtr = iface.ptr typ = iface.typ } if ifacePtr == nil { isDirectedNil := typ != nil && typ.Kind() == reflect.Struct && !runtime.IfaceIndir(typ) if !isDirectedNil { b = appendNullComma(ctx, b) code = code.Next break } } ctx.KeepRefs = append(ctx.KeepRefs, up) ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ))) if err != nil { return nil, err } totalLength := uintptr(code.Length) + 3 nextTotalLength := uintptr(ifaceCodeSet.CodeLength) + 3 var c *encoder.Opcode if (ctx.Option.Flag & encoder.HTMLEscapeOption) != 0 { c = ifaceCodeSet.InterfaceEscapeKeyCode } else { c = ifaceCodeSet.InterfaceNoescapeKeyCode } curlen := uintptr(len(ctx.Ptrs)) offsetNum := ptrOffset / uintptrSize oldOffset := ptrOffset ptrOffset += totalLength * uintptrSize oldBaseIndent := ctx.BaseIndent ctx.BaseIndent += code.Indent newLen := offsetNum + totalLength + nextTotalLength if curlen < newLen { ctx.Ptrs = append(ctx.Ptrs, make([]uintptr, newLen-curlen)...) } ctxptr = ctx.Ptr() + ptrOffset // assign new ctxptr end := ifaceCodeSet.EndCode store(ctxptr, c.Idx, uintptr(ifacePtr)) store(ctxptr, end.Idx, oldOffset) store(ctxptr, end.ElemIdx, uintptr(unsafe.Pointer(code.Next))) storeIndent(ctxptr, end, uintptr(oldBaseIndent)) code = c recursiveLevel++ case encoder.OpInterfaceEnd: recursiveLevel-- // restore ctxptr offset := load(ctxptr, code.Idx) restoreIndent(ctx, code, ctxptr) ctx.SeenPtr = ctx.SeenPtr[:len(ctx.SeenPtr)-1] codePtr := load(ctxptr, code.ElemIdx) code = (*encoder.Opcode)(ptrToUnsafePtr(codePtr)) ctxptr = ctx.Ptr() + offset ptrOffset = offset case encoder.OpMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, ptrToPtr(p)) fallthrough case encoder.OpMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } if (code.Flags&encoder.IsNilableTypeFlags) != 0 && (code.Flags&encoder.IndirectFlags) != 0 { p = ptrToPtr(p) } bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, ptrToPtr(p)) fallthrough case encoder.OpMarshalText: p := load(ctxptr, code.Idx) if p == 0 { b = append(b, `""`...) b = appendComma(ctx, b) code = code.Next break } if (code.Flags&encoder.IsNilableTypeFlags) != 0 && (code.Flags&encoder.IndirectFlags) != 0 { p = ptrToPtr(p) } bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpSlicePtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpSlice: p := load(ctxptr, code.Idx) slice := ptrToSlice(p) if p == 0 || slice.Data == nil { b = appendNullComma(ctx, b) code = code.End.Next break } store(ctxptr, code.ElemIdx, 0) store(ctxptr, code.Length, uintptr(slice.Len)) store(ctxptr, code.Idx, uintptr(slice.Data)) if slice.Len > 0 { b = appendArrayHead(ctx, code, b) code = code.Next store(ctxptr, code.Idx, uintptr(slice.Data)) } else { b = appendEmptyArray(ctx, b) code = code.End.Next } case encoder.OpSliceElem: idx := load(ctxptr, code.ElemIdx) length := load(ctxptr, code.Length) idx++ if idx < length { b = appendArrayElemIndent(ctx, code, b) store(ctxptr, code.ElemIdx, idx) data := load(ctxptr, code.Idx) size := uintptr(code.Size) code = code.Next store(ctxptr, code.Idx, data+idx*size) } else { b = appendArrayEnd(ctx, code, b) code = code.End.Next } case encoder.OpArrayPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpArray: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } if code.Length > 0 { b = appendArrayHead(ctx, code, b) store(ctxptr, code.ElemIdx, 0) code = code.Next store(ctxptr, code.Idx, p) } else { b = appendEmptyArray(ctx, b) code = code.End.Next } case encoder.OpArrayElem: idx := load(ctxptr, code.ElemIdx) idx++ if idx < uintptr(code.Length) { b = appendArrayElemIndent(ctx, code, b) store(ctxptr, code.ElemIdx, idx) p := load(ctxptr, code.Idx) size := uintptr(code.Size) code = code.Next store(ctxptr, code.Idx, p+idx*size) } else { b = appendArrayEnd(ctx, code, b) code = code.End.Next } case encoder.OpMapPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpMap: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } uptr := ptrToUnsafePtr(p) mlen := maplen(uptr) if mlen <= 0 { b = appendEmptyObject(ctx, b) code = code.End.Next break } b = appendStructHead(ctx, b) unorderedMap := (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 mapCtx := encoder.NewMapContext(mlen, unorderedMap) mapiterinit(code.Type, uptr, &mapCtx.Iter) store(ctxptr, code.Idx, uintptr(unsafe.Pointer(mapCtx))) ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(mapCtx)) if unorderedMap { b = appendMapKeyIndent(ctx, code.Next, b) } else { mapCtx.Start = len(b) mapCtx.First = len(b) } key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next case encoder.OpMapKey: mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) idx := mapCtx.Idx idx++ if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { if idx < mapCtx.Len { b = appendMapKeyIndent(ctx, code, b) mapCtx.Idx = int(idx) key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next } else { b = appendObjectEnd(ctx, code, b) encoder.ReleaseMapContext(mapCtx) code = code.End.Next } } else { mapCtx.Slice.Items[mapCtx.Idx].Value = b[mapCtx.Start:len(b)] if idx < mapCtx.Len { mapCtx.Idx = int(idx) mapCtx.Start = len(b) key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next } else { code = code.End } } case encoder.OpMapValue: mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { b = appendColon(ctx, b) } else { mapCtx.Slice.Items[mapCtx.Idx].Key = b[mapCtx.Start:len(b)] mapCtx.Start = len(b) } value := mapitervalue(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(value)) mapiternext(&mapCtx.Iter) code = code.Next case encoder.OpMapEnd: // this operation only used by sorted map. mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) sort.Sort(mapCtx.Slice) buf := mapCtx.Buf for _, item := range mapCtx.Slice.Items { buf = appendMapKeyValue(ctx, code, buf, item.Key, item.Value) } buf = appendMapEnd(ctx, code, buf) b = b[:mapCtx.First] b = append(b, buf...) mapCtx.Buf = buf encoder.ReleaseMapContext(mapCtx) code = code.Next case encoder.OpRecursivePtr: p := load(ctxptr, code.Idx) if p == 0 { code = code.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpRecursive: ptr := load(ctxptr, code.Idx) if ptr != 0 { if recursiveLevel > encoder.StartDetectingCyclesAfter { for _, seen := range ctx.SeenPtr { if ptr == seen { return nil, errUnsupportedValue(code, ptr) } } } } ctx.SeenPtr = append(ctx.SeenPtr, ptr) c := code.Jmp.Code curlen := uintptr(len(ctx.Ptrs)) offsetNum := ptrOffset / uintptrSize oldOffset := ptrOffset ptrOffset += code.Jmp.CurLen * uintptrSize oldBaseIndent := ctx.BaseIndent indentDiffFromTop := c.Indent - 1 ctx.BaseIndent += code.Indent - indentDiffFromTop newLen := offsetNum + code.Jmp.CurLen + code.Jmp.NextLen if curlen < newLen { ctx.Ptrs = append(ctx.Ptrs, make([]uintptr, newLen-curlen)...) } ctxptr = ctx.Ptr() + ptrOffset // assign new ctxptr store(ctxptr, c.Idx, ptr) store(ctxptr, c.End.Next.Idx, oldOffset) store(ctxptr, c.End.Next.ElemIdx, uintptr(unsafe.Pointer(code.Next))) storeIndent(ctxptr, c.End.Next, uintptr(oldBaseIndent)) code = c recursiveLevel++ case encoder.OpRecursiveEnd: recursiveLevel-- // restore ctxptr restoreIndent(ctx, code, ctxptr) offset := load(ctxptr, code.Idx) ctx.SeenPtr = ctx.SeenPtr[:len(ctx.SeenPtr)-1] codePtr := load(ctxptr, code.ElemIdx) code = (*encoder.Opcode)(ptrToUnsafePtr(codePtr)) ctxptr = ctx.Ptr() + offset ptrOffset = offset case encoder.OpStructPtrHead: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHead: p := load(ctxptr, code.Idx) if p == 0 && ((code.Flags&encoder.IndirectFlags) != 0 || code.Next.Op == encoder.OpStructEnd) { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if len(code.Key) > 0 { if (code.Flags&encoder.IsTaggedKeyFlags) != 0 || code.Flags&encoder.AnonymousKeyFlags == 0 { b = appendStructKey(ctx, code, b) } } p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructPtrHeadOmitEmpty: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmpty: p := load(ctxptr, code.Idx) if p == 0 && ((code.Flags&encoder.IndirectFlags) != 0 || code.Next.Op == encoder.OpStructEnd) { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) if p == 0 || (ptrToPtr(p) == 0 && (code.Flags&encoder.IsNextOpPtrTypeFlags) != 0) { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadInt: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadInt: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyInt: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyInt: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadIntString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadIntString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyIntString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyIntString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) u64 := ptrToUint64(p, code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadIntPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadIntPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendInt(ctx, b, p, code) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyIntPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyIntPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p, code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadIntPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadIntPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyIntPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyIntPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadUint: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadUint: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyUint: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyUint: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadUintString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadUintString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyUintString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyUintString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadUintPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadUintPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendUint(ctx, b, p, code) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyUintPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyUintPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p, code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadUintPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadUintPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyUintPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyUintPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadFloat32: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadFloat32: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat32: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyFloat32: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat32(p + uintptr(code.Offset)) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, v) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadFloat32String: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadFloat32String: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat32String: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyFloat32String: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat32(p + uintptr(code.Offset)) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadFloat32Ptr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadFloat32Ptr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendFloat32(ctx, b, ptrToFloat32(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat32Ptr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyFloat32Ptr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadFloat32PtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadFloat32PtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat32PtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyFloat32PtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadFloat64: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadFloat64: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat64: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyFloat64: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat64(p + uintptr(code.Offset)) if v == 0 { code = code.NextField } else { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadFloat64String: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadFloat64String: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat64String: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyFloat64String: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat64(p + uintptr(code.Offset)) if v == 0 { code = code.NextField } else { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadFloat64Ptr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadFloat64Ptr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat64Ptr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyFloat64Ptr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadFloat64PtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadFloat64PtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat64PtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyFloat64PtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNull(ctx, b) b = appendComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToString(p + uintptr(code.Offset)) if v == "" { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, v) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadStringString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadStringString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p+uintptr(code.Offset))))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyStringString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyStringString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToString(p + uintptr(code.Offset)) if v == "" { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, v))) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadStringPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadStringPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, ptrToString(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyStringPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyStringPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadStringPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadStringPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyStringPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyStringPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadBool: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadBool: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBool: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyBool: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, v) b = appendComma(ctx, b) code = code.Next } else { code = code.NextField } case encoder.OpStructPtrHeadBoolString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadBoolString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBoolString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyBoolString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } else { code = code.NextField } case encoder.OpStructPtrHeadBoolPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadBoolPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendBool(ctx, b, ptrToBool(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBoolPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyBoolPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadBoolPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadBoolPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBoolPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyBoolPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadBytes: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadBytes: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBytes: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyBytes: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToBytes(p + uintptr(code.Offset)) if len(v) == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, v) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadBytesPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadBytesPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendByteSlice(ctx, b, ptrToBytes(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBytesPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyBytesPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadNumber: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadNumber: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpStructPtrHeadOmitEmptyNumber: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyNumber: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToNumber(p + uintptr(code.Offset)) if v == "" { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next } case encoder.OpStructPtrHeadNumberString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadNumberString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyNumberString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyNumberString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToNumber(p + uintptr(code.Offset)) if v == "" { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadNumberPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadNumberPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyNumberPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyNumberPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructPtrHeadNumberPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadNumberPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyNumberPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyNumberPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadArray, encoder.OpStructPtrHeadSlice: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadArray, encoder.OpStructHeadSlice: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructPtrHeadOmitEmptyArray: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyArray: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructPtrHeadOmitEmptySlice: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptySlice: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) slice := ptrToSlice(p) if slice.Len == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadArrayPtr, encoder.OpStructPtrHeadSlicePtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadArrayPtr, encoder.OpStructHeadSlicePtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNullComma(ctx, b) code = code.NextField } else { code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadOmitEmptyArrayPtr, encoder.OpStructPtrHeadOmitEmptySlicePtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyArrayPtr, encoder.OpStructHeadOmitEmptySlicePtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadMap: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadMap: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if p != 0 && (code.Flags&encoder.IndirectFlags) != 0 { p = ptrToPtr(p + uintptr(code.Offset)) } code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructPtrHeadOmitEmptyMap: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyMap: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if p != 0 && (code.Flags&encoder.IndirectFlags) != 0 { p = ptrToPtr(p + uintptr(code.Offset)) } if maplen(ptrToUnsafePtr(p)) == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadMapPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadMapPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if p == 0 { b = appendNullComma(ctx, b) code = code.NextField break } p = ptrToPtr(p + uintptr(code.Offset)) if p == 0 { b = appendNullComma(ctx, b) code = code.NextField } else { if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p, code.PtrNum) } code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadOmitEmptyMapPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyMapPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if p == 0 { code = code.NextField break } p = ptrToPtr(p + uintptr(code.Offset)) if p == 0 { code = code.NextField } else { if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p, code.PtrNum) } b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadMarshalJSON { p = ptrToPtr(p) } } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadOmitEmptyMarshalJSON { p = ptrToPtr(p) } } iface := ptrToInterface(code, p) if (code.Flags&encoder.NilCheckFlags) != 0 && encoder.IsNilForMarshaler(iface) { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendMarshalJSON(ctx, code, b, iface) if err != nil { return nil, err } b = bb b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if p == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadMarshalText: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadMarshalText: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadMarshalText { p = ptrToPtr(p) } } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyMarshalText: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyMarshalText: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadOmitEmptyMarshalText { p = ptrToPtr(p) } } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if p == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb b = appendComma(ctx, b) code = code.Next } case encoder.OpStructField: if code.Flags&encoder.IsTaggedKeyFlags != 0 || code.Flags&encoder.AnonymousKeyFlags == 0 { b = appendStructKey(ctx, code, b) } p := load(ctxptr, code.Idx) + uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmpty: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) if ptrToPtr(p) == 0 && (code.Flags&encoder.IsNextOpPtrTypeFlags) != 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructFieldInt: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyInt: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldIntString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyIntString: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldIntPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendInt(ctx, b, p, code) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyIntPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p, code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldIntPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyIntPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldUint: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyUint: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldUintString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyUintString: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldUintPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendUint(ctx, b, p, code) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyUintPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p, code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldUintPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyUintPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat32: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat32: p := load(ctxptr, code.Idx) v := ptrToFloat32(p + uintptr(code.Offset)) if v != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat32String: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat32String: p := load(ctxptr, code.Idx) v := ptrToFloat32(p + uintptr(code.Offset)) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat32Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendFloat32(ctx, b, ptrToFloat32(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat32Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat32PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat32PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat64: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat64: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if v != 0 { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat64String: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat64String: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if v != 0 { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat64Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat64Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat64PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat64PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyString: p := load(ctxptr, code.Idx) v := ptrToString(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldStringString: p := load(ctxptr, code.Idx) s := ptrToString(p + uintptr(code.Offset)) b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, s))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyStringString: p := load(ctxptr, code.Idx) v := ptrToString(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, v))) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldStringPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, ptrToString(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyStringPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldStringPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyStringPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBool: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBool: p := load(ctxptr, code.Idx) v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBoolString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBoolString: p := load(ctxptr, code.Idx) v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBoolPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendBool(ctx, b, ptrToBool(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBoolPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBoolPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBoolPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBytes: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBytes: p := load(ctxptr, code.Idx) v := ptrToBytes(p + uintptr(code.Offset)) if len(v) > 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBytesPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendByteSlice(ctx, b, ptrToBytes(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBytesPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldNumber: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpStructFieldOmitEmptyNumber: p := load(ctxptr, code.Idx) v := ptrToNumber(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructFieldNumberString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyNumberString: p := load(ctxptr, code.Idx) v := ptrToNumber(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldNumberPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyNumberPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructFieldNumberPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyNumberPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldMarshalJSON: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { p = ptrToPtr(p) } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyMarshalJSON: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { p = ptrToPtr(p) } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { code = code.NextField break } iface := ptrToInterface(code, p) if (code.Flags&encoder.NilCheckFlags) != 0 && encoder.IsNilForMarshaler(iface) { code = code.NextField break } b = appendStructKey(ctx, code, b) bb, err := appendMarshalJSON(ctx, code, b, iface) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpStructFieldMarshalJSONPtr: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyMarshalJSONPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructFieldMarshalText: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { p = ptrToPtr(p) } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyMarshalText: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { p = ptrToPtr(p) } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { code = code.NextField break } b = appendStructKey(ctx, code, b) bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpStructFieldMarshalTextPtr: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyMarshalTextPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructFieldArray: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyArray: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldArrayPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyArrayPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } else { code = code.NextField } case encoder.OpStructFieldSlice: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptySlice: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) slice := ptrToSlice(p) if slice.Len == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructFieldSlicePtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptySlicePtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } else { code = code.NextField } case encoder.OpStructFieldMap: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToPtr(p + uintptr(code.Offset)) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyMap: p := load(ctxptr, code.Idx) p = ptrToPtr(p + uintptr(code.Offset)) if p == 0 || maplen(ptrToUnsafePtr(p)) == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructFieldMapPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToPtr(p + uintptr(code.Offset)) if p != 0 { p = ptrToNPtr(p, code.PtrNum) } code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyMapPtr: p := load(ctxptr, code.Idx) p = ptrToPtr(p + uintptr(code.Offset)) if p != 0 { p = ptrToNPtr(p, code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } else { code = code.NextField } case encoder.OpStructFieldStruct: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyStruct: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) if ptrToPtr(p) == 0 && (code.Flags&encoder.IsNextOpPtrTypeFlags) != 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructEnd: b = appendStructEndSkipLast(ctx, code, b) code = code.Next case encoder.OpStructEndInt: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyInt: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndIntString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyIntString: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndIntPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendInt(ctx, b, p, code) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyIntPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p, code) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndIntPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyIntPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndUint: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyUint: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndUintString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyUintString: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndUintPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendUint(ctx, b, p, code) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyUintPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p, code) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndUintPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyUintPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat32: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat32: p := load(ctxptr, code.Idx) v := ptrToFloat32(p + uintptr(code.Offset)) if v != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat32String: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat32String: p := load(ctxptr, code.Idx) v := ptrToFloat32(p + uintptr(code.Offset)) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat32Ptr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendFloat32(ctx, b, ptrToFloat32(p)) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat32Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p)) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat32PtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat32PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat64: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat64: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if v != 0 { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat64String: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat64String: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if v != 0 { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat64Ptr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) b = appendStructEnd(ctx, code, b) code = code.Next break } v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat64Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat64PtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat64PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p+uintptr(code.Offset))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyString: p := load(ctxptr, code.Idx) v := ptrToString(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndStringString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) s := ptrToString(p + uintptr(code.Offset)) b = appendString(ctx, b, string(appendString(ctx, []byte{}, s))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyStringString: p := load(ctxptr, code.Idx) v := ptrToString(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, v))) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndStringPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, ptrToString(p)) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyStringPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p)) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndStringPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyStringPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBool: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBool: p := load(ctxptr, code.Idx) v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBoolString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBoolString: p := load(ctxptr, code.Idx) v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBoolPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendBool(ctx, b, ptrToBool(p)) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBoolPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p)) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBoolPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBoolPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBytes: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p+uintptr(code.Offset))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBytes: p := load(ctxptr, code.Idx) v := ptrToBytes(p + uintptr(code.Offset)) if len(v) > 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBytesPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendByteSlice(ctx, b, ptrToBytes(p)) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBytesPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p)) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndNumber: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = appendStructEnd(ctx, code, bb) code = code.Next case encoder.OpStructEndOmitEmptyNumber: p := load(ctxptr, code.Idx) v := ptrToNumber(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = appendStructEnd(ctx, code, bb) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndNumberString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = append(bb, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyNumberString: p := load(ctxptr, code.Idx) v := ptrToNumber(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = append(bb, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndNumberPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = bb } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyNumberPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = appendStructEnd(ctx, code, bb) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndNumberPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyNumberPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpEnd: goto END } } END: return b, nil } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/vm_color_indent/debug_vm.go ================================================ package vm_color_indent import ( "fmt" "github.com/goccy/go-json/internal/encoder" ) func DebugRun(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]byte, error) { var code *encoder.Opcode if (ctx.Option.Flag & encoder.HTMLEscapeOption) != 0 { code = codeSet.EscapeKeyCode } else { code = codeSet.NoescapeKeyCode } defer func() { if err := recover(); err != nil { w := ctx.Option.DebugOut fmt.Fprintln(w, "=============[DEBUG]===============") fmt.Fprintln(w, "* [TYPE]") fmt.Fprintln(w, codeSet.Type) fmt.Fprintf(w, "\n") fmt.Fprintln(w, "* [ALL OPCODE]") fmt.Fprintln(w, code.Dump()) fmt.Fprintf(w, "\n") fmt.Fprintln(w, "* [CONTEXT]") fmt.Fprintf(w, "%+v\n", ctx) fmt.Fprintln(w, "===================================") panic(err) } }() return Run(ctx, b, codeSet) } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/vm_color_indent/util.go ================================================ package vm_color_indent import ( "encoding/json" "fmt" "unsafe" "github.com/goccy/go-json/internal/encoder" "github.com/goccy/go-json/internal/runtime" ) const uintptrSize = 4 << (^uintptr(0) >> 63) var ( appendIndent = encoder.AppendIndent appendStructEnd = encoder.AppendStructEndIndent errUnsupportedValue = encoder.ErrUnsupportedValue errUnsupportedFloat = encoder.ErrUnsupportedFloat mapiterinit = encoder.MapIterInit mapiterkey = encoder.MapIterKey mapitervalue = encoder.MapIterValue mapiternext = encoder.MapIterNext maplen = encoder.MapLen ) type emptyInterface struct { typ *runtime.Type ptr unsafe.Pointer } type nonEmptyInterface struct { itab *struct { ityp *runtime.Type // static interface type typ *runtime.Type // dynamic concrete type // unused fields... } ptr unsafe.Pointer } func errUnimplementedOp(op encoder.OpType) error { return fmt.Errorf("encoder (indent): opcode %s has not been implemented", op) } func load(base uintptr, idx uint32) uintptr { addr := base + uintptr(idx) return **(**uintptr)(unsafe.Pointer(&addr)) } func store(base uintptr, idx uint32, p uintptr) { addr := base + uintptr(idx) **(**uintptr)(unsafe.Pointer(&addr)) = p } func loadNPtr(base uintptr, idx uint32, ptrNum uint8) uintptr { addr := base + uintptr(idx) p := **(**uintptr)(unsafe.Pointer(&addr)) for i := uint8(0); i < ptrNum; i++ { if p == 0 { return 0 } p = ptrToPtr(p) } return p } func ptrToUint64(p uintptr, bitSize uint8) uint64 { switch bitSize { case 8: return (uint64)(**(**uint8)(unsafe.Pointer(&p))) case 16: return (uint64)(**(**uint16)(unsafe.Pointer(&p))) case 32: return (uint64)(**(**uint32)(unsafe.Pointer(&p))) case 64: return **(**uint64)(unsafe.Pointer(&p)) } return 0 } func ptrToFloat32(p uintptr) float32 { return **(**float32)(unsafe.Pointer(&p)) } func ptrToFloat64(p uintptr) float64 { return **(**float64)(unsafe.Pointer(&p)) } func ptrToBool(p uintptr) bool { return **(**bool)(unsafe.Pointer(&p)) } func ptrToBytes(p uintptr) []byte { return **(**[]byte)(unsafe.Pointer(&p)) } func ptrToNumber(p uintptr) json.Number { return **(**json.Number)(unsafe.Pointer(&p)) } func ptrToString(p uintptr) string { return **(**string)(unsafe.Pointer(&p)) } func ptrToSlice(p uintptr) *runtime.SliceHeader { return *(**runtime.SliceHeader)(unsafe.Pointer(&p)) } func ptrToPtr(p uintptr) uintptr { return uintptr(**(**unsafe.Pointer)(unsafe.Pointer(&p))) } func ptrToNPtr(p uintptr, ptrNum uint8) uintptr { for i := uint8(0); i < ptrNum; i++ { if p == 0 { return 0 } p = ptrToPtr(p) } return p } func ptrToUnsafePtr(p uintptr) unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Pointer(&p)) } func ptrToInterface(code *encoder.Opcode, p uintptr) interface{} { return *(*interface{})(unsafe.Pointer(&emptyInterface{ typ: code.Type, ptr: *(*unsafe.Pointer)(unsafe.Pointer(&p)), })) } func appendInt(ctx *encoder.RuntimeContext, b []byte, p uintptr, code *encoder.Opcode) []byte { format := ctx.Option.ColorScheme.Int b = append(b, format.Header...) b = encoder.AppendInt(ctx, b, p, code) return append(b, format.Footer...) } func appendUint(ctx *encoder.RuntimeContext, b []byte, p uintptr, code *encoder.Opcode) []byte { format := ctx.Option.ColorScheme.Uint b = append(b, format.Header...) b = encoder.AppendUint(ctx, b, p, code) return append(b, format.Footer...) } func appendFloat32(ctx *encoder.RuntimeContext, b []byte, v float32) []byte { format := ctx.Option.ColorScheme.Float b = append(b, format.Header...) b = encoder.AppendFloat32(ctx, b, v) return append(b, format.Footer...) } func appendFloat64(ctx *encoder.RuntimeContext, b []byte, v float64) []byte { format := ctx.Option.ColorScheme.Float b = append(b, format.Header...) b = encoder.AppendFloat64(ctx, b, v) return append(b, format.Footer...) } func appendString(ctx *encoder.RuntimeContext, b []byte, v string) []byte { format := ctx.Option.ColorScheme.String b = append(b, format.Header...) b = encoder.AppendString(ctx, b, v) return append(b, format.Footer...) } func appendByteSlice(ctx *encoder.RuntimeContext, b []byte, src []byte) []byte { format := ctx.Option.ColorScheme.Binary b = append(b, format.Header...) b = encoder.AppendByteSlice(ctx, b, src) return append(b, format.Footer...) } func appendNumber(ctx *encoder.RuntimeContext, b []byte, n json.Number) ([]byte, error) { format := ctx.Option.ColorScheme.Int b = append(b, format.Header...) bb, err := encoder.AppendNumber(ctx, b, n) if err != nil { return nil, err } return append(bb, format.Footer...), nil } func appendBool(ctx *encoder.RuntimeContext, b []byte, v bool) []byte { format := ctx.Option.ColorScheme.Bool b = append(b, format.Header...) if v { b = append(b, "true"...) } else { b = append(b, "false"...) } return append(b, format.Footer...) } func appendNull(ctx *encoder.RuntimeContext, b []byte) []byte { format := ctx.Option.ColorScheme.Null b = append(b, format.Header...) b = append(b, "null"...) return append(b, format.Footer...) } func appendComma(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, ',', '\n') } func appendNullComma(ctx *encoder.RuntimeContext, b []byte) []byte { format := ctx.Option.ColorScheme.Null b = append(b, format.Header...) b = append(b, "null"...) return append(append(b, format.Footer...), ',', '\n') } func appendColon(_ *encoder.RuntimeContext, b []byte) []byte { return append(b[:len(b)-2], ':', ' ') } func appendMapKeyValue(ctx *encoder.RuntimeContext, code *encoder.Opcode, b, key, value []byte) []byte { b = appendIndent(ctx, b, code.Indent+1) b = append(b, key...) b[len(b)-2] = ':' b[len(b)-1] = ' ' return append(b, value...) } func appendMapEnd(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { b = b[:len(b)-2] b = append(b, '\n') b = appendIndent(ctx, b, code.Indent) return append(b, '}', ',', '\n') } func appendArrayHead(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { b = append(b, '[', '\n') return appendIndent(ctx, b, code.Indent+1) } func appendArrayEnd(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { b = b[:len(b)-2] b = append(b, '\n') b = appendIndent(ctx, b, code.Indent) return append(b, ']', ',', '\n') } func appendEmptyArray(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, '[', ']', ',', '\n') } func appendEmptyObject(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, '{', '}', ',', '\n') } func appendObjectEnd(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { last := len(b) - 1 // replace comma to newline b[last-1] = '\n' b = appendIndent(ctx, b[:last], code.Indent) return append(b, '}', ',', '\n') } func appendMarshalJSON(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte, v interface{}) ([]byte, error) { return encoder.AppendMarshalJSONIndent(ctx, code, b, v) } func appendMarshalText(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte, v interface{}) ([]byte, error) { format := ctx.Option.ColorScheme.String b = append(b, format.Header...) bb, err := encoder.AppendMarshalTextIndent(ctx, code, b, v) if err != nil { return nil, err } return append(bb, format.Footer...), nil } func appendStructHead(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, '{', '\n') } func appendStructKey(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { b = appendIndent(ctx, b, code.Indent) format := ctx.Option.ColorScheme.ObjectKey b = append(b, format.Header...) b = append(b, code.Key[:len(code.Key)-1]...) b = append(b, format.Footer...) return append(b, ':', ' ') } func appendStructEndSkipLast(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { last := len(b) - 1 if b[last-1] == '{' { b[last] = '}' } else { if b[last] == '\n' { // to remove ',' and '\n' characters b = b[:len(b)-2] } b = append(b, '\n') b = appendIndent(ctx, b, code.Indent-1) b = append(b, '}') } return appendComma(ctx, b) } func restoreIndent(ctx *encoder.RuntimeContext, code *encoder.Opcode, ctxptr uintptr) { ctx.BaseIndent = uint32(load(ctxptr, code.Length)) } func storeIndent(ctxptr uintptr, code *encoder.Opcode, indent uintptr) { store(ctxptr, code.Length, indent) } func appendArrayElemIndent(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { return appendIndent(ctx, b, code.Indent+1) } func appendMapKeyIndent(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { return appendIndent(ctx, b, code.Indent) } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/vm_color_indent/vm.go ================================================ // Code generated by internal/cmd/generator. DO NOT EDIT! package vm_color_indent import ( "math" "reflect" "sort" "unsafe" "github.com/goccy/go-json/internal/encoder" "github.com/goccy/go-json/internal/runtime" ) func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]byte, error) { recursiveLevel := 0 ptrOffset := uintptr(0) ctxptr := ctx.Ptr() var code *encoder.Opcode if (ctx.Option.Flag & encoder.HTMLEscapeOption) != 0 { code = codeSet.EscapeKeyCode } else { code = codeSet.NoescapeKeyCode } for { switch code.Op { default: return nil, errUnimplementedOp(code.Op) case encoder.OpPtr: p := load(ctxptr, code.Idx) code = code.Next store(ctxptr, code.Idx, ptrToPtr(p)) case encoder.OpIntPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpInt: b = appendInt(ctx, b, load(ctxptr, code.Idx), code) b = appendComma(ctx, b) code = code.Next case encoder.OpUintPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpUint: b = appendUint(ctx, b, load(ctxptr, code.Idx), code) b = appendComma(ctx, b) code = code.Next case encoder.OpIntString: b = append(b, '"') b = appendInt(ctx, b, load(ctxptr, code.Idx), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpUintString: b = append(b, '"') b = appendUint(ctx, b, load(ctxptr, code.Idx), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpFloat32Ptr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNull(ctx, b) b = appendComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpFloat32: b = appendFloat32(ctx, b, ptrToFloat32(load(ctxptr, code.Idx))) b = appendComma(ctx, b) code = code.Next case encoder.OpFloat64Ptr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpFloat64: v := ptrToFloat64(load(ctxptr, code.Idx)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next case encoder.OpStringPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpString: b = appendString(ctx, b, ptrToString(load(ctxptr, code.Idx))) b = appendComma(ctx, b) code = code.Next case encoder.OpBoolPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpBool: b = appendBool(ctx, b, ptrToBool(load(ctxptr, code.Idx))) b = appendComma(ctx, b) code = code.Next case encoder.OpBytesPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpBytes: b = appendByteSlice(ctx, b, ptrToBytes(load(ctxptr, code.Idx))) b = appendComma(ctx, b) code = code.Next case encoder.OpNumberPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpNumber: bb, err := appendNumber(ctx, b, ptrToNumber(load(ctxptr, code.Idx))) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpInterfacePtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpInterface: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } if recursiveLevel > encoder.StartDetectingCyclesAfter { for _, seen := range ctx.SeenPtr { if p == seen { return nil, errUnsupportedValue(code, p) } } } ctx.SeenPtr = append(ctx.SeenPtr, p) var ( typ *runtime.Type ifacePtr unsafe.Pointer ) up := ptrToUnsafePtr(p) if code.Flags&encoder.NonEmptyInterfaceFlags != 0 { iface := (*nonEmptyInterface)(up) ifacePtr = iface.ptr if iface.itab != nil { typ = iface.itab.typ } } else { iface := (*emptyInterface)(up) ifacePtr = iface.ptr typ = iface.typ } if ifacePtr == nil { isDirectedNil := typ != nil && typ.Kind() == reflect.Struct && !runtime.IfaceIndir(typ) if !isDirectedNil { b = appendNullComma(ctx, b) code = code.Next break } } ctx.KeepRefs = append(ctx.KeepRefs, up) ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ))) if err != nil { return nil, err } totalLength := uintptr(code.Length) + 3 nextTotalLength := uintptr(ifaceCodeSet.CodeLength) + 3 var c *encoder.Opcode if (ctx.Option.Flag & encoder.HTMLEscapeOption) != 0 { c = ifaceCodeSet.InterfaceEscapeKeyCode } else { c = ifaceCodeSet.InterfaceNoescapeKeyCode } curlen := uintptr(len(ctx.Ptrs)) offsetNum := ptrOffset / uintptrSize oldOffset := ptrOffset ptrOffset += totalLength * uintptrSize oldBaseIndent := ctx.BaseIndent ctx.BaseIndent += code.Indent newLen := offsetNum + totalLength + nextTotalLength if curlen < newLen { ctx.Ptrs = append(ctx.Ptrs, make([]uintptr, newLen-curlen)...) } ctxptr = ctx.Ptr() + ptrOffset // assign new ctxptr end := ifaceCodeSet.EndCode store(ctxptr, c.Idx, uintptr(ifacePtr)) store(ctxptr, end.Idx, oldOffset) store(ctxptr, end.ElemIdx, uintptr(unsafe.Pointer(code.Next))) storeIndent(ctxptr, end, uintptr(oldBaseIndent)) code = c recursiveLevel++ case encoder.OpInterfaceEnd: recursiveLevel-- // restore ctxptr offset := load(ctxptr, code.Idx) restoreIndent(ctx, code, ctxptr) ctx.SeenPtr = ctx.SeenPtr[:len(ctx.SeenPtr)-1] codePtr := load(ctxptr, code.ElemIdx) code = (*encoder.Opcode)(ptrToUnsafePtr(codePtr)) ctxptr = ctx.Ptr() + offset ptrOffset = offset case encoder.OpMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, ptrToPtr(p)) fallthrough case encoder.OpMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } if (code.Flags&encoder.IsNilableTypeFlags) != 0 && (code.Flags&encoder.IndirectFlags) != 0 { p = ptrToPtr(p) } bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, ptrToPtr(p)) fallthrough case encoder.OpMarshalText: p := load(ctxptr, code.Idx) if p == 0 { b = append(b, `""`...) b = appendComma(ctx, b) code = code.Next break } if (code.Flags&encoder.IsNilableTypeFlags) != 0 && (code.Flags&encoder.IndirectFlags) != 0 { p = ptrToPtr(p) } bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpSlicePtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpSlice: p := load(ctxptr, code.Idx) slice := ptrToSlice(p) if p == 0 || slice.Data == nil { b = appendNullComma(ctx, b) code = code.End.Next break } store(ctxptr, code.ElemIdx, 0) store(ctxptr, code.Length, uintptr(slice.Len)) store(ctxptr, code.Idx, uintptr(slice.Data)) if slice.Len > 0 { b = appendArrayHead(ctx, code, b) code = code.Next store(ctxptr, code.Idx, uintptr(slice.Data)) } else { b = appendEmptyArray(ctx, b) code = code.End.Next } case encoder.OpSliceElem: idx := load(ctxptr, code.ElemIdx) length := load(ctxptr, code.Length) idx++ if idx < length { b = appendArrayElemIndent(ctx, code, b) store(ctxptr, code.ElemIdx, idx) data := load(ctxptr, code.Idx) size := uintptr(code.Size) code = code.Next store(ctxptr, code.Idx, data+idx*size) } else { b = appendArrayEnd(ctx, code, b) code = code.End.Next } case encoder.OpArrayPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpArray: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } if code.Length > 0 { b = appendArrayHead(ctx, code, b) store(ctxptr, code.ElemIdx, 0) code = code.Next store(ctxptr, code.Idx, p) } else { b = appendEmptyArray(ctx, b) code = code.End.Next } case encoder.OpArrayElem: idx := load(ctxptr, code.ElemIdx) idx++ if idx < uintptr(code.Length) { b = appendArrayElemIndent(ctx, code, b) store(ctxptr, code.ElemIdx, idx) p := load(ctxptr, code.Idx) size := uintptr(code.Size) code = code.Next store(ctxptr, code.Idx, p+idx*size) } else { b = appendArrayEnd(ctx, code, b) code = code.End.Next } case encoder.OpMapPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpMap: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } uptr := ptrToUnsafePtr(p) mlen := maplen(uptr) if mlen <= 0 { b = appendEmptyObject(ctx, b) code = code.End.Next break } b = appendStructHead(ctx, b) unorderedMap := (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 mapCtx := encoder.NewMapContext(mlen, unorderedMap) mapiterinit(code.Type, uptr, &mapCtx.Iter) store(ctxptr, code.Idx, uintptr(unsafe.Pointer(mapCtx))) ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(mapCtx)) if unorderedMap { b = appendMapKeyIndent(ctx, code.Next, b) } else { mapCtx.Start = len(b) mapCtx.First = len(b) } key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next case encoder.OpMapKey: mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) idx := mapCtx.Idx idx++ if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { if idx < mapCtx.Len { b = appendMapKeyIndent(ctx, code, b) mapCtx.Idx = int(idx) key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next } else { b = appendObjectEnd(ctx, code, b) encoder.ReleaseMapContext(mapCtx) code = code.End.Next } } else { mapCtx.Slice.Items[mapCtx.Idx].Value = b[mapCtx.Start:len(b)] if idx < mapCtx.Len { mapCtx.Idx = int(idx) mapCtx.Start = len(b) key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next } else { code = code.End } } case encoder.OpMapValue: mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { b = appendColon(ctx, b) } else { mapCtx.Slice.Items[mapCtx.Idx].Key = b[mapCtx.Start:len(b)] mapCtx.Start = len(b) } value := mapitervalue(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(value)) mapiternext(&mapCtx.Iter) code = code.Next case encoder.OpMapEnd: // this operation only used by sorted map. mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) sort.Sort(mapCtx.Slice) buf := mapCtx.Buf for _, item := range mapCtx.Slice.Items { buf = appendMapKeyValue(ctx, code, buf, item.Key, item.Value) } buf = appendMapEnd(ctx, code, buf) b = b[:mapCtx.First] b = append(b, buf...) mapCtx.Buf = buf encoder.ReleaseMapContext(mapCtx) code = code.Next case encoder.OpRecursivePtr: p := load(ctxptr, code.Idx) if p == 0 { code = code.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpRecursive: ptr := load(ctxptr, code.Idx) if ptr != 0 { if recursiveLevel > encoder.StartDetectingCyclesAfter { for _, seen := range ctx.SeenPtr { if ptr == seen { return nil, errUnsupportedValue(code, ptr) } } } } ctx.SeenPtr = append(ctx.SeenPtr, ptr) c := code.Jmp.Code curlen := uintptr(len(ctx.Ptrs)) offsetNum := ptrOffset / uintptrSize oldOffset := ptrOffset ptrOffset += code.Jmp.CurLen * uintptrSize oldBaseIndent := ctx.BaseIndent indentDiffFromTop := c.Indent - 1 ctx.BaseIndent += code.Indent - indentDiffFromTop newLen := offsetNum + code.Jmp.CurLen + code.Jmp.NextLen if curlen < newLen { ctx.Ptrs = append(ctx.Ptrs, make([]uintptr, newLen-curlen)...) } ctxptr = ctx.Ptr() + ptrOffset // assign new ctxptr store(ctxptr, c.Idx, ptr) store(ctxptr, c.End.Next.Idx, oldOffset) store(ctxptr, c.End.Next.ElemIdx, uintptr(unsafe.Pointer(code.Next))) storeIndent(ctxptr, c.End.Next, uintptr(oldBaseIndent)) code = c recursiveLevel++ case encoder.OpRecursiveEnd: recursiveLevel-- // restore ctxptr restoreIndent(ctx, code, ctxptr) offset := load(ctxptr, code.Idx) ctx.SeenPtr = ctx.SeenPtr[:len(ctx.SeenPtr)-1] codePtr := load(ctxptr, code.ElemIdx) code = (*encoder.Opcode)(ptrToUnsafePtr(codePtr)) ctxptr = ctx.Ptr() + offset ptrOffset = offset case encoder.OpStructPtrHead: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHead: p := load(ctxptr, code.Idx) if p == 0 && ((code.Flags&encoder.IndirectFlags) != 0 || code.Next.Op == encoder.OpStructEnd) { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if len(code.Key) > 0 { if (code.Flags&encoder.IsTaggedKeyFlags) != 0 || code.Flags&encoder.AnonymousKeyFlags == 0 { b = appendStructKey(ctx, code, b) } } p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructPtrHeadOmitEmpty: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmpty: p := load(ctxptr, code.Idx) if p == 0 && ((code.Flags&encoder.IndirectFlags) != 0 || code.Next.Op == encoder.OpStructEnd) { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) if p == 0 || (ptrToPtr(p) == 0 && (code.Flags&encoder.IsNextOpPtrTypeFlags) != 0) { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadInt: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadInt: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyInt: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyInt: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadIntString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadIntString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyIntString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyIntString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) u64 := ptrToUint64(p, code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadIntPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadIntPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendInt(ctx, b, p, code) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyIntPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyIntPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p, code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadIntPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadIntPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyIntPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyIntPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadUint: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadUint: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyUint: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyUint: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadUintString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadUintString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyUintString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyUintString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadUintPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadUintPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendUint(ctx, b, p, code) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyUintPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyUintPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p, code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadUintPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadUintPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyUintPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyUintPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadFloat32: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadFloat32: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat32: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyFloat32: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat32(p + uintptr(code.Offset)) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, v) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadFloat32String: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadFloat32String: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat32String: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyFloat32String: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat32(p + uintptr(code.Offset)) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadFloat32Ptr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadFloat32Ptr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendFloat32(ctx, b, ptrToFloat32(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat32Ptr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyFloat32Ptr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadFloat32PtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadFloat32PtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat32PtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyFloat32PtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadFloat64: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadFloat64: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat64: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyFloat64: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat64(p + uintptr(code.Offset)) if v == 0 { code = code.NextField } else { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadFloat64String: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadFloat64String: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat64String: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyFloat64String: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat64(p + uintptr(code.Offset)) if v == 0 { code = code.NextField } else { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadFloat64Ptr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadFloat64Ptr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat64Ptr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyFloat64Ptr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadFloat64PtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadFloat64PtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat64PtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyFloat64PtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNull(ctx, b) b = appendComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToString(p + uintptr(code.Offset)) if v == "" { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, v) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadStringString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadStringString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p+uintptr(code.Offset))))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyStringString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyStringString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToString(p + uintptr(code.Offset)) if v == "" { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, v))) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadStringPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadStringPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, ptrToString(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyStringPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyStringPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadStringPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadStringPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyStringPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyStringPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadBool: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadBool: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBool: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyBool: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, v) b = appendComma(ctx, b) code = code.Next } else { code = code.NextField } case encoder.OpStructPtrHeadBoolString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadBoolString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBoolString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyBoolString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } else { code = code.NextField } case encoder.OpStructPtrHeadBoolPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadBoolPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendBool(ctx, b, ptrToBool(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBoolPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyBoolPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadBoolPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadBoolPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBoolPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyBoolPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadBytes: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadBytes: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBytes: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyBytes: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToBytes(p + uintptr(code.Offset)) if len(v) == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, v) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadBytesPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadBytesPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendByteSlice(ctx, b, ptrToBytes(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBytesPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyBytesPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadNumber: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadNumber: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpStructPtrHeadOmitEmptyNumber: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyNumber: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToNumber(p + uintptr(code.Offset)) if v == "" { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next } case encoder.OpStructPtrHeadNumberString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadNumberString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyNumberString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyNumberString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToNumber(p + uintptr(code.Offset)) if v == "" { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadNumberPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadNumberPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyNumberPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyNumberPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructPtrHeadNumberPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadNumberPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyNumberPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyNumberPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadArray, encoder.OpStructPtrHeadSlice: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadArray, encoder.OpStructHeadSlice: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructPtrHeadOmitEmptyArray: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyArray: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructPtrHeadOmitEmptySlice: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptySlice: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) slice := ptrToSlice(p) if slice.Len == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadArrayPtr, encoder.OpStructPtrHeadSlicePtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadArrayPtr, encoder.OpStructHeadSlicePtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNullComma(ctx, b) code = code.NextField } else { code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadOmitEmptyArrayPtr, encoder.OpStructPtrHeadOmitEmptySlicePtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyArrayPtr, encoder.OpStructHeadOmitEmptySlicePtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadMap: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadMap: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if p != 0 && (code.Flags&encoder.IndirectFlags) != 0 { p = ptrToPtr(p + uintptr(code.Offset)) } code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructPtrHeadOmitEmptyMap: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyMap: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if p != 0 && (code.Flags&encoder.IndirectFlags) != 0 { p = ptrToPtr(p + uintptr(code.Offset)) } if maplen(ptrToUnsafePtr(p)) == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadMapPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadMapPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if p == 0 { b = appendNullComma(ctx, b) code = code.NextField break } p = ptrToPtr(p + uintptr(code.Offset)) if p == 0 { b = appendNullComma(ctx, b) code = code.NextField } else { if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p, code.PtrNum) } code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadOmitEmptyMapPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyMapPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if p == 0 { code = code.NextField break } p = ptrToPtr(p + uintptr(code.Offset)) if p == 0 { code = code.NextField } else { if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p, code.PtrNum) } b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadMarshalJSON { p = ptrToPtr(p) } } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadOmitEmptyMarshalJSON { p = ptrToPtr(p) } } iface := ptrToInterface(code, p) if (code.Flags&encoder.NilCheckFlags) != 0 && encoder.IsNilForMarshaler(iface) { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendMarshalJSON(ctx, code, b, iface) if err != nil { return nil, err } b = bb b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if p == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadMarshalText: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadMarshalText: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadMarshalText { p = ptrToPtr(p) } } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyMarshalText: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyMarshalText: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadOmitEmptyMarshalText { p = ptrToPtr(p) } } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if p == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb b = appendComma(ctx, b) code = code.Next } case encoder.OpStructField: if code.Flags&encoder.IsTaggedKeyFlags != 0 || code.Flags&encoder.AnonymousKeyFlags == 0 { b = appendStructKey(ctx, code, b) } p := load(ctxptr, code.Idx) + uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmpty: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) if ptrToPtr(p) == 0 && (code.Flags&encoder.IsNextOpPtrTypeFlags) != 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructFieldInt: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyInt: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldIntString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyIntString: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldIntPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendInt(ctx, b, p, code) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyIntPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p, code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldIntPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyIntPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldUint: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyUint: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldUintString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyUintString: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldUintPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendUint(ctx, b, p, code) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyUintPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p, code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldUintPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyUintPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat32: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat32: p := load(ctxptr, code.Idx) v := ptrToFloat32(p + uintptr(code.Offset)) if v != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat32String: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat32String: p := load(ctxptr, code.Idx) v := ptrToFloat32(p + uintptr(code.Offset)) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat32Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendFloat32(ctx, b, ptrToFloat32(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat32Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat32PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat32PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat64: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat64: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if v != 0 { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat64String: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat64String: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if v != 0 { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat64Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat64Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat64PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat64PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyString: p := load(ctxptr, code.Idx) v := ptrToString(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldStringString: p := load(ctxptr, code.Idx) s := ptrToString(p + uintptr(code.Offset)) b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, s))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyStringString: p := load(ctxptr, code.Idx) v := ptrToString(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, v))) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldStringPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, ptrToString(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyStringPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldStringPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyStringPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBool: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBool: p := load(ctxptr, code.Idx) v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBoolString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBoolString: p := load(ctxptr, code.Idx) v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBoolPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendBool(ctx, b, ptrToBool(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBoolPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBoolPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBoolPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBytes: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBytes: p := load(ctxptr, code.Idx) v := ptrToBytes(p + uintptr(code.Offset)) if len(v) > 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBytesPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendByteSlice(ctx, b, ptrToBytes(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBytesPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldNumber: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpStructFieldOmitEmptyNumber: p := load(ctxptr, code.Idx) v := ptrToNumber(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructFieldNumberString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyNumberString: p := load(ctxptr, code.Idx) v := ptrToNumber(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldNumberPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyNumberPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructFieldNumberPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyNumberPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldMarshalJSON: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { p = ptrToPtr(p) } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyMarshalJSON: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { p = ptrToPtr(p) } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { code = code.NextField break } iface := ptrToInterface(code, p) if (code.Flags&encoder.NilCheckFlags) != 0 && encoder.IsNilForMarshaler(iface) { code = code.NextField break } b = appendStructKey(ctx, code, b) bb, err := appendMarshalJSON(ctx, code, b, iface) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpStructFieldMarshalJSONPtr: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyMarshalJSONPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructFieldMarshalText: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { p = ptrToPtr(p) } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyMarshalText: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { p = ptrToPtr(p) } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { code = code.NextField break } b = appendStructKey(ctx, code, b) bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpStructFieldMarshalTextPtr: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyMarshalTextPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructFieldArray: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyArray: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldArrayPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyArrayPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } else { code = code.NextField } case encoder.OpStructFieldSlice: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptySlice: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) slice := ptrToSlice(p) if slice.Len == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructFieldSlicePtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptySlicePtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } else { code = code.NextField } case encoder.OpStructFieldMap: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToPtr(p + uintptr(code.Offset)) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyMap: p := load(ctxptr, code.Idx) p = ptrToPtr(p + uintptr(code.Offset)) if p == 0 || maplen(ptrToUnsafePtr(p)) == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructFieldMapPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToPtr(p + uintptr(code.Offset)) if p != 0 { p = ptrToNPtr(p, code.PtrNum) } code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyMapPtr: p := load(ctxptr, code.Idx) p = ptrToPtr(p + uintptr(code.Offset)) if p != 0 { p = ptrToNPtr(p, code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } else { code = code.NextField } case encoder.OpStructFieldStruct: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyStruct: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) if ptrToPtr(p) == 0 && (code.Flags&encoder.IsNextOpPtrTypeFlags) != 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructEnd: b = appendStructEndSkipLast(ctx, code, b) code = code.Next case encoder.OpStructEndInt: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyInt: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndIntString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyIntString: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndIntPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendInt(ctx, b, p, code) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyIntPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p, code) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndIntPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyIntPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndUint: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyUint: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndUintString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyUintString: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndUintPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendUint(ctx, b, p, code) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyUintPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p, code) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndUintPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyUintPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat32: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat32: p := load(ctxptr, code.Idx) v := ptrToFloat32(p + uintptr(code.Offset)) if v != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat32String: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat32String: p := load(ctxptr, code.Idx) v := ptrToFloat32(p + uintptr(code.Offset)) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat32Ptr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendFloat32(ctx, b, ptrToFloat32(p)) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat32Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p)) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat32PtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat32PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat64: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat64: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if v != 0 { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat64String: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat64String: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if v != 0 { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat64Ptr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) b = appendStructEnd(ctx, code, b) code = code.Next break } v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat64Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat64PtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat64PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p+uintptr(code.Offset))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyString: p := load(ctxptr, code.Idx) v := ptrToString(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndStringString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) s := ptrToString(p + uintptr(code.Offset)) b = appendString(ctx, b, string(appendString(ctx, []byte{}, s))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyStringString: p := load(ctxptr, code.Idx) v := ptrToString(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, v))) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndStringPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, ptrToString(p)) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyStringPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p)) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndStringPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyStringPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBool: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBool: p := load(ctxptr, code.Idx) v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBoolString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBoolString: p := load(ctxptr, code.Idx) v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBoolPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendBool(ctx, b, ptrToBool(p)) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBoolPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p)) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBoolPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBoolPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBytes: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p+uintptr(code.Offset))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBytes: p := load(ctxptr, code.Idx) v := ptrToBytes(p + uintptr(code.Offset)) if len(v) > 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBytesPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendByteSlice(ctx, b, ptrToBytes(p)) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBytesPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p)) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndNumber: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = appendStructEnd(ctx, code, bb) code = code.Next case encoder.OpStructEndOmitEmptyNumber: p := load(ctxptr, code.Idx) v := ptrToNumber(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = appendStructEnd(ctx, code, bb) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndNumberString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = append(bb, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyNumberString: p := load(ctxptr, code.Idx) v := ptrToNumber(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = append(bb, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndNumberPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = bb } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyNumberPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = appendStructEnd(ctx, code, bb) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndNumberPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyNumberPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpEnd: goto END } } END: return b, nil } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/vm_indent/debug_vm.go ================================================ package vm_indent import ( "fmt" "github.com/goccy/go-json/internal/encoder" ) func DebugRun(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]byte, error) { var code *encoder.Opcode if (ctx.Option.Flag & encoder.HTMLEscapeOption) != 0 { code = codeSet.EscapeKeyCode } else { code = codeSet.NoescapeKeyCode } defer func() { if err := recover(); err != nil { w := ctx.Option.DebugOut fmt.Fprintln(w, "=============[DEBUG]===============") fmt.Fprintln(w, "* [TYPE]") fmt.Fprintln(w, codeSet.Type) fmt.Fprintf(w, "\n") fmt.Fprintln(w, "* [ALL OPCODE]") fmt.Fprintln(w, code.Dump()) fmt.Fprintf(w, "\n") fmt.Fprintln(w, "* [CONTEXT]") fmt.Fprintf(w, "%+v\n", ctx) fmt.Fprintln(w, "===================================") panic(err) } }() return Run(ctx, b, codeSet) } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/vm_indent/hack.go ================================================ package vm_indent import ( // HACK: compile order // `vm`, `vm_indent`, `vm_color`, `vm_color_indent` packages uses a lot of memory to compile, // so forcibly make dependencies and avoid compiling in concurrent. // dependency order: vm => vm_indent => vm_color => vm_color_indent _ "github.com/goccy/go-json/internal/encoder/vm_color" ) ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/vm_indent/util.go ================================================ package vm_indent import ( "encoding/json" "fmt" "unsafe" "github.com/goccy/go-json/internal/encoder" "github.com/goccy/go-json/internal/runtime" ) const uintptrSize = 4 << (^uintptr(0) >> 63) var ( appendInt = encoder.AppendInt appendUint = encoder.AppendUint appendFloat32 = encoder.AppendFloat32 appendFloat64 = encoder.AppendFloat64 appendString = encoder.AppendString appendByteSlice = encoder.AppendByteSlice appendNumber = encoder.AppendNumber appendStructEnd = encoder.AppendStructEndIndent appendIndent = encoder.AppendIndent errUnsupportedValue = encoder.ErrUnsupportedValue errUnsupportedFloat = encoder.ErrUnsupportedFloat mapiterinit = encoder.MapIterInit mapiterkey = encoder.MapIterKey mapitervalue = encoder.MapIterValue mapiternext = encoder.MapIterNext maplen = encoder.MapLen ) type emptyInterface struct { typ *runtime.Type ptr unsafe.Pointer } type nonEmptyInterface struct { itab *struct { ityp *runtime.Type // static interface type typ *runtime.Type // dynamic concrete type // unused fields... } ptr unsafe.Pointer } func errUnimplementedOp(op encoder.OpType) error { return fmt.Errorf("encoder (indent): opcode %s has not been implemented", op) } func load(base uintptr, idx uint32) uintptr { addr := base + uintptr(idx) return **(**uintptr)(unsafe.Pointer(&addr)) } func store(base uintptr, idx uint32, p uintptr) { addr := base + uintptr(idx) **(**uintptr)(unsafe.Pointer(&addr)) = p } func loadNPtr(base uintptr, idx uint32, ptrNum uint8) uintptr { addr := base + uintptr(idx) p := **(**uintptr)(unsafe.Pointer(&addr)) for i := uint8(0); i < ptrNum; i++ { if p == 0 { return 0 } p = ptrToPtr(p) } return p } func ptrToUint64(p uintptr, bitSize uint8) uint64 { switch bitSize { case 8: return (uint64)(**(**uint8)(unsafe.Pointer(&p))) case 16: return (uint64)(**(**uint16)(unsafe.Pointer(&p))) case 32: return (uint64)(**(**uint32)(unsafe.Pointer(&p))) case 64: return **(**uint64)(unsafe.Pointer(&p)) } return 0 } func ptrToFloat32(p uintptr) float32 { return **(**float32)(unsafe.Pointer(&p)) } func ptrToFloat64(p uintptr) float64 { return **(**float64)(unsafe.Pointer(&p)) } func ptrToBool(p uintptr) bool { return **(**bool)(unsafe.Pointer(&p)) } func ptrToBytes(p uintptr) []byte { return **(**[]byte)(unsafe.Pointer(&p)) } func ptrToNumber(p uintptr) json.Number { return **(**json.Number)(unsafe.Pointer(&p)) } func ptrToString(p uintptr) string { return **(**string)(unsafe.Pointer(&p)) } func ptrToSlice(p uintptr) *runtime.SliceHeader { return *(**runtime.SliceHeader)(unsafe.Pointer(&p)) } func ptrToPtr(p uintptr) uintptr { return uintptr(**(**unsafe.Pointer)(unsafe.Pointer(&p))) } func ptrToNPtr(p uintptr, ptrNum uint8) uintptr { for i := uint8(0); i < ptrNum; i++ { if p == 0 { return 0 } p = ptrToPtr(p) } return p } func ptrToUnsafePtr(p uintptr) unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Pointer(&p)) } func ptrToInterface(code *encoder.Opcode, p uintptr) interface{} { return *(*interface{})(unsafe.Pointer(&emptyInterface{ typ: code.Type, ptr: *(*unsafe.Pointer)(unsafe.Pointer(&p)), })) } func appendBool(_ *encoder.RuntimeContext, b []byte, v bool) []byte { if v { return append(b, "true"...) } return append(b, "false"...) } func appendNull(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, "null"...) } func appendComma(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, ',', '\n') } func appendNullComma(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, "null,\n"...) } func appendColon(_ *encoder.RuntimeContext, b []byte) []byte { return append(b[:len(b)-2], ':', ' ') } func appendMapKeyValue(ctx *encoder.RuntimeContext, code *encoder.Opcode, b, key, value []byte) []byte { b = appendIndent(ctx, b, code.Indent+1) b = append(b, key...) b[len(b)-2] = ':' b[len(b)-1] = ' ' return append(b, value...) } func appendMapEnd(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { b = b[:len(b)-2] b = append(b, '\n') b = appendIndent(ctx, b, code.Indent) return append(b, '}', ',', '\n') } func appendArrayHead(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { b = append(b, '[', '\n') return appendIndent(ctx, b, code.Indent+1) } func appendArrayEnd(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { b = b[:len(b)-2] b = append(b, '\n') b = appendIndent(ctx, b, code.Indent) return append(b, ']', ',', '\n') } func appendEmptyArray(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, '[', ']', ',', '\n') } func appendEmptyObject(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, '{', '}', ',', '\n') } func appendObjectEnd(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { last := len(b) - 1 // replace comma to newline b[last-1] = '\n' b = appendIndent(ctx, b[:last], code.Indent) return append(b, '}', ',', '\n') } func appendMarshalJSON(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte, v interface{}) ([]byte, error) { return encoder.AppendMarshalJSONIndent(ctx, code, b, v) } func appendMarshalText(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte, v interface{}) ([]byte, error) { return encoder.AppendMarshalTextIndent(ctx, code, b, v) } func appendStructHead(_ *encoder.RuntimeContext, b []byte) []byte { return append(b, '{', '\n') } func appendStructKey(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { b = appendIndent(ctx, b, code.Indent) b = append(b, code.Key...) return append(b, ' ') } func appendStructEndSkipLast(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { last := len(b) - 1 if b[last-1] == '{' { b[last] = '}' } else { if b[last] == '\n' { // to remove ',' and '\n' characters b = b[:len(b)-2] } b = append(b, '\n') b = appendIndent(ctx, b, code.Indent-1) b = append(b, '}') } return appendComma(ctx, b) } func restoreIndent(ctx *encoder.RuntimeContext, code *encoder.Opcode, ctxptr uintptr) { ctx.BaseIndent = uint32(load(ctxptr, code.Length)) } func storeIndent(ctxptr uintptr, code *encoder.Opcode, indent uintptr) { store(ctxptr, code.Length, indent) } func appendArrayElemIndent(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { return appendIndent(ctx, b, code.Indent+1) } func appendMapKeyIndent(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { return appendIndent(ctx, b, code.Indent) } ================================================ FILE: vendor/github.com/goccy/go-json/internal/encoder/vm_indent/vm.go ================================================ // Code generated by internal/cmd/generator. DO NOT EDIT! package vm_indent import ( "math" "reflect" "sort" "unsafe" "github.com/goccy/go-json/internal/encoder" "github.com/goccy/go-json/internal/runtime" ) func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]byte, error) { recursiveLevel := 0 ptrOffset := uintptr(0) ctxptr := ctx.Ptr() var code *encoder.Opcode if (ctx.Option.Flag & encoder.HTMLEscapeOption) != 0 { code = codeSet.EscapeKeyCode } else { code = codeSet.NoescapeKeyCode } for { switch code.Op { default: return nil, errUnimplementedOp(code.Op) case encoder.OpPtr: p := load(ctxptr, code.Idx) code = code.Next store(ctxptr, code.Idx, ptrToPtr(p)) case encoder.OpIntPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpInt: b = appendInt(ctx, b, load(ctxptr, code.Idx), code) b = appendComma(ctx, b) code = code.Next case encoder.OpUintPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpUint: b = appendUint(ctx, b, load(ctxptr, code.Idx), code) b = appendComma(ctx, b) code = code.Next case encoder.OpIntString: b = append(b, '"') b = appendInt(ctx, b, load(ctxptr, code.Idx), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpUintString: b = append(b, '"') b = appendUint(ctx, b, load(ctxptr, code.Idx), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpFloat32Ptr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNull(ctx, b) b = appendComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpFloat32: b = appendFloat32(ctx, b, ptrToFloat32(load(ctxptr, code.Idx))) b = appendComma(ctx, b) code = code.Next case encoder.OpFloat64Ptr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpFloat64: v := ptrToFloat64(load(ctxptr, code.Idx)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next case encoder.OpStringPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpString: b = appendString(ctx, b, ptrToString(load(ctxptr, code.Idx))) b = appendComma(ctx, b) code = code.Next case encoder.OpBoolPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpBool: b = appendBool(ctx, b, ptrToBool(load(ctxptr, code.Idx))) b = appendComma(ctx, b) code = code.Next case encoder.OpBytesPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpBytes: b = appendByteSlice(ctx, b, ptrToBytes(load(ctxptr, code.Idx))) b = appendComma(ctx, b) code = code.Next case encoder.OpNumberPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpNumber: bb, err := appendNumber(ctx, b, ptrToNumber(load(ctxptr, code.Idx))) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpInterfacePtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpInterface: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } if recursiveLevel > encoder.StartDetectingCyclesAfter { for _, seen := range ctx.SeenPtr { if p == seen { return nil, errUnsupportedValue(code, p) } } } ctx.SeenPtr = append(ctx.SeenPtr, p) var ( typ *runtime.Type ifacePtr unsafe.Pointer ) up := ptrToUnsafePtr(p) if code.Flags&encoder.NonEmptyInterfaceFlags != 0 { iface := (*nonEmptyInterface)(up) ifacePtr = iface.ptr if iface.itab != nil { typ = iface.itab.typ } } else { iface := (*emptyInterface)(up) ifacePtr = iface.ptr typ = iface.typ } if ifacePtr == nil { isDirectedNil := typ != nil && typ.Kind() == reflect.Struct && !runtime.IfaceIndir(typ) if !isDirectedNil { b = appendNullComma(ctx, b) code = code.Next break } } ctx.KeepRefs = append(ctx.KeepRefs, up) ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ))) if err != nil { return nil, err } totalLength := uintptr(code.Length) + 3 nextTotalLength := uintptr(ifaceCodeSet.CodeLength) + 3 var c *encoder.Opcode if (ctx.Option.Flag & encoder.HTMLEscapeOption) != 0 { c = ifaceCodeSet.InterfaceEscapeKeyCode } else { c = ifaceCodeSet.InterfaceNoescapeKeyCode } curlen := uintptr(len(ctx.Ptrs)) offsetNum := ptrOffset / uintptrSize oldOffset := ptrOffset ptrOffset += totalLength * uintptrSize oldBaseIndent := ctx.BaseIndent ctx.BaseIndent += code.Indent newLen := offsetNum + totalLength + nextTotalLength if curlen < newLen { ctx.Ptrs = append(ctx.Ptrs, make([]uintptr, newLen-curlen)...) } ctxptr = ctx.Ptr() + ptrOffset // assign new ctxptr end := ifaceCodeSet.EndCode store(ctxptr, c.Idx, uintptr(ifacePtr)) store(ctxptr, end.Idx, oldOffset) store(ctxptr, end.ElemIdx, uintptr(unsafe.Pointer(code.Next))) storeIndent(ctxptr, end, uintptr(oldBaseIndent)) code = c recursiveLevel++ case encoder.OpInterfaceEnd: recursiveLevel-- // restore ctxptr offset := load(ctxptr, code.Idx) restoreIndent(ctx, code, ctxptr) ctx.SeenPtr = ctx.SeenPtr[:len(ctx.SeenPtr)-1] codePtr := load(ctxptr, code.ElemIdx) code = (*encoder.Opcode)(ptrToUnsafePtr(codePtr)) ctxptr = ctx.Ptr() + offset ptrOffset = offset case encoder.OpMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, ptrToPtr(p)) fallthrough case encoder.OpMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } if (code.Flags&encoder.IsNilableTypeFlags) != 0 && (code.Flags&encoder.IndirectFlags) != 0 { p = ptrToPtr(p) } bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } store(ctxptr, code.Idx, ptrToPtr(p)) fallthrough case encoder.OpMarshalText: p := load(ctxptr, code.Idx) if p == 0 { b = append(b, `""`...) b = appendComma(ctx, b) code = code.Next break } if (code.Flags&encoder.IsNilableTypeFlags) != 0 && (code.Flags&encoder.IndirectFlags) != 0 { p = ptrToPtr(p) } bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpSlicePtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpSlice: p := load(ctxptr, code.Idx) slice := ptrToSlice(p) if p == 0 || slice.Data == nil { b = appendNullComma(ctx, b) code = code.End.Next break } store(ctxptr, code.ElemIdx, 0) store(ctxptr, code.Length, uintptr(slice.Len)) store(ctxptr, code.Idx, uintptr(slice.Data)) if slice.Len > 0 { b = appendArrayHead(ctx, code, b) code = code.Next store(ctxptr, code.Idx, uintptr(slice.Data)) } else { b = appendEmptyArray(ctx, b) code = code.End.Next } case encoder.OpSliceElem: idx := load(ctxptr, code.ElemIdx) length := load(ctxptr, code.Length) idx++ if idx < length { b = appendArrayElemIndent(ctx, code, b) store(ctxptr, code.ElemIdx, idx) data := load(ctxptr, code.Idx) size := uintptr(code.Size) code = code.Next store(ctxptr, code.Idx, data+idx*size) } else { b = appendArrayEnd(ctx, code, b) code = code.End.Next } case encoder.OpArrayPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpArray: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } if code.Length > 0 { b = appendArrayHead(ctx, code, b) store(ctxptr, code.ElemIdx, 0) code = code.Next store(ctxptr, code.Idx, p) } else { b = appendEmptyArray(ctx, b) code = code.End.Next } case encoder.OpArrayElem: idx := load(ctxptr, code.ElemIdx) idx++ if idx < uintptr(code.Length) { b = appendArrayElemIndent(ctx, code, b) store(ctxptr, code.ElemIdx, idx) p := load(ctxptr, code.Idx) size := uintptr(code.Size) code = code.Next store(ctxptr, code.Idx, p+idx*size) } else { b = appendArrayEnd(ctx, code, b) code = code.End.Next } case encoder.OpMapPtr: p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpMap: p := load(ctxptr, code.Idx) if p == 0 { b = appendNullComma(ctx, b) code = code.End.Next break } uptr := ptrToUnsafePtr(p) mlen := maplen(uptr) if mlen <= 0 { b = appendEmptyObject(ctx, b) code = code.End.Next break } b = appendStructHead(ctx, b) unorderedMap := (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 mapCtx := encoder.NewMapContext(mlen, unorderedMap) mapiterinit(code.Type, uptr, &mapCtx.Iter) store(ctxptr, code.Idx, uintptr(unsafe.Pointer(mapCtx))) ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(mapCtx)) if unorderedMap { b = appendMapKeyIndent(ctx, code.Next, b) } else { mapCtx.Start = len(b) mapCtx.First = len(b) } key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next case encoder.OpMapKey: mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) idx := mapCtx.Idx idx++ if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { if idx < mapCtx.Len { b = appendMapKeyIndent(ctx, code, b) mapCtx.Idx = int(idx) key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next } else { b = appendObjectEnd(ctx, code, b) encoder.ReleaseMapContext(mapCtx) code = code.End.Next } } else { mapCtx.Slice.Items[mapCtx.Idx].Value = b[mapCtx.Start:len(b)] if idx < mapCtx.Len { mapCtx.Idx = int(idx) mapCtx.Start = len(b) key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next } else { code = code.End } } case encoder.OpMapValue: mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { b = appendColon(ctx, b) } else { mapCtx.Slice.Items[mapCtx.Idx].Key = b[mapCtx.Start:len(b)] mapCtx.Start = len(b) } value := mapitervalue(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(value)) mapiternext(&mapCtx.Iter) code = code.Next case encoder.OpMapEnd: // this operation only used by sorted map. mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) sort.Sort(mapCtx.Slice) buf := mapCtx.Buf for _, item := range mapCtx.Slice.Items { buf = appendMapKeyValue(ctx, code, buf, item.Key, item.Value) } buf = appendMapEnd(ctx, code, buf) b = b[:mapCtx.First] b = append(b, buf...) mapCtx.Buf = buf encoder.ReleaseMapContext(mapCtx) code = code.Next case encoder.OpRecursivePtr: p := load(ctxptr, code.Idx) if p == 0 { code = code.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpRecursive: ptr := load(ctxptr, code.Idx) if ptr != 0 { if recursiveLevel > encoder.StartDetectingCyclesAfter { for _, seen := range ctx.SeenPtr { if ptr == seen { return nil, errUnsupportedValue(code, ptr) } } } } ctx.SeenPtr = append(ctx.SeenPtr, ptr) c := code.Jmp.Code curlen := uintptr(len(ctx.Ptrs)) offsetNum := ptrOffset / uintptrSize oldOffset := ptrOffset ptrOffset += code.Jmp.CurLen * uintptrSize oldBaseIndent := ctx.BaseIndent indentDiffFromTop := c.Indent - 1 ctx.BaseIndent += code.Indent - indentDiffFromTop newLen := offsetNum + code.Jmp.CurLen + code.Jmp.NextLen if curlen < newLen { ctx.Ptrs = append(ctx.Ptrs, make([]uintptr, newLen-curlen)...) } ctxptr = ctx.Ptr() + ptrOffset // assign new ctxptr store(ctxptr, c.Idx, ptr) store(ctxptr, c.End.Next.Idx, oldOffset) store(ctxptr, c.End.Next.ElemIdx, uintptr(unsafe.Pointer(code.Next))) storeIndent(ctxptr, c.End.Next, uintptr(oldBaseIndent)) code = c recursiveLevel++ case encoder.OpRecursiveEnd: recursiveLevel-- // restore ctxptr restoreIndent(ctx, code, ctxptr) offset := load(ctxptr, code.Idx) ctx.SeenPtr = ctx.SeenPtr[:len(ctx.SeenPtr)-1] codePtr := load(ctxptr, code.ElemIdx) code = (*encoder.Opcode)(ptrToUnsafePtr(codePtr)) ctxptr = ctx.Ptr() + offset ptrOffset = offset case encoder.OpStructPtrHead: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHead: p := load(ctxptr, code.Idx) if p == 0 && ((code.Flags&encoder.IndirectFlags) != 0 || code.Next.Op == encoder.OpStructEnd) { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if len(code.Key) > 0 { if (code.Flags&encoder.IsTaggedKeyFlags) != 0 || code.Flags&encoder.AnonymousKeyFlags == 0 { b = appendStructKey(ctx, code, b) } } p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructPtrHeadOmitEmpty: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmpty: p := load(ctxptr, code.Idx) if p == 0 && ((code.Flags&encoder.IndirectFlags) != 0 || code.Next.Op == encoder.OpStructEnd) { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) if p == 0 || (ptrToPtr(p) == 0 && (code.Flags&encoder.IsNextOpPtrTypeFlags) != 0) { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadInt: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadInt: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyInt: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyInt: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadIntString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadIntString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyIntString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyIntString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) u64 := ptrToUint64(p, code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadIntPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadIntPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendInt(ctx, b, p, code) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyIntPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyIntPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p, code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadIntPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadIntPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyIntPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyIntPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadUint: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadUint: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyUint: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyUint: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadUintString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadUintString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyUintString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyUintString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadUintPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadUintPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendUint(ctx, b, p, code) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyUintPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyUintPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p, code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadUintPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadUintPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyUintPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyUintPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadFloat32: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadFloat32: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat32: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyFloat32: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat32(p + uintptr(code.Offset)) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, v) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadFloat32String: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadFloat32String: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat32String: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyFloat32String: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat32(p + uintptr(code.Offset)) if v == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadFloat32Ptr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadFloat32Ptr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendFloat32(ctx, b, ptrToFloat32(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat32Ptr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyFloat32Ptr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadFloat32PtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadFloat32PtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat32PtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyFloat32PtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadFloat64: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadFloat64: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat64: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyFloat64: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat64(p + uintptr(code.Offset)) if v == 0 { code = code.NextField } else { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadFloat64String: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadFloat64String: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat64String: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyFloat64String: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToFloat64(p + uintptr(code.Offset)) if v == 0 { code = code.NextField } else { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadFloat64Ptr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadFloat64Ptr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat64Ptr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyFloat64Ptr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadFloat64PtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadFloat64PtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyFloat64PtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyFloat64PtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNull(ctx, b) b = appendComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToString(p + uintptr(code.Offset)) if v == "" { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, v) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadStringString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadStringString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p+uintptr(code.Offset))))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyStringString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyStringString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToString(p + uintptr(code.Offset)) if v == "" { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, v))) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadStringPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadStringPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, ptrToString(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyStringPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyStringPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadStringPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadStringPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyStringPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyStringPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadBool: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadBool: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBool: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyBool: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, v) b = appendComma(ctx, b) code = code.Next } else { code = code.NextField } case encoder.OpStructPtrHeadBoolString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadBoolString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBoolString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyBoolString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next } else { code = code.NextField } case encoder.OpStructPtrHeadBoolPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadBoolPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendBool(ctx, b, ptrToBool(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBoolPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyBoolPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadBoolPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadBoolPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBoolPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyBoolPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadBytes: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadBytes: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBytes: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyBytes: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToBytes(p + uintptr(code.Offset)) if len(v) == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, v) b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadBytesPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadBytesPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = appendByteSlice(ctx, b, ptrToBytes(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyBytesPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyBytesPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadNumber: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadNumber: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpStructPtrHeadOmitEmptyNumber: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyNumber: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToNumber(p + uintptr(code.Offset)) if v == "" { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next } case encoder.OpStructPtrHeadNumberString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadNumberString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyNumberString: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyNumberString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } v := ptrToNumber(p + uintptr(code.Offset)) if v == "" { code = code.NextField } else { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadNumberPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadNumberPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyNumberPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyNumberPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructPtrHeadNumberPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadNumberPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyNumberPtrString: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyNumberPtrString: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructPtrHeadArray, encoder.OpStructPtrHeadSlice: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadArray, encoder.OpStructHeadSlice: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructPtrHeadOmitEmptyArray: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyArray: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructPtrHeadOmitEmptySlice: if (code.Flags & encoder.IndirectFlags) != 0 { p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptySlice: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) slice := ptrToSlice(p) if slice.Len == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadArrayPtr, encoder.OpStructPtrHeadSlicePtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadArrayPtr, encoder.OpStructHeadSlicePtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNullComma(ctx, b) code = code.NextField } else { code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadOmitEmptyArrayPtr, encoder.OpStructPtrHeadOmitEmptySlicePtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyArrayPtr, encoder.OpStructHeadOmitEmptySlicePtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadMap: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadMap: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if p != 0 && (code.Flags&encoder.IndirectFlags) != 0 { p = ptrToPtr(p + uintptr(code.Offset)) } code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructPtrHeadOmitEmptyMap: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyMap: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if p != 0 && (code.Flags&encoder.IndirectFlags) != 0 { p = ptrToPtr(p + uintptr(code.Offset)) } if maplen(ptrToUnsafePtr(p)) == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadMapPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadMapPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if p == 0 { b = appendNullComma(ctx, b) code = code.NextField break } p = ptrToPtr(p + uintptr(code.Offset)) if p == 0 { b = appendNullComma(ctx, b) code = code.NextField } else { if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p, code.PtrNum) } code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadOmitEmptyMapPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyMapPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if p == 0 { code = code.NextField break } p = ptrToPtr(p + uintptr(code.Offset)) if p == 0 { code = code.NextField } else { if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p, code.PtrNum) } b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructPtrHeadMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadMarshalJSON { p = ptrToPtr(p) } } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyMarshalJSON: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadOmitEmptyMarshalJSON { p = ptrToPtr(p) } } iface := ptrToInterface(code, p) if (code.Flags&encoder.NilCheckFlags) != 0 && encoder.IsNilForMarshaler(iface) { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendMarshalJSON(ctx, code, b, iface) if err != nil { return nil, err } b = bb b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyMarshalJSONPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if p == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadMarshalText: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadMarshalText: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadMarshalText { p = ptrToPtr(p) } } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyMarshalText: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) } fallthrough case encoder.OpStructHeadOmitEmptyMarshalText: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadOmitEmptyMarshalText { p = ptrToPtr(p) } } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb b = appendComma(ctx, b) code = code.Next } case encoder.OpStructPtrHeadMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } b = appendStructKey(ctx, code, b) if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructPtrHeadOmitEmptyMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } store(ctxptr, code.Idx, ptrToNPtr(p, code.PtrNum)) fallthrough case encoder.OpStructHeadOmitEmptyMarshalTextPtr: p := load(ctxptr, code.Idx) if p == 0 && (code.Flags&encoder.IndirectFlags) != 0 { if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendNullComma(ctx, b) } code = code.End.Next break } if (code.Flags & encoder.IndirectFlags) != 0 { p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) } if code.Flags&encoder.AnonymousHeadFlags == 0 { b = appendStructHead(ctx, b) } if p == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb b = appendComma(ctx, b) code = code.Next } case encoder.OpStructField: if code.Flags&encoder.IsTaggedKeyFlags != 0 || code.Flags&encoder.AnonymousKeyFlags == 0 { b = appendStructKey(ctx, code, b) } p := load(ctxptr, code.Idx) + uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmpty: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) if ptrToPtr(p) == 0 && (code.Flags&encoder.IsNextOpPtrTypeFlags) != 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructFieldInt: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyInt: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldIntString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyIntString: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldIntPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendInt(ctx, b, p, code) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyIntPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p, code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldIntPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyIntPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldUint: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyUint: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldUintString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyUintString: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldUintPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendUint(ctx, b, p, code) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyUintPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p, code) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldUintPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyUintPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat32: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat32: p := load(ctxptr, code.Idx) v := ptrToFloat32(p + uintptr(code.Offset)) if v != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat32String: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat32String: p := load(ctxptr, code.Idx) v := ptrToFloat32(p + uintptr(code.Offset)) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat32Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendFloat32(ctx, b, ptrToFloat32(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat32Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat32PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat32PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat64: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat64: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if v != 0 { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat64String: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat64String: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if v != 0 { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat64Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNullComma(ctx, b) code = code.Next break } v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat64Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldFloat64PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyFloat64PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyString: p := load(ctxptr, code.Idx) v := ptrToString(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldStringString: p := load(ctxptr, code.Idx) s := ptrToString(p + uintptr(code.Offset)) b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, s))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyStringString: p := load(ctxptr, code.Idx) v := ptrToString(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, v))) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldStringPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, ptrToString(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyStringPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldStringPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyStringPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBool: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBool: p := load(ctxptr, code.Idx) v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBoolString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = append(b, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBoolString: p := load(ctxptr, code.Idx) v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, v) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBoolPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendBool(ctx, b, ptrToBool(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBoolPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBoolPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBoolPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBytes: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p+uintptr(code.Offset))) b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBytes: p := load(ctxptr, code.Idx) v := ptrToBytes(p + uintptr(code.Offset)) if len(v) > 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, v) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldBytesPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = appendByteSlice(ctx, b, ptrToBytes(p)) } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyBytesPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p)) b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldNumber: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpStructFieldOmitEmptyNumber: p := load(ctxptr, code.Idx) v := ptrToNumber(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructFieldNumberString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyNumberString: p := load(ctxptr, code.Idx) v := ptrToNumber(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldNumberPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyNumberPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructFieldNumberPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) b = appendStructKey(ctx, code, b) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyNumberPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') b = appendComma(ctx, b) } code = code.Next case encoder.OpStructFieldMarshalJSON: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { p = ptrToPtr(p) } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyMarshalJSON: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { p = ptrToPtr(p) } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { code = code.NextField break } iface := ptrToInterface(code, p) if (code.Flags&encoder.NilCheckFlags) != 0 && encoder.IsNilForMarshaler(iface) { code = code.NextField break } b = appendStructKey(ctx, code, b) bb, err := appendMarshalJSON(ctx, code, b, iface) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpStructFieldMarshalJSONPtr: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyMarshalJSONPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructFieldMarshalText: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { p = ptrToPtr(p) } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyMarshalText: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) if (code.Flags & encoder.IsNilableTypeFlags) != 0 { p = ptrToPtr(p) } if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 { code = code.NextField break } b = appendStructKey(ctx, code, b) bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) code = code.Next case encoder.OpStructFieldMarshalTextPtr: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = bb } b = appendComma(ctx, b) code = code.Next case encoder.OpStructFieldOmitEmptyMarshalTextPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendMarshalText(ctx, code, b, ptrToInterface(code, p)) if err != nil { return nil, err } b = appendComma(ctx, bb) } code = code.Next case encoder.OpStructFieldArray: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyArray: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldArrayPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyArrayPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } else { code = code.NextField } case encoder.OpStructFieldSlice: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptySlice: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) slice := ptrToSlice(p) if slice.Len == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructFieldSlicePtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptySlicePtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } else { code = code.NextField } case encoder.OpStructFieldMap: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToPtr(p + uintptr(code.Offset)) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyMap: p := load(ctxptr, code.Idx) p = ptrToPtr(p + uintptr(code.Offset)) if p == 0 || maplen(ptrToUnsafePtr(p)) == 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructFieldMapPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToPtr(p + uintptr(code.Offset)) if p != 0 { p = ptrToNPtr(p, code.PtrNum) } code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyMapPtr: p := load(ctxptr, code.Idx) p = ptrToPtr(p + uintptr(code.Offset)) if p != 0 { p = ptrToNPtr(p, code.PtrNum) } if p != 0 { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } else { code = code.NextField } case encoder.OpStructFieldStruct: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p += uintptr(code.Offset) code = code.Next store(ctxptr, code.Idx, p) case encoder.OpStructFieldOmitEmptyStruct: p := load(ctxptr, code.Idx) p += uintptr(code.Offset) if ptrToPtr(p) == 0 && (code.Flags&encoder.IsNextOpPtrTypeFlags) != 0 { code = code.NextField } else { b = appendStructKey(ctx, code, b) code = code.Next store(ctxptr, code.Idx, p) } case encoder.OpStructEnd: b = appendStructEndSkipLast(ctx, code, b) code = code.Next case encoder.OpStructEndInt: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyInt: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndIntString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyIntString: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndIntPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendInt(ctx, b, p, code) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyIntPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendInt(ctx, b, p, code) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndIntPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyIntPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendInt(ctx, b, p, code) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndUint: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyUint: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndUintString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyUintString: p := load(ctxptr, code.Idx) u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize) v := u64 & ((1 << code.NumBitSize) - 1) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p+uintptr(code.Offset), code) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndUintPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendUint(ctx, b, p, code) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyUintPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendUint(ctx, b, p, code) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndUintPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyUintPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendUint(ctx, b, p, code) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat32: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat32: p := load(ctxptr, code.Idx) v := ptrToFloat32(p + uintptr(code.Offset)) if v != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat32String: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p+uintptr(code.Offset))) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat32String: p := load(ctxptr, code.Idx) v := ptrToFloat32(p + uintptr(code.Offset)) if v != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat32Ptr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendFloat32(ctx, b, ptrToFloat32(p)) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat32Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendFloat32(ctx, b, ptrToFloat32(p)) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat32PtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat32PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat32(ctx, b, ptrToFloat32(p)) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat64: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat64: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if v != 0 { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = appendFloat64(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat64String: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat64String: p := load(ctxptr, code.Idx) v := ptrToFloat64(p + uintptr(code.Offset)) if v != 0 { if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat64Ptr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) b = appendStructEnd(ctx, code, b) code = code.Next break } v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat64Ptr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndFloat64PtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = appendFloat64(ctx, b, v) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyFloat64PtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) v := ptrToFloat64(p) if math.IsInf(v, 0) || math.IsNaN(v) { return nil, errUnsupportedFloat(v) } b = append(b, '"') b = appendFloat64(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p+uintptr(code.Offset))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyString: p := load(ctxptr, code.Idx) v := ptrToString(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndStringString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) s := ptrToString(p + uintptr(code.Offset)) b = appendString(ctx, b, string(appendString(ctx, []byte{}, s))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyStringString: p := load(ctxptr, code.Idx) v := ptrToString(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, v))) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndStringPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, ptrToString(p)) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyStringPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, ptrToString(p)) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndStringPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyStringPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendString(ctx, b, string(appendString(ctx, []byte{}, ptrToString(p)))) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBool: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBool: p := load(ctxptr, code.Idx) v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBoolString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p+uintptr(code.Offset))) b = append(b, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBoolString: p := load(ctxptr, code.Idx) v := ptrToBool(p + uintptr(code.Offset)) if v { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, v) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBoolPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendBool(ctx, b, ptrToBool(p)) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBoolPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendBool(ctx, b, ptrToBool(p)) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBoolPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBoolPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') b = appendBool(ctx, b, ptrToBool(p)) b = append(b, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBytes: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p+uintptr(code.Offset))) b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBytes: p := load(ctxptr, code.Idx) v := ptrToBytes(p + uintptr(code.Offset)) if len(v) > 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, v) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndBytesPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = appendByteSlice(ctx, b, ptrToBytes(p)) } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyBytesPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = appendByteSlice(ctx, b, ptrToBytes(p)) b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndNumber: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = appendStructEnd(ctx, code, bb) code = code.Next case encoder.OpStructEndOmitEmptyNumber: p := load(ctxptr, code.Idx) v := ptrToNumber(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = appendStructEnd(ctx, code, bb) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndNumberString: p := load(ctxptr, code.Idx) b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p+uintptr(code.Offset))) if err != nil { return nil, err } b = append(bb, '"') b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyNumberString: p := load(ctxptr, code.Idx) v := ptrToNumber(p + uintptr(code.Offset)) if v != "" { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, v) if err != nil { return nil, err } b = append(bb, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndNumberPtr: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = bb } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyNumberPtr: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = appendStructEnd(ctx, code, bb) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpStructEndNumberPtrString: b = appendStructKey(ctx, code, b) p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p == 0 { b = appendNull(ctx, b) } else { b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') } b = appendStructEnd(ctx, code, b) code = code.Next case encoder.OpStructEndOmitEmptyNumberPtrString: p := load(ctxptr, code.Idx) p = ptrToNPtr(p+uintptr(code.Offset), code.PtrNum) if p != 0 { b = appendStructKey(ctx, code, b) b = append(b, '"') bb, err := appendNumber(ctx, b, ptrToNumber(p)) if err != nil { return nil, err } b = append(bb, '"') b = appendStructEnd(ctx, code, b) } else { b = appendStructEndSkipLast(ctx, code, b) } code = code.Next case encoder.OpEnd: goto END } } END: return b, nil } ================================================ FILE: vendor/github.com/goccy/go-json/internal/errors/error.go ================================================ package errors import ( "fmt" "reflect" "strconv" ) type InvalidUTF8Error struct { S string // the whole string value that caused the error } func (e *InvalidUTF8Error) Error() string { return fmt.Sprintf("json: invalid UTF-8 in string: %s", strconv.Quote(e.S)) } type InvalidUnmarshalError struct { Type reflect.Type } func (e *InvalidUnmarshalError) Error() string { if e.Type == nil { return "json: Unmarshal(nil)" } if e.Type.Kind() != reflect.Ptr { return fmt.Sprintf("json: Unmarshal(non-pointer %s)", e.Type) } return fmt.Sprintf("json: Unmarshal(nil %s)", e.Type) } // A MarshalerError represents an error from calling a MarshalJSON or MarshalText method. type MarshalerError struct { Type reflect.Type Err error sourceFunc string } func (e *MarshalerError) Error() string { srcFunc := e.sourceFunc if srcFunc == "" { srcFunc = "MarshalJSON" } return fmt.Sprintf("json: error calling %s for type %s: %s", srcFunc, e.Type, e.Err.Error()) } // Unwrap returns the underlying error. func (e *MarshalerError) Unwrap() error { return e.Err } // A SyntaxError is a description of a JSON syntax error. type SyntaxError struct { msg string // description of error Offset int64 // error occurred after reading Offset bytes } func (e *SyntaxError) Error() string { return e.msg } // An UnmarshalFieldError describes a JSON object key that // led to an unexported (and therefore unwritable) struct field. // // Deprecated: No longer used; kept for compatibility. type UnmarshalFieldError struct { Key string Type reflect.Type Field reflect.StructField } func (e *UnmarshalFieldError) Error() string { return fmt.Sprintf("json: cannot unmarshal object key %s into unexported field %s of type %s", strconv.Quote(e.Key), e.Field.Name, e.Type.String(), ) } // An UnmarshalTypeError describes a JSON value that was // not appropriate for a value of a specific Go type. type UnmarshalTypeError struct { Value string // description of JSON value - "bool", "array", "number -5" Type reflect.Type // type of Go value it could not be assigned to Offset int64 // error occurred after reading Offset bytes Struct string // name of the struct type containing the field Field string // the full path from root node to the field } func (e *UnmarshalTypeError) Error() string { if e.Struct != "" || e.Field != "" { return fmt.Sprintf("json: cannot unmarshal %s into Go struct field %s.%s of type %s", e.Value, e.Struct, e.Field, e.Type, ) } return fmt.Sprintf("json: cannot unmarshal %s into Go value of type %s", e.Value, e.Type) } // An UnsupportedTypeError is returned by Marshal when attempting // to encode an unsupported value type. type UnsupportedTypeError struct { Type reflect.Type } func (e *UnsupportedTypeError) Error() string { return fmt.Sprintf("json: unsupported type: %s", e.Type) } type UnsupportedValueError struct { Value reflect.Value Str string } func (e *UnsupportedValueError) Error() string { return fmt.Sprintf("json: unsupported value: %s", e.Str) } func ErrSyntax(msg string, offset int64) *SyntaxError { return &SyntaxError{msg: msg, Offset: offset} } func ErrMarshaler(typ reflect.Type, err error, msg string) *MarshalerError { return &MarshalerError{ Type: typ, Err: err, sourceFunc: msg, } } func ErrExceededMaxDepth(c byte, cursor int64) *SyntaxError { return &SyntaxError{ msg: fmt.Sprintf(`invalid character "%c" exceeded max depth`, c), Offset: cursor, } } func ErrNotAtBeginningOfValue(cursor int64) *SyntaxError { return &SyntaxError{msg: "not at beginning of value", Offset: cursor} } func ErrUnexpectedEndOfJSON(msg string, cursor int64) *SyntaxError { return &SyntaxError{ msg: fmt.Sprintf("json: %s unexpected end of JSON input", msg), Offset: cursor, } } func ErrExpected(msg string, cursor int64) *SyntaxError { return &SyntaxError{msg: fmt.Sprintf("expected %s", msg), Offset: cursor} } func ErrInvalidCharacter(c byte, context string, cursor int64) *SyntaxError { if c == 0 { return &SyntaxError{ msg: fmt.Sprintf("json: invalid character as %s", context), Offset: cursor, } } return &SyntaxError{ msg: fmt.Sprintf("json: invalid character %c as %s", c, context), Offset: cursor, } } func ErrInvalidBeginningOfValue(c byte, cursor int64) *SyntaxError { return &SyntaxError{ msg: fmt.Sprintf("invalid character '%c' looking for beginning of value", c), Offset: cursor, } } type PathError struct { msg string } func (e *PathError) Error() string { return fmt.Sprintf("json: invalid path format: %s", e.msg) } func ErrInvalidPath(msg string, args ...interface{}) *PathError { if len(args) != 0 { return &PathError{msg: fmt.Sprintf(msg, args...)} } return &PathError{msg: msg} } func ErrEmptyPath() *PathError { return &PathError{msg: "path is empty"} } ================================================ FILE: vendor/github.com/goccy/go-json/internal/runtime/rtype.go ================================================ package runtime import ( "reflect" "unsafe" ) // Type representing reflect.rtype for noescape trick type Type struct{} //go:linkname rtype_Align reflect.(*rtype).Align //go:noescape func rtype_Align(*Type) int func (t *Type) Align() int { return rtype_Align(t) } //go:linkname rtype_FieldAlign reflect.(*rtype).FieldAlign //go:noescape func rtype_FieldAlign(*Type) int func (t *Type) FieldAlign() int { return rtype_FieldAlign(t) } //go:linkname rtype_Method reflect.(*rtype).Method //go:noescape func rtype_Method(*Type, int) reflect.Method func (t *Type) Method(a0 int) reflect.Method { return rtype_Method(t, a0) } //go:linkname rtype_MethodByName reflect.(*rtype).MethodByName //go:noescape func rtype_MethodByName(*Type, string) (reflect.Method, bool) func (t *Type) MethodByName(a0 string) (reflect.Method, bool) { return rtype_MethodByName(t, a0) } //go:linkname rtype_NumMethod reflect.(*rtype).NumMethod //go:noescape func rtype_NumMethod(*Type) int func (t *Type) NumMethod() int { return rtype_NumMethod(t) } //go:linkname rtype_Name reflect.(*rtype).Name //go:noescape func rtype_Name(*Type) string func (t *Type) Name() string { return rtype_Name(t) } //go:linkname rtype_PkgPath reflect.(*rtype).PkgPath //go:noescape func rtype_PkgPath(*Type) string func (t *Type) PkgPath() string { return rtype_PkgPath(t) } //go:linkname rtype_Size reflect.(*rtype).Size //go:noescape func rtype_Size(*Type) uintptr func (t *Type) Size() uintptr { return rtype_Size(t) } //go:linkname rtype_String reflect.(*rtype).String //go:noescape func rtype_String(*Type) string func (t *Type) String() string { return rtype_String(t) } //go:linkname rtype_Kind reflect.(*rtype).Kind //go:noescape func rtype_Kind(*Type) reflect.Kind func (t *Type) Kind() reflect.Kind { return rtype_Kind(t) } //go:linkname rtype_Implements reflect.(*rtype).Implements //go:noescape func rtype_Implements(*Type, reflect.Type) bool func (t *Type) Implements(u reflect.Type) bool { return rtype_Implements(t, u) } //go:linkname rtype_AssignableTo reflect.(*rtype).AssignableTo //go:noescape func rtype_AssignableTo(*Type, reflect.Type) bool func (t *Type) AssignableTo(u reflect.Type) bool { return rtype_AssignableTo(t, u) } //go:linkname rtype_ConvertibleTo reflect.(*rtype).ConvertibleTo //go:noescape func rtype_ConvertibleTo(*Type, reflect.Type) bool func (t *Type) ConvertibleTo(u reflect.Type) bool { return rtype_ConvertibleTo(t, u) } //go:linkname rtype_Comparable reflect.(*rtype).Comparable //go:noescape func rtype_Comparable(*Type) bool func (t *Type) Comparable() bool { return rtype_Comparable(t) } //go:linkname rtype_Bits reflect.(*rtype).Bits //go:noescape func rtype_Bits(*Type) int func (t *Type) Bits() int { return rtype_Bits(t) } //go:linkname rtype_ChanDir reflect.(*rtype).ChanDir //go:noescape func rtype_ChanDir(*Type) reflect.ChanDir func (t *Type) ChanDir() reflect.ChanDir { return rtype_ChanDir(t) } //go:linkname rtype_IsVariadic reflect.(*rtype).IsVariadic //go:noescape func rtype_IsVariadic(*Type) bool func (t *Type) IsVariadic() bool { return rtype_IsVariadic(t) } //go:linkname rtype_Elem reflect.(*rtype).Elem //go:noescape func rtype_Elem(*Type) reflect.Type func (t *Type) Elem() *Type { return Type2RType(rtype_Elem(t)) } //go:linkname rtype_Field reflect.(*rtype).Field //go:noescape func rtype_Field(*Type, int) reflect.StructField func (t *Type) Field(i int) reflect.StructField { return rtype_Field(t, i) } //go:linkname rtype_FieldByIndex reflect.(*rtype).FieldByIndex //go:noescape func rtype_FieldByIndex(*Type, []int) reflect.StructField func (t *Type) FieldByIndex(index []int) reflect.StructField { return rtype_FieldByIndex(t, index) } //go:linkname rtype_FieldByName reflect.(*rtype).FieldByName //go:noescape func rtype_FieldByName(*Type, string) (reflect.StructField, bool) func (t *Type) FieldByName(name string) (reflect.StructField, bool) { return rtype_FieldByName(t, name) } //go:linkname rtype_FieldByNameFunc reflect.(*rtype).FieldByNameFunc //go:noescape func rtype_FieldByNameFunc(*Type, func(string) bool) (reflect.StructField, bool) func (t *Type) FieldByNameFunc(match func(string) bool) (reflect.StructField, bool) { return rtype_FieldByNameFunc(t, match) } //go:linkname rtype_In reflect.(*rtype).In //go:noescape func rtype_In(*Type, int) reflect.Type func (t *Type) In(i int) reflect.Type { return rtype_In(t, i) } //go:linkname rtype_Key reflect.(*rtype).Key //go:noescape func rtype_Key(*Type) reflect.Type func (t *Type) Key() *Type { return Type2RType(rtype_Key(t)) } //go:linkname rtype_Len reflect.(*rtype).Len //go:noescape func rtype_Len(*Type) int func (t *Type) Len() int { return rtype_Len(t) } //go:linkname rtype_NumField reflect.(*rtype).NumField //go:noescape func rtype_NumField(*Type) int func (t *Type) NumField() int { return rtype_NumField(t) } //go:linkname rtype_NumIn reflect.(*rtype).NumIn //go:noescape func rtype_NumIn(*Type) int func (t *Type) NumIn() int { return rtype_NumIn(t) } //go:linkname rtype_NumOut reflect.(*rtype).NumOut //go:noescape func rtype_NumOut(*Type) int func (t *Type) NumOut() int { return rtype_NumOut(t) } //go:linkname rtype_Out reflect.(*rtype).Out //go:noescape func rtype_Out(*Type, int) reflect.Type //go:linkname PtrTo reflect.(*rtype).ptrTo //go:noescape func PtrTo(*Type) *Type func (t *Type) Out(i int) reflect.Type { return rtype_Out(t, i) } //go:linkname IfaceIndir reflect.ifaceIndir //go:noescape func IfaceIndir(*Type) bool //go:linkname RType2Type reflect.toType //go:noescape func RType2Type(t *Type) reflect.Type //go:nolint structcheck type emptyInterface struct { _ *Type ptr unsafe.Pointer } func Type2RType(t reflect.Type) *Type { return (*Type)(((*emptyInterface)(unsafe.Pointer(&t))).ptr) } ================================================ FILE: vendor/github.com/goccy/go-json/internal/runtime/struct_field.go ================================================ package runtime import ( "reflect" "strings" "unicode" ) func getTag(field reflect.StructField) string { return field.Tag.Get("json") } func IsIgnoredStructField(field reflect.StructField) bool { if field.PkgPath != "" { if field.Anonymous { t := field.Type if t.Kind() == reflect.Ptr { t = t.Elem() } if t.Kind() != reflect.Struct { return true } } else { // private field return true } } tag := getTag(field) return tag == "-" } type StructTag struct { Key string IsTaggedKey bool IsOmitEmpty bool IsString bool Field reflect.StructField } type StructTags []*StructTag func (t StructTags) ExistsKey(key string) bool { for _, tt := range t { if tt.Key == key { return true } } return false } func isValidTag(s string) bool { if s == "" { return false } for _, c := range s { switch { case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c): // Backslash and quote chars are reserved, but // otherwise any punctuation chars are allowed // in a tag name. case !unicode.IsLetter(c) && !unicode.IsDigit(c): return false } } return true } func StructTagFromField(field reflect.StructField) *StructTag { keyName := field.Name tag := getTag(field) st := &StructTag{Field: field} opts := strings.Split(tag, ",") if len(opts) > 0 { if opts[0] != "" && isValidTag(opts[0]) { keyName = opts[0] st.IsTaggedKey = true } } st.Key = keyName if len(opts) > 1 { for _, opt := range opts[1:] { switch opt { case "omitempty": st.IsOmitEmpty = true case "string": st.IsString = true } } } return st } ================================================ FILE: vendor/github.com/goccy/go-json/internal/runtime/type.go ================================================ package runtime import ( "reflect" "unsafe" ) type SliceHeader struct { Data unsafe.Pointer Len int Cap int } const ( maxAcceptableTypeAddrRange = 1024 * 1024 * 2 // 2 Mib ) type TypeAddr struct { BaseTypeAddr uintptr MaxTypeAddr uintptr AddrRange uintptr AddrShift uintptr } var ( typeAddr *TypeAddr alreadyAnalyzed bool ) //go:linkname typelinks reflect.typelinks func typelinks() ([]unsafe.Pointer, [][]int32) //go:linkname rtypeOff reflect.rtypeOff func rtypeOff(unsafe.Pointer, int32) unsafe.Pointer func AnalyzeTypeAddr() *TypeAddr { defer func() { alreadyAnalyzed = true }() if alreadyAnalyzed { return typeAddr } sections, offsets := typelinks() if len(sections) != 1 { return nil } if len(offsets) != 1 { return nil } section := sections[0] offset := offsets[0] var ( min uintptr = uintptr(^uint(0)) max uintptr = 0 isAligned64 = true isAligned32 = true ) for i := 0; i < len(offset); i++ { typ := (*Type)(rtypeOff(section, offset[i])) addr := uintptr(unsafe.Pointer(typ)) if min > addr { min = addr } if max < addr { max = addr } if typ.Kind() == reflect.Ptr { addr = uintptr(unsafe.Pointer(typ.Elem())) if min > addr { min = addr } if max < addr { max = addr } } isAligned64 = isAligned64 && (addr-min)&63 == 0 isAligned32 = isAligned32 && (addr-min)&31 == 0 } addrRange := max - min if addrRange == 0 { return nil } var addrShift uintptr if isAligned64 { addrShift = 6 } else if isAligned32 { addrShift = 5 } cacheSize := addrRange >> addrShift if cacheSize > maxAcceptableTypeAddrRange { return nil } typeAddr = &TypeAddr{ BaseTypeAddr: min, MaxTypeAddr: max, AddrRange: addrRange, AddrShift: addrShift, } return typeAddr } ================================================ FILE: vendor/github.com/goccy/go-json/json.go ================================================ package json import ( "bytes" "context" "encoding/json" "github.com/goccy/go-json/internal/encoder" ) // Marshaler is the interface implemented by types that // can marshal themselves into valid JSON. type Marshaler interface { MarshalJSON() ([]byte, error) } // MarshalerContext is the interface implemented by types that // can marshal themselves into valid JSON with context.Context. type MarshalerContext interface { MarshalJSON(context.Context) ([]byte, error) } // Unmarshaler is the interface implemented by types // that can unmarshal a JSON description of themselves. // The input can be assumed to be a valid encoding of // a JSON value. UnmarshalJSON must copy the JSON data // if it wishes to retain the data after returning. // // By convention, to approximate the behavior of Unmarshal itself, // Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op. type Unmarshaler interface { UnmarshalJSON([]byte) error } // UnmarshalerContext is the interface implemented by types // that can unmarshal with context.Context a JSON description of themselves. type UnmarshalerContext interface { UnmarshalJSON(context.Context, []byte) error } // Marshal returns the JSON encoding of v. // // Marshal traverses the value v recursively. // If an encountered value implements the Marshaler interface // and is not a nil pointer, Marshal calls its MarshalJSON method // to produce JSON. If no MarshalJSON method is present but the // value implements encoding.TextMarshaler instead, Marshal calls // its MarshalText method and encodes the result as a JSON string. // The nil pointer exception is not strictly necessary // but mimics a similar, necessary exception in the behavior of // UnmarshalJSON. // // Otherwise, Marshal uses the following type-dependent default encodings: // // Boolean values encode as JSON booleans. // // Floating point, integer, and Number values encode as JSON numbers. // // String values encode as JSON strings coerced to valid UTF-8, // replacing invalid bytes with the Unicode replacement rune. // The angle brackets "<" and ">" are escaped to "\u003c" and "\u003e" // to keep some browsers from misinterpreting JSON output as HTML. // Ampersand "&" is also escaped to "\u0026" for the same reason. // This escaping can be disabled using an Encoder that had SetEscapeHTML(false) // called on it. // // Array and slice values encode as JSON arrays, except that // []byte encodes as a base64-encoded string, and a nil slice // encodes as the null JSON value. // // Struct values encode as JSON objects. // Each exported struct field becomes a member of the object, using the // field name as the object key, unless the field is omitted for one of the // reasons given below. // // The encoding of each struct field can be customized by the format string // stored under the "json" key in the struct field's tag. // The format string gives the name of the field, possibly followed by a // comma-separated list of options. The name may be empty in order to // specify options without overriding the default field name. // // The "omitempty" option specifies that the field should be omitted // from the encoding if the field has an empty value, defined as // false, 0, a nil pointer, a nil interface value, and any empty array, // slice, map, or string. // // As a special case, if the field tag is "-", the field is always omitted. // Note that a field with name "-" can still be generated using the tag "-,". // // Examples of struct field tags and their meanings: // // // Field appears in JSON as key "myName". // Field int `json:"myName"` // // // Field appears in JSON as key "myName" and // // the field is omitted from the object if its value is empty, // // as defined above. // Field int `json:"myName,omitempty"` // // // Field appears in JSON as key "Field" (the default), but // // the field is skipped if empty. // // Note the leading comma. // Field int `json:",omitempty"` // // // Field is ignored by this package. // Field int `json:"-"` // // // Field appears in JSON as key "-". // Field int `json:"-,"` // // The "string" option signals that a field is stored as JSON inside a // JSON-encoded string. It applies only to fields of string, floating point, // integer, or boolean types. This extra level of encoding is sometimes used // when communicating with JavaScript programs: // // Int64String int64 `json:",string"` // // The key name will be used if it's a non-empty string consisting of // only Unicode letters, digits, and ASCII punctuation except quotation // marks, backslash, and comma. // // Anonymous struct fields are usually marshaled as if their inner exported fields // were fields in the outer struct, subject to the usual Go visibility rules amended // as described in the next paragraph. // An anonymous struct field with a name given in its JSON tag is treated as // having that name, rather than being anonymous. // An anonymous struct field of interface type is treated the same as having // that type as its name, rather than being anonymous. // // The Go visibility rules for struct fields are amended for JSON when // deciding which field to marshal or unmarshal. If there are // multiple fields at the same level, and that level is the least // nested (and would therefore be the nesting level selected by the // usual Go rules), the following extra rules apply: // // 1) Of those fields, if any are JSON-tagged, only tagged fields are considered, // even if there are multiple untagged fields that would otherwise conflict. // // 2) If there is exactly one field (tagged or not according to the first rule), that is selected. // // 3) Otherwise there are multiple fields, and all are ignored; no error occurs. // // Handling of anonymous struct fields is new in Go 1.1. // Prior to Go 1.1, anonymous struct fields were ignored. To force ignoring of // an anonymous struct field in both current and earlier versions, give the field // a JSON tag of "-". // // Map values encode as JSON objects. The map's key type must either be a // string, an integer type, or implement encoding.TextMarshaler. The map keys // are sorted and used as JSON object keys by applying the following rules, // subject to the UTF-8 coercion described for string values above: // - string keys are used directly // - encoding.TextMarshalers are marshaled // - integer keys are converted to strings // // Pointer values encode as the value pointed to. // A nil pointer encodes as the null JSON value. // // Interface values encode as the value contained in the interface. // A nil interface value encodes as the null JSON value. // // Channel, complex, and function values cannot be encoded in JSON. // Attempting to encode such a value causes Marshal to return // an UnsupportedTypeError. // // JSON cannot represent cyclic data structures and Marshal does not // handle them. Passing cyclic structures to Marshal will result in // an infinite recursion. // func Marshal(v interface{}) ([]byte, error) { return MarshalWithOption(v) } // MarshalNoEscape returns the JSON encoding of v and doesn't escape v. func MarshalNoEscape(v interface{}) ([]byte, error) { return marshalNoEscape(v) } // MarshalContext returns the JSON encoding of v with context.Context and EncodeOption. func MarshalContext(ctx context.Context, v interface{}, optFuncs ...EncodeOptionFunc) ([]byte, error) { return marshalContext(ctx, v, optFuncs...) } // MarshalWithOption returns the JSON encoding of v with EncodeOption. func MarshalWithOption(v interface{}, optFuncs ...EncodeOptionFunc) ([]byte, error) { return marshal(v, optFuncs...) } // MarshalIndent is like Marshal but applies Indent to format the output. // Each JSON element in the output will begin on a new line beginning with prefix // followed by one or more copies of indent according to the indentation nesting. func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { return MarshalIndentWithOption(v, prefix, indent) } // MarshalIndentWithOption is like Marshal but applies Indent to format the output with EncodeOption. func MarshalIndentWithOption(v interface{}, prefix, indent string, optFuncs ...EncodeOptionFunc) ([]byte, error) { return marshalIndent(v, prefix, indent, optFuncs...) } // Unmarshal parses the JSON-encoded data and stores the result // in the value pointed to by v. If v is nil or not a pointer, // Unmarshal returns an InvalidUnmarshalError. // // Unmarshal uses the inverse of the encodings that // Marshal uses, allocating maps, slices, and pointers as necessary, // with the following additional rules: // // To unmarshal JSON into a pointer, Unmarshal first handles the case of // the JSON being the JSON literal null. In that case, Unmarshal sets // the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into // the value pointed at by the pointer. If the pointer is nil, Unmarshal // allocates a new value for it to point to. // // To unmarshal JSON into a value implementing the Unmarshaler interface, // Unmarshal calls that value's UnmarshalJSON method, including // when the input is a JSON null. // Otherwise, if the value implements encoding.TextUnmarshaler // and the input is a JSON quoted string, Unmarshal calls that value's // UnmarshalText method with the unquoted form of the string. // // To unmarshal JSON into a struct, Unmarshal matches incoming object // keys to the keys used by Marshal (either the struct field name or its tag), // preferring an exact match but also accepting a case-insensitive match. By // default, object keys which don't have a corresponding struct field are // ignored (see Decoder.DisallowUnknownFields for an alternative). // // To unmarshal JSON into an interface value, // Unmarshal stores one of these in the interface value: // // bool, for JSON booleans // float64, for JSON numbers // string, for JSON strings // []interface{}, for JSON arrays // map[string]interface{}, for JSON objects // nil for JSON null // // To unmarshal a JSON array into a slice, Unmarshal resets the slice length // to zero and then appends each element to the slice. // As a special case, to unmarshal an empty JSON array into a slice, // Unmarshal replaces the slice with a new empty slice. // // To unmarshal a JSON array into a Go array, Unmarshal decodes // JSON array elements into corresponding Go array elements. // If the Go array is smaller than the JSON array, // the additional JSON array elements are discarded. // If the JSON array is smaller than the Go array, // the additional Go array elements are set to zero values. // // To unmarshal a JSON object into a map, Unmarshal first establishes a map to // use. If the map is nil, Unmarshal allocates a new map. Otherwise Unmarshal // reuses the existing map, keeping existing entries. Unmarshal then stores // key-value pairs from the JSON object into the map. The map's key type must // either be any string type, an integer, implement json.Unmarshaler, or // implement encoding.TextUnmarshaler. // // If a JSON value is not appropriate for a given target type, // or if a JSON number overflows the target type, Unmarshal // skips that field and completes the unmarshaling as best it can. // If no more serious errors are encountered, Unmarshal returns // an UnmarshalTypeError describing the earliest such error. In any // case, it's not guaranteed that all the remaining fields following // the problematic one will be unmarshaled into the target object. // // The JSON null value unmarshals into an interface, map, pointer, or slice // by setting that Go value to nil. Because null is often used in JSON to mean // ``not present,'' unmarshaling a JSON null into any other Go type has no effect // on the value and produces no error. // // When unmarshaling quoted strings, invalid UTF-8 or // invalid UTF-16 surrogate pairs are not treated as an error. // Instead, they are replaced by the Unicode replacement // character U+FFFD. // func Unmarshal(data []byte, v interface{}) error { return unmarshal(data, v) } // UnmarshalContext parses the JSON-encoded data and stores the result // in the value pointed to by v. If you implement the UnmarshalerContext interface, // call it with ctx as an argument. func UnmarshalContext(ctx context.Context, data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error { return unmarshalContext(ctx, data, v) } func UnmarshalWithOption(data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error { return unmarshal(data, v, optFuncs...) } func UnmarshalNoEscape(data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error { return unmarshalNoEscape(data, v, optFuncs...) } // A Token holds a value of one of these types: // // Delim, for the four JSON delimiters [ ] { } // bool, for JSON booleans // float64, for JSON numbers // Number, for JSON numbers // string, for JSON string literals // nil, for JSON null // type Token = json.Token // A Number represents a JSON number literal. type Number = json.Number // RawMessage is a raw encoded JSON value. // It implements Marshaler and Unmarshaler and can // be used to delay JSON decoding or precompute a JSON encoding. type RawMessage = json.RawMessage // A Delim is a JSON array or object delimiter, one of [ ] { or }. type Delim = json.Delim // Compact appends to dst the JSON-encoded src with // insignificant space characters elided. func Compact(dst *bytes.Buffer, src []byte) error { return encoder.Compact(dst, src, false) } // Indent appends to dst an indented form of the JSON-encoded src. // Each element in a JSON object or array begins on a new, // indented line beginning with prefix followed by one or more // copies of indent according to the indentation nesting. // The data appended to dst does not begin with the prefix nor // any indentation, to make it easier to embed inside other formatted JSON data. // Although leading space characters (space, tab, carriage return, newline) // at the beginning of src are dropped, trailing space characters // at the end of src are preserved and copied to dst. // For example, if src has no trailing spaces, neither will dst; // if src ends in a trailing newline, so will dst. func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error { return encoder.Indent(dst, src, prefix, indent) } // HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029 // characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029 // so that the JSON will be safe to embed inside HTML