feat: implement shell operators (|, >, >>, <, &, &&, ||, ;) and kernel-level TTY FDs

This commit is contained in:
boreddevnl 2026-05-13 15:50:43 +02:00
parent f450ba4b51
commit 74f7710ea0
6 changed files with 756 additions and 76 deletions

View file

@ -7,11 +7,88 @@ The BoredOS Terminal provides a powerful command-line interface (CLI) for advanc
The default shell in BoredOS is **BoredShell (Bsh)**, a userspace shell with a dedicated terminal app. It features: The default shell in BoredOS is **BoredShell (Bsh)**, a userspace shell with a dedicated terminal app. It features:
- **ANSI Color Support**: Rich text output with colors and styles. - **ANSI Color Support**: Rich text output with colors and styles.
- **Command History**: Use the **Up** and **Down** arrow keys to navigate through your previous commands (up to 64 history entries). - **Command History**: Use the **Up** and **Down** arrow keys to navigate through your previous commands (up to 64 history entries).
- **Output Redirection**: - **Redirection**:
- `command > file`: Write output to a new file (or overwrite existing). - `command > file`: Write output to a new file (or overwrite existing).
- `command >> file`: Append output to an existing file. - `command >> file`: Append output to an existing file.
- `command < file`: Use a file as command input.
- **Piping**: - **Piping**:
- `command1 | command2`: Pass the output of the first command as input to the second. - `command1 | command2`: Pass the output of the first command as input to the second.
- **Command Chaining**:
- `cmd1 ; cmd2`: Run `cmd2` after `cmd1`.
- `cmd1 && cmd2`: Run `cmd2` only if `cmd1` succeeds.
- `cmd1 || cmd2`: Run `cmd2` only if `cmd1` fails.
- **Background Launch**:
- `command &`: Start command without blocking the prompt.
## Shell Operators & Scripting
Bsh provides a suite of operators for managing I/O and controlling execution flow.
### I/O Redirection
Redirection allows you to change where a command reads its input from or writes its output to by manipulating kernel-level file descriptors (FDs).
| Operator | Action | Description |
| :--- | :--- | :--- |
| `>` | **Overwrite** | Redirects standard output (FD 1) to a file, creating it if it doesn't exist or truncating it if it does. |
| `>>` | **Append** | Redirects standard output (FD 1) to a file, appending new data to the end without clearing existing content. |
| `<` | **Input** | Redirects standard input (FD 0) to read from a file instead of the terminal. |
**Example:**
```bash
echo "Hello World" > greetings.txt
echo "Second line" >> greetings.txt
cat < greetings.txt
```
**How it works:**
The shell uses `sys_open` to obtain a handle for the target file, then uses `sys_dup2` to replace the process's standard FD (0 or 1) with the new file handle. This ensures the command's standard library calls (like `printf` or `scanf`) interact with the file instead of the terminal.
### Pipelines (`|`)
Pipelines connect the output of one command directly to the input of another.
**Usage:** `cmd1 | cmd2 | cmd3`
**Example:**
```bash
ls /bin | cat | cat
```
**How it works:**
The shell creates an anonymous pipe using `sys_pipe`, which returns two FDs: a read end and a write end.
- The shell duplicates the **write end** to FD 1 for the first command.
- The shell duplicates the **read end** to FD 0 for the second command.
- Both commands run in parallel, and the kernel manages the data buffer between them.
### Execution Control
| Operator | Name | Description |
| :--- | :--- | :--- |
| `;` | **Semicolon** | Executes commands sequentially. `cmd2` runs only after `cmd1` finishes. |
| `&&` | **Logical AND** | Executes `cmd2` only if `cmd1` returns a success status (exit code 0). |
| `||` | **Logical OR** | Executes `cmd2` only if `cmd1` fails (returns a non-zero exit code). |
**Example:**
```bash
# Compile and run only on success
make && ./boredos.elf
# Recover from a missing command
missing_tool || echo "Tool not found, using fallback"
```
### Background Execution (`&`)
Appending `&` to a command tells the shell not to wait for the process to complete before returning to the prompt.
**How it works:**
Normally, the shell calls `sys_waitpid` to block until a child process exits. With `&`, the shell skips this wait, allowing the process to run asynchronously while you continue using the terminal.
Operator precedence follows common POSIX shell rules:
1. Redirection and pipelines (`<`, `>`, `>>`, `|`)
2. Conditionals (`&&`, `||`)
3. List separators (`;`, `&`)
### Bsh Configuration ### Bsh Configuration
@ -49,13 +126,12 @@ Below are some of the most used commands available in `/bin`:
| `ls` | List files and directories in the current path. | | `ls` | List files and directories in the current path. |
| `cd` | Change the current working directory. | | `cd` | Change the current working directory. |
| `cat` | Display the contents of a file. | | `cat` | Display the contents of a file. |
| `ls` | List directory contents. |
| `rm` | Remove a file. | | `rm` | Remove a file. |
| `mkdir` | Create a new directory. | | `mkdir` | Create a new directory. |
| `man` | View the manual for a specific command (e.g., `man ls`). | | `man` | View the manual for a specific command (e.g., `man ls`). |
| `lsblk` | List block devices and partitions with size, type, filesystem, label, and flags. | | `lsblk` | List block devices and partitions. |
| `du` | Report disk usage for files and directories, recursively. | | `du` | Report disk usage for files and directories. |
| `sysfetch` | Display system and hardware information. | | `sysfetch` | Display system information and BoredOS branding. |
--- ---

View file

@ -302,6 +302,40 @@ process_t* process_create_elf(const char* filepath, const char* args_str, bool t
new_proc->fd_kind[i] = 0; new_proc->fd_kind[i] = 0;
new_proc->fd_flags[i] = 0; new_proc->fd_flags[i] = 0;
} }
process_t *parent = process_get_current();
if (parent) {
for (int i = 0; i < MAX_PROCESS_FDS; i++) {
if (parent->fds[i]) {
new_proc->fds[i] = parent->fds[i];
new_proc->fd_kind[i] = parent->fd_kind[i];
new_proc->fd_flags[i] = parent->fd_flags[i];
if (new_proc->fd_kind[i] == PROC_FD_KIND_FILE) {
process_fd_file_ref_t *ref = (process_fd_file_ref_t *)new_proc->fds[i];
if (ref) ref->refs++;
} else if (new_proc->fd_kind[i] == PROC_FD_KIND_PIPE_READ) {
process_fd_pipe_t *pipe = (process_fd_pipe_t *)new_proc->fds[i];
if (pipe) pipe->readers++;
} else if (new_proc->fd_kind[i] == PROC_FD_KIND_PIPE_WRITE) {
process_fd_pipe_t *pipe = (process_fd_pipe_t *)new_proc->fds[i];
if (pipe) pipe->writers++;
}
}
}
}
// Always set up TTY FDs if a TTY is provided and they aren't already set
if (tty_id >= 0) {
for (int i = 0; i < 3; i++) {
if (!new_proc->fds[i]) {
new_proc->fds[i] = (void*)(uint64_t)1;
new_proc->fd_kind[i] = PROC_FD_KIND_TTY;
new_proc->fd_flags[i] = (i == 0) ? 0 : 1;
}
}
}
new_proc->gui_event_head = 0; new_proc->gui_event_head = 0;
new_proc->gui_event_tail = 0; new_proc->gui_event_tail = 0;
new_proc->ui_window = NULL; new_proc->ui_window = NULL;
@ -314,7 +348,6 @@ process_t* process_create_elf(const char* filepath, const char* args_str, bool t
new_proc->exit_status = 0; new_proc->exit_status = 0;
process_init_signal_state(new_proc); process_init_signal_state(new_proc);
process_t *parent = process_get_current();
if (parent) { if (parent) {
extern void mem_memcpy(void *dest, const void *src, size_t len); extern void mem_memcpy(void *dest, const void *src, size_t len);
mem_memcpy(new_proc->cwd, parent->cwd, 1024); mem_memcpy(new_proc->cwd, parent->cwd, 1024);

View file

@ -17,6 +17,7 @@
#define PROC_FD_KIND_FILE 1 #define PROC_FD_KIND_FILE 1
#define PROC_FD_KIND_PIPE_READ 2 #define PROC_FD_KIND_PIPE_READ 2
#define PROC_FD_KIND_PIPE_WRITE 3 #define PROC_FD_KIND_PIPE_WRITE 3
#define PROC_FD_KIND_TTY 4
typedef struct { typedef struct {
void *file; void *file;

View file

@ -1102,6 +1102,12 @@ static uint64_t fs_cmd_read(const syscall_args_t *args) {
return n; return n;
} }
if (proc->fd_kind[fd] == PROC_FD_KIND_TTY) {
if (proc->tty_id < 0) return (uint64_t)-1;
extern int tty_read_input(int tty_id, char *buf, size_t len);
return (uint64_t)tty_read_input(proc->tty_id, (char *)buf, (size_t)len);
}
return -1; return -1;
} }
@ -1139,6 +1145,12 @@ static uint64_t fs_cmd_write(const syscall_args_t *args) {
return n; return n;
} }
if (proc->fd_kind[fd] == PROC_FD_KIND_TTY) {
if (proc->tty_id < 0) return (uint64_t)-1;
extern int tty_write_output(int tty_id, const char *buf, size_t len);
return (uint64_t)tty_write_output(proc->tty_id, (const char *)buf, (size_t)len);
}
return -1; return -1;
} }

View file

@ -2,6 +2,8 @@
// This software is released under the GNU General Public License v3.0. See LICENSE file for details. // This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms. // This header needs to maintain in any file it is present in, as per the GPL license terms.
#include <stdlib.h> #include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <syscall.h> #include <syscall.h>
#include <stdbool.h> #include <stdbool.h>
#include "utf-8.h" #include "utf-8.h"
@ -324,6 +326,17 @@ static void reset_color(void) {
sys_set_text_color(g_color_default); sys_set_text_color(g_color_default);
} }
static void shell_write(const char *buf, int len) {
if (!buf || len <= 0) return;
write(1, buf, (size_t)len);
}
static void shell_writeln(const char *buf) {
if (!buf) return;
shell_write(buf, (int)strlen(buf));
shell_write("\n", 1);
}
static void prompt_emit(const char *text, int len, char *out, int *out_idx, int max_len, bool do_write) { static void prompt_emit(const char *text, int len, char *out, int *out_idx, int max_len, bool do_write) {
if (!text || len <= 0) return; if (!text || len <= 0) return;
if (do_write) sys_write(1, text, len); if (do_write) sys_write(1, text, len);
@ -897,9 +910,10 @@ static void show_matches(const char *prompt_tmpl, const char *line, int len, cha
redraw_input(prompt_tmpl, line, len, len); redraw_input(prompt_tmpl, line, len, len);
} }
static void wait_for_pid(int pid) { static int wait_for_pid(int pid) {
int status = 0;
while (1) { while (1) {
int rc = sys_waitpid(pid, NULL, 1); int rc = sys_waitpid(pid, &status, 1);
if (rc == pid || rc < 0) break; if (rc == pid || rc < 0) break;
if (g_tty_id >= 0) { if (g_tty_id >= 0) {
int fg = sys_tty_get_fg(g_tty_id); int fg = sys_tty_get_fg(g_tty_id);
@ -907,6 +921,13 @@ static void wait_for_pid(int pid) {
} }
sleep(10); sleep(10);
} }
return status;
}
static int bsh_open_file(const char *path, const char *mode, bool *is_kernel) {
if (!path || !mode) return -1;
if (is_kernel) *is_kernel = true;
return sys_open(path, mode);
} }
static void cmd_clear(void) { static void cmd_clear(void) {
@ -927,7 +948,7 @@ static int builtin_cd(int argc, char *argv[]) {
static int builtin_pwd(void) { static int builtin_pwd(void) {
char cwd[256]; char cwd[256];
if (getcwd(cwd, sizeof(cwd))) { if (getcwd(cwd, sizeof(cwd))) {
printf("%s\n", cwd); shell_writeln(cwd);
return 0; return 0;
} }
return 1; return 1;
@ -935,10 +956,10 @@ static int builtin_pwd(void) {
static int builtin_echo(int argc, char *argv[]) { static int builtin_echo(int argc, char *argv[]) {
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {
if (i > 1) sys_write(1, " ", 1); if (i > 1) shell_write(" ", 1);
sys_write(1, argv[i], (int)strlen(argv[i])); shell_write(argv[i], (int)strlen(argv[i]));
} }
sys_write(1, "\n", 1); shell_write("\n", 1);
return 0; return 0;
} }
@ -956,7 +977,7 @@ static int builtin_ls(int argc, char *argv[]) {
} }
if (!info.is_directory) { if (!info.is_directory) {
set_color(g_color_file); set_color(g_color_file);
printf("%s\n", info.name); shell_writeln(info.name);
reset_color(); reset_color();
return 0; return 0;
} }
@ -973,12 +994,18 @@ static int builtin_ls(int argc, char *argv[]) {
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
if (entries[i].is_directory) { if (entries[i].is_directory) {
set_color(g_color_dir); set_color(g_color_dir);
printf("[DIR] %s\n", entries[i].name); shell_write("[DIR] ", 7);
shell_writeln(entries[i].name);
} else { } else {
set_color(g_color_file); set_color(g_color_file);
printf("[FILE] %s", entries[i].name); shell_write("[FILE] ", 7);
shell_write(entries[i].name, (int)strlen(entries[i].name));
set_color(g_color_size); set_color(g_color_size);
printf(" (%d bytes)\n", entries[i].size); char size_buf[32];
itoa((int)entries[i].size, size_buf);
shell_write(" (", 2);
shell_write(size_buf, (int)strlen(size_buf));
shell_write(" bytes)\n", 8);
} }
} }
reset_color(); reset_color();
@ -987,10 +1014,12 @@ static int builtin_ls(int argc, char *argv[]) {
static int builtin_cat(int argc, char *argv[]) { static int builtin_cat(int argc, char *argv[]) {
if (argc < 2) { if (argc < 2) {
set_color(g_color_error); char buffer[4096];
printf("Usage: cat <file>\n"); int bytes;
reset_color(); while ((bytes = read(0, buffer, sizeof(buffer))) > 0) {
return 1; shell_write(buffer, bytes);
}
return 0;
} }
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {
@ -1004,7 +1033,7 @@ static int builtin_cat(int argc, char *argv[]) {
char buffer[4096]; char buffer[4096];
int bytes; int bytes;
while ((bytes = sys_read(fd, buffer, sizeof(buffer))) > 0) { while ((bytes = sys_read(fd, buffer, sizeof(buffer))) > 0) {
sys_write(1, buffer, bytes); shell_write(buffer, bytes);
} }
sys_close(fd); sys_close(fd);
} }
@ -1230,17 +1259,20 @@ static int builtin_man(int argc, char *argv[]) {
char buffer[4096]; char buffer[4096];
int bytes; int bytes;
while ((bytes = sys_read(fd, buffer, sizeof(buffer))) > 0) { while ((bytes = sys_read(fd, buffer, sizeof(buffer))) > 0) {
sys_write(1, buffer, bytes); shell_write(buffer, bytes);
} }
sys_close(fd); sys_close(fd);
printf("\n"); shell_write("\n", 1);
return 0; return 0;
} }
static int builtin_alias(int argc, char *argv[]) { static int builtin_alias(int argc, char *argv[]) {
if (argc == 1) { if (argc == 1) {
for (int i = 0; i < g_alias_count; i++) { for (int i = 0; i < g_alias_count; i++) {
printf("alias %s=%s\n", g_aliases[i].name, g_aliases[i].value); shell_write("alias ", 6);
shell_write(g_aliases[i].name, (int)strlen(g_aliases[i].name));
shell_write("=", 1);
shell_writeln(g_aliases[i].value);
} }
return 0; return 0;
} }
@ -1251,7 +1283,10 @@ static int builtin_alias(int argc, char *argv[]) {
if (*eq != '=') { if (*eq != '=') {
const char *val = alias_get(argv[i]); const char *val = alias_get(argv[i]);
if (val) { if (val) {
printf("alias %s=%s\n", argv[i], val); shell_write("alias ", 6);
shell_write(argv[i], (int)strlen(argv[i]));
shell_write("=", 1);
shell_writeln(val);
continue; continue;
} }
set_color(g_color_error); set_color(g_color_error);
@ -1311,19 +1346,220 @@ static int execute_builtin(int argc, char *argv[]) {
return -1; return -1;
} }
static int execute_line_inner(const char *line, int depth) { static bool is_builtin_name(const char *name) {
if (!line || !line[0]) return 0; return str_eq(name, "cd") || str_eq(name, "pwd") || str_eq(name, "ls") ||
if (depth > 8) return 1; str_eq(name, "cat") || str_eq(name, "echo") || str_eq(name, "clear") ||
str_eq(name, "mkdir") || str_eq(name, "rm") || str_eq(name, "touch") ||
str_eq(name, "cp") || str_eq(name, "mv") || str_eq(name, "man") ||
str_eq(name, "alias") || str_eq(name, "unalias") || str_eq(name, ".") ||
str_eq(name, "exit");
}
char line_copy[MAX_LINE]; typedef enum {
str_copy(line_copy, line, sizeof(line_copy)); TOK_WORD = 0,
trim(line_copy); TOK_PIPE,
if (!line_copy[0]) return 0; TOK_GT,
if (line_copy[0] == '#') return 0; TOK_GTGT,
TOK_LT,
TOK_AMP,
TOK_ANDAND,
TOK_OROR,
TOK_SEMI,
TOK_END
} bsh_tok_type_t;
typedef struct {
bsh_tok_type_t type;
char text[MAX_MATCH_LEN];
} bsh_token_t;
typedef struct {
char *argv[MAX_ARGS]; char *argv[MAX_ARGS];
int argc = split_args(line_copy, argv, MAX_ARGS); int argc;
char *redir_in;
char *redir_out;
bool redir_append;
} bsh_simple_cmd_t;
#define MAX_TOKENS 128
#define MAX_PIPE_CMDS 16
static bool is_op_char(char c) {
return c == '|' || c == '&' || c == ';' || c == '<' || c == '>';
}
static void print_syntax_error(const char *msg) {
set_color(g_color_error);
printf("bsh: syntax error: %s\n", msg);
reset_color();
}
static void alias_snapshot_save(alias_t out_aliases[], int *out_count) {
if (!out_aliases || !out_count) return;
*out_count = g_alias_count;
for (int i = 0; i < g_alias_count && i < MAX_ALIASES; i++) {
out_aliases[i] = g_aliases[i];
}
}
static void alias_snapshot_restore(const alias_t saved_aliases[], int saved_count) {
g_alias_count = 0;
if (!saved_aliases || saved_count <= 0) return;
int copy_count = saved_count < MAX_ALIASES ? saved_count : MAX_ALIASES;
for (int i = 0; i < copy_count; i++) {
g_aliases[i] = saved_aliases[i];
}
g_alias_count = copy_count;
}
static int tokenize_line(const char *line, bsh_token_t toks[], int max_toks) {
if (!line || !toks || max_toks < 2) return -1;
int tcount = 0;
int i = 0;
while (line[i]) {
while (line[i] == ' ' || line[i] == '\t') i++;
if (!line[i]) break;
if (tcount >= max_toks - 1) {
print_syntax_error("too many tokens");
return -1;
}
if (line[i] == '&' && line[i + 1] == '&') {
toks[tcount].type = TOK_ANDAND;
toks[tcount].text[0] = 0;
tcount++;
i += 2;
continue;
}
if (line[i] == '|' && line[i + 1] == '|') {
toks[tcount].type = TOK_OROR;
toks[tcount].text[0] = 0;
tcount++;
i += 2;
continue;
}
if (line[i] == '>' && line[i + 1] == '>') {
toks[tcount].type = TOK_GTGT;
toks[tcount].text[0] = 0;
tcount++;
i += 2;
continue;
}
if (line[i] == '|') {
toks[tcount].type = TOK_PIPE;
toks[tcount].text[0] = 0;
tcount++;
i++;
continue;
}
if (line[i] == '&') {
toks[tcount].type = TOK_AMP;
toks[tcount].text[0] = 0;
tcount++;
i++;
continue;
}
if (line[i] == ';') {
toks[tcount].type = TOK_SEMI;
toks[tcount].text[0] = 0;
tcount++;
i++;
continue;
}
if (line[i] == '<') {
toks[tcount].type = TOK_LT;
toks[tcount].text[0] = 0;
tcount++;
i++;
continue;
}
if (line[i] == '>') {
toks[tcount].type = TOK_GT;
toks[tcount].text[0] = 0;
tcount++;
i++;
continue;
}
toks[tcount].type = TOK_WORD;
int out = 0;
while (line[i] && line[i] != ' ' && line[i] != '\t' && !is_op_char(line[i])) {
if (line[i] == '"' || line[i] == '\'') {
char quote = line[i++];
while (line[i] && line[i] != quote) {
if (out < MAX_MATCH_LEN - 1) toks[tcount].text[out++] = line[i];
i++;
}
if (line[i] == quote) i++;
continue;
}
if (out < MAX_MATCH_LEN - 1) toks[tcount].text[out++] = line[i];
i++;
}
toks[tcount].text[out] = 0;
if (out == 0) {
print_syntax_error("unexpected token");
return -1;
}
tcount++;
}
toks[tcount].type = TOK_END;
toks[tcount].text[0] = 0;
return tcount;
}
static bool parse_simple_command(bsh_token_t toks[], int *idx, bsh_simple_cmd_t *cmd) {
if (!toks || !idx || !cmd) return false;
cmd->argc = 0;
cmd->redir_in = NULL;
cmd->redir_out = NULL;
cmd->redir_append = false;
while (1) {
bsh_tok_type_t t = toks[*idx].type;
if (t == TOK_WORD) {
if (cmd->argc >= MAX_ARGS - 1) {
print_syntax_error("too many arguments");
return false;
}
cmd->argv[cmd->argc++] = toks[*idx].text;
(*idx)++;
continue;
}
if (t == TOK_LT || t == TOK_GT || t == TOK_GTGT) {
bsh_tok_type_t redir = t;
(*idx)++;
if (toks[*idx].type != TOK_WORD) {
print_syntax_error("missing filename after redirection");
return false;
}
if (redir == TOK_LT) {
cmd->redir_in = toks[*idx].text;
} else {
cmd->redir_out = toks[*idx].text;
cmd->redir_append = (redir == TOK_GTGT);
}
(*idx)++;
continue;
}
break;
}
cmd->argv[cmd->argc] = NULL;
if (cmd->argc <= 0) {
print_syntax_error("expected command");
return false;
}
return true;
}
static int execute_argv_inner(int argc, char *argv[], int depth, bool isolated, bool background, bool *want_exit, int *out_pid) {
if (argc <= 0) return 0; if (argc <= 0) return 0;
if (depth > 8) return 1;
if (out_pid) *out_pid = -1;
if (!str_eq(argv[0], "alias") && !str_eq(argv[0], "unalias")) { if (!str_eq(argv[0], "alias") && !str_eq(argv[0], "unalias")) {
const char *alias_val = alias_get(argv[0]); const char *alias_val = alias_get(argv[0]);
@ -1337,13 +1573,37 @@ static int execute_line_inner(const char *line, int depth) {
str_append(expanded, " ", sizeof(expanded)); str_append(expanded, " ", sizeof(expanded));
str_append(expanded, tail, sizeof(expanded)); str_append(expanded, tail, sizeof(expanded));
} }
return execute_line_inner(expanded, depth + 1);
char split_buf[MAX_LINE];
str_copy(split_buf, expanded, sizeof(split_buf));
char *expanded_argv[MAX_ARGS];
int expanded_argc = split_args(split_buf, expanded_argv, MAX_ARGS);
if (expanded_argc <= 0) return 0;
return execute_argv_inner(expanded_argc, expanded_argv, depth + 1, isolated, background, want_exit, out_pid);
} }
} }
int bi = execute_builtin(argc, argv); int bi = -1;
if (bi == 2) return 2; if (isolated) {
if (bi >= 0) return 0; alias_t saved_aliases[MAX_ALIASES];
int saved_alias_count = 0;
char saved_cwd[256];
bool had_cwd = getcwd(saved_cwd, sizeof(saved_cwd)) != NULL;
alias_snapshot_save(saved_aliases, &saved_alias_count);
bi = execute_builtin(argc, argv);
alias_snapshot_restore(saved_aliases, saved_alias_count);
if (had_cwd) chdir(saved_cwd);
} else {
bi = execute_builtin(argc, argv);
}
if (bi == 2) {
if (!isolated && want_exit) *want_exit = true;
return 0;
}
if (bi >= 0) return bi == 0 ? 0 : 1;
char full_path[256]; char full_path[256];
int cmd_res = resolve_command(argv[0], full_path, sizeof(full_path)); int cmd_res = resolve_command(argv[0], full_path, sizeof(full_path));
@ -1369,9 +1629,13 @@ static int execute_line_inner(const char *line, int depth) {
char args_buf[256]; char args_buf[256];
build_args_string(argc, argv, 1, args_buf, sizeof(args_buf)); build_args_string(argc, argv, 1, args_buf, sizeof(args_buf));
uint64_t spawn_flags = background
? (SPAWN_FLAG_INHERIT_TTY | SPAWN_FLAG_BACKGROUND)
: (SPAWN_FLAG_TERMINAL | SPAWN_FLAG_INHERIT_TTY);
int pid = -1; int pid = -1;
for (int attempt = 0; attempt < 5; attempt++) { for (int attempt = 0; attempt < 5; attempt++) {
pid = sys_spawn(full_path, args_buf[0] ? args_buf : NULL, SPAWN_FLAG_TERMINAL | SPAWN_FLAG_INHERIT_TTY, 0); pid = sys_spawn(full_path, args_buf[0] ? args_buf : NULL, spawn_flags, 0);
if (pid >= 0) break; if (pid >= 0) break;
sleep(10); sleep(10);
} }
@ -1382,14 +1646,278 @@ static int execute_line_inner(const char *line, int depth) {
return 1; return 1;
} }
if (out_pid) *out_pid = pid;
if (background) return 0;
if (g_tty_id >= 0) sys_tty_set_fg(g_tty_id, pid); if (g_tty_id >= 0) sys_tty_set_fg(g_tty_id, pid);
wait_for_pid(pid); int status = wait_for_pid(pid);
if (g_tty_id >= 0) sys_tty_set_fg(g_tty_id, 0); if (g_tty_id >= 0) sys_tty_set_fg(g_tty_id, 0);
return 0; return status;
}
static int run_simple_command_with_fds(
bsh_simple_cmd_t *cmd,
int in_fd,
int out_fd,
bool isolate,
bool background,
bool *want_exit,
int *out_pid
) {
if (out_pid) *out_pid = -1;
if (!cmd || cmd->argc <= 0) return 0;
int saved_in = sys_dup(0);
int saved_out = sys_dup(1);
if (saved_in < 0 || saved_out < 0) {
if (saved_in >= 0) sys_close(saved_in);
if (saved_out >= 0) sys_close(saved_out);
set_color(g_color_error);
printf("bsh: redirection/pipeline is unavailable in this session\n");
reset_color();
return 1;
}
int redir_in_fd = -1;
int redir_out_fd = -1;
bool in_is_kernel = false;
bool out_is_kernel = false;
int rc = 0;
// 1. Handle Pipe Input
if (in_fd >= 0 && sys_dup2(in_fd, 0) < 0) {
set_color(g_color_error);
printf("bsh: failed to set pipeline input\n");
reset_color();
rc = 1;
goto done;
}
// 2. Handle File Redirection Input (overrides pipe)
if (cmd->redir_in) {
redir_in_fd = bsh_open_file(cmd->redir_in, "r", &in_is_kernel);
if (redir_in_fd < 0 || sys_dup2(redir_in_fd, 0) < 0) {
set_color(g_color_error);
printf("bsh: cannot read from %s\n", cmd->redir_in);
reset_color();
rc = 1;
goto done;
}
}
// 3. Handle Pipe Output
if (out_fd >= 0 && sys_dup2(out_fd, 1) < 0) {
set_color(g_color_error);
printf("bsh: failed to set pipeline output\n");
reset_color();
rc = 1;
goto done;
}
// 4. Handle File Redirection Output (overrides pipe)
if (cmd->redir_out) {
const char *mode = cmd->redir_append ? "a" : "w";
redir_out_fd = bsh_open_file(cmd->redir_out, mode, &out_is_kernel);
if (redir_out_fd < 0 || sys_dup2(redir_out_fd, 1) < 0) {
set_color(g_color_error);
printf("bsh: cannot write to %s\n", cmd->redir_out);
reset_color();
rc = 1;
goto done;
}
}
rc = execute_argv_inner(cmd->argc, cmd->argv, 0, isolate, background, want_exit, out_pid);
done:
if (redir_in_fd >= 0) sys_close(redir_in_fd);
if (redir_out_fd >= 0) sys_close(redir_out_fd);
sys_dup2(saved_in, 0);
sys_dup2(saved_out, 1);
sys_close(saved_in);
sys_close(saved_out);
return rc;
}
static bool parse_pipeline(
bsh_token_t toks[],
int *idx,
bsh_simple_cmd_t cmds[],
int *cmd_count
) {
if (!toks || !idx || !cmds || !cmd_count) return false;
*cmd_count = 0;
if (!parse_simple_command(toks, idx, &cmds[*cmd_count])) return false;
(*cmd_count)++;
while (toks[*idx].type == TOK_PIPE) {
(*idx)++;
if (*cmd_count >= MAX_PIPE_CMDS) {
print_syntax_error("pipeline too long");
return false;
}
if (!parse_simple_command(toks, idx, &cmds[*cmd_count])) return false;
(*cmd_count)++;
}
return true;
}
static int execute_pipeline_cmds(
bsh_simple_cmd_t cmds[],
int cmd_count,
bool background,
bool *want_exit
) {
if (!cmds || cmd_count <= 0) return 0;
if (cmd_count == 1) {
int pid = -1;
int res = run_simple_command_with_fds(&cmds[0], -1, -1, background, background, want_exit, &pid);
return background ? 0 : res;
}
int pipes[MAX_PIPE_CMDS - 1][2];
int pids[MAX_PIPE_CMDS];
for (int i = 0; i < cmd_count; i++) pids[i] = -1;
for (int i = 0; i < cmd_count - 1; i++) {
if (pipe(pipes[i]) < 0) {
set_color(g_color_error);
printf("bsh: failed to create pipe\n");
reset_color();
for (int j = 0; j < i; j++) {
close(pipes[j][0]);
close(pipes[j][1]);
}
return 1;
}
}
for (int i = 0; i < cmd_count; i++) {
int in_fd = (i == 0) ? -1 : pipes[i - 1][0];
int out_fd = (i == cmd_count - 1) ? -1 : pipes[i][1];
run_simple_command_with_fds(&cmds[i], in_fd, out_fd, true, true, want_exit, &pids[i]);
if (in_fd >= 0) close(in_fd);
if (out_fd >= 0) close(out_fd);
}
if (background) return 0;
int last_status = 0;
for (int i = 0; i < cmd_count; i++) {
if (pids[i] > 0) {
int s = wait_for_pid(pids[i]);
if (i == cmd_count - 1) last_status = s;
}
}
return last_status;
}
static bool parse_conditional_end_index(bsh_token_t toks[], int start_idx, int *out_end_idx) {
int idx = start_idx;
bsh_simple_cmd_t cmds[MAX_PIPE_CMDS];
int cmd_count = 0;
if (!parse_pipeline(toks, &idx, cmds, &cmd_count)) return false;
while (toks[idx].type == TOK_ANDAND || toks[idx].type == TOK_OROR) {
idx++;
if (!parse_pipeline(toks, &idx, cmds, &cmd_count)) return false;
}
*out_end_idx = idx;
return true;
}
static int execute_conditional_range(
bsh_token_t toks[],
int start_idx,
int end_idx,
bool background,
bool *want_exit
) {
int idx = start_idx;
int status = 0;
bool execute_next = true;
while (idx < end_idx) {
bsh_simple_cmd_t cmds[MAX_PIPE_CMDS];
int cmd_count = 0;
if (!parse_pipeline(toks, &idx, cmds, &cmd_count)) return 1;
if (execute_next) {
status = execute_pipeline_cmds(cmds, cmd_count, background, want_exit);
if (*want_exit) return 2;
}
if (idx >= end_idx) break;
bsh_tok_type_t op = toks[idx].type;
idx++;
if (op == TOK_ANDAND) {
execute_next = (status == 0);
} else if (op == TOK_OROR) {
execute_next = (status != 0);
} else {
print_syntax_error("unexpected conditional operator");
return 1;
}
}
return status;
} }
static int execute_line(const char *line) { static int execute_line(const char *line) {
return execute_line_inner(line, 0); if (!line || !line[0]) return 0;
char line_copy[MAX_LINE];
str_copy(line_copy, line, sizeof(line_copy));
trim(line_copy);
if (!line_copy[0]) return 0;
if (line_copy[0] == '#') return 0;
bsh_token_t toks[MAX_TOKENS];
int tcount = tokenize_line(line_copy, toks, MAX_TOKENS);
if (tcount < 0) return 1;
if (tcount == 0 || toks[0].type == TOK_END) return 0;
int idx = 0;
int status = 0;
bool want_exit = false;
while (toks[idx].type != TOK_END) {
if (toks[idx].type == TOK_SEMI || toks[idx].type == TOK_AMP) {
print_syntax_error("unexpected separator");
return 1;
}
int end_idx = idx;
if (!parse_conditional_end_index(toks, idx, &end_idx)) return 1;
bool background = false;
if (toks[end_idx].type == TOK_AMP) background = true;
status = execute_conditional_range(toks, idx, end_idx, background, &want_exit);
if (want_exit) return 2;
idx = end_idx;
if (toks[idx].type == TOK_SEMI || toks[idx].type == TOK_AMP) {
idx++;
continue;
}
if (toks[idx].type != TOK_END) {
print_syntax_error("unexpected token after command list");
return 1;
}
}
return status;
} }
static bool run_script(const char *path) { static bool run_script(const char *path) {

View file

@ -53,6 +53,7 @@ typedef struct {
int refcount; int refcount;
int flags; int flags;
int kernel_fd; int kernel_fd;
int owns_kernel_fd;
pipe_state_t *pipe; pipe_state_t *pipe;
} fd_handle_t; } fd_handle_t;
@ -60,6 +61,10 @@ static fd_handle_t *g_fd_table[POSIX_MAX_FDS];
static fd_handle_t g_stdio_handles[3]; static fd_handle_t g_stdio_handles[3];
static int g_fd_initialized = 0; static int g_fd_initialized = 0;
static int _b_is_stdio_handle(const fd_handle_t *h) {
return (h >= &g_stdio_handles[0] && h <= &g_stdio_handles[2]);
}
static void _b_fd_init(void) { static void _b_fd_init(void) {
int i; int i;
if (g_fd_initialized) { if (g_fd_initialized) {
@ -73,6 +78,7 @@ static void _b_fd_init(void) {
g_stdio_handles[i].refcount = 1; g_stdio_handles[i].refcount = 1;
g_stdio_handles[i].flags = O_RDWR; g_stdio_handles[i].flags = O_RDWR;
g_stdio_handles[i].kernel_fd = i; g_stdio_handles[i].kernel_fd = i;
g_stdio_handles[i].owns_kernel_fd = 0;
g_stdio_handles[i].pipe = NULL; g_stdio_handles[i].pipe = NULL;
g_fd_table[i] = &g_stdio_handles[i]; g_fd_table[i] = &g_stdio_handles[i];
} }
@ -274,6 +280,7 @@ __attribute__((weak)) int open(const char *pathname, int flags, ...) {
h->refcount = 1; h->refcount = 1;
h->flags = flags; h->flags = flags;
h->kernel_fd = kfd; h->kernel_fd = kfd;
h->owns_kernel_fd = 1;
h->pipe = NULL; h->pipe = NULL;
g_fd_table[fd] = h; g_fd_table[fd] = h;
@ -300,7 +307,7 @@ __attribute__((weak)) int close(int fd) {
} }
if (h->type == HANDLE_KERNEL_FD) { if (h->type == HANDLE_KERNEL_FD) {
if (h->kernel_fd >= 3) { if (h->owns_kernel_fd) {
sys_close(h->kernel_fd); sys_close(h->kernel_fd);
} }
} else if (h->type == HANDLE_PIPE_READ || h->type == HANDLE_PIPE_WRITE) { } else if (h->type == HANDLE_PIPE_READ || h->type == HANDLE_PIPE_WRITE) {
@ -384,10 +391,13 @@ __attribute__((weak)) ssize_t write(int fd, const void *buf, size_t count) {
return (ssize_t)n; return (ssize_t)n;
} }
if (h->kernel_fd <= 2) { if (_b_is_stdio_handle(h)) {
n = sys_write(h->kernel_fd, (const char *)buf, (int)count); n = sys_write(h->kernel_fd, (const char *)buf, (int)count);
} else { } else {
n = sys_write_fs(h->kernel_fd, buf, (uint32_t)count); n = sys_write_fs(h->kernel_fd, buf, (uint32_t)count);
if (n < 0) {
n = sys_write(h->kernel_fd, (const char *)buf, (int)count);
}
} }
if (n < 0) { if (n < 0) {
@ -442,7 +452,7 @@ __attribute__((weak)) int isatty(int fd) {
errno = EBADF; errno = EBADF;
return 0; return 0;
} }
return (h->type == HANDLE_KERNEL_FD && h->kernel_fd <= 2) ? 1 : 0; return (h->type == HANDLE_KERNEL_FD && _b_is_stdio_handle(h)) ? 1 : 0;
} }
__attribute__((weak)) int fstat(int fd, struct stat *statbuf) { __attribute__((weak)) int fstat(int fd, struct stat *statbuf) {
@ -471,31 +481,38 @@ __attribute__((weak)) int fstat(int fd, struct stat *statbuf) {
__attribute__((weak)) int dup(int oldfd) { __attribute__((weak)) int dup(int oldfd) {
fd_handle_t *h; fd_handle_t *h;
fd_handle_t *src;
int newfd; int newfd;
int newkfd; int newkfd;
_b_fd_init(); _b_fd_init();
h = _b_get_handle(oldfd); src = _b_get_handle(oldfd);
if (!h) { if (!src) {
errno = EBADF; errno = EBADF;
return -1; return -1;
} }
if (h->type != HANDLE_KERNEL_FD) { if (src->type != HANDLE_KERNEL_FD) {
errno = ENOTSUP; errno = ENOTSUP;
return -1; return -1;
} }
newkfd = sys_dup(h->kernel_fd); newkfd = sys_dup(src->kernel_fd);
if (newkfd < 0) { if (newkfd < 0) {
errno = EBADF; if (_b_is_stdio_handle(src)) {
return -1; newkfd = src->kernel_fd;
} else {
errno = EBADF;
return -1;
}
} }
newfd = _b_alloc_fd_from(0); newfd = _b_alloc_fd_from(0);
if (newfd < 0) { if (newfd < 0) {
sys_close(newkfd); if (newkfd >= 3) {
errno = EBUSY; sys_close(newkfd);
}
errno = EBADF;
return -1; return -1;
} }
@ -510,19 +527,20 @@ __attribute__((weak)) int dup(int oldfd) {
h->refcount = 1; h->refcount = 1;
h->flags = O_RDWR; h->flags = O_RDWR;
h->kernel_fd = newkfd; h->kernel_fd = newkfd;
h->owns_kernel_fd = (newkfd != src->kernel_fd) ? 1 : 0;
h->pipe = NULL; h->pipe = NULL;
g_fd_table[newfd] = h; g_fd_table[newfd] = h;
return newfd; return newfd;
} }
__attribute__((weak)) int dup2(int oldfd, int newfd) { __attribute__((weak)) int dup2(int oldfd, int newfd) {
fd_handle_t *h; fd_handle_t *src;
fd_handle_t *nh; fd_handle_t *nh;
int newkfd; int kfd_res;
_b_fd_init(); _b_fd_init();
h = _b_get_handle(oldfd); src = _b_get_handle(oldfd);
if (!h || newfd < 0 || newfd >= POSIX_MAX_FDS) { if (!src || newfd < 0 || newfd >= POSIX_MAX_FDS) {
errno = EBADF; errno = EBADF;
return -1; return -1;
} }
@ -531,36 +549,48 @@ __attribute__((weak)) int dup2(int oldfd, int newfd) {
return newfd; return newfd;
} }
if (h->type != HANDLE_KERNEL_FD) { if (src->type != HANDLE_KERNEL_FD) {
errno = ENOTSUP; errno = ENOTSUP;
return -1; return -1;
} }
if (g_fd_table[newfd]) { // Force kernel to update its FD table for the new slot
if (close(newfd) != 0) { kfd_res = sys_dup2(src->kernel_fd, newfd);
return -1; if (kfd_res < 0) {
}
}
newkfd = sys_dup(h->kernel_fd);
if (newkfd < 0) {
errno = EBADF; errno = EBADF;
return -1; return -1;
} }
nh = (fd_handle_t *)malloc(sizeof(fd_handle_t)); // If newfd was already open in libc, we need to replace its handle
if (!nh) { if (g_fd_table[newfd] && !_b_is_stdio_handle(g_fd_table[newfd])) {
errno = ENOMEM; close(newfd);
return -1;
} }
nh->type = HANDLE_KERNEL_FD; // If it's a stdio handle, we update it in place
nh->refcount = 1; if (newfd >= 0 && newfd <= 2) {
nh->flags = h->flags; nh = &g_stdio_handles[newfd];
nh->kernel_fd = newkfd; nh->type = HANDLE_KERNEL_FD;
nh->pipe = NULL; nh->kernel_fd = newfd;
nh->flags = src->flags;
nh->refcount = 1;
nh->owns_kernel_fd = 0;
nh->pipe = NULL;
g_fd_table[newfd] = nh;
} else {
nh = (fd_handle_t *)malloc(sizeof(fd_handle_t));
if (!nh) {
errno = ENOMEM;
return -1;
}
nh->type = HANDLE_KERNEL_FD;
nh->refcount = 1;
nh->flags = src->flags;
nh->kernel_fd = newfd;
nh->owns_kernel_fd = 1;
nh->pipe = NULL;
g_fd_table[newfd] = nh;
}
g_fd_table[newfd] = nh;
return newfd; return newfd;
} }