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.
This commit is contained in:
54
issues/15-builtins.md
Normal file
54
issues/15-builtins.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# 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?
|
||||
Reference in New Issue
Block a user