2026-10-14 00:01:03 +03:00
|
|
|
/* util.cpp — string building, UTF-8, filtering, color, IDs, MD5, misc. */
|
|
|
|
|
#include "bantam.hpp"
|
|
|
|
|
#include <array>
|
|
|
|
|
#include <cerrno>
|
|
|
|
|
#include <cstdio>
|
|
|
|
|
#include <cstdlib>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
#include <ctime>
|
|
|
|
|
#include <signal.h>
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
/* ----------------------------- formatting ----------------------------- */
|
|
|
|
|
static std::string vfmt(const char *fmt, va_list ap) {
|
2026-10-14 15:30:12 +03:00
|
|
|
char stack_buf[512];
|
2026-10-14 00:01:03 +03:00
|
|
|
va_list cp;
|
|
|
|
|
va_copy(cp, ap);
|
2026-10-14 15:30:12 +03:00
|
|
|
int need = std::vsnprintf(stack_buf, sizeof stack_buf, fmt, cp);
|
2026-10-14 00:01:03 +03:00
|
|
|
va_end(cp);
|
|
|
|
|
if (need < 0) return {};
|
2026-10-14 15:30:12 +03:00
|
|
|
if ((size_t)need < sizeof stack_buf) {
|
|
|
|
|
return std::string(stack_buf, (size_t)need);
|
|
|
|
|
}
|
2026-10-14 00:01:03 +03:00
|
|
|
std::string s;
|
|
|
|
|
s.resize((size_t)need);
|
|
|
|
|
std::vsnprintf(s.data(), (size_t)need + 1, fmt, ap);
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
std::string sfmt(const char *fmt, ...) {
|
|
|
|
|
va_list ap;
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
|
std::string s = vfmt(fmt, ap);
|
|
|
|
|
va_end(ap);
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
void str_appendf(std::string &s, const char *fmt, ...) {
|
2026-10-14 15:30:12 +03:00
|
|
|
char stack_buf[512];
|
|
|
|
|
va_list ap, cp;
|
2026-10-14 00:01:03 +03:00
|
|
|
va_start(ap, fmt);
|
2026-10-14 15:30:12 +03:00
|
|
|
va_copy(cp, ap);
|
|
|
|
|
int need = std::vsnprintf(stack_buf, sizeof stack_buf, fmt, cp);
|
|
|
|
|
va_end(cp);
|
|
|
|
|
if (need > 0) {
|
|
|
|
|
if ((size_t)need < sizeof stack_buf) {
|
|
|
|
|
s.append(stack_buf, (size_t)need);
|
|
|
|
|
} else {
|
|
|
|
|
size_t old_sz = s.size();
|
|
|
|
|
s.resize(old_sz + (size_t)need);
|
|
|
|
|
std::vsnprintf(s.data() + old_sz, (size_t)need + 1, fmt, ap);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-14 00:01:03 +03:00
|
|
|
va_end(ap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ----------------------------- UTF-8 ---------------------------------- */
|
|
|
|
|
int utf8_decode(const unsigned char *p, size_t avail, size_t *plen) {
|
|
|
|
|
if (avail == 0) { *plen = 0; return 0xFFFD; }
|
|
|
|
|
unsigned char b0 = p[0];
|
|
|
|
|
if (b0 < 0x80) { *plen = 1; return b0; }
|
|
|
|
|
if ((b0 & 0xE0) == 0xC0) {
|
|
|
|
|
if (avail < 2 || (p[1] & 0xC0) != 0x80) { *plen = 1; return 0xFFFD; }
|
|
|
|
|
int cp = ((b0 & 0x1F) << 6) | (p[1] & 0x3F);
|
|
|
|
|
if (cp < 0x80) { *plen = 1; return 0xFFFD; }
|
|
|
|
|
*plen = 2; return cp;
|
|
|
|
|
}
|
|
|
|
|
if ((b0 & 0xF0) == 0xE0) {
|
|
|
|
|
if (avail < 3 || (p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80) { *plen = 1; return 0xFFFD; }
|
|
|
|
|
int cp = ((b0 & 0x0F) << 12) | ((p[1] & 0x3F) << 6) | (p[2] & 0x3F);
|
|
|
|
|
if (cp < 0x800 || (cp >= 0xD800 && cp <= 0xDFFF)) { *plen = 1; return 0xFFFD; }
|
|
|
|
|
*plen = 3; return cp;
|
|
|
|
|
}
|
|
|
|
|
if ((b0 & 0xF8) == 0xF0) {
|
|
|
|
|
if (avail < 4 || (p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80 || (p[3] & 0xC0) != 0x80) { *plen = 1; return 0xFFFD; }
|
|
|
|
|
int cp = ((b0 & 0x07) << 18) | ((p[1] & 0x3F) << 12) | ((p[2] & 0x3F) << 6) | (p[3] & 0x3F);
|
|
|
|
|
if (cp < 0x10000 || cp > 0x10FFFF) { *plen = 1; return 0xFFFD; }
|
|
|
|
|
*plen = 4; return cp;
|
|
|
|
|
}
|
|
|
|
|
*plen = 1; return 0xFFFD;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string_view utf8_truncate(std::string_view s, size_t max_bytes) {
|
|
|
|
|
if (s.size() <= max_bytes) return s;
|
|
|
|
|
const unsigned char *p = (const unsigned char *)s.data();
|
|
|
|
|
size_t pos = 0, avail = s.size();
|
|
|
|
|
while (avail > 0) {
|
|
|
|
|
size_t pl;
|
|
|
|
|
utf8_decode(p + pos, avail, &pl);
|
|
|
|
|
if (pl == 0 || pos + pl > max_bytes) break;
|
|
|
|
|
pos += pl;
|
|
|
|
|
avail -= pl;
|
|
|
|
|
}
|
|
|
|
|
return s.substr(0, pos);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ----------------------------- filtering ------------------------------ */
|
|
|
|
|
static bool kept_rune(int r) {
|
|
|
|
|
if (r == ' ' || r == '\t' || r == '\n') return true;
|
|
|
|
|
if (r < 0x20) return false; /* C0 controls */
|
|
|
|
|
if (r == 0x7f) return false; /* DEL */
|
|
|
|
|
if (r >= 0x80 && r <= 0x9f) return false; /* C1 controls */
|
|
|
|
|
switch (r) {
|
|
|
|
|
case 0x00a0: case 0x00ad:
|
|
|
|
|
case 0x2000: case 0x2001: case 0x2002: case 0x2003: case 0x2004:
|
|
|
|
|
case 0x2005: case 0x2006: case 0x2007: case 0x2008: case 0x2009:
|
|
|
|
|
case 0x200a: case 0x2028: case 0x2029: case 0x202f: case 0x205f:
|
|
|
|
|
case 0x3000:
|
|
|
|
|
case 0x200b: case 0x200c: case 0x200d: case 0x2060: case 0xfeff:
|
|
|
|
|
case 0x200e: case 0x200f:
|
|
|
|
|
case 0x202a: case 0x202b: case 0x202c: case 0x202d: case 0x202e:
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
std::string filter_text(std::string_view s) {
|
|
|
|
|
std::string out;
|
|
|
|
|
out.reserve(s.size());
|
|
|
|
|
const unsigned char *p = (const unsigned char *)s.data();
|
|
|
|
|
size_t avail = s.size();
|
|
|
|
|
while (avail > 0) {
|
2026-10-14 15:30:12 +03:00
|
|
|
/* Fast path: scan contiguous printable ASCII plus '\t' and '\n'. */
|
|
|
|
|
size_t run = 0;
|
|
|
|
|
while (run < avail) {
|
|
|
|
|
unsigned char b = p[run];
|
|
|
|
|
if ((b >= 0x20 && b <= 0x7e) || b == '\n' || b == '\t') run++;
|
|
|
|
|
else break;
|
|
|
|
|
}
|
|
|
|
|
if (run > 0) {
|
|
|
|
|
out.append((const char *)p, run);
|
|
|
|
|
p += run;
|
|
|
|
|
avail -= run;
|
|
|
|
|
if (avail == 0) break;
|
|
|
|
|
}
|
2026-10-14 00:01:03 +03:00
|
|
|
size_t pl;
|
|
|
|
|
int cp = utf8_decode(p, avail, &pl);
|
|
|
|
|
if (cp == 0xFFFD && pl == 1) { p++; avail--; continue; }
|
|
|
|
|
if (kept_rune(cp)) out.append((const char *)p, pl);
|
|
|
|
|
p += pl; avail -= pl;
|
|
|
|
|
}
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ----------------------------- color ---------------------------------- */
|
|
|
|
|
int g_col = 0;
|
|
|
|
|
volatile int g_interrupted = 0;
|
|
|
|
|
|
|
|
|
|
int visible_len(std::string_view s) {
|
|
|
|
|
int n = 0;
|
|
|
|
|
const unsigned char *p = (const unsigned char *)s.data();
|
|
|
|
|
size_t avail = s.size();
|
|
|
|
|
while (avail > 0) {
|
|
|
|
|
if (p[0] == 0x1b) {
|
|
|
|
|
size_t i = 1;
|
|
|
|
|
if (i < avail && p[1] == '[') {
|
|
|
|
|
i++;
|
|
|
|
|
while (i < avail && !(p[i] >= 0x40 && p[i] <= 0x7e)) i++;
|
|
|
|
|
if (i < avail) i++;
|
|
|
|
|
} else {
|
|
|
|
|
i = 1;
|
|
|
|
|
}
|
|
|
|
|
p += i; avail -= i;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (p[0] == 0x01 || p[0] == 0x02) { p++; avail--; continue; }
|
|
|
|
|
size_t pl;
|
|
|
|
|
int cp = utf8_decode(p, avail, &pl);
|
|
|
|
|
n += rune_width(cp);
|
|
|
|
|
p += pl; avail -= pl;
|
|
|
|
|
}
|
|
|
|
|
return n;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Display width of a single Unicode code point. Most code points occupy one
|
|
|
|
|
* terminal column, but East-Asian Wide/Fullwidth characters (notably CJK) and
|
|
|
|
|
* many emoji occupy two. Combining marks and zero-width characters occupy
|
|
|
|
|
* none. Without this, tables containing Chinese text were misaligned because
|
|
|
|
|
* visible_len counted every rune as a single column. */
|
|
|
|
|
int rune_width(int cp) {
|
|
|
|
|
if (cp < 0x20) return 0;
|
|
|
|
|
if (cp == 0x7f) return 0;
|
|
|
|
|
/* combining marks / zero-width */
|
|
|
|
|
if ((cp >= 0x0300 && cp <= 0x036f) || (cp >= 0x0483 && cp <= 0x0489) ||
|
|
|
|
|
(cp >= 0x0591 && cp <= 0x05bd) || (cp >= 0x0610 && cp <= 0x061a) ||
|
|
|
|
|
(cp >= 0x064b && cp <= 0x065f) || (cp >= 0x0670 && cp <= 0x0670) ||
|
|
|
|
|
(cp >= 0x06d6 && cp <= 0x06dc) || (cp >= 0x0e31 && cp <= 0x0e3a) ||
|
|
|
|
|
(cp >= 0x0e47 && cp <= 0x0e4e) || (cp >= 0x0eb1 && cp <= 0x0eb9) ||
|
|
|
|
|
(cp >= 0x0ebb && cp <= 0x0ebd) || (cp >= 0x0ec8 && cp <= 0x0ecd) ||
|
|
|
|
|
(cp >= 0x200b && cp <= 0x200f) || (cp >= 0x202a && cp <= 0x202e) ||
|
|
|
|
|
(cp >= 0x2060 && cp <= 0x2064) || (cp >= 0xfe00 && cp <= 0xfe0f) ||
|
|
|
|
|
(cp == 0xfeff) || (cp >= 0xfff9 && cp <= 0xfffb) ||
|
|
|
|
|
(cp >= 0x1d173 && cp <= 0x1d17a))
|
|
|
|
|
return 0;
|
|
|
|
|
/* East Asian Wide / Fullwidth */
|
|
|
|
|
if (cp == 0x3000) return 2;
|
|
|
|
|
if (cp >= 0x1100 && cp <= 0x115f) return 2;
|
|
|
|
|
if (cp >= 0x2e80 && cp <= 0x303e) return 2;
|
|
|
|
|
if (cp >= 0x3041 && cp <= 0x33ff) return 2;
|
|
|
|
|
if (cp >= 0x3400 && cp <= 0x4dbf) return 2;
|
|
|
|
|
if (cp >= 0x4e00 && cp <= 0x9fff) return 2;
|
|
|
|
|
if (cp >= 0xa000 && cp <= 0xa4cf) return 2;
|
|
|
|
|
if (cp >= 0xac00 && cp <= 0xd7a3) return 2;
|
|
|
|
|
if (cp >= 0xf900 && cp <= 0xfaff) return 2;
|
|
|
|
|
if (cp >= 0xfe30 && cp <= 0xfe4f) return 2;
|
|
|
|
|
if (cp >= 0xff00 && cp <= 0xff60) return 2;
|
|
|
|
|
if (cp >= 0xffe0 && cp <= 0xffe6) return 2;
|
|
|
|
|
if (cp >= 0x1f300 && cp <= 0x1faff) return 2;
|
|
|
|
|
if (cp >= 0x20000 && cp <= 0x3fffd) return 2;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ----------------------------- paths ---------------------------------- */
|
|
|
|
|
void mkdir_p(std::string_view dir) {
|
|
|
|
|
if (dir.empty()) return;
|
|
|
|
|
std::string path(dir);
|
2026-10-14 15:30:12 +03:00
|
|
|
struct stat st;
|
|
|
|
|
if (stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) return;
|
2026-10-14 00:01:03 +03:00
|
|
|
for (size_t i = 1; i < path.size(); i++) {
|
|
|
|
|
if (path[i] == '/') {
|
|
|
|
|
path[i] = '\0';
|
2026-10-14 15:30:12 +03:00
|
|
|
if (stat(path.c_str(), &st) != 0) mkdir(path.c_str(), 0755);
|
2026-10-14 00:01:03 +03:00
|
|
|
path[i] = '/';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
mkdir(path.c_str(), 0755);
|
|
|
|
|
}
|
2026-10-14 12:19:11 +03:00
|
|
|
std::string bantam_tmp_dir(std::string_view sess_id) {
|
2026-10-14 00:01:03 +03:00
|
|
|
const char *tmp = std::getenv("TMPDIR");
|
|
|
|
|
if (!tmp || !*tmp) tmp = "/tmp";
|
2026-10-14 12:19:11 +03:00
|
|
|
std::string base(tmp);
|
|
|
|
|
/* Drop a single trailing slash so we never build a "//bantam" path (which
|
|
|
|
|
* some platforms treat as a special "super-root"); keep "/" itself intact. */
|
|
|
|
|
if (base.size() > 1 && base.back() == '/') base.pop_back();
|
|
|
|
|
std::string p = base;
|
|
|
|
|
if (p != "/") p += "/";
|
|
|
|
|
p += "bantam";
|
|
|
|
|
/* Segregate temporary artifacts per session so different projects/sessions
|
|
|
|
|
* never share or collide on scratch files. */
|
|
|
|
|
std::string sid(sess_id);
|
|
|
|
|
if (sid.empty()) sid = project_id();
|
|
|
|
|
if (!sid.empty()) { p += "/"; p += sid; }
|
2026-10-14 00:01:03 +03:00
|
|
|
mkdir_p(p);
|
|
|
|
|
return p;
|
|
|
|
|
}
|
2026-10-14 12:19:11 +03:00
|
|
|
|
|
|
|
|
std::string tool_res_dir(std::string_view sess_id) {
|
|
|
|
|
std::string base = bantam_tmp_dir(sess_id);
|
2026-10-14 00:01:03 +03:00
|
|
|
std::string p = base + "/toolres";
|
|
|
|
|
mkdir_p(p);
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
std::string project_id() {
|
2026-10-14 15:30:12 +03:00
|
|
|
static const std::string cached_id = []() {
|
|
|
|
|
char cwd[4096];
|
|
|
|
|
if (!getcwd(cwd, sizeof cwd)) std::strcpy(cwd, ".");
|
|
|
|
|
char *abs = realpath(cwd, nullptr);
|
|
|
|
|
std::string src = abs ? abs : cwd;
|
|
|
|
|
if (abs) free(abs);
|
|
|
|
|
return md5_hex(src);
|
|
|
|
|
}();
|
|
|
|
|
return cached_id;
|
2026-10-14 00:01:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ----------------------------- MD5 ------------------------------------ */
|
|
|
|
|
#define MD5_ROTLEFT(x, c) (((x) << (c)) | ((x) >> (32 - (c))))
|
|
|
|
|
struct MD5_CTX { uint32_t state[4]; uint32_t count[2]; unsigned char buf[64]; };
|
|
|
|
|
static void md5_transform(uint32_t state[4], const unsigned char block[64]);
|
|
|
|
|
static void md5_init(MD5_CTX *c) {
|
|
|
|
|
c->state[0] = 0x67452301; c->state[1] = 0xefcdab89;
|
|
|
|
|
c->state[2] = 0x98badcfe; c->state[3] = 0x10325476;
|
|
|
|
|
c->count[0] = c->count[1] = 0;
|
|
|
|
|
}
|
|
|
|
|
static void md5_update(MD5_CTX *c, const unsigned char *in, size_t len) {
|
|
|
|
|
uint32_t bits = (uint32_t)(c->count[0] >> 3) & 0x3F;
|
|
|
|
|
if ((c->count[0] += (uint32_t)(len << 3)) < (uint32_t)(len << 3)) c->count[1]++;
|
|
|
|
|
c->count[1] += (uint32_t)(len >> 29);
|
|
|
|
|
while (len--) {
|
|
|
|
|
c->buf[bits++] = *in++;
|
|
|
|
|
if (bits == 64) { md5_transform(c->state, c->buf); bits = 0; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
static void md5_final(unsigned char digest[16], MD5_CTX *c) {
|
|
|
|
|
unsigned char bits[8];
|
|
|
|
|
for (int i = 0; i < 8; i++) bits[i] = (unsigned char)((c->count[i >> 2] >> ((i & 3) * 8)) & 0xFF);
|
|
|
|
|
unsigned char pad = (unsigned char)((c->count[0] >> 3) & 0x3F);
|
|
|
|
|
unsigned char padlen = (pad < 56) ? (56 - pad) : (120 - pad);
|
|
|
|
|
unsigned char p = 0x80;
|
|
|
|
|
md5_update(c, &p, 1);
|
|
|
|
|
unsigned char zero = 0;
|
|
|
|
|
for (int i = 0; i < padlen - 1; i++) md5_update(c, &zero, 1);
|
|
|
|
|
md5_update(c, bits, 8);
|
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
|
digest[i * 4 + 0] = (unsigned char)(c->state[i] & 0xFF);
|
|
|
|
|
digest[i * 4 + 1] = (unsigned char)((c->state[i] >> 8) & 0xFF);
|
|
|
|
|
digest[i * 4 + 2] = (unsigned char)((c->state[i] >> 16) & 0xFF);
|
|
|
|
|
digest[i * 4 + 3] = (unsigned char)((c->state[i] >> 24) & 0xFF);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
static void md5_transform(uint32_t state[4], const unsigned char b[64]) {
|
|
|
|
|
uint32_t a = state[0], d = state[3], c = state[2], e = state[1];
|
|
|
|
|
uint32_t x[16];
|
|
|
|
|
for (int i = 0, j = 0; i < 16; i++, j += 4)
|
|
|
|
|
x[i] = (uint32_t)b[j] | ((uint32_t)b[j+1] << 8) | ((uint32_t)b[j+2] << 16) | ((uint32_t)b[j+3] << 24);
|
|
|
|
|
static const uint32_t K[64] = {
|
|
|
|
|
0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee,0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501,
|
|
|
|
|
0x698098d8,0x8b44f7af,0xffff5bb1,0x895cd7be,0x6b901122,0xfd987193,0xa679438e,0x49b40821,
|
|
|
|
|
0xf61e2562,0xc040b340,0x265e5a51,0xe9b6c7aa,0xd62f105d,0x02441453,0xd8a1e681,0xe7d3fbc8,
|
|
|
|
|
0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed,0xa9e3e905,0xfcefa3f8,0x676f02d9,0x8d2a4c8a,
|
|
|
|
|
0xfffa3942,0x8771f681,0x6d9d6122,0xfde5380c,0xa4beea44,0x4bdecfa9,0xf6bb4b60,0xbebfbc70,
|
|
|
|
|
0x289b7ec6,0xeaa127fa,0xd4ef3085,0x04881d05,0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665,
|
|
|
|
|
0xf4292244,0x432aff97,0xab9423a7,0xfc93a039,0x655b59c3,0x8f0ccc92,0xffeff47d,0x85845dd1,
|
|
|
|
|
0x6fa87e4f,0xfe2ce6e0,0xa3014314,0x4e0811a1,0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391};
|
|
|
|
|
static const int S[64] = {
|
|
|
|
|
7,12,17,22, 7,12,17,22, 7,12,17,22, 7,12,17,22,
|
|
|
|
|
5,9,14,20, 5,9,14,20, 5,9,14,20, 5,9,14,20,
|
|
|
|
|
4,11,16,23, 4,11,16,23, 4,11,16,23, 4,11,16,23,
|
|
|
|
|
6,10,15,21, 6,10,15,21, 6,10,15,21, 6,10,15,21};
|
|
|
|
|
for (int i = 0; i < 64; i++) {
|
|
|
|
|
uint32_t f; int g;
|
|
|
|
|
if (i < 16) { f = (e & c) | (~e & d); g = i; }
|
|
|
|
|
else if (i < 32) { f = (d & e) | (~d & c); g = (5 * i + 1) % 16; }
|
|
|
|
|
else if (i < 48) { f = e ^ c ^ d; g = (3 * i + 5) % 16; }
|
|
|
|
|
else { f = c ^ (e | (~d)); g = (7 * i) % 16; }
|
|
|
|
|
f = f + a + K[i] + x[g];
|
|
|
|
|
a = d; d = c; c = e;
|
|
|
|
|
e = e + MD5_ROTLEFT(f, S[i]);
|
|
|
|
|
}
|
|
|
|
|
state[0] += a; state[1] += e; state[2] += c; state[3] += d;
|
|
|
|
|
}
|
|
|
|
|
std::string md5_hex(std::string_view data) {
|
|
|
|
|
MD5_CTX ctx; md5_init(&ctx);
|
|
|
|
|
md5_update(&ctx, (const unsigned char *)data.data(), data.size());
|
|
|
|
|
unsigned char dig[16];
|
|
|
|
|
md5_final(dig, &ctx);
|
|
|
|
|
std::string out;
|
|
|
|
|
out.reserve(33);
|
|
|
|
|
static const char *hx = "0123456789abcdef";
|
|
|
|
|
for (int i = 0; i < 16; i++) {
|
|
|
|
|
out += hx[dig[i] >> 4];
|
|
|
|
|
out += hx[dig[i] & 0xF];
|
|
|
|
|
}
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ----------------------------- misc ----------------------------------- */
|
|
|
|
|
int to_int_val(std::string_view s, int def) {
|
|
|
|
|
if (s.empty()) return def;
|
|
|
|
|
std::string tmp(s);
|
|
|
|
|
char *end;
|
|
|
|
|
long v = std::strtol(tmp.c_str(), &end, 10);
|
|
|
|
|
if (end == tmp.c_str() || *end != '\0') return def;
|
|
|
|
|
return (int)v;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-14 08:36:39 +03:00
|
|
|
bool read_file_str(std::string_view path, std::string &out) {
|
|
|
|
|
out.clear();
|
|
|
|
|
std::string p(path);
|
|
|
|
|
FILE *f = std::fopen(p.c_str(), "rb");
|
|
|
|
|
if (!f) return false;
|
2026-10-14 15:30:12 +03:00
|
|
|
struct stat st;
|
|
|
|
|
if (fstat(fileno(f), &st) == 0 && S_ISREG(st.st_mode) && st.st_size > 0) {
|
|
|
|
|
out.reserve((size_t)st.st_size);
|
|
|
|
|
}
|
|
|
|
|
char buf[16384];
|
2026-10-14 08:36:39 +03:00
|
|
|
size_t n;
|
|
|
|
|
while ((n = std::fread(buf, 1, sizeof buf, f)) > 0) {
|
|
|
|
|
out.append(buf, n);
|
|
|
|
|
}
|
|
|
|
|
bool ok = (std::ferror(f) == 0);
|
|
|
|
|
std::fclose(f);
|
|
|
|
|
return ok;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool write_file_str(std::string_view path, std::string_view data) {
|
|
|
|
|
std::string p(path);
|
|
|
|
|
FILE *f = std::fopen(p.c_str(), "wb");
|
|
|
|
|
if (!f) return false;
|
|
|
|
|
size_t written = 0;
|
|
|
|
|
while (written < data.size()) {
|
|
|
|
|
size_t n = std::fwrite(data.data() + written, 1, data.size() - written, f);
|
|
|
|
|
if (n == 0) {
|
|
|
|
|
std::fclose(f);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
written += n;
|
|
|
|
|
}
|
|
|
|
|
return std::fclose(f) == 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-14 00:01:03 +03:00
|
|
|
/* ----------------------------- signal --------------------------------- */
|
|
|
|
|
static void sigint_handler(int sig) {
|
|
|
|
|
(void)sig;
|
|
|
|
|
g_interrupted = 1;
|
|
|
|
|
}
|
|
|
|
|
void setup_signal_handler() {
|
|
|
|
|
struct sigaction sa;
|
|
|
|
|
std::memset(&sa, 0, sizeof sa);
|
|
|
|
|
sa.sa_handler = sigint_handler;
|
|
|
|
|
sigaction(SIGINT, &sa, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int color_enabled(const Cfg &cfg) {
|
|
|
|
|
const char *nc = std::getenv("NO_COLOR");
|
|
|
|
|
if (nc && *nc) return 0;
|
|
|
|
|
const char *bnc = std::getenv("BANTAM_NO_COLOR");
|
|
|
|
|
if (bnc && *bnc) return 0;
|
|
|
|
|
const std::string &c = cfg.color;
|
|
|
|
|
if (c == "always") return 1;
|
|
|
|
|
if (c == "never") return 0;
|
|
|
|
|
return is_terminal(STDOUT_FILENO);
|
|
|
|
|
}
|
|
|
|
|
|