diff --git a/builtin-functions/kphp-light/stdlib/server-functions.txt b/builtin-functions/kphp-light/stdlib/server-functions.txt index 5d39ff9683..176aae5c06 100644 --- a/builtin-functions/kphp-light/stdlib/server-functions.txt +++ b/builtin-functions/kphp-light/stdlib/server-functions.txt @@ -4,6 +4,8 @@ function ini_get ($s ::: string): string | false; +function getopt ($options ::: string, $longopt ::: array = array(), ?int &$rest_index = null) ::: mixed[] | false; + // === Handlers =================================================================================== function register_shutdown_function (callable():void $callback) ::: void; @@ -118,8 +120,6 @@ function kphp_extended_instance_cache_metrics_init(callable(string $key):string function register_kphp_on_warning_callback(callable(string $warning_message, string[] $stacktrace):void $stacktrace) ::: void; function register_kphp_on_oom_callback(callable():void $callback) ::: bool; -/** @kphp-extern-func-info stub */ -function getopt ($options ::: string, $longopt ::: array = array(), ?int &$rest_index = null) ::: mixed[] | false; /** @kphp-extern-func-info stub generation-required */ function profiler_set_function_label($label ::: string) ::: void; diff --git a/runtime-light/state/component-state.cpp b/runtime-light/state/component-state.cpp index 4bbf460660..7df60a3545 100644 --- a/runtime-light/state/component-state.cpp +++ b/runtime-light/state/component-state.cpp @@ -44,6 +44,21 @@ void ComponentState::parse_ini_arg(std::string_view key_view, std::string_view v ini_opts.set_value(key_str, value_str); } +void ComponentState::parse_cli_arg(std::string_view key_view, std::string_view value_view) noexcept { + if (!key_view.starts_with(CLI_ARG_PREFIX)) [[unlikely]] { + php_warning("wrong cli argument format %s", key_view.data()); + return; + } + + string key_str{std::next(key_view.data(), CLI_ARG_PREFIX.size()), static_cast(key_view.size() - CLI_ARG_PREFIX.size())}; + key_str.set_reference_counter_to(ExtraRefCnt::for_global_const); + + string value_str{value_view.data(), static_cast(value_view.size())}; + value_str.set_reference_counter_to(ExtraRefCnt::for_global_const); + + cli_opts.set_value(key_str, value_str); +} + void ComponentState::parse_runtime_config_arg(std::string_view value_view) noexcept { // FIXME: actually no need to allocate string here auto [config, ok]{json_decode(string{value_view.data(), static_cast(value_view.size())})}; @@ -62,6 +77,8 @@ void ComponentState::parse_args() noexcept { if (key_view.starts_with(INI_ARG_PREFIX)) { parse_ini_arg(key_view, value_view); + } else if (key_view.starts_with(CLI_ARG_PREFIX)) { + parse_cli_arg(key_view, value_view); } else if (key_view == RUNTIME_CONFIG_ARG) { parse_runtime_config_arg(value_view); } else { @@ -70,4 +87,5 @@ void ComponentState::parse_args() noexcept { } runtime_config.set_reference_counter_to(ExtraRefCnt::for_global_const); ini_opts.set_reference_counter_to(ExtraRefCnt::for_global_const); + cli_opts.set_reference_counter_to(ExtraRefCnt::for_global_const); } diff --git a/runtime-light/state/component-state.h b/runtime-light/state/component-state.h index ab18612cc6..45cfafb2a9 100644 --- a/runtime-light/state/component-state.h +++ b/runtime-light/state/component-state.h @@ -19,6 +19,7 @@ struct ComponentState final : private vk::not_copyable { const uint32_t argc; mixed runtime_config; array ini_opts; + array cli_opts; const uint32_t envc; array env; @@ -26,6 +27,7 @@ struct ComponentState final : private vk::not_copyable { : allocator(INIT_COMPONENT_ALLOCATOR_SIZE, 0) , argc(k2::args_count()) , ini_opts(array_size{argc, false}) /* overapproximation */ + , cli_opts(array_size{argc, false}) , envc(k2::env_count()) , env(array_size{envc, false}) { parse_env(); @@ -42,6 +44,7 @@ struct ComponentState final : private vk::not_copyable { private: static constexpr std::string_view INI_ARG_PREFIX = "ini "; + static constexpr std::string_view CLI_ARG_PREFIX = "cli "; static constexpr std::string_view RUNTIME_CONFIG_ARG = "runtime-config"; static constexpr auto INIT_COMPONENT_ALLOCATOR_SIZE = static_cast(512U * 1024U); // 512KB @@ -51,5 +54,7 @@ struct ComponentState final : private vk::not_copyable { void parse_ini_arg(std::string_view, std::string_view) noexcept; + void parse_cli_arg(std::string_view, std::string_view) noexcept; + void parse_runtime_config_arg(std::string_view) noexcept; }; diff --git a/runtime-light/state/init-functions.cpp b/runtime-light/state/init-functions.cpp index d291f11760..5c844cec05 100644 --- a/runtime-light/state/init-functions.cpp +++ b/runtime-light/state/init-functions.cpp @@ -5,6 +5,7 @@ #include "runtime-light/state/init-functions.h" #include +#include #include #include "runtime-common/core/utils/kphp-assert-core.h" @@ -14,6 +15,7 @@ #include "runtime-light/server/http/init-functions.h" #include "runtime-light/server/init-functions.h" #include "runtime-light/server/job-worker/job-worker-server-state.h" +#include "runtime-light/state/component-state.h" #include "runtime-light/state/instance-state.h" #include "runtime-light/streams/streams.h" #include "runtime-light/tl/tl-core.h" @@ -21,6 +23,48 @@ namespace { +array format_cli_argv() { + constexpr std::string_view INI_ARG_PHP_PREFIX = "-D"; + constexpr std::string_view SHORT_ARG_PHP_PREFIX = "-"; + constexpr std::string_view LONG_ARG_PHP_PREFIX = "--"; + constexpr std::string_view RUNTIME_CONFIG_PHP_PREFIX = "--runtime_config"; + + array argv; + + const auto &component_state{ComponentState::get()}; + argv.reserve(component_state.argc * 2, false); + + for (const auto &ini_opt : component_state.ini_opts) { + argv.push_back(string{INI_ARG_PHP_PREFIX.data()}); + argv.push_back(ini_opt.get_key()); + + const auto &value{ini_opt.get_value()}; + if (!value.empty()) { + argv.push_back(string{"="}); + argv.push_back(value); + } + } + + for (const auto &cli_opt : component_state.cli_opts) { + const bool is_short_option = cli_opt.get_key().as_string().size() == 1; + argv.push_back((is_short_option ? string{SHORT_ARG_PHP_PREFIX.data()} : string{LONG_ARG_PHP_PREFIX.data()}).append(cli_opt.get_key())); + + const auto &value{cli_opt.get_value()}; + if (!value.empty()) { + argv.push_back(string{"="}); + argv.push_back(value); + } + } + + if (!component_state.runtime_config.empty()) { + argv.push_back(string{RUNTIME_CONFIG_PHP_PREFIX.data()}); + argv.push_back(string{"="}); + argv.push_back(component_state.runtime_config); + } + + return argv; +} + void process_k2_invoke_http(tl::TLBuffer &tlb) noexcept { tl::K2InvokeHttp invoke_http{}; if (!invoke_http.fetch(tlb)) { @@ -44,8 +88,8 @@ task_t init_kphp_cli_component() noexcept { { // TODO superglobals init auto &superglobals{InstanceState::get().php_script_mutable_globals_singleton.get_superglobals()}; using namespace PhpServerSuperGlobalIndices; - superglobals.v$argc = static_cast(0); - superglobals.v$argv = array{}; + superglobals.v$argv = format_cli_argv(); + superglobals.v$argc = superglobals.v$argv.as_array().size().size; superglobals.v$_SERVER.set_value(string{ARGC.data(), ARGC.size()}, superglobals.v$argc); superglobals.v$_SERVER.set_value(string{ARGV.data(), ARGV.size()}, superglobals.v$argv); superglobals.v$_SERVER.set_value(string{PHP_SELF.data(), PHP_SELF.size()}, string{}); diff --git a/runtime-light/stdlib/server/args-functions.cpp b/runtime-light/stdlib/server/args-functions.cpp new file mode 100644 index 0000000000..a35604ec72 --- /dev/null +++ b/runtime-light/stdlib/server/args-functions.cpp @@ -0,0 +1,92 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2025 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#include +#include +#include +#include + +#include "runtime-common/core/runtime-core.h" +#include "runtime-light/state/instance-state.h" +#include "runtime-light/stdlib/server/args-functions.h" + +Optional> f$getopt(const string &short_options, const array &long_options, + [[maybe_unused]] Optional>> rest_index) noexcept { + if (const auto &instance_st{InstanceState::get()}; instance_st.image_kind() != ImageKind::CLI) [[unlikely]] { + return false; + } + + enum class option_kind : uint8_t { flag, required, optional }; + + const auto &cli_opts{ComponentState::get().cli_opts}; + array options; + + std::string_view short_options_view{short_options.c_str(), short_options.size()}; + // parse short options + for (size_t pos = 0; pos < short_options_view.size(); ++pos) { + if (!std::isalnum(short_options_view[pos])) { + continue; + } + const string option{1, short_options_view[pos]}; + + option_kind kind{option_kind::flag}; + // check that char followed by a colon + if (pos + 1 < short_options_view.size() && short_options_view[pos + 1] == ':') { + kind = option_kind::required; + pos++; + + // check that char followed by a two colon + if (pos + 1 < short_options_view.size() && short_options_view[pos + 1] == ':') { + kind = option_kind::optional; + pos++; + } + } + + if (!cli_opts.has_key(option)) [[unlikely]] { + // option has not been set + continue; + } + + const mixed &value{cli_opts.get_value(option)}; + if (kind == option_kind::optional) { + options.set_value(option, value.empty() ? false : value); + } else if (kind == option_kind::required && !value.empty()) { + options.set_value(option, value); + } else if (kind == option_kind::flag) { + options.set_value(option, false); + } + } + + // parse long options + for (const auto &long_option : long_options) { + const std::string_view option_view{long_option.get_value().c_str(), long_option.get_value().size()}; + uint8_t offset{}; + + option_kind kind{option_kind::flag}; + if (option_view.ends_with("::")) { + kind = option_kind::optional; + offset = 2; + } else if (option_view.ends_with(":")) { + kind = option_kind::required; + offset = 1; + } + const string option{option_view.data(), static_cast(option_view.size() - offset)}; + + if (!cli_opts.has_key(option)) [[unlikely]] { + // option has not been set + continue; + } + + const mixed &value{cli_opts.get_value(option)}; + if (kind == option_kind::optional) { + options.set_value(option, value.empty() ? false : value); + } else if (kind == option_kind::required && !value.empty()) { + options.set_value(option, value); + } else if (kind == option_kind::flag) { + options.set_value(option, false); + } + } + + return options; +} diff --git a/runtime-light/stdlib/server/args-functions.h b/runtime-light/stdlib/server/args-functions.h index 379be03a9d..6474956134 100644 --- a/runtime-light/stdlib/server/args-functions.h +++ b/runtime-light/stdlib/server/args-functions.h @@ -4,6 +4,9 @@ #pragma once +#include +#include + #include "runtime-common/core/runtime-core.h" #include "runtime-light/state/component-state.h" @@ -11,3 +14,6 @@ inline Optional f$ini_get(const string &key) noexcept { const auto &component_st{ComponentState::get()}; return component_st.ini_opts.has_key(key) ? Optional{component_st.ini_opts.get_value(key)} : Optional{false}; } + +Optional> f$getopt(const string &short_options, const array &long_options = {}, + Optional>> rest_index = {}) noexcept; diff --git a/runtime-light/stdlib/stdlib.cmake b/runtime-light/stdlib/stdlib.cmake index 9c51e338d5..73aa8e4828 100644 --- a/runtime-light/stdlib/stdlib.cmake +++ b/runtime-light/stdlib/stdlib.cmake @@ -22,6 +22,7 @@ prepend( rpc/rpc-tl-query.cpp rpc/rpc-tl-request.cpp serialization/serialization-state.cpp + server/args-functions.cpp server/http-functions.cpp string/regex-functions.cpp string/regex-state.cpp diff --git a/runtime-light/stdlib/system/system-functions.h b/runtime-light/stdlib/system/system-functions.h index 5b2b59ef09..1838d0f289 100644 --- a/runtime-light/stdlib/system/system-functions.h +++ b/runtime-light/stdlib/system/system-functions.h @@ -29,11 +29,6 @@ inline int64_t f$system(const string & /*command*/, int64_t & /*result_code*/ = php_critical_error("call to unsupported function"); } -inline Optional> f$getopt(const string & /*options*/, const array & /*longopts*/ = {}, - Optional & /*rest_index*/ = SystemInstanceState::get().rest_index_dummy) { - php_critical_error("call to unsupported function"); -} - inline int64_t f$numa_get_bound_node() noexcept { return -1; }