#include "interp.h" #include "arena_alloc.h" #include "context.h" #include "edit.h" #include "parser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static bool run = true; struct posvar posvars[POSVAR_MAX]; int posvar_count = 0; 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_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_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); } 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 mkvol(struct context* context, const char* volume, const char* str_fs_type, const char* device) { int fs_type; if (strcmp(str_fs_type, "tar") == 0) { fs_type = FS_TARFS; } else if (strcmp(str_fs_type, "fat16") == 0) { fs_type = FS_FAT16; } else if (strcmp(str_fs_type, "fat32") == 0) { fs_type = FS_FAT32; } else if (strcmp(str_fs_type, "iso9660") == 0) { fs_type = FS_ISO9660; } else { cprintf(context, "ERROR Unknown filesystem '%s'\n", str_fs_type); return; } int ret = create_volume(volume, fs_type, device); if (ret < 0) cprintf(context, "ERROR Could not create volume: %s\n", str_status[-ret]); } 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 ...\n"); cprintf(context, "cat \n"); cprintf(context, "ls \n"); cprintf(context, "mkfile \n"); cprintf(context, "mkdir \n"); cprintf(context, "rm \n"); cprintf(context, "edit \n"); cprintf(context, "terminfo\n"); cprintf(context, "cls\n"); cprintf(context, "mkvol \n"); cprintf(context, "procinfo\n"); cprintf(context, "quit\n"); cprintf(context, "stall \n"); cprintf(context, "copy \n"); } struct cmd_write_proc_ctx { int pgid; int cancel_pid; }; static void cmd_collect_proc(void* arg) { int pgid = (int)(uintptr_t)arg; char recv[1024]; int n; for (;;) { if ((n = stream_read(pgid, STREAM_OUT, (void*)recv, sizeof(recv) - 1)) > 0) stream_write(process_get_pgid(), STREAM_OUT, (void*)recv, n); else sched(); } } static void cmd_write_proc(void* arg) { struct cmd_write_proc_ctx* ctx = arg; uint8_t ch = 0; for (;;) { if (stream_read(process_get_pgid(), 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 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, "terminfo") == 0) { terminfo(context); } else if (strcmp(cmd->name, "cls") == 0) { cls(context); } else if (strcmp(cmd->name, "mkvol") == 0) { if (cmd->arg_count == 3) mkvol(context, cmd->args[0], cmd->args[1], cmd->args[2]); else cprintf(context, "ERROR No volume key, filesystem type or device key provided\n"); } 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; } int pid = exec_partial(volume, path); if (pid < 0) { cprintf(context, "ERROR could not run '%s': %s\n", cmd->name, str_status[-pid]); return; } int pgid = get_procgroup(pid); int i = 0; while (i < cmd->arg_count) { char* arg = cmd->args[i]; char *key, *value; if (arg[0] == '-') { key = &arg[1]; if (i < cmd->arg_count - 1) { value = cmd->args[++i]; } else { value = "yes"; } env_set(pgid, key, value, strlen(value) + 1); } i++; } 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 process_data* write_pdata = process_spawn(&cmd_write_proc, (void*)&wpctx); struct process_data* collect_pdata = process_spawn(&cmd_collect_proc, (void*)(uintptr_t)pgid); 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 - 1) / chunk_size; size_t rem = (context->strbuf.count - 1) % 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; } }