skal/shell/shell.odin
2025-08-01 23:16:51 +02:00

197 lines
5.7 KiB
Odin

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
}