package shell import "core:fmt" import "core:strings" import "core:sys/posix" import "core:os" import "core:log" import "core:mem" import "../parser" ShellState :: enum { Continue, Stop } get_prompt :: proc() -> string { dir := os.get_current_directory(context.temp_allocator) home := string(posix.getenv("HOME")) if strings.contains(dir, home) { dir, _ = strings.replace(dir, home, "~", 1, context.temp_allocator) } uid := posix.getuid() pw := posix.getpwuid(uid) user: string if pw == nil { user = "Skal" } else { user = string(pw.pw_name) } promt_parts := []string {user, " :: ", dir, " ยป "} prompt, err := strings.concatenate(promt_parts[:], context.temp_allocator) log.assertf(err == nil, "Memory allocation failed") return prompt } pipe_command :: proc(pipe_seq: ^parser.PipeSequence, maybe_fd: Maybe(posix.FD), idx: int) { cmd := pipe_seq.commands[idx] fd, missing_fd := maybe_fd.? if missing_fd { dup_status := posix.dup2(fd, posix.STDOUT_FILENO) log.assertf(dup_status > -1, "Pipe failed") close_status := posix.close(fd) log.assertf(close_status == .OK, "Pipe failed") } if idx == 0 { if pipe_seq^.rstdin != "" { stdin_fd := posix.open(pipe_seq^.rstdin, { .RDWR }, { .IRUSR, .IWUSR, .IRGRP, .IROTH }) log.assertf(stdin_fd > -1, "Pipe failed") dup_status := posix.dup2(stdin_fd, posix.STDIN_FILENO) log.assertf(dup_status > -1, "Pipe failed") close_status := posix.close(stdin_fd) log.assertf(close_status == .OK, "Pipe failed") } if pipe_seq^.rstdout != "" { stdout_fd := posix.open(pipe_seq^.rstdout, { .WRONLY, .CREAT, .TRUNC }, { .IRUSR, .IWUSR, .IRGRP, .IROTH }) log.assertf(stdout_fd > -1, "Pipe failed") dup_status := posix.dup2(stdout_fd, posix.STDOUT_FILENO) log.assertf(dup_status > -1, "Pipe failed") close_status := posix.close(stdout_fd) log.assertf(close_status == .OK, "Pipe failed") } if pipe_seq^.rstderr != "" { stderr_fd := posix.open(pipe_seq^.rstderr, { .WRONLY, .CREAT, .TRUNC }, { .IRUSR, .IWUSR, .IRGRP, .IROTH }) log.assertf(stderr_fd > -1, "Pipe failed") dup_status := posix.dup2(stderr_fd, posix.STDERR_FILENO) log.assertf(dup_status > -1, "Pipe failed") close_status := posix.close(stderr_fd) log.assertf(close_status == .OK, "Pipe failed") } } else { pipe: [2]posix.FD log.assertf(posix.pipe(&pipe) == .OK, "Pipe failed") pid := posix.fork() switch pid { case -1: // Error log.assertf(posix.pipe(&pipe) == .OK, "Fork failed") case 0: // Child close_status := posix.close(pipe[0]) log.assertf(close_status == .OK, "Pipe failed") pipe_command(pipe_seq, pipe[1], idx-1) case: // Parent close_status := posix.close(pipe[1]) log.assertf(close_status == .OK, "Pipe failed") dup_status := posix.dup2(pipe[0], posix.STDIN_FILENO) log.assertf(dup_status > -1, "Pipe failed") close_status = posix.close(pipe[0]) log.assertf(close_status == .OK, "Pipe failed") _ = posix.waitpid(pid, nil, { .UNTRACED, .CONTINUED }) } } argv, mem_err := make([]cstring, len(cmd.args)+1, context.temp_allocator) log.assertf(mem_err == nil, "Memory allocation failed") argv[0] = cmd.name for i := 0; i < len(cmd.args); i += 1 do argv[i+1] = cmd.args[i] _ = posix.execvp(argv[0], raw_data(argv)) fmt.printfln("skal: command not found: %s", argv[0]) os.exit(1) } @private change_dir :: proc(cmd: ^parser.Command) { if len(cmd.args) > 0 { ch_status := posix.chdir(cmd.args[0]) if ch_status != .OK do fmt.printf("cd: No such file or directory: %s\n", cmd.args[0]) } else { ch_status := posix.chdir(posix.getenv("HOME")) if ch_status != .OK do fmt.printf("cd: No such file or directory: %s\n", posix.getenv("HOME")) } } stop_chain :: proc(sequence_type: parser.SequenceType, exec_failed: bool) -> bool { stop_chain: bool switch sequence_type { case .Or: stop_chain = !exec_failed case .And: stop_chain = exec_failed case .Head: stop_chain = false } return stop_chain } execute_cmd_seq :: proc(cmd_seq: ^parser.CommandSequence) { for &pipe_seq in cmd_seq^.pipe_sequences { pid := posix.fork() switch pid { case -1: // Error log.assertf(true, "Fork failed") case 0: // Child pipe_command(&pipe_seq, nil, len(pipe_seq.commands)-1) case: // Parent if pipe_seq.is_background { fmt.printf("Background process: [%d]\n", pid) posix.waitpid(pid, nil, { .NOHANG}) } else { status: i32 posix.waitpid(pid, &status, { .UNTRACED, .CONTINUED }) exec_failed := posix.WIFEXITED(status) && posix.WEXITSTATUS(status) != 0 if stop_chain(pipe_seq.sequence_type, exec_failed) do return } } } } execute :: proc(cmd_seq: ^parser.CommandSequence) -> ShellState { if len(cmd_seq^.pipe_sequences) == 0 do return .Continue // if pipe sequence is above 0 it has at least one command first_cmd := cmd_seq^.pipe_sequences[0].commands[0] switch first_cmd.name { case "cd": change_dir(&first_cmd) case "exit": return .Stop case: execute_cmd_seq(cmd_seq) } return .Continue }