55#include <string.h>
66#include <unistd.h>
77#include <sys/wait.h>
8+ #include <sys/stat.h>
9+ #include <time.h>
810
911static 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
1720static 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
58138static 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
84153int 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