Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions builtin-functions/kphp-light/stdlib/server-functions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
18 changes: 18 additions & 0 deletions runtime-light/state/component-state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<string::size_type>(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<string::size_type>(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<string::size_type>(value_view.size())})};
Expand All @@ -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 {
Expand All @@ -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);
}
5 changes: 5 additions & 0 deletions runtime-light/state/component-state.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ struct ComponentState final : private vk::not_copyable {
const uint32_t argc;
mixed runtime_config;
array<string> ini_opts;
array<mixed> cli_opts;
const uint32_t envc;
array<string> env;

ComponentState() noexcept
: 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();
Expand All @@ -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<size_t>(512U * 1024U); // 512KB

Expand All @@ -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;
};
48 changes: 46 additions & 2 deletions runtime-light/state/init-functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "runtime-light/state/init-functions.h"

#include <cinttypes>
#include <string_view>
#include <cstdint>

#include "runtime-common/core/utils/kphp-assert-core.h"
Expand All @@ -14,13 +15,56 @@
#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"
#include "runtime-light/tl/tl-functions.h"

namespace {

array<mixed> 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<mixed> 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)) {
Expand All @@ -44,8 +88,8 @@ task_t<uint64_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<int64_t>(0);
superglobals.v$argv = array<mixed>{};
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{});
Expand Down
92 changes: 92 additions & 0 deletions runtime-light/stdlib/server/args-functions.cpp
Original file line number Diff line number Diff line change
@@ -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 <cctype>
#include <cstdint>
#include <functional>
#include <string_view>

#include "runtime-common/core/runtime-core.h"
#include "runtime-light/state/instance-state.h"
#include "runtime-light/stdlib/server/args-functions.h"

Optional<array<mixed>> f$getopt(const string &short_options, const array<string> &long_options,
[[maybe_unused]] Optional<std::optional<std::reference_wrapper<string>>> 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<mixed> 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<string::size_type>(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;
}
6 changes: 6 additions & 0 deletions runtime-light/stdlib/server/args-functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@

#pragma once

#include <functional>
#include <optional>

#include "runtime-common/core/runtime-core.h"
#include "runtime-light/state/component-state.h"

inline Optional<string> f$ini_get(const string &key) noexcept {
const auto &component_st{ComponentState::get()};
return component_st.ini_opts.has_key(key) ? Optional<string>{component_st.ini_opts.get_value(key)} : Optional<string>{false};
}

Optional<array<mixed>> f$getopt(const string &short_options, const array<string> &long_options = {},
Optional<std::optional<std::reference_wrapper<string>>> rest_index = {}) noexcept;
1 change: 1 addition & 0 deletions runtime-light/stdlib/stdlib.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 0 additions & 5 deletions runtime-light/stdlib/system/system-functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<array<mixed>> f$getopt(const string & /*options*/, const array<string> & /*longopts*/ = {},
Optional<int64_t> & /*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;
}
Expand Down
Loading