Skip to content

Commit 855d891

Browse files
committed
Add default exact-response saving and transport support to C CLI
1 parent bc1c4c3 commit 855d891

1 file changed

Lines changed: 117 additions & 52 deletions

File tree

bin/blackbelt-c.c

Lines changed: 117 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,21 @@
55
#include <string.h>
66
#include <unistd.h>
77
#include <sys/wait.h>
8+
#include <sys/stat.h>
9+
#include <time.h>
810

911
static void usage(const char *p) {
10-
printf("Usage: %s [--file FILE] [--version] [--help]\n", p);
12+
printf("Usage: %s [--file FILE] [--save FILE] [--no-save] [--version] [--help]\n", p);
1113
printf(" %s < audit.json\n\n", p);
1214
printf("Black Belt Ethical Auditor Linux CLI (C).\n");
1315
printf("Interactive mode accepts questions or JSON audit objects.\n");
14-
printf("Environment: BBEA_ENGINE_CMD, BBEA_ENGINE_URL, BBEA_MODEL, BBEA_SYSTEM_PROMPT.\n");
16+
printf("Responses are saved exactly as received by default.\n");
17+
printf("Environment: BBEA_ENGINE_CMD, BBEA_ENGINE_URL, BBEA_MODEL, BBEA_SYSTEM_PROMPT, BBEA_SAVE_DIR, BBEA_TRANSPORT.\n");
1518
}
1619

1720
static const char *engine_command(char *buffer, size_t size) {
1821
const char *configured = getenv("BBEA_ENGINE_CMD");
1922
if (configured && *configured) return configured;
20-
2123
ssize_t n = readlink("/proc/self/exe", buffer, size - 1);
2224
if (n > 0) {
2325
buffer[n] = '\0';
@@ -30,75 +32,138 @@ static const char *engine_command(char *buffer, size_t size) {
3032
return "bin/blackbelt-engine.sh";
3133
}
3234

33-
static int run_engine(const char *input) {
34-
char tmp[] = "/tmp/blackbelt-c-XXXXXX";
35-
int fd = mkstemp(tmp);
36-
if (fd < 0) { perror("mkstemp"); return 2; }
37-
FILE *out = fdopen(fd, "wb");
38-
if (!out) { perror("fdopen"); close(fd); unlink(tmp); return 2; }
39-
size_t length = strlen(input);
40-
if (fwrite(input, 1, length, out) != length || fputc('\n', out) == EOF) {
41-
perror("write"); fclose(out); unlink(tmp); return 2;
35+
static const char *output_helper(char *buffer, size_t size) {
36+
ssize_t n = readlink("/proc/self/exe", buffer, size - 1);
37+
if (n > 0) {
38+
buffer[n] = '\0';
39+
char *slash = strrchr(buffer, '/');
40+
if (slash) {
41+
snprintf(slash + 1, size - (size_t)(slash + 1 - buffer), "blackbelt-output.sh");
42+
return buffer;
43+
}
44+
}
45+
return "bin/blackbelt-output.sh";
46+
}
47+
48+
static void shell_quote(const char *src, char *dst, size_t size) {
49+
size_t used = 0;
50+
if (size) dst[used++] = '\'';
51+
for (; *src && used + 5 < size; ++src) {
52+
if (*src == '\'') {
53+
dst[used++] = '\''; dst[used++] = '\\'; dst[used++] = '\''; dst[used++] = '\'';
54+
} else dst[used++] = *src;
55+
}
56+
if (used + 2 <= size) { dst[used++] = '\''; dst[used] = '\0'; }
57+
}
58+
59+
static int save_and_transport(const char *response_path, const char *requested_path) {
60+
if (getenv("BBEA_NO_SAVE") && !strcmp(getenv("BBEA_NO_SAVE"), "1") && !requested_path) return 0;
61+
62+
char helper[4096], quoted[8192], command[12288];
63+
const char *helper_path = output_helper(helper, sizeof(helper));
64+
shell_quote(helper_path, quoted, sizeof(quoted));
65+
66+
if (requested_path) {
67+
FILE *src = fopen(response_path, "rb");
68+
FILE *dst = fopen(requested_path, "wb");
69+
if (!src || !dst) { perror("save"); if (src) fclose(src); if (dst) fclose(dst); return 4; }
70+
char buf[65536]; size_t n;
71+
while ((n = fread(buf, 1, sizeof(buf), src)) > 0) if (fwrite(buf, 1, n, dst) != n) { perror("save"); fclose(src); fclose(dst); return 4; }
72+
fclose(src); fclose(dst);
4273
}
43-
fclose(out);
4474

45-
char engine_path[4096];
46-
const char *engine = engine_command(engine_path, sizeof(engine_path));
47-
char command[8192];
48-
int written = snprintf(command, sizeof(command), "cat '%s' | '%s'", tmp, engine);
49-
if (written < 0 || (size_t)written >= sizeof(command)) {
50-
fprintf(stderr, "Engine command is too long.\n"); unlink(tmp); return 3;
75+
if (!requested_path && !(getenv("BBEA_NO_SAVE") && !strcmp(getenv("BBEA_NO_SAVE"), "1"))) {
76+
const char *dir = getenv("BBEA_SAVE_DIR");
77+
if (!dir || !*dir) {
78+
const char *home = getenv("HOME");
79+
static char default_dir[4096];
80+
snprintf(default_dir, sizeof(default_dir), "%s/.local/state/blackbelt/responses", home ? home : ".");
81+
dir = default_dir;
82+
}
83+
char mkdir_cmd[8192]; shell_quote(dir, mkdir_cmd, sizeof(mkdir_cmd));
84+
snprintf(command, sizeof(command), "mkdir -p %s", mkdir_cmd);
85+
if (system(command) != 0) return 5;
86+
char filename[8192]; time_t now = time(NULL); struct tm tmv;
87+
localtime_r(&now, &tmv);
88+
snprintf(filename, sizeof(filename), "%s/blackbelt-%04d%02d%02d-%02d%02d%02d-%ld.json", dir,
89+
tmv.tm_year + 1900, tmv.tm_mon + 1, tmv.tm_mday, tmv.tm_hour, tmv.tm_min, tmv.tm_sec, (long)getpid());
90+
FILE *src = fopen(response_path, "rb"); FILE *dst = fopen(filename, "wb");
91+
if (!src || !dst) { perror("response save"); if (src) fclose(src); if (dst) fclose(dst); return 5; }
92+
char buf[65536]; size_t n;
93+
while ((n = fread(buf, 1, sizeof(buf), src)) > 0) if (fwrite(buf, 1, n, dst) != n) { perror("response save"); fclose(src); fclose(dst); return 5; }
94+
fclose(src); fclose(dst);
95+
fprintf(stderr, "[Black Belt] saved exact response: %s\n", filename);
96+
requested_path = filename;
5197
}
98+
99+
if (getenv("BBEA_TRANSPORT") && *getenv("BBEA_TRANSPORT")) {
100+
char qpath[8192]; shell_quote(requested_path ? requested_path : response_path, qpath, sizeof(qpath));
101+
snprintf(command, sizeof(command), "bash %s %s", quoted, qpath);
102+
if (system(command) != 0) return 6;
103+
}
104+
return 0;
105+
}
106+
107+
static int run_engine(const char *input, const char *save_path) {
108+
char in_tmp[] = "/tmp/blackbelt-c-in-XXXXXX";
109+
char out_tmp[] = "/tmp/blackbelt-c-out-XXXXXX";
110+
int fd = mkstemp(in_tmp); if (fd < 0) { perror("mkstemp"); return 2; }
111+
FILE *out = fdopen(fd, "wb");
112+
if (!out) { perror("fdopen"); close(fd); unlink(in_tmp); return 2; }
113+
if (fwrite(input, 1, strlen(input), out) != strlen(input) || fputc('\n', out) == EOF) { perror("write"); fclose(out); unlink(in_tmp); return 2; }
114+
fclose(out);
115+
int ofd = mkstemp(out_tmp); if (ofd < 0) { perror("mkstemp"); unlink(in_tmp); return 2; }
116+
close(ofd);
117+
118+
char engine_path[4096], qin[8192], qengine[8192], qout[8192], command[24576];
119+
shell_quote(in_tmp, qin, sizeof(qin)); shell_quote(engine_command(engine_path, sizeof(engine_path)), qengine, sizeof(qengine)); shell_quote(out_tmp, qout, sizeof(qout));
120+
int written = snprintf(command, sizeof(command), "cat %s | bash %s > %s", qin, qengine, qout);
121+
if (written < 0 || (size_t)written >= sizeof(command)) { fprintf(stderr, "Engine command is too long.\n"); unlink(in_tmp); unlink(out_tmp); return 3; }
52122
int status = system(command);
53-
unlink(tmp);
54-
if (status == -1) return 3;
55-
return WIFEXITED(status) ? WEXITSTATUS(status) : 3;
123+
unlink(in_tmp);
124+
if (status == -1) { unlink(out_tmp); return 3; }
125+
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { int rc = WIFEXITED(status) ? WEXITSTATUS(status) : 3; unlink(out_tmp); return rc; }
126+
127+
FILE *response = fopen(out_tmp, "rb");
128+
if (!response) { perror("response"); unlink(out_tmp); return 4; }
129+
char buf[65536]; size_t n;
130+
while ((n = fread(buf, 1, sizeof(buf), response)) > 0) fwrite(buf, 1, n, stdout);
131+
fclose(response); fflush(stdout);
132+
133+
int rc = save_and_transport(out_tmp, save_path);
134+
unlink(out_tmp);
135+
return rc;
56136
}
57137

58138
static int interactive(void) {
59-
char *line = NULL;
60-
size_t capacity = 0;
61-
puts("Black Belt Ethical Auditor");
62-
puts("Ask a question or enter a JSON audit object. Type 'help' or 'quit'.");
139+
char *line = NULL; size_t capacity = 0;
140+
puts("Black Belt Ethical Auditor"); puts("Ask a question or enter a JSON audit object. Type 'help' or 'quit'.");
63141
for (;;) {
64-
fputs("black-belt> ", stdout);
65-
fflush(stdout);
66-
ssize_t length = getline(&line, &capacity, stdin);
67-
if (length < 0) break;
142+
fputs("black-belt> ", stdout); fflush(stdout);
143+
ssize_t length = getline(&line, &capacity, stdin); if (length < 0) break;
68144
while (length > 0 && (line[length - 1] == '\n' || line[length - 1] == '\r')) line[--length] = '\0';
69145
if (length == 0) continue;
70146
if (!strcmp(line, "quit") || !strcmp(line, "exit")) break;
71-
if (!strcmp(line, "help")) {
72-
puts("Enter a natural-language question or a complete BBEA JSON audit object.");
73-
puts("Commands: help, quit, exit");
74-
continue;
75-
}
76-
int rc = run_engine(line);
77-
if (rc != 0) fprintf(stderr, "Audit failed (exit %d).\n", rc);
147+
if (!strcmp(line, "help")) { puts("Enter a natural-language question or a complete BBEA JSON audit object."); puts("Commands: help, quit, exit"); continue; }
148+
int rc = run_engine(line, NULL); if (rc != 0) fprintf(stderr, "Audit failed (exit %d).\n", rc);
78149
}
79-
free(line);
80-
putchar('\n');
81-
return 0;
150+
free(line); putchar('\n'); return 0;
82151
}
83152

84153
int main(int argc, char **argv) {
85-
const char *file = NULL;
154+
const char *file = NULL, *save_path = NULL; int no_save = 0;
86155
for (int i = 1; i < argc; ++i) {
87156
if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) { usage(argv[0]); return 0; }
88-
if (!strcmp(argv[i], "--version")) { puts("blackbelt-c 1.2 (BBEA CLI v2 transport)"); return 0; }
157+
if (!strcmp(argv[i], "--version")) { puts("blackbelt-c 1.3 (BBEA output/transport)"); return 0; }
89158
if (!strcmp(argv[i], "--file") && i + 1 < argc) { file = argv[++i]; continue; }
159+
if (!strcmp(argv[i], "--save") && i + 1 < argc) { save_path = argv[++i]; continue; }
160+
if (!strcmp(argv[i], "--no-save")) { no_save = 1; continue; }
90161
fprintf(stderr, "Unknown argument: %s\n", argv[i]); usage(argv[0]); return 2;
91162
}
163+
if (no_save) setenv("BBEA_NO_SAVE", "1", 1);
92164
if (!file && isatty(STDIN_FILENO)) return interactive();
93-
94-
FILE *in = stdin;
95-
if (file) { in = fopen(file, "rb"); if (!in) { perror(file); return 2; } }
96-
char *input = NULL;
97-
size_t capacity = 0;
98-
ssize_t length = getdelim(&input, &capacity, '\0', in);
99-
if (file) fclose(in);
165+
FILE *in = stdin; if (file) { in = fopen(file, "rb"); if (!in) { perror(file); return 2; } }
166+
char *input = NULL; size_t capacity = 0; ssize_t length = getdelim(&input, &capacity, '\0', in); if (file) fclose(in);
100167
if (length < 0 || !input || length == 0) { free(input); fprintf(stderr, "No audit input received.\n"); return 2; }
101-
int rc = run_engine(input);
102-
free(input);
103-
return rc;
168+
int rc = run_engine(input, save_path); free(input); return rc;
104169
}

0 commit comments

Comments
 (0)