EMILUA(3em)
π EMILUA(3em) β the vibe check
okay so picture this: you cracked open the manual for Emilua, and the section header says EMILUA(3em) β that's the manual name right there, the Emilua reference, section 3em, bestie, don't let it scare you π«Ά
β¨ NAME β¨
Emilua β it's a Lua execution engine π and that's literally the whole flex.
Like, Lua runs inside it. It's the engine. That's the job. Iconic behavior, no notes. π
π SYNOPSIS π
here's the shape of it, copy-paste ready, exact characters, do not touch:
local system = require "system"
system.spawn(opts: table) -> subprocess
so first you pull in the module with require "system" and stash it in a local called system β then you call system.spawn with a single opts argument that's a table, and what comes back is a subprocess. π¦
opts: table β table in. -> subprocess β subprocess out. that's the whole trade, bestie. π
part 1 energy only β the rest of this page is coming, keep scrolling, we're literally not done π§
π DESCRIPTION π
Creates a new process. Slay! You're literally spawning a whole new little guy into existence. π
β¨ Named parameters β¨
program:string|filesystem.path|file_descriptorβ this is what you're running, bestie. Three ways to say it:stringβ a simple filename. The system goes hunting for this file in the list of directories specified byPATH(exactly the same way as forexecvp(3)). Very "look it up in the group chat directory" energy.filesystem.pathβ the path (which can be absolute or relative) of the executable. You know exactly where it lives.file_descriptorβ a file descriptor pointing straight to the executable. Seefexecve(3). Iconic, lowkey advanced.
arguments:string[]|nilβ a table of strings that will be used as the created process'argv. Onnil, an emptyargvwill be used.
β οΈ Caution: Don't forget to include the name of the program as the first argument. Not me forgetting argv[0] again π fr fr remember it.
environment:{ [string]: string }|nilβ a table of strings that will be used as the created process'envp. Onnil, an emptyenvpwill be used.
If "\0pid" is used as the value for an environment variable, the value will be replaced by the pid of the child. This is useful to implement some FD3-based protocols that use variables such as LISTEN_PID for robustness. Only one environment variable may use the value "\0pid". One (1) single variable, bestie, don't get greedy π
stdin,stdout,stderr:"share"|file_descriptor|nilβ the standard handles, obviously. Three moods:"share"β the spawned process will share the specified standard handle (stdin,stdout, orstderr) with the caller process. Same vibe, same handle π«Άfile_descriptorβ use the file descriptor as the specified standard handle (stdin,stdout, orstderr) for the spawned process.nilβ create and use a closed pipe end as the specified standard handle (stdin,stdout, orstderr) for the spawned process.
π©· Note: On Windows, it's unspecified (will vary depending on whether any redirection is done at all, dwCreationFlags's value, etc). Chaos behaviour, ngl, don't count on it.
extra_fds:{ [integer]: file_descriptor|libc_service.slave }|nilβ extra file descriptors for the child to inherit. Parent and child processes don't need to share the same numeric value reference for a given file description. The file descriptor number used in the child process will be the one specified in the key portion of the dictionary argument. Only file descriptors numbered from 3 to 9 are acceptable (i.e. the same limitations of low fds that you're likely to face on older UNIX shells). If you need to pass more than 10 file descriptors βstdin,stdout,stderr, plus these extra 7 file descriptors β use another interface (e.g.SCM_RIGHTS). Seven extra fds max, that's the whole budget bestie π
π©· Note: Not available on Windows.
signal_on_gcreaper:integer=system.signal.SIGTERMβ okay so this one's a whole saga, sit down. Each process is responsible for reaping its own children. A process that fails to reap its children will soon exhaust its OS-provided resources. For short-lived programs that's hardly a problem given the process quits and its children are re-parented to the next subreaper in the chain (usually theinitprocess). However for a concurrency runtime such as Emilua we expect other concurrent tasks to remain unaffected by the one failing task (be it a single fiber or the whole VM). Emilua will then transparently reap any child process for which its handle has been GC'ed.signal_on_gcreaperallows the user to specify a signal to be sent to the child that's about to be reaped at this occasion.
By default, the signal system.signal.SIGTERM will be sent to the child and then the main Emilua process will β indefinitely, non-blockingly, and non-pollingly β await for all of its children to finish even if there's no longer any Lua program being executed. Use the more dangerous system.signal.SIGKILL if you don't want the main Emilua process to wait long for the child. Use 0 if you don't want the Emilua reaper to send any signal before awaiting for the child.
π©· Note: Ideally the system kernel would expose some re-parent syscall, but until then (if ever), signal_on_gcreaper will be necessary. Manifesting that syscall for us π
π©· Note: Only available on Linux.
pd_daemon:boolean= see-below β instead of the default terminate-on-close behaviour, allow the process to live until it is explicitly killed withkill(2). Main character energy: it stays alive until it decides otherwise.
By default, it's true unless the parent process is in capability mode (see cap_enter(2)).
π©· Note: Only available on FreeBSD.
scheduler.policy:string|nilβ the scheduling vibe check. Values acceptable on Linux for non-real-time policies are:"other"β seeSCHED_OTHER."batch"β seeSCHED_BATCH."idle"β seeSCHED_IDLE.
Values acceptable on Linux for real-time policies are:
"fifo"β seeSCHED_FIFO. Must also setscheduler.priority. Required, no skipping π"rr"β seeSCHED_RR. Must also setscheduler.priority. Also required, don't test me.
π©· Note: Not available on Windows.
scheduler.priority:integer|nilβ the interpretation of this parameter is dependant onscheduler.policy. It literally takes its meaning from the policy, iykyk.
π©· Note: Not available on Windows.
scheduler.reset_on_fork:boolean=falseβ iftrue, grandchildren created as a result of a call tofork(2)from the direct child will not inherit privileged scheduling policies. If set, must also setscheduler.policy. Yes, you need the policy, them's the rules.
π©· Note: Not available on Windows.
start_new_session:boolean=falseβ whether to create a new session and become the session leader. Ontrue, callssetsid()on the child. Fresh session, fresh start, literally β¨
π©· Note: On Windows, DETACHED_PROCESS|CREATE_NEW_PROCESS_GROUP is used in creation flags.
set_ctty:file_descriptor|nilβ set the controlling terminal for the child. It is an error to specifyset_ctty, but omitstart_new_session. Both or neither π
π©· Note: It's an error to specify bothset_cttyandforeground. Pick a lane bestie.
π©· Note: Not available on Windows.
process_group:integer|nilβ set the process group (it callssetpgid()on the child). On0, the child's process group ID is made the same as its process ID. Cute little self-referential moment.
π©· Note: On Windows, only0is supported (CREATE_NEW_PROCESS_GROUPis used in creation flags).
foreground:"stdin"|"stdout"|"stderr"|file_descriptor|nilβ make the child be the foreground job for the specified controlling terminal by callingtcsetpgrp()(SIGTTOUwill be blocked for the duration of the call). It is an error to specifyforeground, but omitprocess_group. So set the process group first, okay? π«Ά
π©· Note:"stdin","stdout", and"stderr"can only be specified if parent and child share the same file for the specified standard handle. Same file or it's not happening.
π©· Note: It's an error to specify bothforegroundandset_ctty. Again. Pick. A. Lane. π
π©· Note: Not available on Windows.
ruid:integer|nilβ set the real user ID.
π©· Note: Not available on Windows.
euid:integer|nilβ set the effective user ID. If the set-user-ID permission bit is enabled on the executable file, its effect will override this setting (seeexecve(2)). The executable can just snatch it back, no cap.
π©· Note: Not available on Windows.
rgid:integer|nilβ set the real group ID.
π©· Note: Not available on Windows.
egid:integer|nilβ set the effective group ID. If the set-group-ID permission bit is enabled on the executable file, its effect will override this setting (seeexecve(2)). Same override saga aseuidπ
π©· Note: Not available on Windows.
extra_groups:integer[]|nilβ set the supplementary group IDs. The whole squad.
π©· Note: Not available on Windows.
set_no_new_privs:boolean=falseβ set theno_new_privsattribute.
π©· Note: Not available on Windows.
seccomp_set_mode_filter:byte_span|nilβ set the secure computing (seccomp) mode to limit the available system calls. Locking it down, security girlie behaviour π
π©· Note: Only available on Linux.
landlock_restrict_self:file_descriptor|nilβ enforce a Landlock ruleset.
π©· Note: Only available on Linux.
umask:integer|nilβ seeumask(3p).
π©· Note: Not available on Windows.
working_directory:filesystem.path|file_descriptor|nilβ sets the working directory for the spawned program. Where it calls home πpdeathsig:integer|nilβ signal that the process will get when its parent dies. If the executable file contains set-user-ID, set-group-ID, or contains associated capabilities,pdeathsigwill be cleared. Rude, but that's the rule.
π©· Note: "Parent" is a difficult term to define here. For Linux, that's not the process, but the thread. For Emilua, the thread will exist for at least as long as the calling Lua VM exists (even if the Lua VM might jump between threads). The thread will also exist for even longer, for as long as other Lua VMs are using it. It's threads all the way down, bestie π
π©· Note: Not available on Windows.
setns_user:file_descriptor|nilβ enter in this Linux user namespace. Whensetns_useris specified, Emilua always enter in the user namespace before any other namespace. User namespace goes first, it's the opener of the group chat β¨
π©· Note: Only available on Linux.
setns_mount:file_descriptor|nilβ enter in this Linux mount namespace.
π©· Note: Only available on Linux.
setns_uts:file_descriptor|nilβ enter in this Linux UTS namespace.
π©· Note: Only available on Linux.
setns_ipc:file_descriptor|nilβ enter in this Linux IPC namespace.
π©· Note: Only available on Linux.
setns_net:file_descriptor|nilβ enter in this Linux net namespace.
π©· Note: Only available on Linux.
show_window:"hide"|"shownormal"|"normal"|"showminimized"|"showmaximized"|"maximize"|"shownoactivate"|"show"|"minimize"|"showminnoactive"|"showna"|"restore"|"forceminimize"|nilβ if present,STARTUPINFO.dwFlagswill includeSTARTF_USESHOWWINDOW, andSTARTUPINFO.wShowWindowwill be initialized with the indicated value. Window drama, but make it flags πͺ
π©· Note: Only available on Windows.
create_breakaway_from_job:boolean=false
π©· Note: Only available on Windows.
create_new_console:boolean=false
π©· Note: Only available on Windows.
create_no_window:boolean=false
π©· Note: Only available on Windows.
detached_process:boolean=false
π©· Note: Only available on Windows.
π subprocess FUNCTIONS π
wait(self)
Okay bestie, this is the one where you actually wait for the process to finish and then reap it π. All the juicy info about how it ended gets stashed in exit_code and exit_signal, so you can peek at those afterwards. Slay.
Note β and this is important, so listen up π«Ά β if your code never bothers to callwait(), the Emilua runtime will reap the process for you anyway, the moment the GC collectsselfand the underlying subprocess finishes. But like, don't rely on that vibe. Reaping your children processes is how you free up OS-associated resources, so it's genuinely iconic behaviour to do it yourself. No cap. π
kill(self, signal: integer)
Send a signal to the process. That's it, that's the function π¦.
Tip β you can pass 0 (the null signal) to not actually send any signal, but still let the OS perform its permission checks. Those get reported as raised errors. Sneaky little vibe check on your privileges, iykyk. β¨
cap_get(self) β system.linux_capabilities
See cap_get_pid(3). Go off, read that one π₯.
πΈ subprocess PROPERTIES πΈ
exit_code: integer
The process return code, exactly as it got passed to exit(3). But here's the plot twist π β if the process was terminated by a signal, this will be 128 + exit_signal, same way BASH does it.
Note β you can only access this field for wait()'ed processes. No peeking early, bestie π.
exit_signal: integer|nil
The signal that terminated the process. If it wasn't terminated by a signal, this is nil. Simple as that π
.
Note β same deal, only accessible for wait()'ed processes. Access it before that and it's giving nothing but trouble π.
pid: integer|nil
The process id the OS uses to represent this child process β like, the number that shows up in /proc on some UNIX systems, you know the one π
For wait()'ed processes, the value is nil. Gone. Reaped. Rest in peace ποΈ.
π BUGS π
Okay so Windows properly supports line-breaks in arguments β good for her π
β but the second you're running a .bat or a .cmd file, there's a bug in CMD.exe that just stops parsing the command line at the line-break. Not me finding out Windows fumbled the bag again π.
And to be clear: this is a bug in Windows. Not in Emilua. Not in your code. Windows. π
To fix it (and honestly, "fix" is doing a lot of work here), you need to install TCC-RT from JP Software β or another CMD.exe replacement such as wineconsole β and then set COMSPEC to this new interpreter. That's the whole workaround. And Microsoft won't fix this bug. Periodt. ππ₯
Alright bestie, that's the whole page π«Άβ¨ β Emilua 0.12.1-nixpkgs1, dated 1980-01-01, EMILUA(3em), and honestly that date is a whole aesthetic of its own π. Now go reap some processes and slay the day. π
π¦ where this page comes from
- nixos 26.05: emilua 0.12.1
- nixpkgs unstable: emilua 0.12.1
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms