Skip to content
9 changes: 4 additions & 5 deletions builtin-functions/kphp-light/stdlib/file-functions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,25 +41,24 @@ 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 */
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;
Expand Down
13 changes: 10 additions & 3 deletions runtime-light/k2-platform/k2-api.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<void, int32_t> 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<void, int32_t> 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;
Expand Down
23 changes: 23 additions & 0 deletions runtime-light/k2-platform/k2-header.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions runtime-light/state/component-state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<char, decltype(std::addressof(kphp::memory::script::free))>{
Expand Down
55 changes: 54 additions & 1 deletion runtime-light/stdlib/file/file-system-functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
#include <unistd.h>
#include <utility>

#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"
Expand Down Expand Up @@ -61,7 +63,7 @@ inline string f$basename(const string& path, const string& suffix = {}) noexcept

inline Optional<int64_t> 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<int64_t>(stat.st_size);
Expand Down Expand Up @@ -201,6 +203,57 @@ inline Optional<string> f$file_get_contents(const string& stream) noexcept {
return false;
}

inline Optional<array<string>> 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<size_t>(stat_buf.st_size)};
if (size > string::max_size()) {
kphp::log::warning("file \"{}\" is too large", name.c_str());
return false;
}

kphp::stl::vector<std::byte, kphp::memory::script_allocator> 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<string> result;
int32_t prev{-1};
for (size_t i{0}; i < size; i++) {
if (static_cast<char>(file_content[i]) == '\n' || i + 1 == size) {
result.push_back(string{reinterpret_cast<char*>(file_content.data()) + prev + 1, static_cast<string::size_type>(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<int64_t> 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()};

Expand Down
4 changes: 2 additions & 2 deletions runtime-light/stdlib/kml/kml-file-api.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ class dir_traverser final : public kphp::kml::dir_traverser_interface<dir_traver
}

struct stat stat {};
if (auto error_code{k2::stat(direntry_path, std::addressof(stat))}; error_code != k2::errno_ok) [[unlikely]] {
kphp::log::warning("[kml] failed to get stat: error code -> {}, 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;
}

Expand Down
Loading