resolve shell from PATH (fallback /bin/sh); rewrite write tool with lineOffset/byteOffset + delLines/delBytes

This commit is contained in:
Luxferre
2026-10-14 10:58:08 +03:00
parent 34f1da34ca
commit 6e80a3e8fc
5 changed files with 132 additions and 56 deletions
+89 -9
View File
@@ -15,6 +15,34 @@
#include <vector>
/* ----------------------------- shell ---------------------------------- */
/* Resolve the Bourne-again shell from the system PATH. Returns a static
* buffer holding the absolute path to `bash`, or "/bin/sh" when bash cannot
* be located anywhere on PATH. Never returns a hardcoded bash path. */
static const char *resolve_shell() {
static char buf[4096];
const char *path = getenv("PATH");
if (path && *path) {
std::string p(path);
size_t start = 0;
while (start <= p.size()) {
size_t end = p.find(':', start);
std::string dir = (end == std::string::npos) ? p.substr(start)
: p.substr(start, end - start);
if (!dir.empty() && dir.back() != '/') dir += '/';
std::string cand = dir + "bash";
struct stat st;
if (stat(cand.c_str(), &st) == 0 && S_ISREG(st.st_mode) &&
(st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0) {
snprintf(buf, sizeof buf, "%s", cand.c_str());
return buf;
}
if (end == std::string::npos) break;
start = end + 1;
}
}
return "/bin/sh";
}
std::string shell_with_workdir(std::string_view cmd, std::string_view workdir,
int timeout, volatile int *interrupted) {
std::string fcmd = filter_text(cmd);
@@ -35,7 +63,9 @@ std::string shell_with_workdir(std::string_view cmd, std::string_view workdir,
close(pipefd[0]);
close(pipefd[1]);
if (!workdir.empty()) chdir(std::string(workdir).c_str());
execl("/bin/bash", "bash", "-c", fcmd.c_str(), (char *)nullptr);
const char *sh = resolve_shell();
const char *shname = (strrchr(sh, '/') ? strrchr(sh, '/') + 1 : sh);
execl(sh, shname, "-c", fcmd.c_str(), (char *)nullptr);
_exit(127);
}
close(pipefd[1]);
@@ -171,7 +201,38 @@ std::string read_file_or_dir(std::string_view path, int offset, int limit, int *
}
/* ----------------------------- write ---------------------------------- */
std::string write_file(std::string_view path, int offset, int del_bytes,
/* Convert a 1-indexed line number to the byte offset of the start of that
* line within `data`. Line 1 begins at byte 0; a line number past the end of
* the file maps to data.size() (i.e. append at end of file). */
static size_t line_to_byte_offset(std::string_view data, long line_no) {
if (line_no <= 1) return 0;
size_t pos = 0;
long cur = 1;
while (pos < data.size()) {
if (data[pos] == '\n') {
cur++;
if (cur == line_no) return pos + 1;
}
pos++;
}
return data.size();
}
/* Byte length of `nlines` complete lines beginning at byte offset `start`.
* Counts the newlines terminating each line and stops at end of data. */
static size_t lines_to_byte_len(std::string_view data, size_t start, long nlines) {
if (nlines <= 0 || start > data.size()) return 0;
size_t pos = start;
long consumed = 0;
while (pos < data.size() && consumed < nlines) {
if (data[pos] == '\n') consumed++;
pos++;
}
return pos - start;
}
std::string write_file(std::string_view path, long line_offset, long byte_offset,
long del_lines, long del_bytes,
std::string_view content, int *err) {
*err = 0;
std::string p(path);
@@ -179,8 +240,6 @@ std::string write_file(std::string_view path, int offset, int del_bytes,
while (s < p.size() && (p[s] == ' ' || p[s] == '\t')) s++;
p = p.substr(s);
if (p.empty()) { *err = 1; return "[tool error: path is required]"; }
if (offset < 0) offset = 0;
if (del_bytes < 0) del_bytes = 0;
std::string data;
struct stat st;
@@ -198,11 +257,32 @@ std::string write_file(std::string_view path, int offset, int del_bytes,
if (slash != std::string::npos) mkdir_p(std::string_view(p).substr(0, slash));
else mkdir_p(".");
}
if ((size_t)offset > data.size()) {
data.resize((size_t)offset, '\0');
/* Resolve insertion byte offset. Line-based takes precedence over byte-based
* ("first and foremost line-based writing"); if neither is given the whole
* file is overwritten by the caller. */
size_t ins_off = 0;
if (line_offset >= 0) {
ins_off = line_to_byte_offset(data, line_offset);
} else if (byte_offset >= 0) {
ins_off = (size_t)byte_offset;
if (ins_off > data.size()) data.resize(ins_off, '\0');
} else {
ins_off = 0;
}
size_t end_del = (size_t)offset + (size_t)del_bytes;
size_t prefix_len = (size_t)offset;
/* Resolve deletion span. Line-based takes precedence over byte-based. */
size_t del_len = 0;
if (del_lines >= 0) {
del_len = lines_to_byte_len(data, ins_off, del_lines);
} else if (del_bytes >= 0) {
del_len = (size_t)del_bytes;
if (ins_off + del_len > data.size())
del_len = (ins_off < data.size()) ? (data.size() - ins_off) : 0;
}
size_t end_del = ins_off + del_len;
size_t prefix_len = ins_off;
size_t suffix_len = (end_del < data.size()) ? (data.size() - end_del) : 0;
size_t clen = content.size();
size_t newlen = prefix_len + clen + suffix_len;
@@ -220,7 +300,7 @@ std::string write_file(std::string_view path, int offset, int del_bytes,
/* ----------------------------- offload -------------------------------- */
std::string offload_tool_result(std::string_view res, int max_res,
std::vector<std::string> &tmps) {
std::vector<std::string> &tmps) {
if (max_res <= 0) max_res = 65536;
if (res.size() <= (size_t)max_res) return std::string(res);
std::string dir = tool_res_dir();