Files
lush/issues/15-builtins.md
Cormac Shannon 4cc352cbec Implement shell builtins: cd, exec, umask (issue #15)
Add builtin dispatch in lcmd.c that checks __builtins table before
fork(), so cd/exec/umask operate on the shell process itself.
2026-03-04 19:20:42 +00:00

55 lines
2.5 KiB
Markdown

# 15 — Shell builtins
**Status:** done
Some commands must run inside the shell process itself to have any effect. An external `cd` binary exists on most systems but it spawns a subprocess, changes directory there, and exits — leaving lush's own working directory unchanged.
Lush only needs builtins for operations that **cannot be implemented in pure Lua or via external binaries** — things that require modifying the shell process state via syscalls with no Lua API.
## Assessment
Every builtin from `man 1 builtin` was evaluated against two questions:
1. Can it be done in Lua? (e.g. `export``$VAR = "val"`, `source``dofile()`, `exit``os.exit()`)
2. Does the external binary work? (e.g. `/bin/pwd`, `/usr/bin/which`, `/usr/bin/kill`)
If either answer is yes, it's not needed as a builtin. What remains:
| Builtin | Why it must be a builtin |
|---------|-------------------------|
| **`cd`** | `chdir(2)` must happen in the lush process. No Lua API. |
| **`exec`** | `execvp(2)` replaces the current process. `os.execute()` spawns a subprocess. No Lua equivalent. |
| **`umask`** | `umask(2)` is per-process. The external `/usr/bin/umask` is a shell script that calls the shell's own builtin. No Lua API. |
## Builtins
### `cd [dir]`
Change working directory via `chdir(2)`.
- No argument: `cd $HOME`
- `cd -`: change to `$OLDPWD`
- Updates `$PWD` and `$OLDPWD` environment variables
- Errors if directory doesn't exist or isn't accessible
### `exec command [args...]`
Replace the shell process with the given command via `execvp(2)`. Does not return on success. On failure, prints an error and the shell continues.
### `umask [mode]`
Get or set the file creation mask via `umask(2)`.
- No argument: print current umask (octal)
- With argument: set umask to the given octal value
## Design considerations
- **Dispatch order:** When a command is entered (backtick or `!` prefix), check builtins *before* searching `$PATH`. A builtin named `cd` must intercept the command before it reaches `execvp`.
- **Implementation:** Expose the syscalls to Lua (e.g. `os.chdir()`, `os.exec()`, `os.umask()`), then implement the builtins as Lua functions registered at startup. This keeps the C side minimal and lets users compose with the primitives directly.
- **Interaction with backticks:** `` `cd /tmp` `` and `!cd /tmp` should both trigger the builtin, not spawn an external process.
## Open questions
- Should builtins be overridable by the user?
- Should there be a `builtin` command to bypass user overrides?