Optimize startup, HTTP/SSE streaming, and fix macOS libedit bindings
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
/* tools.cpp — bash/read/write tools and oversized result offloading. */
|
||||
#include "bantam.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
@@ -20,6 +21,8 @@
|
||||
* be located anywhere on PATH. Never returns a hardcoded bash path. */
|
||||
static const char *resolve_shell() {
|
||||
static char buf[4096];
|
||||
static const char *cached = nullptr;
|
||||
if (cached) return cached;
|
||||
const char *path = getenv("PATH");
|
||||
if (path && *path) {
|
||||
std::string p(path);
|
||||
@@ -34,19 +37,22 @@ static const char *resolve_shell() {
|
||||
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;
|
||||
cached = buf;
|
||||
return cached;
|
||||
}
|
||||
if (end == std::string::npos) break;
|
||||
start = end + 1;
|
||||
}
|
||||
}
|
||||
return "/bin/sh";
|
||||
cached = "/bin/sh";
|
||||
return cached;
|
||||
}
|
||||
|
||||
std::string shell_with_workdir(std::string_view cmd, std::string_view workdir,
|
||||
int timeout, volatile int *interrupted) {
|
||||
std::string fcmd = filter_text(cmd);
|
||||
if (timeout <= 0) timeout = 120;
|
||||
const char *sh = resolve_shell();
|
||||
|
||||
int pipefd[2];
|
||||
if (pipe(pipefd) != 0) return "[tool error: pipe failed]";
|
||||
@@ -57,13 +63,14 @@ std::string shell_with_workdir(std::string_view cmd, std::string_view workdir,
|
||||
return "[tool error: fork failed]";
|
||||
}
|
||||
if (pid == 0) {
|
||||
/* child */
|
||||
/* child: create a dedicated process group so timeout/SIGINT kills the
|
||||
* entire process tree spawned by the shell. */
|
||||
setpgid(0, 0);
|
||||
dup2(pipefd[1], STDOUT_FILENO);
|
||||
dup2(pipefd[1], STDERR_FILENO);
|
||||
close(pipefd[0]);
|
||||
close(pipefd[1]);
|
||||
if (!workdir.empty()) chdir(std::string(workdir).c_str());
|
||||
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);
|
||||
@@ -81,9 +88,11 @@ std::string shell_with_workdir(std::string_view cmd, std::string_view workdir,
|
||||
if (*interrupted) break;
|
||||
int remain = (int)(deadline - time(nullptr));
|
||||
if (remain <= 0) { killed = true; break; }
|
||||
int pr = poll(&pfd, 1, remain * 1000);
|
||||
int wait_ms = remain * 1000;
|
||||
if (wait_ms > 200) wait_ms = 200;
|
||||
int pr = poll(&pfd, 1, wait_ms);
|
||||
if (pr < 0) { if (errno == EINTR) continue; break; }
|
||||
if (pr == 0) { killed = true; break; }
|
||||
if (pr == 0) continue;
|
||||
char buf[4096];
|
||||
ssize_t n = read(pipefd[0], buf, sizeof buf);
|
||||
if (n > 0) out.append(buf, (size_t)n);
|
||||
@@ -92,6 +101,7 @@ std::string shell_with_workdir(std::string_view cmd, std::string_view workdir,
|
||||
else break;
|
||||
}
|
||||
if (*interrupted || killed) {
|
||||
kill(-pid, SIGKILL);
|
||||
kill(pid, SIGKILL);
|
||||
/* drain remaining output briefly */
|
||||
struct pollfd dpfd; dpfd.fd = pipefd[0]; dpfd.events = POLLIN;
|
||||
@@ -141,29 +151,41 @@ std::string read_file_or_dir(std::string_view path, int offset, int limit, int *
|
||||
return sfmt("[tool error: read %s: %s]", p.c_str(), strerror(errno));
|
||||
}
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
std::vector<std::string> raw_names;
|
||||
struct DirEnt {
|
||||
std::string name;
|
||||
int is_dir; /* -1 = unknown, 0 = non-dir, 1 = dir */
|
||||
};
|
||||
std::vector<DirEnt> entries;
|
||||
{
|
||||
DIR *dir = opendir(p.c_str());
|
||||
if (!dir) { *style = 31; return "[tool error: read (directory listing failed)]"; }
|
||||
struct dirent *de;
|
||||
while ((de = readdir(dir))) {
|
||||
std::string nm = de->d_name;
|
||||
if (nm == "." || nm == "..") continue;
|
||||
raw_names.push_back(std::move(nm));
|
||||
if (de->d_name[0] == '.' &&
|
||||
(de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0')))
|
||||
continue;
|
||||
int isdir = -1;
|
||||
#if defined(DT_DIR) && defined(DT_UNKNOWN) && defined(DT_LNK)
|
||||
if (de->d_type != DT_UNKNOWN && de->d_type != DT_LNK) {
|
||||
isdir = (de->d_type == DT_DIR) ? 1 : 0;
|
||||
}
|
||||
#endif
|
||||
entries.push_back(DirEnt{de->d_name, isdir});
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
for (size_t i = 1; i < raw_names.size(); i++) {
|
||||
for (size_t j = i; j > 0 && raw_names[j - 1] > raw_names[j]; j--) {
|
||||
std::swap(raw_names[j - 1], raw_names[j]);
|
||||
}
|
||||
}
|
||||
std::sort(entries.begin(), entries.end(), [](const DirEnt &a, const DirEnt &b) {
|
||||
return a.name < b.name;
|
||||
});
|
||||
std::string b;
|
||||
for (auto &nm : raw_names) {
|
||||
std::string full = p + "/" + nm;
|
||||
struct stat ds;
|
||||
bool isdir = (stat(full.c_str(), &ds) == 0 && S_ISDIR(ds.st_mode));
|
||||
b += nm;
|
||||
for (auto &ent : entries) {
|
||||
bool isdir = (ent.is_dir == 1);
|
||||
if (ent.is_dir < 0) {
|
||||
std::string full = p + "/" + ent.name;
|
||||
struct stat ds;
|
||||
isdir = (stat(full.c_str(), &ds) == 0 && S_ISDIR(ds.st_mode));
|
||||
}
|
||||
b += ent.name;
|
||||
if (isdir) b += '/';
|
||||
b += '\n';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user