forked from valdanylchuk/breezybox
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbreezy_exec.c
More file actions
485 lines (397 loc) · 11.6 KB
/
breezy_exec.c
File metadata and controls
485 lines (397 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
#include "breezy_exec.h"
#include "breezy_vfs.h"
#include "esp_console.h"
#include "esp_log.h"
#include "esp_elf.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#define TEMP_PIPE_FILE BREEZYBOX_MOUNT_POINT "/.pipe_tmp"
#define TEMP_OUT_FILE BREEZYBOX_MOUNT_POINT "/.out_tmp"
// PATH for executable search (colon-separated like Unix)
#define EXEC_PATH "/root/bin"
// ELF magic bytes
static const uint8_t ELF_MAGIC[4] = {0x7f, 'E', 'L', 'F'};
static const char *TAG = "exec";
static vprintf_like_t s_orig_vprintf = NULL;
// Custom log handler that suppresses logs during redirects
static int null_vprintf(const char *fmt, va_list args)
{
return 0;
}
void breezybox_exec_init(void)
{
s_orig_vprintf = esp_log_set_vprintf(vprintf);
esp_log_set_vprintf(s_orig_vprintf);
}
// Check if file exists
static int file_exists(const char *path)
{
struct stat st;
return stat(path, &st) == 0 && S_ISREG(st.st_mode);
}
// Check if file has ELF magic
static int is_elf_file(const char *path)
{
FILE *f = fopen(path, "rb");
if (!f) return 0;
uint8_t magic[4];
size_t n = fread(magic, 1, 4, f);
fclose(f);
return (n == 4 && memcmp(magic, ELF_MAGIC, 4) == 0);
}
// Search for executable in PATH and CWD
// Returns allocated string with full path, or NULL if not found
static char *find_executable(const char *name)
{
char path[BREEZYBOX_MAX_PATH * 2];
// If name contains '/', treat as path (absolute or relative)
if (strchr(name, '/')) {
if (name[0] == '/') {
// Absolute path
if (file_exists(name)) {
return strdup(name);
}
} else {
// Relative path - resolve from CWD
breezybox_resolve_path(name, path, sizeof(path));
if (file_exists(path)) {
return strdup(path);
}
}
return NULL;
}
// Search in CWD first
breezybox_resolve_path(name, path, sizeof(path));
if (file_exists(path)) {
return strdup(path);
}
// Search in PATH
snprintf(path, sizeof(path), "%s/%s", EXEC_PATH, name);
if (file_exists(path)) {
return strdup(path);
}
return NULL;
}
// Argument parsing context
typedef struct {
char *buffer; // Original strdup'd buffer
char **argv; // Argument pointers
int argc; // Argument count
} parsed_args_t;
// Parse command line into argc/argv with basic quote support
// Returns 0 on success, -1 on error
static int parse_args(const char *cmdline, parsed_args_t *args)
{
args->buffer = NULL;
args->argv = NULL;
args->argc = 0;
if (!cmdline || !*cmdline) return 0;
// Make a working copy
char *buf = strdup(cmdline);
if (!buf) return -1;
// First pass: count arguments
int argc = 0;
char *p = buf;
while (*p) {
while (*p == ' ') p++;
if (!*p) break;
argc++;
// Handle quoted strings
if (*p == '"' || *p == '\'') {
char quote = *p++;
while (*p && *p != quote) p++;
if (*p) p++;
} else {
while (*p && *p != ' ') p++;
}
}
if (argc == 0) {
free(buf);
return 0;
}
// Allocate argv array
char **argv = malloc((argc + 1) * sizeof(char *));
if (!argv) {
free(buf);
return -1;
}
// Second pass: extract arguments
p = buf;
int i = 0;
while (*p && i < argc) {
while (*p == ' ') p++;
if (!*p) break;
// Handle quoted strings
if (*p == '"' || *p == '\'') {
char quote = *p++;
argv[i++] = p;
while (*p && *p != quote) p++;
if (*p) *p++ = '\0';
} else {
argv[i++] = p;
while (*p && *p != ' ') p++;
if (*p) *p++ = '\0';
}
}
argv[argc] = NULL;
args->buffer = buf;
args->argv = argv;
args->argc = argc;
return 0;
}
// Free parsed args
static void free_args(parsed_args_t *args)
{
if (args) {
free(args->argv);
free(args->buffer);
args->argv = NULL;
args->buffer = NULL;
args->argc = 0;
}
}
// Run an ELF file
static int run_elf(const char *path, int argc, char **argv)
{
ESP_LOGI(TAG, "Loading ELF: %s", path);
// Read entire file into memory
FILE *f = fopen(path, "rb");
if (!f) {
printf("Cannot open: %s\n", path);
return -1;
}
fseek(f, 0, SEEK_END);
long file_size = ftell(f);
fseek(f, 0, SEEK_SET);
if (file_size <= 0) {
printf("Invalid file: %s\n", path);
fclose(f);
return -1;
}
uint8_t *elf_data = malloc(file_size);
if (!elf_data) {
printf("Out of memory (%ld bytes needed)\n", file_size);
fclose(f);
return -1;
}
size_t bytes_read = fread(elf_data, 1, file_size, f);
fclose(f);
if (bytes_read != (size_t)file_size) {
printf("Read error\n");
free(elf_data);
return -1;
}
ESP_LOGI(TAG, "Loaded %ld bytes, initializing ELF loader", file_size);
// Initialize and relocate ELF
esp_elf_t elf;
int ret;
ret = esp_elf_init(&elf);
if (ret < 0) {
printf("ELF init failed: %d\n", ret);
free(elf_data);
return ret;
}
ret = esp_elf_relocate(&elf, elf_data);
if (ret < 0) {
printf("ELF relocate failed: %d\n", ret);
esp_elf_deinit(&elf);
free(elf_data);
return ret;
}
ESP_LOGI(TAG, "Executing with %d args", argc);
// Execute - pass argc/argv like a normal main()
ret = esp_elf_request(&elf, 0, argc, argv);
ESP_LOGI(TAG, "ELF returned: %d", ret);
esp_elf_deinit(&elf);
free(elf_data);
return ret;
}
// Sentinel value meaning "command not found as external"
#define EXEC_NOT_FOUND INT_MIN
// Try to run as external command (ELF binary)
// Returns EXEC_NOT_FOUND if not found, otherwise returns ELF's return code
static int try_run_external(const char *cmdline)
{
parsed_args_t args;
if (parse_args(cmdline, &args) != 0 || args.argc == 0) {
return EXEC_NOT_FOUND;
}
// Find executable
char *exe_path = find_executable(args.argv[0]);
if (!exe_path) {
free_args(&args);
return EXEC_NOT_FOUND; // Not found
}
// Check if it's an ELF
if (!is_elf_file(exe_path)) {
free(exe_path);
free_args(&args);
return EXEC_NOT_FOUND; // Not an ELF
}
int ret = run_elf(exe_path, args.argc, args.argv);
free(exe_path);
free_args(&args);
return ret;
}
// Execute with output redirect using temp file
static int exec_with_output_redirect(const char *cmd, const char *outfile, int append)
{
int ret = 0;
esp_log_set_vprintf(null_vprintf);
FILE *old_stdout = stdout;
FILE *tmp = fopen(TEMP_OUT_FILE, "w");
if (!tmp) {
esp_log_set_vprintf(s_orig_vprintf);
printf("Cannot create temp file\n");
return -1;
}
// Swap stdout
stdout = tmp;
// Try external first, then builtin
ret = try_run_external(cmd);
if (ret == EXEC_NOT_FOUND) {
esp_console_run(cmd, &ret);
}
fflush(stdout);
// Restore stdout
fclose(tmp);
stdout = old_stdout;
esp_log_set_vprintf(s_orig_vprintf);
// Copy temp to destination
FILE *src = fopen(TEMP_OUT_FILE, "r");
if (!src) return ret;
FILE *dst = fopen(outfile, append ? "a" : "w");
if (!dst) {
fclose(src);
unlink(TEMP_OUT_FILE);
return -1;
}
char buf[128];
size_t n;
while ((n = fread(buf, 1, sizeof(buf), src)) > 0) {
fwrite(buf, 1, n, dst);
}
fclose(src);
fclose(dst);
unlink(TEMP_OUT_FILE);
return ret;
}
static int exec_with_input_redirect(const char *cmd, const char *infile)
{
int ret = 0;
FILE *old_stdin = stdin;
FILE *in = fopen(infile, "r");
if (!in) {
printf("Cannot open: %s\n", infile);
return -1;
}
stdin = in;
// Try external first, then builtin
ret = try_run_external(cmd);
if (ret == EXEC_NOT_FOUND) {
esp_console_run(cmd, &ret);
}
fclose(in);
stdin = old_stdin;
return ret;
}
// Parse and execute a command with redirect support
int breezybox_exec(const char *cmdline)
{
if (!cmdline || !*cmdline) return 0;
// Make a working copy
char *line = strdup(cmdline);
if (!line) return -1;
char *cmd1 = NULL, *cmd2 = NULL;
char *infile = NULL, *outfile = NULL;
int append = 0;
int ret = 0;
// Check for pipe first
char *pipe_pos = strchr(line, '|');
if (pipe_pos) {
*pipe_pos = '\0';
cmd1 = line;
cmd2 = pipe_pos + 1;
// Trim whitespace
while (*cmd1 == ' ') cmd1++;
while (*cmd2 == ' ') cmd2++;
char *end1 = cmd1 + strlen(cmd1) - 1;
char *end2 = cmd2 + strlen(cmd2) - 1;
while (end1 > cmd1 && *end1 == ' ') *end1-- = '\0';
while (end2 > cmd2 && *end2 == ' ') *end2-- = '\0';
// Execute: cmd1 > tmpfile; cmd2 < tmpfile
exec_with_output_redirect(cmd1, TEMP_PIPE_FILE, 0);
ret = exec_with_input_redirect(cmd2, TEMP_PIPE_FILE);
unlink(TEMP_PIPE_FILE);
free(line);
return ret;
}
// Check for output redirect (>> or >)
char *redir_out = strstr(line, ">>");
if (redir_out) {
append = 1;
*redir_out = '\0';
outfile = redir_out + 2;
} else {
redir_out = strchr(line, '>');
if (redir_out) {
*redir_out = '\0';
outfile = redir_out + 1;
}
}
// Check for input redirect
char *redir_in = strchr(line, '<');
if (redir_in) {
*redir_in = '\0';
infile = redir_in + 1;
}
// Trim whitespace from all parts
cmd1 = line;
while (*cmd1 == ' ') cmd1++;
char *end = cmd1 + strlen(cmd1) - 1;
while (end > cmd1 && *end == ' ') *end-- = '\0';
if (outfile) {
while (*outfile == ' ') outfile++;
end = outfile + strlen(outfile) - 1;
while (end > outfile && *end == ' ') *end-- = '\0';
}
if (infile) {
while (*infile == ' ') infile++;
end = infile + strlen(infile) - 1;
while (end > infile && *end == ' ') *end-- = '\0';
}
// Resolve relative paths for redirects
char resolved_in[BREEZYBOX_MAX_PATH * 2];
char resolved_out[BREEZYBOX_MAX_PATH * 2];
if (infile && infile[0] != '/') {
breezybox_resolve_path(infile, resolved_in, sizeof(resolved_in));
infile = resolved_in;
}
if (outfile && outfile[0] != '/') {
breezybox_resolve_path(outfile, resolved_out, sizeof(resolved_out));
outfile = resolved_out;
}
// Execute with appropriate redirects
if (outfile && infile) {
// Both redirects - output takes precedence for now
ret = exec_with_output_redirect(cmd1, outfile, append);
} else if (outfile) {
ret = exec_with_output_redirect(cmd1, outfile, append);
} else if (infile) {
ret = exec_with_input_redirect(cmd1, infile);
} else {
// No redirects - try external first, then builtin
ret = try_run_external(cmd1);
if (ret == EXEC_NOT_FOUND) {
// Not found as external, try builtin
esp_console_run(cmd1, &ret);
}
}
free(line);
return ret;
}