673 lines
17 KiB
C
673 lines
17 KiB
C
#include "interp.h"
|
|
#include "arena_alloc.h"
|
|
#include "context.h"
|
|
#include "edit.h"
|
|
#include "parser.h"
|
|
#include <debugconsole.h>
|
|
#include <desc.h>
|
|
#include <filereader.h>
|
|
#include <filewriter.h>
|
|
#include <fs_types.h>
|
|
#include <kb.h>
|
|
#include <malloc.h>
|
|
#include <mprintf.h>
|
|
#include <path.h>
|
|
#include <printf.h>
|
|
#include <proc_info.h>
|
|
#include <process.h>
|
|
#include <stall.h>
|
|
#include <stddef.h>
|
|
#include <str_status.h>
|
|
#include <strconv.h>
|
|
#include <streams.h>
|
|
#include <string.h>
|
|
#include <system.h>
|
|
#include <tcursor.h>
|
|
#include <terminal.h>
|
|
#include <tscreen.h>
|
|
#include <write_file.h>
|
|
|
|
struct posvar posvars[POSVAR_MAX];
|
|
int posvar_count = 0;
|
|
|
|
static bool run = true;
|
|
|
|
bool interp_is_running(void) { return run; }
|
|
|
|
void interp_shutdown(void) { run = false; }
|
|
|
|
static void human_size(double size, double* out, char** str) {
|
|
static char* units[] = {"B", "KiB", "MiB", "GiB", "TiB", "PiB"};
|
|
|
|
size_t i = 0;
|
|
while (size >= 1024.0 && i < (sizeof(units) / sizeof(units[0])) - 1) {
|
|
size /= 1024.0;
|
|
i++;
|
|
}
|
|
|
|
*out = size;
|
|
*str = units[i];
|
|
}
|
|
|
|
static void echo(struct context* context, char** strings, size_t strings_count) {
|
|
for (size_t i = 0; i < strings_count; i++) {
|
|
cprintf(context, "%s", strings[i]);
|
|
|
|
if (i != strings_count - 1)
|
|
cprintf(context, " ");
|
|
}
|
|
}
|
|
|
|
static void mkfile(struct context* context, char** file_paths, size_t files_count) {
|
|
char volume[VOLUME_MAX];
|
|
const char* path;
|
|
int ret;
|
|
|
|
for (size_t i = 0; i < files_count; i++) {
|
|
const char* file_path = file_paths[i];
|
|
|
|
if (!path_parse(file_path, volume, &path)) {
|
|
cprintf(context, "ERROR bad path '%s'\n", file_path);
|
|
continue;
|
|
}
|
|
|
|
if ((ret = volume_open(volume)) < 0) {
|
|
cprintf(context, "ERROR could not open volume '%s': %s\n", volume, str_status[-ret]);
|
|
continue;
|
|
}
|
|
|
|
if ((ret = create_file(path)) < 0) {
|
|
cprintf(context, "ERROR could not create file '%s': %s\n", file_path, str_status[-ret]);
|
|
}
|
|
|
|
volume_close();
|
|
}
|
|
}
|
|
|
|
static void terminfo(struct context* context) {
|
|
size_t cols = 0, rows = 0;
|
|
terminal_dimensions(&cols, &rows);
|
|
cprintf(context, "%zu x %zu\n", cols, rows);
|
|
}
|
|
|
|
static void procinfo(struct context* context) {
|
|
struct proc_info* infos = malloc(sizeof(struct proc_info) * 1024);
|
|
memset(infos, 0, sizeof(struct proc_info) * 1024);
|
|
|
|
int count = get_proc_info(infos, 1024);
|
|
|
|
const char* proc_states[] = {"ready", "suspended", "partial"};
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
struct proc_info* info = &infos[i];
|
|
cprintf(context, "%-40s CPU=%-2d EXEC_PID=%-4d FLAGS=%08x PGID=%-4d PID=%-4d STATE=%-10s\n",
|
|
info->name, info->cpu, info->exec_pid, info->flags, info->pgid, info->pid,
|
|
proc_states[info->state]);
|
|
}
|
|
|
|
free(infos);
|
|
}
|
|
|
|
static void mkdir(struct context* context, char** dir_paths, size_t dirs_count) {
|
|
char volume[VOLUME_MAX];
|
|
const char* path;
|
|
int ret;
|
|
|
|
for (size_t i = 0; i < dirs_count; i++) {
|
|
const char* dir_path = dir_paths[i];
|
|
|
|
if (!path_parse(dir_path, volume, &path)) {
|
|
cprintf(context, "ERROR bad path '%s'\n", dir_path);
|
|
continue;
|
|
}
|
|
|
|
if ((ret = volume_open(volume)) < 0) {
|
|
cprintf(context, "ERROR could not open volume '%s': %s\n", volume, str_status[-ret]);
|
|
continue;
|
|
}
|
|
|
|
create_dir(path);
|
|
|
|
volume_close();
|
|
}
|
|
}
|
|
|
|
static void rm(struct context* context, char** paths, size_t count) {
|
|
char volume[VOLUME_MAX];
|
|
const char* path;
|
|
int ret;
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
const char* path1 = paths[i];
|
|
|
|
if (!path_parse(path1, volume, &path)) {
|
|
cprintf(context, "ERROR bad path '%s'\n", path1);
|
|
continue;
|
|
}
|
|
|
|
if ((ret = volume_open(volume)) < 0) {
|
|
cprintf(context, "ERROR could not open volume '%s': %s\n", volume, str_status[-ret]);
|
|
continue;
|
|
}
|
|
|
|
remove(path);
|
|
|
|
volume_close();
|
|
}
|
|
}
|
|
|
|
static void cat(struct context* context, char** file_paths, size_t files_count) {
|
|
char volume[VOLUME_MAX];
|
|
const char* path;
|
|
int ret;
|
|
|
|
for (size_t i = 0; i < files_count; i++) {
|
|
const char* file_path = file_paths[i];
|
|
|
|
if (!path_parse(file_path, volume, &path)) {
|
|
cprintf(context, "ERROR bad path '%s'\n", file_path);
|
|
continue;
|
|
}
|
|
|
|
struct filereader fr;
|
|
if ((ret = filereader_init(&fr, volume, path)) < 0) {
|
|
cprintf(context, "ERROR could not initialize filereader for '%s:%s'\n", volume, path);
|
|
return;
|
|
}
|
|
|
|
size_t chunk_size = 1024;
|
|
char* buffer = arena_malloc(&arena, chunk_size);
|
|
|
|
size_t chunks = fr.file_size / chunk_size;
|
|
size_t rem = fr.file_size % chunk_size;
|
|
|
|
for (size_t chunk = 0; chunk < chunks; chunk++) {
|
|
if ((ret = filereader_read(&fr, (uint8_t*)buffer, chunk_size)) < 0) {
|
|
filereader_fini(&fr);
|
|
cprintf(context, "ERROR filereader failed to read from '%s:%s'\n", volume, path);
|
|
return;
|
|
}
|
|
|
|
stream_write(process_get_exec_pgid(), STREAM_OUT, buffer, chunk_size);
|
|
}
|
|
|
|
if (rem > 0) {
|
|
if ((ret = filereader_read(&fr, (uint8_t*)buffer, rem)) < 0) {
|
|
filereader_fini(&fr);
|
|
cprintf(context, "ERROR filereader failed to read from '%s:%s'\n", volume, path);
|
|
return;
|
|
}
|
|
|
|
stream_write(process_get_exec_pgid(), STREAM_OUT, buffer, rem);
|
|
}
|
|
|
|
filereader_fini(&fr);
|
|
}
|
|
}
|
|
|
|
static void copy(struct context* context, const char* path_a, const char* path_b) {
|
|
char volume_a[VOLUME_MAX], volume_b[VOLUME_MAX];
|
|
const char *path_a1, *path_b1;
|
|
int ret;
|
|
|
|
if (!path_parse(path_a, volume_a, &path_a1)) {
|
|
cprintf(context, "ERROR bad path '%s'\n", path_a);
|
|
return;
|
|
}
|
|
|
|
if (!(path_parse(path_b, volume_b, &path_b1))) {
|
|
cprintf(context, "ERROR bad path '%s'\n", path_b);
|
|
return;
|
|
}
|
|
|
|
struct desc desc;
|
|
|
|
if ((ret = volume_open(volume_a)) < 0) {
|
|
cprintf(context, "ERROR could not open volume '%s'\n", volume_a);
|
|
return;
|
|
}
|
|
|
|
if ((ret = describe(path_a1, &desc)) < 0 || desc.type != FS_FILE) {
|
|
volume_close();
|
|
cprintf(context, "ERROR invalid source '%s:%s'\n", volume_a, path_a1);
|
|
return;
|
|
}
|
|
|
|
size_t file_size = desc.size;
|
|
|
|
volume_close();
|
|
|
|
if ((ret = volume_open(volume_b)) < 0) {
|
|
cprintf(context, "ERROR could not open volume '%s'\n", volume_b);
|
|
return;
|
|
}
|
|
|
|
create_file(path_b1);
|
|
|
|
volume_close();
|
|
|
|
size_t chunk_size = 10 * 1024 * 1024;
|
|
char* buffer = malloc(chunk_size);
|
|
|
|
if (buffer == NULL) {
|
|
return;
|
|
}
|
|
|
|
size_t cursor = 0;
|
|
|
|
while (cursor < file_size) {
|
|
size_t to_read = chunk_size;
|
|
|
|
if (file_size - cursor < to_read)
|
|
to_read = file_size - cursor;
|
|
|
|
if ((ret = volume_open(volume_a)) < 0) {
|
|
cprintf(context, "ERROR could not open volume '%s'\n", volume_a);
|
|
break;
|
|
}
|
|
|
|
if ((ret = read_file(path_a1, cursor, (uint8_t*)buffer, to_read)) < 0) {
|
|
volume_close();
|
|
cprintf(context, "ERROR read '%s:%s'\n", volume_a, path_a1);
|
|
break;
|
|
}
|
|
|
|
volume_close();
|
|
|
|
if ((ret = volume_open(volume_b)) < 0) {
|
|
cprintf(context, "ERROR could not open volume '%s'\n", volume_b);
|
|
break;
|
|
}
|
|
|
|
uint32_t wf_flags = (cursor == 0) ? WF_TRUNCATE : 0;
|
|
|
|
if ((ret = write_file(path_b1, cursor, (uint8_t*)buffer, to_read, wf_flags)) < 0) {
|
|
volume_close();
|
|
cprintf(context, "ERROR write '%s:%s'\n", volume_b, path_b1);
|
|
break;
|
|
}
|
|
|
|
volume_close();
|
|
|
|
cursor += to_read;
|
|
}
|
|
|
|
free(buffer);
|
|
}
|
|
|
|
static void ls(struct context* context, const char* path_string) {
|
|
struct desc desc;
|
|
char volume[VOLUME_MAX];
|
|
const char* path;
|
|
int ret;
|
|
struct dir_entry entry;
|
|
|
|
if (!path_parse(path_string, volume, &path)) {
|
|
cprintf(context, "ERROR bad path '%s'\n", path_string);
|
|
return;
|
|
}
|
|
|
|
if ((ret = volume_open(volume)) < 0) {
|
|
cprintf(context, "ERROR could not open volume '%s': %s\n", volume, str_status[-ret]);
|
|
return;
|
|
}
|
|
|
|
describe(path, &desc);
|
|
|
|
if (desc.type != FS_DIR) {
|
|
cprintf(context, "ERROR '%s' is not a directory\n", path_string);
|
|
volume_close();
|
|
return;
|
|
}
|
|
|
|
size_t entries = desc.size;
|
|
cprintf(context, "total entries: %zu\n", entries);
|
|
cprintf(context, "\n");
|
|
|
|
for (size_t entry_num = 0; entry_num < entries; entry_num++) {
|
|
memset(&desc, 0, sizeof(desc));
|
|
memset(&entry, 0, sizeof(entry));
|
|
|
|
read_dir_entry(path, &entry, entry_num);
|
|
describe(entry.path, &desc);
|
|
|
|
char* hs_string;
|
|
double hs;
|
|
human_size((double)desc.size, &hs, &hs_string);
|
|
|
|
char size_buf[64];
|
|
snprintf(size_buf, sizeof(size_buf), "%.2f %s", hs, hs_string);
|
|
|
|
char type = (desc.type == FS_DIR ? 'D' : 'F');
|
|
|
|
cprintf(context, "%c %-50s %-10s\n", type, path_basename(entry.path), size_buf);
|
|
}
|
|
|
|
volume_close();
|
|
}
|
|
|
|
static void edit(struct context* context, const char* path_string) {
|
|
struct desc desc;
|
|
char volume[VOLUME_MAX];
|
|
const char* path;
|
|
int ret;
|
|
|
|
if (!path_parse(path_string, volume, &path)) {
|
|
cprintf(context, "ERROR bad path '%s'\n", path_string);
|
|
return;
|
|
}
|
|
|
|
if ((ret = volume_open(volume)) < 0) {
|
|
cprintf(context, "ERROR could not open volume '%s': %s\n", volume, str_status[-ret]);
|
|
return;
|
|
}
|
|
|
|
if ((ret = describe(path, &desc)) < 0) {
|
|
cprintf(context, "ERROR could not describe '%s': %s\n", path, str_status[-ret]);
|
|
volume_close();
|
|
return;
|
|
}
|
|
|
|
char* str = malloc(desc.size + 1);
|
|
|
|
if (str == NULL) {
|
|
volume_close();
|
|
return;
|
|
}
|
|
|
|
memset(str, 0, desc.size);
|
|
|
|
if ((ret = read_file(path, 0, (uint8_t*)str, desc.size)) < 0) {
|
|
free(str);
|
|
volume_close();
|
|
return;
|
|
}
|
|
|
|
volume_close();
|
|
|
|
edit_start(volume, path, str, desc.size, false);
|
|
}
|
|
|
|
static void view(struct context* context, const char* path_string) {
|
|
struct desc desc;
|
|
char volume[VOLUME_MAX];
|
|
const char* path;
|
|
int ret;
|
|
|
|
if (!path_parse(path_string, volume, &path)) {
|
|
cprintf(context, "ERROR bad path '%s'\n", path_string);
|
|
return;
|
|
}
|
|
|
|
if ((ret = volume_open(volume)) < 0) {
|
|
cprintf(context, "ERROR could not open volume '%s': %s\n", volume, str_status[-ret]);
|
|
return;
|
|
}
|
|
|
|
if ((ret = describe(path, &desc)) < 0) {
|
|
cprintf(context, "ERROR could not describe '%s': %s\n", path, str_status[-ret]);
|
|
volume_close();
|
|
return;
|
|
}
|
|
|
|
char* str = malloc(desc.size + 1);
|
|
|
|
if (str == NULL) {
|
|
volume_close();
|
|
return;
|
|
}
|
|
|
|
memset(str, 0, desc.size);
|
|
|
|
if ((ret = read_file(path, 0, (uint8_t*)str, desc.size)) < 0) {
|
|
free(str);
|
|
volume_close();
|
|
return;
|
|
}
|
|
|
|
volume_close();
|
|
|
|
edit_start(volume, path, str, desc.size, true);
|
|
}
|
|
|
|
static void quit1(struct context* context) {
|
|
cprintf(context, "Goodbye!\n");
|
|
interp_shutdown();
|
|
}
|
|
|
|
static void cls(struct context* context) { cprintf(context, ANSIQ_CUR_HOME ANSIQ_SCR_CLR_ALL); }
|
|
|
|
static void stall(uint64_t ms) { stall_ms(ms); }
|
|
|
|
static void help(struct context* context) {
|
|
cprintf(context, "Available commands:\n");
|
|
cprintf(context, "help\n");
|
|
cprintf(context, "echo <word1> <word2> <word3> ...\n");
|
|
cprintf(context, "cat <file path>\n");
|
|
cprintf(context, "ls <directory path>\n");
|
|
cprintf(context, "mkfile <file path>\n");
|
|
cprintf(context, "mkdir <directory path>\n");
|
|
cprintf(context, "rm <path>\n");
|
|
cprintf(context, "edit <path>\n");
|
|
cprintf(context, "view <path>\n");
|
|
cprintf(context, "terminfo\n");
|
|
cprintf(context, "cls\n");
|
|
cprintf(context, "procinfo\n");
|
|
cprintf(context, "quit\n");
|
|
cprintf(context, "stall <milliseconds>\n");
|
|
cprintf(context, "copy <path A> <path B>\n");
|
|
}
|
|
|
|
struct cmd_write_proc_ctx {
|
|
int pgid;
|
|
int cancel_pid;
|
|
};
|
|
|
|
struct cmd_collect_proc_ctx {
|
|
int pgid;
|
|
int collect_pid;
|
|
};
|
|
|
|
static void cmd_write_proc(void* arg) {
|
|
struct cmd_write_proc_ctx* ctx = arg;
|
|
|
|
uint8_t ch = 0;
|
|
for (;;) {
|
|
if (stream_read(STREAM_IN, &ch, 1) > 0) {
|
|
if (ch == KB_CTRL('C')) {
|
|
kill(ctx->cancel_pid);
|
|
return;
|
|
}
|
|
|
|
stream_write(ctx->pgid, STREAM_IN, &ch, 1);
|
|
} else {
|
|
sched();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cmd_collect_proc(void* arg) {
|
|
struct cmd_collect_proc_ctx* ctx = arg;
|
|
|
|
int n;
|
|
char recv[1024];
|
|
for (;;) {
|
|
if ((n = stream_read(STREAM_OUT, recv, sizeof(recv))) > 0) {
|
|
stream_write(process_get_exec_pgid(), STREAM_OUT, recv, n);
|
|
} else {
|
|
sched();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void execute_cmd(struct ast_cmd* cmd, struct context* context, bool run_bg) {
|
|
if (strcmp(cmd->name, "echo") == 0) {
|
|
echo(context, cmd->args, cmd->arg_count);
|
|
} else if (strcmp(cmd->name, "help") == 0) {
|
|
help(context);
|
|
} else if (strcmp(cmd->name, "cat") == 0) {
|
|
cat(context, cmd->args, cmd->arg_count);
|
|
} else if (strcmp(cmd->name, "ls") == 0) {
|
|
if (cmd->arg_count == 1)
|
|
ls(context, cmd->args[0]);
|
|
else
|
|
cprintf(context, "ERROR No directory path provided\n");
|
|
} else if (strcmp(cmd->name, "quit") == 0) {
|
|
quit1(context);
|
|
} else if (strcmp(cmd->name, "mkfile") == 0) {
|
|
mkfile(context, cmd->args, cmd->arg_count);
|
|
} else if (strcmp(cmd->name, "mkdir") == 0) {
|
|
mkdir(context, cmd->args, cmd->arg_count);
|
|
} else if (strcmp(cmd->name, "rm") == 0) {
|
|
rm(context, cmd->args, cmd->arg_count);
|
|
} else if (strcmp(cmd->name, "edit") == 0) {
|
|
if (cmd->arg_count == 1)
|
|
edit(context, cmd->args[0]);
|
|
else
|
|
cprintf(context, "ERROR No file path provided\n");
|
|
} else if (strcmp(cmd->name, "view") == 0) {
|
|
if (cmd->arg_count == 1)
|
|
view(context, cmd->args[0]);
|
|
else
|
|
cprintf(context, "ERROR No file path provided\n");
|
|
} else if (strcmp(cmd->name, "terminfo") == 0) {
|
|
terminfo(context);
|
|
} else if (strcmp(cmd->name, "cls") == 0) {
|
|
cls(context);
|
|
} else if (strcmp(cmd->name, "procinfo") == 0) {
|
|
procinfo(context);
|
|
} else if (strcmp(cmd->name, "stall") == 0) {
|
|
if (cmd->arg_count == 1) {
|
|
uint64_t ms = str_to_uint64(cmd->args[0]);
|
|
stall(ms);
|
|
} else {
|
|
cprintf(context, "ERROR stall requires a timeout argument\n");
|
|
}
|
|
} else if (strcmp(cmd->name, "copy") == 0) {
|
|
if (cmd->arg_count == 2)
|
|
copy(context, cmd->args[0], cmd->args[1]);
|
|
else
|
|
cprintf(context, "ERROR copy requires source and destination\n");
|
|
} else {
|
|
char volume[VOLUME_MAX];
|
|
const char* path;
|
|
|
|
if (!(path_parse(cmd->name, volume, &path))) {
|
|
cprintf(context, "ERROR bad path '%s'\n", cmd->name);
|
|
return;
|
|
}
|
|
|
|
struct strbuf tmpstrbuf;
|
|
memset(&tmpstrbuf, 0, sizeof(tmpstrbuf));
|
|
|
|
for (int i = 0; i < cmd->arg_count; i++) {
|
|
strbuf_append_str(&tmpstrbuf, cmd->args[i]);
|
|
strbuf_append(&tmpstrbuf, ' ');
|
|
}
|
|
|
|
strbuf_append(&tmpstrbuf, '\0');
|
|
|
|
int pid = exec_partial(volume, path, tmpstrbuf.items);
|
|
|
|
if (pid < 0) {
|
|
cprintf(context, "ERROR could not run '%s': %s\n", cmd->name, str_status[-pid]);
|
|
return;
|
|
}
|
|
|
|
int pgid = get_procgroup(pid);
|
|
|
|
if (run_bg) {
|
|
exec_partial_fini(pid);
|
|
|
|
cprintf(context, "started background process: PID %d\n", pid);
|
|
} else {
|
|
struct cmd_write_proc_ctx wpctx = {.pgid = pgid, .cancel_pid = pid};
|
|
struct cmd_collect_proc_ctx cpctx = {.pgid = pgid, .collect_pid = pid};
|
|
|
|
struct process_data* write_pdata = process_spawn(&cmd_write_proc, (void*)&wpctx);
|
|
struct process_data* collect_pdata = process_spawn(&cmd_collect_proc, (void*)&cpctx);
|
|
|
|
exec_partial_fini(pid);
|
|
wait_for_pid(pid);
|
|
|
|
kill(write_pdata->pid);
|
|
process_data_free(write_pdata);
|
|
kill(collect_pdata->pid);
|
|
process_data_free(collect_pdata);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void execute_redir(struct ast_redir* redir, struct context* context) {
|
|
char volume[VOLUME_MAX];
|
|
const char* path;
|
|
int ret;
|
|
|
|
if (!(path_parse(redir->file_path, volume, &path))) {
|
|
cprintf(context, "ERROR bad path '%s'\n", redir->file_path);
|
|
return;
|
|
}
|
|
|
|
uint32_t fw_flags = FW_CREATE_FILE | FW_TRUNCATE;
|
|
|
|
struct filewriter fw;
|
|
if ((ret = filewriter_init(&fw, volume, path, fw_flags)) < 0) {
|
|
cprintf(context, "ERROR could not initialize filewriter for '%s:%s'\n", volume, path);
|
|
return;
|
|
}
|
|
|
|
if (context->strbuf.count == 0) {
|
|
filewriter_fini(&fw);
|
|
return;
|
|
}
|
|
|
|
size_t chunk_size = 1024;
|
|
size_t chunks = context->strbuf.count / chunk_size;
|
|
size_t rem = context->strbuf.count % chunk_size;
|
|
|
|
for (size_t chunk = 0; chunk < chunks; chunk++) {
|
|
if ((ret = filewriter_write(&fw, (uint8_t*)&context->strbuf.items[chunk * chunk_size],
|
|
chunk_size)) < 0) {
|
|
filewriter_fini(&fw);
|
|
cprintf(context, "ERROR filewriter failed to write to '%s:%s'\n", volume, path);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (rem > 0) {
|
|
if ((ret = filewriter_write(&fw, (uint8_t*)&context->strbuf.items[chunks * chunk_size], rem)) <
|
|
0) {
|
|
filewriter_fini(&fw);
|
|
cprintf(context, "ERROR filewriter failed to write to '%s:%s'\n", volume, path);
|
|
return;
|
|
}
|
|
}
|
|
|
|
filewriter_fini(&fw);
|
|
}
|
|
|
|
void execute(struct ast_node* root, struct context* context, bool run_bg) {
|
|
struct context subcontext;
|
|
memset(&subcontext, 0, sizeof(subcontext));
|
|
|
|
switch (root->class) {
|
|
case AST_NODE_CLASS_CMD:
|
|
execute_cmd(&root->u.cmd, context, run_bg);
|
|
break;
|
|
case AST_NODE_CLASS_SUBSHELL:
|
|
execute(root->u.subshell.inner, &subcontext, run_bg);
|
|
break;
|
|
case AST_NODE_CLASS_SEQ:
|
|
execute(root->u.seq.left, context, run_bg);
|
|
execute(root->u.seq.right, context, run_bg);
|
|
break;
|
|
case AST_NODE_CLASS_REDIR:
|
|
execute(root->u.redir.source, &subcontext, run_bg);
|
|
execute_redir(&root->u.redir, &subcontext);
|
|
break;
|
|
case AST_NODE_CLASS_RUN_BG:
|
|
execute(root->u.run_bg.expr, context, true);
|
|
break;
|
|
}
|
|
}
|