Platform support
This page lists the systems DataMind Oda runs on, and the native pieces behind its process confinement and session file locking. It is for people who build, ship or debug Oda itself.
Which systems Oda runs on
| System | Desktop app | Released runtime |
|---|
| Windows, x64 | Yes | Yes |
| Windows on Arm | No | No |
| Linux, x64 and arm64 | No | Yes |
| macOS, x64 and arm64 | No | Yes, on macOS 14 or later |
- The desktop app runs on Windows 10 and Windows 11, 64-bit, and carries a prepared copy of the runtime.
- Released runtime packages ship the runtime for other systems, to drive from your own programs, for example with the Oda Python SDK.
- Windows is x64 only (
Windows supports x64 only.). There is no Windows on Arm release.
- Everything else is refused rather than approximated: on a system with no published package, nothing falls back to another system's build, the confinement launcher's probe reports unusable and lock acquisition rejects.
The confinement launcher on Linux
Linux releases carry a small executable that confines a command to the paths you grant, then starts it under those restrictions. It uses Landlock, the Linux kernel's confinement mechanism.
- Everything is an allow-list: whatever is not granted is denied.
--ro <path> grants read and execute under a path, and --rw <path> grants full access under a path. The restrictions cover the command and every descendant process it starts.
- Before it restricts anything, the launcher stops the command from gaining privileges through setuid or setgid binaries.
- The launcher can probe this kernel and reports one of three verdicts:
- full (probe line
landlock: fully enforced): the kernel enforces everything the launcher asks of it.
- partial (probe line
landlock: partially enforced (older ABI)): the kernel enforces an older version of the mechanism, so the access types added later are not covered. Runs continue, and each one prints landlock-run: partial enforcement (older Landlock ABI) on standard error.
- unusable: the launcher cannot enforce anything. This verdict deliberately merges three causes: a kernel without the mechanism, a disabled security module, or a missing binary. The answer is the same in each case: do not trust this launcher to confine the command.
- The probe is a real enforcement attempt, not a version check: it actually restricts a short-lived process with a maximal ruleset, so a kernel that has the calls but refuses enforcement still reports unusable.
- The probe waits up to two seconds for an answer, and a hang counts as unusable.
- Fail closed: if the ruleset cannot be created or the kernel does not enforce it, the launcher exits with code 125 and the wrapped command is not run. Every launcher-level failure ends the same way: bad usage, a kernel that cannot enforce, an unopenable grant path, or a failed start. The kernel refusal reads
landlock is not enforced by this kernel (ABI unsupported or disabled).
- Code 125 alone is not proof. A command you wrap may use 125 for its own failures; treat 125 as a launcher failure only when a fatal line, prefixed with
landlock-run: , appears on standard error.
- Usage errors also exit 125 before anything runs:
usage error: missing `-- <argv>...` command, usage error: unknown argument: <flag>, --ro requires a path, --probe takes no other arguments.
- The launcher takes its instructions only from its own arguments. No environment variable changes what is granted, and which binary confines a process is never decided by the ambient environment.
Session file locks
While a session log is open for writing, Oda holds an exclusive lock on it so only one writer appends at a time. The lock binding is a separate native piece; the messages a user sees when a chat is already being written are in Session log problems.
- The attempt is exclusive and non-blocking. Contention rejects with
EAGAIN or EWOULDBLOCK instead of waiting, so a busy file surfaces as a refusal, never as a stall.
- It is an advisory lock, not an I/O permission. It coordinates writers that take it, and another program can still read or write the file.
- The caller opens the file and keeps the descriptor open while it holds the lock. The binding never opens, duplicates, closes or explicitly unlocks the file on the caller's behalf.
- The lock is released when the last descriptor for that open file is closed, including when the process holding it exits or is killed. There is no stale-lock timeout, and closing one descriptor does not release the lock while another descriptor for the same open file remains.
- There is no blocking wait and no shared lock: the only lock on offer is exclusive and non-blocking.
- The native piece loads lazily, on the first lock attempt. A missing binary surfaces as a refused attempt, not as an import failure.
- Errors identify themselves: an invalid descriptor rejects with
EBADF; syscall failures carry the error code, a positive errno and syscall: flock, and the message names the code, for example EAGAIN: flock failed; a descriptor outside the signed integer range is a range error, fd must be a signed C int.
- An unsupported platform rejects with its own error,
flock is not supported on <platform>-<arch> (ERR_FLOCK_UNSUPPORTED_PLATFORM). Windows does not use this mechanism; Oda keeps its existing locking implementation there.
- A missing or unloadable binding rejects acquisition. Nothing compiles during installation, and an unprotected lock is never granted silently.
- Behaviour follows the host filesystem's own lock semantics. Network filesystems can differ, and replacing or removing the file at a path does not carry a lock to the new file.
What a release carries
- A released runtime keeps its pieces together beside the runtime executable: the executable itself, a search helper, a process-spawn helper on macOS, the Office conversion pieces, and a resources directory. The Office pieces stay beside the executable on the normal filesystem.
- On Linux, the same package serves both common C libraries, glibc and musl.
- The packages declare their systems in metadata, and an install takes only the package that matches the running operating system and processor. There are no installation scripts: nothing compiles or downloads when the packages are installed.
How a release is checked
- The native packages share one version, and platform packages publish before the package that selects them, so a published version never points ahead of its payloads.
- A release is refused unless it runs from a release tag, and a tag that does not match the package versions stops it.
- Packing refuses missing pieces: a package without its built files, or a platform package whose binaries are missing or built for the wrong architecture, is stopped with a message naming what is absent.
- A rehearsal install then follows: the packed packages are installed into a throwaway consumer outside the project, checked for installation scripts and compared byte for byte with the build, and the installed launcher and lock binding are driven end to end. A launcher that loses its executable bit on the way through packing is caught and named.
- To build Oda and its installer from source, see Building and shipping Oda.
Questions, answered
Is there a Windows on Arm build?
No. Windows is x64 only (Windows supports x64 only.), and the desktop app runs on Windows 10 and Windows 11, 64-bit. A system with no published package is refused rather than approximated.
The probe reports unusable. What does that mean?
Not necessarily a fault, and the verdict does not say which of three causes it is: a kernel without the mechanism, a disabled security module, or a missing binary. The answer is the same in each case: do not trust this launcher to confine anything. Fix what you find, and treat a probe that still reports unusable as confinement being unavailable.
A command exited with 125. Did the launcher fail?
Maybe. 125 is the launcher's failure code, but a wrapped command may also exit 125 on its own. Check standard error: a launcher failure always prints a fatal line prefixed with landlock-run: , and in that case the wrapped command was not run.
Can a command run when the launcher cannot enforce the restrictions?
No. The launcher fails closed: when the ruleset cannot be created or the kernel does not enforce it, it exits with code 125 and the wrapped command is not run.
What does partial enforcement mean?
The kernel enforces an older version of the mechanism, so the access types added later are not covered. Runs continue, and each prints landlock-run: partial enforcement (older Landlock ABI) on standard error. Where every access type must be governed, run on a host whose probe reports full.
Can an environment variable change which binary confines a process?
No, by design. The choice is never decidable from the ambient environment: the launcher takes its instructions only from its own arguments, and nothing the calling process exports changes what is confined.
Does the file lock stop other programs from reading or writing the file?
No. It is an advisory lock: it coordinates processes that respect it, and another program can still read or write the file directly. It is not a way to protect a file from other programs.
When is a session lock released?
When the last descriptor for that open file is closed, including when the process holding it exits or is killed. There is no stale-lock timeout, and closing one descriptor does not release the lock while another still refers to the same open file.
What happens if the lock binding is missing on this system?
Acquisition rejects. Nothing compiles during installation, and an unprotected lock is never granted silently, so the failure is a refused attempt rather than a broken import. Use a system with a published package.
Is there a blocking wait or a shared lock?
No. The only lock on offer is exclusive and non-blocking: contention rejects with EAGAIN or EWOULDBLOCK straight away, and there is no shared or waiting variant.
Why did packing stop on a missing file?
Packing is the last gate before publishing, and it checks the files it is about to ship: a package whose built files are missing, or a platform package without its binaries or with binaries for the wrong architecture, is stopped instead of being published. Run the project build first, then pack again.