# Directories, Mounting, and Process Control

This page is the "Directories, Mounting, and Process Control" section of the Voxlang language reference. It is generated from LANGUAGE.md in the Voxlang compiler's own repository, so it says what the specification says and nothing more.

A URL is the heading, lowercased, with punctuation dropped and spaces turned into hyphens. Each "##" section of the spec is a page under /docs/, and every heading inside it is a fragment on that page, so "File I/O" is /docs/file-io/ and "Reading a whole file" inside it is /docs/file-io/#reading-a-whole-file.

As data: https://vox-lang.dev/docs/index.json lists every section and every heading with its URL, https://vox-lang.dev/docs/search.json carries one entry per heading with its first sentence and keywords, and https://vox-lang.dev/docs/anchors.json maps every slug to the page it lives on. Search is also a JSON endpoint: https://vox-lang.dev/docs/search?q=<words> returns ranked results as JSON, no page load.

Source: LANGUAGE.md at bf2cba0, 2026-08-22. https://github.com/Vox-lang/vox/blob/bf2cba037976586c30d22a48e7b7246d347f54a1/LANGUAGE.md

[Reference](https://vox-lang.dev/docs/) > Directories, Mounting, and Process Control

These constructs were added for writing early-userspace/init-style programs in Vox - see [examples/initramfs.vox](https://github.com/Vox-lang/vox/blob/bf2cba037976586c30d22a48e7b7246d347f54a1/examples/initramfs.vox) for a complete, working early-userspace init sequence exercising all of them together.

## Directories

```
Create a directory called "/proc".
Remove the directory called "/proc".
Delete the directory "/proc".
Change directory to "/newroot".
```

**Rules:**

- `Create a directory called '<path>'.` - `mkdir(2)`, mode `0755`. The article (`a`) is optional; `called` is required.
- `Remove the directory called '<path>'.` / `Delete the directory "<path>".` - `rmdir(2)`. Both `Remove` and `Delete` work; `the` and `called` are optional.
- `Change directory to "<path>".` - `chdir(2)`.
- All three set the error flag on failure - use `On error` to catch it.

## Mounting Filesystems

```
Mount "proc" at "/proc" with type "proc".
Mount "tmpfs" at "/dev/shm" with type "tmpfs" with options "size=64m".
On error print "mount failed", exit 1.

Unmount "/dev/shm".
Unmount "/dev/shm" lazily.
On error print "unmount failed".
```

**Rules:**

- `Mount "<source>" at "<target>" with type "<fstype>" [with options "<options>"].` lowers directly to `mount(2)`. `source`/`target`/`fstype`/`options` accept string literals, text variables, or buffers (including format-string-built buffers).

- Moving/binding an already-mounted filesystem uses `fstype "none"` with `options "move"` or `options "bind"` - Vox recognizes this pattern and translates it into the correct `MS_MOVE`/`MS_BIND` mount flags:

  ```
  Mount "/proc" at "/newroot/proc" with type "none" with options "move".
  ```

- `Unmount "<target>".` - `umount2(2)`. `umount` is accepted as an alias for `Unmount`. Append `lazily` for `MNT_DETACH` (detaches immediately and releases the mount once nothing is using it any longer, instead of failing with "device busy") - needed when unmounting a filesystem your own running program was loaded from.

- Both set the error flag on failure.

## Device Nodes

```
Create a device node called "/dev/null" with type "c" major 1 minor 3.
Create a device node called "/dev/loop0" with type "b" major 7 minor 0.
```

`mknod(2)`. `type` is `"c"` (character device) or `"b"` (block device); `major`/`minor` are the standard Linux device-driver identification numbers (see `man 4 null`/the kernel's `Documentation/admin-guide/devices.txt` for the registry of standard values). Sets the error flag on failure.

## Symbolic Links

```
Create symbolic link from "/proc/self/fd" to "/dev/fd".
```

`symlink(2)`: `Create symbolic link from '<target>' to "<linkpath>".` Sets the error flag on failure.

## Switching the Root Filesystem

```
Pivot root to "/newroot" with old root "/newroot/oldroot".
```

`pivot_root(2)`. `put_old` (the second path) must be a directory that already exists *inside* `new_root` - create it after mounting the new root, not before. After a successful pivot, the previous root filesystem is accessible at `put_old`'s path relative to the new root (here, `/oldroot`), and should typically be released with `Unmount "..." lazily` once your program has `chdir`'d away from it. Sets the error flag on failure.

## Executing Programs

```
Execute "/bin/sh".
Execute "/bin/echo" with arguments ["hello", "world"].

a list called cmdargs is ["hello", "world"].
Execute "/bin/echo" with arguments cmdargs.

On error print "execve failed", exit 1.
```

`execve(2)` - replaces the current process image entirely. Three forms:

- **No arguments**: `Execute "<path>".` synthesizes `argv = [path, NULL]` (argc 1).
- **Literal argument list**: `Execute '<path>' with arguments [...].` - argv is built at compile time.
- **List variable**: `Execute '<path>' with arguments <list>.` - argv is built at runtime from the list's current length and contents, sized and bounds-checked from that single length read so the argv array cannot be overrun regardless of the list's contents.

The environment is inherited from the calling process in all three forms. `execve` only ever returns on failure (there is no "success" path to return to - the process image is gone), so `On error` after `Execute` is the normal and only way to detect that it didn't work.

## Process Control: fork and reap

```
Set pid to fork the process.
If pid is 0 then,
    (this branch runs in the child)
    Execute "/bin/some-program".
If pid is greater than 0 then,
    (this branch runs in the parent - pid holds the child's real PID)
    Set reaped to reap any child process.
```

These are **expressions**, not statements - use them anywhere an expression is valid (typically the right-hand side of `Set`/`a number called ... is`).

- `fork the process` (the trailing `the process` is optional; bare `fork` also works) - `fork(2)`. Returns `0` in the child, the child's PID in the parent, or a negative value on error. Sets the error flag on failure.
- `reap any child process` - `wait4(2)` with `pid = -1`, waiting for any child. Returns the reaped child's PID, or a negative value on error.
- `reap process <pid-expr>` / `reap child <pid-expr>` - `wait4(2)` for a specific PID.

Both set the error flag on failure (e.g. `On error` after `reap process 999999` catches `ECHILD` when the PID is not actually your child).

### Non-blocking reap: `without waiting`

Any reap form takes a `without waiting` suffix, which calls `wait4(2)` with `WNOHANG` instead of blocking:

```
Set r to reap any child process without waiting.
Set r to reap child pid without waiting.
Set r to reap process pid without waiting.
```

The return value is the whole point of the form, and the three cases must be told apart:

- a child finished → its PID, error flag cleared;
- children exist but none has finished → `0`, error flag cleared (this is **not** an error — it is how you tell "still running" from "gone");
- genuine error, e.g. no such child (`ECHILD`) → negative, error flag set, catchable with `On error`.

A non-blocking reap that returns `0` reaps nothing, so it does **not** disturb `the reaped status` (below) — only a reap that actually returns a child's PID changes it. `without` is already a reserved keyword (it is the `print ... without newline` token), so the suffix cannot be confused with a call argument after the pid expression, and `waiting` remains an ordinary identifier everywhere it is not this suffix.

### The reaped status

```
Set r to reap child pid.
Set status to the reaped status.
```

`the reaped status` is an expression yielding the raw `wait4` status word as a plain number — exactly the `int status` the kernel writes, undecoded. It reflects the most recent *successful* reap in the current process. Before any successful reap it is `-1`, a sentinel no real status can take, so "never reaped" is distinguishable from "exited 0". The sentinel lives in loader-initialized `.data`, not `.bss`, because `_start` (which would zero a `.bss` global) is only emitted for executables — a `--shared` library would otherwise read `0` and silently report "exited cleanly" with no child ever reaped.

`reaped` stays an ordinary identifier: `the reaped status` is consumed only as that exact phrase, and `the reaped` followed by anything else is an ordinary variable reference. (`tests/102_fork_reap.vox` does `Set reaped to reap any child process.` and keeps passing.)

### Decoding the status

The compiler knows nothing about the wait-status encoding — `the reaped status` hands back the raw word, and a program decodes it with `divide`, `modulo`, and `bit-and`. Vox has no standard library on purpose, and this feature is complete with nothing installed:

```
To 'exit code of' with a number called status.
  Return a number, status divide 256 modulo 256.

To 'signal of' with a number called status.
  Return a number, status bit-and 127.
```

For ready-made decoding — these two plus `crashed` and `'exited normally'`, matching the `<sys/wait.h>` macros — the `process` library lives at [Vox-lang/vox-libs](https://github.com/Vox-lang/vox-libs), installable as an ordinary shared library:

```
see process version "0.1" from "./libprocess.lib".
```

It provides four functions over the raw status word, matching the `<sys/wait.h>` macros: `'exit code of'` (bits 8–15), `'signal of'` (the low 7 bits), `crashed` (true if a signal killed it), and `'exited normally'` (true if no signal was involved). Use them at the call site, where they read as English:

```
If crashed of status then,
    Print "died by signal {'signal of' of status}".
If 'exited normally' of status then,
    Print "exit {'exit code of' of status}".
```

### A supervisor loop, with no shelling out

These pieces compose into a complete supervisor — poll a child with non-blocking reap, time it out, kill it, and report how it died — using only Vox, no `/bin/sh` and no coreutils. [`examples/supervisor.vox`](https://github.com/Vox-lang/vox/blob/bf2cba037976586c30d22a48e7b7246d347f54a1/examples/supervisor.vox) is this loop as a runnable program, supervising both a job that finishes and a job that hangs:

```
see process version "0.1" from "./libprocess.lib".

Set pid to fork the process.
If pid is 0 then,
    Exit 0.

a timer called clock.
Start the clock.
a boolean called 'child is still running' is true.
a boolean called 'child was killed' is false.
While 'child is still running',
    Set 'reap result' to reap child pid without waiting,
    If 'reap result' is pid then,
        Set 'child is still running' to false.
    If 'child is still running' then,
        a number called 'milliseconds waited' is the clock's elapsed in milliseconds,
        If 'milliseconds waited' is greater than 5000 then,
            Send signal 9 to process pid,
            Set 'reap result' to reap child pid,
            Set 'child is still running' to false,
            Set 'child was killed' to true.
    If 'child is still running' then,
        Wait 10 milliseconds.

If 'child was killed' then,
    Print "hang".
If 'child was killed' is false then,
    Set status to the reaped status,
    If crashed of status then,
        Print "died by signal {'signal of' of status}".
    If 'exited normally' of status then,
        Print "exit {'exit code of' of status}".
```

A note on timing: `the clock's elapsed in milliseconds` reports true milliseconds, so the 5000-millisecond deadline above fires accurately at the five-second mark.

### Send a signal: `Send signal`

Unlike `fork`/`reap`, this is a **statement**, not an expression:

```vox fragment
Send signal <N-expr> to process <pid-expr>.
```

It performs `kill(2)` (syscall 62): `<pid-expr>` is the target PID (loaded into `rdi`), `<N-expr>` is the signal number (loaded into `rsi`). `child` is accepted as an alias for `process`, mirroring `reap process/child`:

```
Send signal 9 to child pid.
```

On success it clears the error flag; on failure (`ESRCH` no such process, `EINVAL` invalid signal, `EPERM` not permitted) it sets it, exactly like the other syscall statements, so `On error` catches the failure:

```
Send signal 0 to process 999999.
On error print "no such process".
```

Signal 0 is the standard existence check: it delivers nothing but returns an error if no process has that PID, which makes it a safe way to probe the error path. A common pattern is to send a real signal to a forked child and reap it:

```
Set pid to fork the process.
If pid is 0 then,
    Wait 30 seconds.
If pid is greater than 0 then,
    Send signal 9 to process pid.
    Set reaped to reap any child process.
    If reaped is pid then,
        Print "sigkilled child reaped with matching pid".
```

## System Control: Shutdown, Reboot, Halt

```
Shutdown.
On error print "shutdown failed - are you root?".

Reboot.
Halt.
```

`reboot(2)`, requiring `CAP_SYS_BOOT` (root). Each statement calls `sync(2)` first to flush filesystem buffers, then issues the matching command:

| Statement | Aliases | Command |
| --- | --- | --- |
| `Shutdown` | `Poweroff` | `LINUX_REBOOT_CMD_POWER_OFF` |
| `Reboot` | `Restart` | `LINUX_REBOOT_CMD_RESTART` |
| `Halt` | - | `LINUX_REBOOT_CMD_HALT` |

**On success, none of these return** - the machine powers off/restarts/halts. On failure (not root, or no `CAP_SYS_BOOT`), the error flag is set instead of crashing or exiting, so `On error` safely catches the failure and execution continues - an unprivileged or accidental invocation can never bring down the machine.
