Skip to content

Commit 04de6cc

Browse files
authored
[k2] add fgets (#1519)
1 parent b61435f commit 04de6cc

File tree

7 files changed

+143
-2
lines changed

7 files changed

+143
-2
lines changed

builtin-functions/kphp-light/stdlib/file-functions.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ function file ($name ::: string) ::: string[] | false;
4949

5050
function is_file ($name ::: string) ::: bool;
5151

52+
function fgets ($stream ::: mixed, $length ::: int = -1) ::: string | false;
53+
5254
// === UNSUPPORTED ===
5355
/** @kphp-extern-func-info stub generation-required */
5456
function chmod ($name ::: string, $mode ::: int) ::: bool;
@@ -92,8 +94,6 @@ function fgetc ($stream ::: mixed) ::: string | false;
9294
/** @kphp-extern-func-info stub generation-required */
9395
function fpassthru ($stream ::: mixed) ::: int | false;
9496
/** @kphp-extern-func-info stub generation-required */
95-
function fgets ($stream ::: mixed, $length ::: int = -1) ::: string | false;
96-
/** @kphp-extern-func-info stub generation-required */
9797
function feof ($stream ::: mixed) ::: bool;
9898

9999
/** @kphp-extern-func-info stub generation-required */

runtime-light/k2-platform/k2-api.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,10 @@ inline size_t pread(k2::descriptor descriptor, std::span<std::byte> buffer, uint
216216
return k2_pread(descriptor, buffer.size(), static_cast<void*>(buffer.data()), offset);
217217
}
218218

219+
inline size_t fgets(k2::descriptor descriptor, std::span<std::byte> buffer) noexcept {
220+
return k2_fgets(descriptor, buffer.size(), static_cast<void*>(buffer.data()));
221+
}
222+
219223
inline void* mmap(k2::descriptor* md, void* addr, size_t length, int32_t prot, int32_t flags, k2::descriptor fd, uint64_t offset) noexcept {
220224
return k2_mmap(md, addr, length, prot, flags, fd, offset);
221225
}

runtime-light/k2-platform/k2-header.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,13 @@ size_t k2_read(uint64_t stream_d, size_t buf_len, void* buf);
356356
*/
357357
size_t k2_pread(uint64_t stream_d, size_t buf_len, void* buf, uint64_t offset);
358358

359+
/**
360+
* equivalent to libc's `fgets` function
361+
*
362+
* @return `0` if EOF is reached or an error occurs. total number of bytes read otherwise
363+
*/
364+
size_t k2_fgets(uint64_t stream_d, size_t buf_len, void* buf);
365+
359366
/**
360367
* Semantically equivalent to libc's `mmap` function.
361368
*

runtime-light/stdlib/file/file-system-functions.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,3 +389,40 @@ mixed f$getimagesize(const string& name) noexcept {
389389

390390
return result;
391391
}
392+
393+
// don't forget to add "interruptible" to file-functions.txt when this function becomes a coroutine
394+
Optional<string> f$fgets(const resource& stream, int64_t length) noexcept {
395+
if (length == 0) {
396+
return false;
397+
}
398+
399+
auto file_resource{from_mixed<class_instance<kphp::fs::file>>(stream, {})};
400+
if (file_resource.is_null()) {
401+
return false;
402+
}
403+
kphp::log::assertion(file_resource.get() != nullptr);
404+
const kphp::fs::file& file{*file_resource.get()};
405+
406+
if (length < 0) {
407+
struct stat st {};
408+
kphp::log::assertion(k2::fstat(file.descriptor(), std::addressof(st)).has_value());
409+
if (st.st_size <= 0) {
410+
return false;
411+
}
412+
length = st.st_size + 1;
413+
}
414+
415+
if (length > string::max_size()) {
416+
kphp::log::warning("parameter length in function fgets mustn't be greater than string::max_size(). length = {}, string::max_size() = {}", length,
417+
string::max_size());
418+
return false;
419+
}
420+
string res{static_cast<string::size_type>(length), false};
421+
auto read_res{k2::fgets(file.descriptor(), std::as_writable_bytes(std::span<char>{res.buffer(), res.size()}))};
422+
423+
if (read_res == 0) {
424+
return false;
425+
}
426+
res.shrink(static_cast<string::size_type>(read_res));
427+
return res;
428+
}

runtime-light/stdlib/file/file-system-functions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,6 @@ inline Optional<int64_t> f$file_put_contents(const string& stream, const mixed&
274274
}
275275

276276
mixed f$getimagesize(const string& name) noexcept;
277+
278+
// don't forget to add "interruptible" to file-functions.txt when this function becomes a coroutine
279+
Optional<string> f$fgets(const resource& stream, int64_t length = -1) noexcept;

runtime-light/stdlib/file/resource.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ class file : public sync_resource {
9393

9494
static auto open(std::string_view path, std::string_view mode) noexcept -> std::expected<file, int32_t>;
9595

96+
auto descriptor() const noexcept -> k2::descriptor;
97+
9698
auto write(std::span<const std::byte> buf) noexcept -> std::expected<size_t, int32_t> override;
9799
auto read(std::span<std::byte> buf) noexcept -> std::expected<size_t, int32_t> override;
98100
auto pread(std::span<std::byte> buf, uint64_t offset) noexcept -> std::expected<size_t, int32_t> override;
@@ -110,6 +112,10 @@ inline auto file::open(std::string_view path, std::string_view mode) noexcept ->
110112
return {file{descriptor}};
111113
}
112114

115+
inline auto file::descriptor() const noexcept -> k2::descriptor {
116+
return m_descriptor;
117+
}
118+
113119
inline auto file::write(std::span<const std::byte> buf) noexcept -> std::expected<size_t, int32_t> {
114120
if (m_descriptor == k2::INVALID_PLATFORM_DESCRIPTOR) [[unlikely]] {
115121
return std::unexpected{k2::errno_enodev};

tests/phpt/dl/473_fgets.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
@ok
2+
<?php
3+
function init() {
4+
$stream = fopen(__DIR__.'/fgets.txt', 'w');
5+
fwrite ($stream, "123\n");
6+
fwrite ($stream, "gucci\n");
7+
fwrite ($stream, "\n");
8+
fwrite ($stream, "php < kphp\n");
9+
fwrite ($stream, "bang");
10+
fclose ($stream);
11+
}
12+
13+
function test_fgets() {
14+
$stream = fopen(__DIR__ . '/fgets.txt', 'r');
15+
16+
var_dump (fgets ($stream)); // line = "123\n"
17+
18+
var_dump (fgets ($stream, 100)); // `length` greater than length of the line. line = "gucci\n"
19+
20+
var_dump (fgets ($stream)); // line = "\n"
21+
22+
var_dump (fgets ($stream, 4)); // `length` less than length of the line. line = "php < kphp\n"
23+
var_dump (fgets ($stream, 1)); // always false
24+
var_dump (fgets ($stream)); // rest of the line
25+
26+
var_dump (fgets ($stream)); // last line. line = "bang"
27+
28+
var_dump (fgets ($stream)); // eof
29+
var_dump (fgets ($stream)); // eof
30+
31+
fclose($stream);
32+
}
33+
34+
function test_fgets_edge_cases() {
35+
$stream = fopen(__DIR__ . '/fgets.txt', 'r');
36+
37+
var_dump (fgets ($stream, 4)); // `length` = line.len() (without '\n'). line = "123\n"
38+
var_dump (fgets ($stream)); // rest of the line
39+
40+
var_dump (fgets ($stream, 7)); // `length` = line.len() + 1 (with '\n'). line = "gucci\n"
41+
42+
// skip all lines except the last one
43+
var_dump (fgets ($stream));
44+
var_dump (fgets ($stream));
45+
46+
var_dump (fgets ($stream, 4)); // last line with `length` = line.len(). line = "bang"
47+
48+
fclose($stream);
49+
}
50+
51+
function test_fgets_edge_case2() {
52+
$stream = fopen(__DIR__ . '/fgets.txt', 'r');
53+
54+
// skip all lines except the last one
55+
var_dump (fgets ($stream));
56+
var_dump (fgets ($stream));
57+
var_dump (fgets ($stream));
58+
var_dump (fgets ($stream));
59+
60+
var_dump (fgets ($stream, 5)); // last line with `length` = line.len() + 1. line = "bang"
61+
62+
fclose($stream);
63+
}
64+
65+
function test_fgets_mixed() {
66+
$stream = fopen(__DIR__ . '/fgets.txt', 'r+');
67+
68+
var_dump (fgets ($stream)); // 123\n
69+
var_dump (fread ($stream, 3)); // guc
70+
var_dump (fgets ($stream)); // ci\n
71+
var_dump (fwrite ($stream, "new string")); // \nphp < kphp\n -> new stringp\n
72+
var_dump (fgets ($stream)); // p\n
73+
var_dump (fread ($stream, 2)); // ba
74+
var_dump (fwrite ($stream, "new string 2")); // ng -> new string 2
75+
var_dump (fgets ($stream)); // eof
76+
77+
fclose($stream);
78+
}
79+
80+
init();
81+
test_fgets();
82+
test_fgets_edge_cases();
83+
test_fgets_edge_case2();
84+
test_fgets_mixed();

0 commit comments

Comments
 (0)