diff --git a/builtin-functions/kphp-light/stdlib/file-functions.txt b/builtin-functions/kphp-light/stdlib/file-functions.txt index 62d9a00a1f..e804db45d3 100644 --- a/builtin-functions/kphp-light/stdlib/file-functions.txt +++ b/builtin-functions/kphp-light/stdlib/file-functions.txt @@ -41,8 +41,11 @@ function vsprintf ($format ::: string, $args ::: array) ::: string; function file_exists ($name ::: string) ::: bool; -// === UNSUPPORTED === +function file ($name ::: string) ::: string[] | false; +function is_file ($name ::: string) ::: bool; + +// === UNSUPPORTED === /** @kphp-extern-func-info stub generation-required */ function chmod ($name ::: string, $mode ::: int) ::: bool; /** @kphp-extern-func-info stub generation-required */ @@ -50,16 +53,12 @@ function copy ($from ::: string, $to ::: string) ::: bool; /** @kphp-extern-func-info stub generation-required */ function dirname ($name ::: string) ::: string; /** @kphp-extern-func-info stub generation-required */ -function file ($name ::: string) ::: string[] | false; -/** @kphp-extern-func-info stub generation-required */ function filectime ($name ::: string) ::: int | false; /** @kphp-extern-func-info stub generation-required */ function filemtime ($name ::: string) ::: int | false; /** @kphp-extern-func-info stub generation-required */ function is_dir ($name ::: string) ::: bool; /** @kphp-extern-func-info stub generation-required */ -function is_file ($name ::: string) ::: bool; -/** @kphp-extern-func-info stub generation-required */ function is_readable ($name ::: string) ::: bool; /** @kphp-extern-func-info stub generation-required */ function mkdir ($name ::: string, $mode ::: int = 0777, $recursive ::: bool = false) ::: bool; diff --git a/runtime-light/k2-platform/k2-api.h b/runtime-light/k2-platform/k2-api.h index 5c849386a5..5818964385 100644 --- a/runtime-light/k2-platform/k2-api.h +++ b/runtime-light/k2-platform/k2-api.h @@ -415,11 +415,18 @@ inline auto canonicalize(std::string_view path) noexcept { return return_type{{unique_ptr_type{resolved_path, std::invoke(deleter_creator, resolved_path_len, resolved_path_align)}, resolved_path_len}}; } -inline int32_t stat(std::string_view path, struct stat* stat) noexcept { +inline std::expected stat(std::string_view path, struct stat* stat) noexcept { if (auto error_code{k2_stat(path.data(), path.size(), stat)}; error_code != k2::errno_ok) [[unlikely]] { - return error_code; + return std::unexpected{error_code}; } - return k2::errno_ok; + return {}; +} + +inline std::expected lstat(std::string_view path, struct stat* stat) noexcept { + if (auto error_code{k2_lstat(path.data(), path.size(), stat)}; error_code != k2::errno_ok) [[unlikely]] { + return std::unexpected{error_code}; + } + return {}; } using CommandArg = CommandArg; diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index 59edef437f..5d16c73890 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -742,6 +742,29 @@ int32_t k2_canonicalize(const char* path, size_t pathlen, char* const* resolved_ */ int32_t k2_stat(const char* pathname, size_t pathname_len, struct stat* statbuf); +/** + * Semantically equivalent to libc's `lstat`. + * + * Possible `errno`: + * `EACCES` => Search permission is denied for one of the directories in the path prefix of `pathname`. + * `EINVAL` => `pathname` or `statbuf` is `NULL`. + * `EFAULT` => Bad address. + * `ELOOP` => Too many symbolic links encountered while traversing the path. + * `ENAMETOOLONG` => `pathname` is too long. + * `ENOENT` => A component of `pathname` does not exist or is a dangling symbolic link. + * `ENOMEM` => Out of memory (i.e., kernel memory). + * `ENOTDIR` => A component of the path prefix of `pathname` is not a directory. + * `EOVERFLOW` => `pathname` refers to a file whose size, inode number, + * or number of blocks cannot be represented in, respectively, + * the types `off_t`, `ino_t`, or `blkcnt_t`. This error can occur + * when, for example, an application compiled on a 32-bit + * platform without `-D_FILE_OFFSET_BITS=64` calls `lstat()` on a + * file whose size exceeds `(1<<31)-1` bytes. + * `ERANGE` => Failed to convert `st_size`, `st_blksize`, or `st_blocks` to `int64_t`. + * `ENOSYS` => Internal error. + */ +int32_t k2_lstat(const char* pathname, size_t pathname_len, struct stat* statbuf); + struct CommandArg { const char* arg; size_t arg_len; diff --git a/runtime-light/state/component-state.cpp b/runtime-light/state/component-state.cpp index 62e8bd93db..16981067fa 100644 --- a/runtime-light/state/component-state.cpp +++ b/runtime-light/state/component-state.cpp @@ -86,8 +86,9 @@ void ComponentState::parse_runtime_config_arg(std::string_view value_view) noexc } struct stat stat {}; - if (auto error_code{k2::stat({runtime_config_path.get(), runtime_config_path_size}, std::addressof(stat))}; error_code != k2::errno_ok) [[unlikely]] { - return kphp::log::warning("error getting runtime-config stat: error code -> {}", error_code); + if (auto expected_stat_result{k2::stat({runtime_config_path.get(), runtime_config_path_size}, std::addressof(stat))}; !expected_stat_result.has_value()) + [[unlikely]] { + return kphp::log::warning("error getting runtime-config stat: error code -> {}", expected_stat_result.error()); } const auto runtime_config_mem{std::unique_ptr{ diff --git a/runtime-light/stdlib/file/file-system-functions.h b/runtime-light/stdlib/file/file-system-functions.h index 675d264fcb..f163d3c2ec 100644 --- a/runtime-light/stdlib/file/file-system-functions.h +++ b/runtime-light/stdlib/file/file-system-functions.h @@ -16,7 +16,9 @@ #include #include +#include "runtime-common/core/allocator/script-allocator.h" #include "runtime-common/core/runtime-core.h" +#include "runtime-common/core/std/containers.h" #include "runtime-common/stdlib/array/array-functions.h" #include "runtime-common/stdlib/string/string-functions.h" #include "runtime-light/coroutine/task.h" @@ -61,7 +63,7 @@ inline string f$basename(const string& path, const string& suffix = {}) noexcept inline Optional f$filesize(const string& filename) noexcept { struct stat stat {}; - if (auto errc{k2::stat({filename.c_str(), filename.size()}, std::addressof(stat))}; errc != k2::errno_ok) [[unlikely]] { + if (auto stat_result{k2::stat({filename.c_str(), filename.size()}, std::addressof(stat))}; !stat_result.has_value()) [[unlikely]] { return false; } return static_cast(stat.st_size); @@ -201,6 +203,57 @@ inline Optional f$file_get_contents(const string& stream) noexcept { return false; } +inline Optional> f$file(const string& name) noexcept { + struct stat stat_buf {}; + + auto expected_file{kphp::fs::file::open(name.c_str(), "r")}; + if (!expected_file.has_value()) { + return false; + } + if (!k2::stat(name.c_str(), std::addressof(stat_buf)).has_value()) { + return false; + } + if (!S_ISREG(stat_buf.st_mode)) { + kphp::log::warning("regular file expected as first argument in function file, \"{}\" is given", name.c_str()); + return false; + } + + const size_t size{static_cast(stat_buf.st_size)}; + if (size > string::max_size()) { + kphp::log::warning("file \"{}\" is too large", name.c_str()); + return false; + } + + kphp::stl::vector file_content; + file_content.resize(size); + { + auto file{std::move(*expected_file)}; + if (auto expected_read_result{file.read(file_content)}; !expected_read_result.has_value() || *expected_read_result < size) { + return false; + } + } + + array result; + int32_t prev{-1}; + for (size_t i{0}; i < size; i++) { + if (static_cast(file_content[i]) == '\n' || i + 1 == size) { + result.push_back(string{reinterpret_cast(file_content.data()) + prev + 1, static_cast(i - prev)}); + prev = i; + } + } + + return result; +} + +inline bool f$is_file(const string& name) noexcept { + struct stat stat_buf {}; + // TODO: the semantics in PHP are different: PHP expects stat + if (!k2::lstat(name.c_str(), std::addressof(stat_buf)).has_value()) { + return false; + } + return S_ISREG(stat_buf.st_mode); +} + inline Optional f$file_put_contents(const string& stream, const mixed& content_var, int64_t flags = 0) noexcept { string content{content_var.is_array() ? f$implode(string{}, content_var.to_array()) : content_var.to_string()}; diff --git a/runtime-light/stdlib/kml/kml-file-api.h b/runtime-light/stdlib/kml/kml-file-api.h index 7606fe36be..99d79ed6b7 100644 --- a/runtime-light/stdlib/kml/kml-file-api.h +++ b/runtime-light/stdlib/kml/kml-file-api.h @@ -120,8 +120,8 @@ class dir_traverser final : public kphp::kml::dir_traverser_interface {}, path -> {}", error_code, direntry_path); + if (auto expected_stat_result{k2::stat(direntry_path, std::addressof(stat))}; !expected_stat_result.has_value()) [[unlikely]] { + kphp::log::warning("[kml] failed to get stat: error code -> {}, path -> {}", expected_stat_result.error(), direntry_path); return std::nullopt; }