76 lines
3.6 KiB
Markdown
76 lines
3.6 KiB
Markdown
# Architecture
|
|
|
|
How doos works internally. Behavior contract is `SPEC.md`; this explains the code.
|
|
|
|
## File map
|
|
|
|
| File | Class / role |
|
|
|---|---|
|
|
| `src/spec.hpp` | `spec::` constants: caps, mount lists, device list, paths. Single source of truth with `SPEC.md`. |
|
|
| `src/util.{hpp,cpp}` | `SysError` (errno-carrying exception), `Fd` (RAII fd), `FileLock` (bounded flock), `fsutil` (mkdirs, read/write, bounded `removeTree`). |
|
|
| `src/network.{hpp,cpp}` | `NetMode` + `Network` (parse, resolv.conf per mode), `SlirpSession` (owns the slirp4netns process). |
|
|
| `src/container.{hpp,cpp}` | `Container` (one container's paths, meta, pid file, layout), `Store` (list, get_or_create, remove). |
|
|
| `src/sandbox.{hpp,cpp}` | `Sandbox`: start a container (clone + mounts + pivot_root + init) or join a running one (setns). |
|
|
| `src/cli.{hpp,cpp}` | `Cli`: argument parsing and the four commands. |
|
|
| `src/main.cpp` | Catches exceptions, prints `doos: <message>`, exit 1. |
|
|
| `tests/run.sh` | End-to-end suite. |
|
|
| `scripts/release.sh` | Bump, build, test, commit, tag. |
|
|
| `apparmor/doos` | Profile granting `userns` on Ubuntu 24.04+. |
|
|
|
|
## attach flow
|
|
|
|
```
|
|
Cli::cmdAttach
|
|
Store::getOrCreate lock, create layout + meta if missing
|
|
Sandbox::run
|
|
FileLock (start-or-join decision)
|
|
runningPid()? yes -> join() no -> start()
|
|
|
|
start():
|
|
clone(NEWUSER|NEWNS|NEWPID|NEWUTS|NEWIPC[|NEWNET]) child = container pid 1
|
|
parent writes /proc/<child>/{setgroups,uid_map,gid_map} 0 -> invoking uid, size 1
|
|
slirp mode: SlirpSession.start(child) waits for ready-fd
|
|
recordPid(child) (pid + starttime, defeats pid reuse)
|
|
lock.unlock() joiners must not wait for container lifetime
|
|
write 'G' on sync pipe child continues
|
|
parent: ignore SIGINT/SIGQUIT, forward SIGTERM/SIGHUP, waitpid(child)
|
|
on exit: pid file cleared, slirp stopped (RAII)
|
|
|
|
child (containerInit, pid 1):
|
|
PDEATHSIG=SIGKILL, close inherited lock fd, wait for 'G'
|
|
setupRoot -> sethostname -> loopback up (isolated netns) -> rlimits/env/no_new_privs
|
|
fork worker -> exec command
|
|
pid 1 loop: forward signals, reap all, on worker exit kill(-1, SIGKILL), exit with its code
|
|
```
|
|
|
|
## setupRoot order
|
|
|
|
1. `MS_REC|MS_PRIVATE` on `/` so nothing propagates to the host.
|
|
2. Bind the container `rootfs/` onto itself (pivot_root needs a mountpoint).
|
|
3. Write `resolv.conf` into the `/etc` upper dir (matches network mode every start).
|
|
4. Overlay mounts (`lowerdir=<host dir>,upperdir,workdir,userxattr`, nosuid, nodev).
|
|
5. Compat dirs: recreate host symlinks (usr-merge) or overlay real dirs.
|
|
6. Bind private dirs (`/root`, `/var`), mount tmpfs (`/tmp`, `/run`).
|
|
7. `/dev`: tmpfs, bind host device nodes, new devpts, `/dev/shm`, symlinks.
|
|
8. `/proc`, then read-only remount and masks from `spec.hpp`.
|
|
9. `chdir(rootfs)`, `pivot_root(".", ".")`, lazy unmount of old root.
|
|
|
|
## join flow
|
|
|
|
Open `/proc/<pid>/ns/{user,mnt,uts,ipc,net,pid}` and `/proc/<pid>/root` first, fork,
|
|
`setns` user first (grants capability for the rest), `fchdir`+`chroot` to the container
|
|
root, fork again (pid ns applies to children), apply env and limits, exec.
|
|
In `host` network mode the net namespace is not joined.
|
|
|
|
## Lifetime model
|
|
|
|
The namespaces live as long as the first `attach` command. When it exits, pid 1 kills
|
|
the rest and the container is `stopped`. State (upper dirs, `/root`, `/var`) persists on
|
|
disk until `delete --yes`. There is no daemon.
|
|
|
|
## Error handling
|
|
|
|
All failures throw (`SysError` or `std::runtime_error`); `main` prints one line.
|
|
Inside the child, errors print `doos: <message>` and exit 125. A parent-side failure
|
|
after `clone` kills the child (`Reaper`), so no half-built container survives.
|