Merge remote-tracking branch 'origin/master' into feature/rebase
This commit is contained in:
@@ -25,6 +25,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
endif()
|
||||
|
||||
add_library(drharness_lib
|
||||
src/encoding.cpp
|
||||
src/tool_registry.cpp
|
||||
src/ollama_client.cpp
|
||||
src/harness.cpp
|
||||
|
||||
@@ -75,6 +75,7 @@ configuration.
|
||||
* `-s <name>`: Manually constrain the harness to a specific subset of skills (can be specified multiple times)
|
||||
* `-i <path>`: Image file to attach to the prompt (can be specified multiple times)
|
||||
* `-m <prompt>`: The user prompt to send to the agent.
|
||||
* `-P <file>`: Read the prompt from a file instead of the command line.
|
||||
* `-M <model>`: Override the Ollama model (e.g., `mistral`, `llama3`).
|
||||
|
||||
## ✍️ Editor Integration
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
char const* validate_utf8(char const* s);
|
||||
std::string ensure_utf8(char const* s);
|
||||
@@ -0,0 +1,99 @@
|
||||
#include "encoding.hpp"
|
||||
|
||||
char const* validate_utf8(char const* s)
|
||||
{
|
||||
auto b = reinterpret_cast<unsigned char const*>(s);
|
||||
|
||||
unsigned char c0 = b[0];
|
||||
|
||||
// ASCII
|
||||
if (c0 < 0x80)
|
||||
return s + 1;
|
||||
|
||||
// Continuation byte or invalid lead byte
|
||||
if (c0 < 0xC2)
|
||||
return nullptr;
|
||||
|
||||
// 2-byte sequence
|
||||
if (c0 <= 0xDF)
|
||||
{
|
||||
unsigned char c1 = b[1];
|
||||
if (c1 == '\0' || (c1 & 0xC0) != 0x80)
|
||||
return nullptr;
|
||||
return s + 2;
|
||||
}
|
||||
|
||||
// 3-byte sequence
|
||||
if (c0 <= 0xEF)
|
||||
{
|
||||
unsigned char c1 = b[1];
|
||||
unsigned char c2 = b[2];
|
||||
|
||||
if (c1 == '\0' || c2 == '\0')
|
||||
return nullptr;
|
||||
|
||||
if ((c1 & 0xC0) != 0x80 || (c2 & 0xC0) != 0x80)
|
||||
return nullptr;
|
||||
|
||||
// Overlong
|
||||
if (c0 == 0xE0 && c1 < 0xA0)
|
||||
return nullptr;
|
||||
|
||||
// Surrogates
|
||||
if (c0 == 0xED && c1 >= 0xA0)
|
||||
return nullptr;
|
||||
|
||||
return s + 3;
|
||||
}
|
||||
|
||||
// 4-byte sequence
|
||||
if (c0 <= 0xF4)
|
||||
{
|
||||
unsigned char c1 = b[1];
|
||||
unsigned char c2 = b[2];
|
||||
unsigned char c3 = b[3];
|
||||
|
||||
if (c1 == '\0' || c2 == '\0' || c3 == '\0')
|
||||
return nullptr;
|
||||
|
||||
if ((c1 & 0xC0) != 0x80 ||
|
||||
(c2 & 0xC0) != 0x80 ||
|
||||
(c3 & 0xC0) != 0x80)
|
||||
return nullptr;
|
||||
|
||||
// Overlong
|
||||
if (c0 == 0xF0 && c1 < 0x90)
|
||||
return nullptr;
|
||||
|
||||
// > U+10FFFF
|
||||
if (c0 == 0xF4 && c1 >= 0x90)
|
||||
return nullptr;
|
||||
|
||||
return s + 4;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string ensure_utf8(char const* s)
|
||||
{
|
||||
std::string ret = "";
|
||||
|
||||
while (*s)
|
||||
{
|
||||
char const* next = validate_utf8(s);
|
||||
if (next == 0)
|
||||
{
|
||||
ret += (0xc2+(((unsigned char)*s)>0xbf));
|
||||
ret += ((((unsigned char)*s)&0x3f))+0x80;
|
||||
s++;
|
||||
continue;
|
||||
}
|
||||
|
||||
ret += std::string(s, next);
|
||||
|
||||
s = next;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
+17
-3
@@ -157,6 +157,7 @@ int main(int argc, char** argv) {
|
||||
char const* file_to_open = nullptr;
|
||||
std::string auto_temp_file; // Path to auto-generated temp file (if any)
|
||||
char const* prompt = nullptr;
|
||||
char const* prompt_file = nullptr;
|
||||
bool start_new = false;
|
||||
bool list_models = false;
|
||||
char const* print_file = nullptr;
|
||||
@@ -195,6 +196,11 @@ int main(int argc, char** argv) {
|
||||
consume_arg();
|
||||
prompt = consume_arg();
|
||||
}
|
||||
else if (argv[0] == std::string("-P"))
|
||||
{
|
||||
consume_arg();
|
||||
prompt_file = consume_arg();
|
||||
}
|
||||
else if (argv[0] == std::string("-M"))
|
||||
{
|
||||
consume_arg();
|
||||
@@ -335,6 +341,7 @@ int main(int argc, char** argv) {
|
||||
<< " -i <path> Image file to attach (can be specified multiple times)\n"
|
||||
<< " -n Start a new conversation (auto-generates temp file if -F not specified)\n"
|
||||
<< " -m <prompt> Prompt message (required)\n"
|
||||
<< " -P <file> Read prompt from file\n"
|
||||
<< " -M <model> Ollama model name\n"
|
||||
<< " -f Open editor with full history (requires -F)\n"
|
||||
<< " -b Edit history (requires -F)\n"
|
||||
@@ -424,7 +431,7 @@ int main(int argc, char** argv) {
|
||||
// Build list of discovered paths for debug output
|
||||
std::vector<std::string> discovered_paths;
|
||||
for (const auto& skill : all_skills) {
|
||||
discovered_paths.push_back(std::filesystem::path(skill.directory) / "SKILL.md");
|
||||
discovered_paths.push_back((std::filesystem::path(skill.directory) / "SKILL.md").string());
|
||||
}
|
||||
|
||||
// Build enabled skills list based on filters
|
||||
@@ -648,12 +655,19 @@ int main(int argc, char** argv) {
|
||||
harness = Harness(create_client(use_debug), master_registery, state_data);
|
||||
}
|
||||
|
||||
std::cerr << "drharness | model: " << cfg.model
|
||||
std::cerr << "drharness v0.3 | model: " << cfg.model
|
||||
<< " | " << cfg.base_url << "\n\n";
|
||||
|
||||
std::string final_prompt;
|
||||
|
||||
if (prompt) {
|
||||
if (prompt_file) {
|
||||
std::ifstream pf(prompt_file);
|
||||
if (!pf.is_open()) {
|
||||
std::cerr << "Error: Cannot open prompt file " << prompt_file << "\n";
|
||||
return 1;
|
||||
}
|
||||
final_prompt = std::string((std::istreambuf_iterator<char>(pf)), std::istreambuf_iterator<char>());
|
||||
} else if (prompt) {
|
||||
final_prompt = prompt;
|
||||
} else {
|
||||
final_prompt = expect_or_exit(run_editor(harness.serialize(), full_history));
|
||||
|
||||
+12
-4
@@ -132,6 +132,10 @@ std::expected<Message, std::string> OllamaClient::chat(const std::vector<Message
|
||||
std::string new_packet = sum.substr(0,sum.find("\n\n"));
|
||||
sum = sum.substr(sum.find("\n\n")+2);
|
||||
|
||||
if (!new_packet.starts_with("data: ")){
|
||||
continue;
|
||||
}
|
||||
|
||||
//std::cerr << new_packet << std::endl;
|
||||
auto j_result = parse_json_safely(new_packet.substr(6));
|
||||
if (!j_result)
|
||||
@@ -163,8 +167,10 @@ std::expected<Message, std::string> OllamaClient::chat(const std::vector<Message
|
||||
append_to_variant_content(total.content, partial_message->get_content_string());
|
||||
total.reasoning_content += partial_message->reasoning_content;
|
||||
total.reasoning += partial_message->reasoning;
|
||||
total.role += partial_message->role;
|
||||
total.tool_call_id += partial_message->tool_call_id;
|
||||
if (partial_message->role != "")
|
||||
total.role = partial_message->role;
|
||||
if (partial_message->tool_call_id != "")
|
||||
total.tool_call_id = partial_message->tool_call_id;
|
||||
|
||||
std::string to_write = partial_message->get_content_string()
|
||||
+ partial_message->reasoning_content
|
||||
@@ -184,8 +190,10 @@ std::expected<Message, std::string> OllamaClient::chat(const std::vector<Message
|
||||
|
||||
total.tool_calls[x.index].function.arguments += x.function.arguments;
|
||||
total.tool_calls[x.index].function.name += x.function.name;
|
||||
total.tool_calls[x.index].id += x.id;
|
||||
total.tool_calls[x.index].type += x.type;
|
||||
if (x.id != "")
|
||||
total.tool_calls[x.index].id = x.id;
|
||||
if (x.type != "")
|
||||
total.tool_calls[x.index].type = x.type;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+27
-1
@@ -1,4 +1,5 @@
|
||||
#include "tool_registry.hpp"
|
||||
#include "encoding.hpp"
|
||||
|
||||
std::expected<void, std::string> ToolRegistry::register_tool(Tool tool) {
|
||||
if (tools_.count(tool.name))
|
||||
@@ -24,12 +25,37 @@ std::vector<Tool> ToolRegistry::tools() const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
// helper type for the visitor
|
||||
template<class... Ts>
|
||||
struct overloads : Ts... { using Ts::operator()...; };
|
||||
|
||||
Content ToolRegistry::invoke(const std::string& name,
|
||||
const nlohmann::json& args) const {
|
||||
auto it = tools_.find(name);
|
||||
if (it == tools_.end())
|
||||
return "Error: unknown tool: " + name;
|
||||
return it->second.execute(args);
|
||||
auto ret = it->second.execute(args);
|
||||
|
||||
ret = std::visit(overloads{
|
||||
[](std::string const& x) -> Content { return ensure_utf8(x.c_str()); },
|
||||
[](std::vector<ContentPart> const& x) -> Content {
|
||||
std::vector<ContentPart> ret = x;
|
||||
|
||||
for (auto& i : ret)
|
||||
{
|
||||
if (i.text)
|
||||
{
|
||||
std::string valid = ensure_utf8(i.text->c_str());
|
||||
i.text = valid;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
}, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::expected<ToolRegistry, std::string> ToolRegistry::create_subregistry(std::vector<std::string> const& names) const
|
||||
|
||||
@@ -24,6 +24,7 @@ add_dr_test(test_tool_read_multi_modal)
|
||||
# Add feature validation tests
|
||||
add_dr_test(test_tool_restrictions)
|
||||
add_dr_test(test_n_flag)
|
||||
add_dr_test(test_prompt_file)
|
||||
add_dr_test(test_load_chat)
|
||||
add_dr_test(process_reasoning)
|
||||
add_dr_test(test_skill_scanner)
|
||||
@@ -156,3 +157,6 @@ add_dr_test(test_multimodal_roundtrip)
|
||||
|
||||
# Add print_history multimodal tests
|
||||
add_dr_test(test_print_history_multimodal)
|
||||
|
||||
# Add encoding tests
|
||||
add_dr_test(test_encoding)
|
||||
|
||||
Executable
+130
@@ -0,0 +1,130 @@
|
||||
#!/bin/bash
|
||||
# test_encoding.sh - Tests for UTF-8 encoding validation and conversion
|
||||
# All tests exercise encoding through the read_file tool
|
||||
|
||||
# Test 1: Valid ASCII passes through unchanged
|
||||
printf 'Hello' > test_ascii.txt
|
||||
tool_call read_file '{"path":"test_ascii.txt"}' >> mock1.txt
|
||||
be_done 'done' >> mock1.txt
|
||||
"${DRHARNESS}" -F chat1.json -n -m "test" -t read_file -d < "mock1.txt" >/dev/null 2>&1
|
||||
RESULT=$(dr_result chat1.json 2)
|
||||
if [ "${RESULT}" != "Hello" ]; then
|
||||
echo "FAIL: ASCII passthrough - expected 'Hello', got '${RESULT}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 2: Valid UTF-8 2-byte sequence (é = c3a9) passes through unchanged
|
||||
printf '\xc3\xa9' > test_utf8_2byte.txt
|
||||
tool_call read_file '{"path":"test_utf8_2byte.txt"}' >> mock2.txt
|
||||
be_done 'done' >> mock2.txt
|
||||
"${DRHARNESS}" -F chat2.json -n -m "test" -t read_file -d < "mock2.txt" >/dev/null 2>&1
|
||||
RESULT=$(dr_result chat2.json 2)
|
||||
RESULT_HEX=$(od -An -tx1 -v <<< "${RESULT}" | tr -d ' \n' | sed 's/0a$//')
|
||||
if [ "${RESULT_HEX}" != "c3a9" ]; then
|
||||
echo "FAIL: valid UTF-8 2-byte - expected 'c3a9', got '${RESULT_HEX}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 3: Valid UTF-8 3-byte sequence (中 = e4b8ad) passes through unchanged
|
||||
printf '\xe4\xb8\xad' > test_utf8_3byte.txt
|
||||
tool_call read_file '{"path":"test_utf8_3byte.txt"}' >> mock3.txt
|
||||
be_done 'done' >> mock3.txt
|
||||
"${DRHARNESS}" -F chat3.json -n -m "test" -t read_file -d < "mock3.txt" >/dev/null 2>&1
|
||||
RESULT=$(dr_result chat3.json 2)
|
||||
RESULT_HEX=$(od -An -tx1 -v <<< "${RESULT}" | tr -d ' \n' | sed 's/0a$//')
|
||||
if [ "${RESULT_HEX}" != "e4b8ad" ]; then
|
||||
echo "FAIL: valid UTF-8 3-byte - expected 'e4b8ad', got '${RESULT_HEX}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 4: Valid UTF-8 4-byte sequence (😀 = f09f9880) passes through unchanged
|
||||
printf '\xf0\x9f\x98\x80' > test_utf8_4byte.txt
|
||||
tool_call read_file '{"path":"test_utf8_4byte.txt"}' >> mock4.txt
|
||||
be_done 'done' >> mock4.txt
|
||||
"${DRHARNESS}" -F chat4.json -n -m "test" -t read_file -d < "mock4.txt" >/dev/null 2>&1
|
||||
RESULT=$(dr_result chat4.json 2)
|
||||
RESULT_HEX=$(od -An -tx1 -v <<< "${RESULT}" | tr -d ' \n' | sed 's/0a$//')
|
||||
if [ "${RESULT_HEX}" != "f09f9880" ]; then
|
||||
echo "FAIL: valid UTF-8 4-byte - expected 'f09f9880', got '${RESULT_HEX}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 5: Latin-1 byte 0xe9 (é in Latin-1) should be converted to UTF-8 c3a9
|
||||
printf '\xe9' > test_latin1_e9.txt
|
||||
tool_call read_file '{"path":"test_latin1_e9.txt"}' >> mock5.txt
|
||||
be_done 'done' >> mock5.txt
|
||||
"${DRHARNESS}" -F chat5.json -n -m "test" -t read_file -d < "mock5.txt" >/dev/null 2>&1
|
||||
RESULT=$(dr_result chat5.json 2)
|
||||
RESULT_HEX=$(od -An -tx1 -v <<< "${RESULT}" | tr -d ' \n' | sed 's/0a$//')
|
||||
if [ "${RESULT_HEX}" != "c3a9" ]; then
|
||||
echo "FAIL: Latin-1 e9 to UTF-8 - expected 'c3a9', got '${RESULT_HEX}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 6: Multiple Latin-1 bytes (café in Latin-1 = 636166e9)
|
||||
printf '\x63\x61\x66\xe9' > test_latin1_cafe.txt
|
||||
tool_call read_file '{"path":"test_latin1_cafe.txt"}' >> mock6.txt
|
||||
be_done 'done' >> mock6.txt
|
||||
"${DRHARNESS}" -F chat6.json -n -m "test" -t read_file -d < "mock6.txt" >/dev/null 2>&1
|
||||
RESULT=$(dr_result chat6.json 2)
|
||||
RESULT_HEX=$(od -An -tx1 -v <<< "${RESULT}" | tr -d ' \n' | sed 's/0a$//')
|
||||
if [ "${RESULT_HEX}" != "636166c3a9" ]; then
|
||||
echo "FAIL: Latin-1 café to UTF-8 - expected '636166c3a9', got '${RESULT_HEX}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 7: Byte 0x80 (isolated continuation byte) should be converted to c280
|
||||
printf '\x80' > test_cont_byte.txt
|
||||
tool_call read_file '{"path":"test_cont_byte.txt"}' >> mock7.txt
|
||||
be_done 'done' >> mock7.txt
|
||||
"${DRHARNESS}" -F chat7.json -n -m "test" -t read_file -d < "mock7.txt" >/dev/null 2>&1
|
||||
RESULT=$(dr_result chat7.json 2)
|
||||
RESULT_HEX=$(od -An -tx1 -v <<< "${RESULT}" | tr -d ' \n' | sed 's/0a$//')
|
||||
if [ "${RESULT_HEX}" != "c280" ]; then
|
||||
echo "FAIL: continuation byte 80 - expected 'c280', got '${RESULT_HEX}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 8: Mixed valid UTF-8 and invalid bytes
|
||||
# "A" + invalid 0xe9 + "B" = 41e942 -> expected: 41c3a942
|
||||
printf '\x41\xe9\x42' > test_mixed.txt
|
||||
tool_call read_file '{"path":"test_mixed.txt"}' >> mock8.txt
|
||||
be_done 'done' >> mock8.txt
|
||||
"${DRHARNESS}" -F chat8.json -n -m "test" -t read_file -d < "mock8.txt" >/dev/null 2>&1
|
||||
RESULT=$(dr_result chat8.json 2)
|
||||
RESULT_HEX=$(od -An -tx1 -v <<< "${RESULT}" | tr -d ' \n' | sed 's/0a$//')
|
||||
if [ "${RESULT_HEX}" != "41c3a942" ]; then
|
||||
echo "FAIL: mixed valid/invalid - expected '41c3a942', got '${RESULT_HEX}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 9: Empty file returns "(empty file)"
|
||||
printf '' > test_empty.txt
|
||||
tool_call read_file '{"path":"test_empty.txt"}' >> mock9.txt
|
||||
be_done 'done' >> mock9.txt
|
||||
"${DRHARNESS}" -F chat9.json -n -m "test" -t read_file -d < "mock9.txt" >/dev/null 2>&1
|
||||
RESULT=$(dr_result chat9.json 2)
|
||||
if [ "${RESULT}" != "(empty file)" ]; then
|
||||
echo "FAIL: empty file - expected '(empty file)', got '${RESULT}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 10: File with newline and non-UTF8 content
|
||||
printf 'line1\n\xe9\nline3\n' > test_multiline.txt
|
||||
tool_call read_file '{"path":"test_multiline.txt"}' >> mock10.txt
|
||||
be_done 'done' >> mock10.txt
|
||||
"${DRHARNESS}" -F chat10.json -n -m "test" -t read_file -d < "mock10.txt" >/dev/null 2>&1
|
||||
RESULT=$(dr_result chat10.json 2)
|
||||
# Check that the file content was properly converted
|
||||
if ! echo "${RESULT}" | grep -q "line1"; then
|
||||
echo "FAIL: multiline - missing 'line1' in result"
|
||||
exit 1
|
||||
fi
|
||||
# Verify the é is properly UTF-8 encoded
|
||||
RESULT_HEX=$(od -An -tx1 -v <<< "${RESULT}" | tr -d ' \n' | sed 's/0a$//')
|
||||
if ! echo "${RESULT_HEX}" | grep -q "c3a9"; then
|
||||
echo "FAIL: multiline - expected UTF-8 é (c3a9) in result, got '${RESULT_HEX}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "PASS: encoding tests"
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
# test_prompt_file.sh - Validates the -P flag reads prompt from file
|
||||
|
||||
# Test 1: Basic prompt file reading
|
||||
echo "This is my prompt from a file" > prompt.txt
|
||||
be_done 'Response received' | "${DRHARNESS}" -F chat.json -n -P prompt.txt -d
|
||||
|
||||
# Verify the prompt from the file was stored in the chat history
|
||||
STORED_PROMPT=$(dr_result chat.json 0)
|
||||
if [ "${STORED_PROMPT}" != "This is my prompt from a file" ]; then
|
||||
echo "FAIL: prompt_file - prompt not read correctly"
|
||||
echo "Expected: 'This is my prompt from a file'"
|
||||
echo "Got: '${STORED_PROMPT}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 2: Multi-line prompt file
|
||||
cat > multiline_prompt.txt << 'PROMPT'
|
||||
Line 1
|
||||
Line 2
|
||||
Line 3
|
||||
PROMPT
|
||||
|
||||
be_done 'Multi-line response' | "${DRHARNESS}" -F chat2.json -n -P multiline_prompt.txt -d
|
||||
|
||||
# Verify multi-line prompt was stored correctly
|
||||
STORED_PROMPT=$(dr_result chat2.json 0)
|
||||
EXPECTED_MULTILINE="Line 1
|
||||
Line 2
|
||||
Line 3"
|
||||
|
||||
if [ "${STORED_PROMPT}" != "${EXPECTED_MULTILINE}" ]; then
|
||||
echo "FAIL: prompt_file multiline - prompt not read correctly"
|
||||
echo "Expected:"
|
||||
echo "${EXPECTED_MULTILINE}"
|
||||
echo "Got:"
|
||||
echo "${STORED_PROMPT}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 3: Non-existent file should fail
|
||||
if "${DRHARNESS}" -F chat3.json -n -P nonexistent.txt -d < /dev/null 2>stderr_output; then
|
||||
echo "FAIL: prompt_file nonexistent - should have failed"
|
||||
cat stderr_output
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "PASS: prompt_file"
|
||||
@@ -40,3 +40,14 @@ be_done 'done' >> mock_responses3.txt
|
||||
timeout 5s "${DRHARNESS}" -F chat3.json -n -m "This is a test" -t run_dangerous_shell -d < "mock_responses3.txt"
|
||||
|
||||
echo "PASS: tool_shell"
|
||||
|
||||
|
||||
seq 1 1044 > long_file
|
||||
tool_call run_dangerous_shell '{"command":"cat long_file"}' >> mock_responses4.txt
|
||||
be_done 'done' >> mock_responses4.txt
|
||||
"${DRHARNESS}" -F chat4.json -n -m "This is a test" -t run_dangerous_shell -d < "mock_responses4.txt"
|
||||
dr_result chat4.json 2 > long_file_from_harness
|
||||
echo "" >> long_file
|
||||
|
||||
diff -u long_file long_file_from_harness
|
||||
|
||||
|
||||
+17
-10
@@ -115,22 +115,29 @@ Tool make_shell_tool(bool safe) {
|
||||
if (pfd.revents & POLLHUP) {
|
||||
// Data might still be available in buffer!
|
||||
if (pfd.revents & POLLIN) {
|
||||
// Read remaining data
|
||||
res = read(pfd.fd, buf.data(), buf.size() - 1);
|
||||
if (res > 0) output += std::string(buf.data(), res);
|
||||
// Read ALL remaining data in a loop
|
||||
while (true) {
|
||||
res = read(pfd.fd, buf.data(), buf.size());
|
||||
if (res <= 0) break; // EOF or error
|
||||
output += std::string(buf.data(), res);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// 3. Read data if available
|
||||
// 3. Read data if available - keep reading until no more data
|
||||
if (pfd.revents & POLLIN) {
|
||||
res = read(pfd.fd, buf.data(), buf.size() - 1);
|
||||
if (res < 0) { // Error
|
||||
break;
|
||||
} else if (res == 0) { // EOF
|
||||
break;
|
||||
while (true) {
|
||||
res = read(pfd.fd, buf.data(), buf.size());
|
||||
if (res < 0) {
|
||||
if (errno == EINTR) continue; // Restart if interrupted by signal
|
||||
break; // Error
|
||||
} else if (res == 0) { // EOF
|
||||
break;
|
||||
}
|
||||
output += std::string(buf.data(), res);
|
||||
}
|
||||
output += std::string(buf.data(), res);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user