EMILUA(3em)
πΈ NAME πΈ
emilua(3em) β Emilua - Lua execution engine π
β¨ SYNOPSIS β¨
local libc_service = require "libc_service"
π DESCRIPTION π
Okay so bestie, hereβs the vibe: the master arbitrates calls to libc that are related to ambient authority in the process holding the slave end. Before a call to the real libc is attempted in the slave end, the process forwards the request to the master and blocks the thread until a reply is received. π«Ά
The protocol follows the request-reply model. Low-level protocol details are hidden by the Emilua runtime, and the Lua programmer only sees a request-reply API. β¨
Multiplexing is not allowed. That means only one thread from the slave end can be served at anytime, and this layout will minimize the opportunity for possible parallelism if the process holding the slave end makes too many calls for ambient authority access. π As a hidden implementation detail, Emilua will transparently pipeline requests from different threads to minimize latency a little. π
π FUNCTIONS π
π
receive(self) π
Receive the next attempted libc call for ambient authority. π
Data about the call is stored in the objectβs properties. π¦
send(self, result: value, errno: integer|generic_error|system_error = 0)
Okay so this is the one you call when you're the one answering the phone π β like, you're the handler for the libc call that's currently being arbitrated, and send() is how you hand the answer back. The signature is literally:
send(self, result: value, errno: integer|generic_error|system_error = 0)
Check the default on that errno bestie: it's already 0 for you, so if everything went fine you literally don't have to type it π
. And errno can be a plain integer, a generic_error, or a system_error β pick your fighter.
Now, what even is result? Depends which call you're answering. Here's the whole menu, no cap π:
openβintegeropenatβintegerunlinkβintegerrenameβintegerstat,lstatβ this one's special, see below πaccess,eaccessβintegermkdirβinteger
Lowkey a lot of integers. It's giving "just give me a number" energy and honestly? Respect. π₯
the stat / lstat situation π
These two are the divas of the group. Not an integer.
- On error, it should be the number
-1. That's it. That's the whole vibe. π - On success, it should be an object (table) with these properties, and I need you to get every single one right because this is the exact struct:
dev: numberβ Device ID.ino: numberβ Inode number.mode: numberβ File access mode bits.type: "regular"|"directory"|"symlink"|"block"|"character"|"fifo"|"socket"β File type. Yes, exactly one of those seven strings, don't get creative β¨nlink: numberβ Number of hardlinks.uid: numberβ User ID of owner.gid: numberβ Group ID of owner.rdev: numberβ Likedev, but for special files.size: numberβ Total size, in bytes.atime: filesystem.clock.time_pointβ The time of the last access.mtime: filesystem.clock.time_pointβ The time of the last modification.ctime: filesystem.clock.time_pointβ The time of the last status change.blksize: numberβ Block size for filesystem I/O.blocks: numberβ Number of blocks allocated.
Bestie the way there are three separate time fields and they all want filesystem.clock.time_point β atime, mtime, ctime, all iconically distinct, all required, don't skip one thinking nobody will notice π
Fr fr: on error it's the number-1, notnil, notfalse, not a vibe.-1. Periodt. π
Go ahead and fill them all in and send() it back like the main character you are π«Ά
β¦and the return-type table just keeps going, bestie, so buckle up π every single one of these returns an integer unless it says otherwise:
rmdirβ returns aninteger, periodt.connect_unixβ also aninteger.connect_inetβinteger.connect_inet6βinteger.bind_unixβinteger.bind_inetβinteger.bind_inet6βinteger.
π getaddrinfo β the one with the extra lore
This icon does not just give you a plain integer, no ma'am. Its return value has β¨rulesβ¨ and you're gonna learn them.
When it fails, the value should be one of these exact strings, ok? Like, literally character for character β no freestyling π
"again""badflags""fail""family""memory""noname""service""socktype""system"
When it succeeds, it should be an array with the following members, and yes, they come in the same order:
ip: ip.addressβ the address the query resolved to. That's it, that's the whole vibe π«service: integer|nilβ the service port the query resolved to. Ornil, she's flexible like that.
π«Ά alternatively β and this is a valid scenario for DNS andgetaddrinfo(), not a bug, don't come for me β if the call should succeed with a reply of 0 elements, you can use the valuenilinstead. Zero elements is a whole mood and she's allowed π
β¨ send_with_fds
send_with_fds(self, result: value, fds: file_descriptor[], errno:
integer|generic_error|system_error = 0)
Okay bestie, this is the one where you actually hand the answer back π β it sends the result for the libc call that's currently being arbitrated. So whichever poor little syscall is sitting there waiting on you, this is how you tell it what happened.
You pass self, you pass the result (and what counts as a valid result depends entirely on which function you're answering for β see the receipts below π), you pass fds as a file_descriptor[], and errno defaults to 0 and can be an integer, a generic_error, or a system_error.
So here's the whole "which function wants which result" situation, laid out like a group chat roll call π«Ά:
openβresultis aninteger.openatβresultis aninteger.unlinkβresultis aninteger.renameβresultis aninteger.stat,lstatβ okay this one's the main character, pay attention π- On error, it should be the number
-1. - On success, it should be an object (table) with these properties:
dev: numberβ Device ID.ino: numberβ Inode number.mode: numberβ File access mode bits.type: "regular"|"directory"|"symlink"|"block"|"character"|"fifo"|"socket"β File type.nlink: numberβ Number of hardlinks.uid: numberβ User ID of owner.gid: numberβ Group ID of owner.rdev: numberβ Likedev, but for special files.size: numberβ Total size, in bytes.atime: filesystem.clock.time_pointβ The time of the last access.mtime: filesystem.clock.time_pointβ The time of the last modification.ctime: filesystem.clock.time_pointβ The time of the last status change.blksize: numberβ Block size for filesystem I/O.blocks: numberβ Number of blocks allocated.
- On error, it should be the number
access,eaccessβresultis aninteger.mkdirβresultis aninteger.rmdirβintegerπconnect_unixβintegerπconnect_inetβintegerπconnect_inet6βintegerπbind_unixβintegerπbind_inetβintegerπbind_inet6βintegerπgetaddrinfoβ okay bestie, this one's the main character of the column, so buckle up π
For getaddrinfo, if it errors, the value should be one of these strings:
"again""badflags""fail""family""memory""noname""service""socktype""system"
And on success it should be an array with these members, in exactly this order, no shuffling π₯
ip: ip.addressβ the address the query resolved to.service: integer|nilβ the service port the query resolved to.
And alternatively β if the call should succeed but come back with a reply of 0 elements (which, yes, is a totally valid scenario for DNS and getaddrinfo()), you can just use the value nil instead. πΈ
β¨ use_slave_credentials(self) β¨
Forward the request to the real libc running in the slave end. Like passing a note to the actual adult in the room π«Ά
β¨ arguments(self) β value... β¨
The arguments passed to the last requested call.
The arguments depend on whichever function got called (peep the function_ property below π).
π open
path: filesystem.pathβ the file path.flags: string[]β the open flags.
flags may contain:
"append"β open the file in append mode."create"β create the file if it doesn't exist."directory"β if the pathname isn't a directory, cause the open to fail."exclusive"β make sure a new file gets created. Has to be combined with"create"."no_follow"β fail if the path resolves to a symbolic link."path"β get a stable reference to an inode without actually opening the contents."read_only"β open the file for reading."read_write"β open the file for reading and writing."sync_all_on_write"β open the file so that write operations automatically synchronise the file data and metadata to disk (FILE_FLAG_WRITE_THROUGH/O_SYNC)."temporary"β create an unnamed temporary regular file."truncate"β open the file with any existing contents truncated."write_only"β open the file for writing.
And then:
mode: integerβ optional argument, bestie. Only present if"create"or"temporary"show up inflags. β¨
π openat
path: filesystem.pathβ the file path.flags: string[]β the open flags.
flags may contain:
"append"β open the file in append mode."create"β create the file if it doesn't exist."directory"β if the pathname isn't a directory, cause the open to fail."exclusive"β make sure a new file gets created. Has to be combined with"create"."no_follow"β fail if the path resolves to a symbolic link."path"β get a stable reference to an inode without actually opening the contents."read_only"β open the file for reading."read_write"β open the file for reading and writing."sync_all_on_write"β open the file so that write operations automatically synchronise the file data and metadata to disk (FILE_FLAG_WRITE_THROUGH/O_SYNC)."temporary"β create an unnamed temporary regular file."truncate"β open the file with any existing contents truncated."write_only"β open the file for writing."resolve_beneath"β path resolution must not cross the fd directory."resolve_in_root"β treat the directory referred to bydirfdas the root directory while resolving pathname. Absolute symbolic links get interpreted relative todirfd. π"resolve_no_magiclinks"β disallow all magic-link resolution during path resolution."resolve_no_symlinks"β disallow resolution of symbolic links during path resolution."resolve_no_xdev"β disallow traversal of mount points during path resolution (including all bind mounts)."resolve_cached"β make the open operation fail unless all path components are already chilling in the kernel's lookup cache.
And then, same deal as before:
mode: integerβ optional argument. Only present if"create"or"temporary"appear inflags. π
π unlink
β¦and unlink is where this part runs out of runway β the rest of it lives in the next chunk, so hold that thought, bestie π¦
path:filesystem.pathβ the file path π€οΈ
rename
path1:filesystem.pathβ the file1 path (the one you're moving from, bestie) π€path2:filesystem.pathβ the file2 path (the one you're moving to) π₯
stat, lstat
path:filesystem.pathβ the file path π
ok so heads up, these two share the exact same param list βstatfollows symlinks,lstatdoesn't, but that β¨loreβ¨ lives in the C docs, not here. All this page is telling you is: onepath, typefilesystem.path.
access, eaccess
path:filesystem.pathβ the file path πamode:"f"|string[]β the requested access mode ποΈ
It's either "f", or an array of strings that may contain any of these lil guys:
"r"βR_OK. Read permission π"w"βW_OK. Write permission βοΈ"x"βX_OK. Execute permission π
ngl the whole "f"-or-array thing is giving "pick a lane", but it's literally what the page says, so π
mkdir
path:filesystem.pathβ the file path πmode:integerβ file access mode bits π’
rmdir
path:filesystem.pathβ the file path ποΈ
connect_unix
path:filesystem.pathβ the UNIX socket path π
connect_inet
ipv4_addr:ip.addressβ the IPv4 address πport:integerβ the port πͺ
connect_inet6
ipv6_addr:ip.addressβ the IPv6 address πport:integerβ the port πͺ
bind_unix
path:filesystem.pathβ the UNIX socket path πͺ’
bind_inet
ipv4_addr:ip.addressβ the IPv4 address πport:integerβ the port πͺ
bind_inet6
ipv6_addr:ip.addressβ the IPv6 address πport:integerβ the port πͺ
getaddrinfo
node:stringβ an Internet host such ashost.exampleπ₯οΈservice:stringβ an Internet service such ashttpπ§Ύprotocol:"tcp"|"udp"|nilβ an Internet protocol. Only really useful if you're resolving theserviceas well π―
descriptors(self) β file_descriptor...
Okay bestie, this one is the payoff π it extracts and returns the file descriptors that came in with the last requested call. Like, it goes and grabs them out of the object for you. Iconic behaviour, honestly.
β οΈ Note β once you call this function, the returned descriptors no longer stay stored in this object. IOW: a second call to this function will always return nothing. So don't call it twice expecting seconds, you will simply get crumbs π grab them once and hold on tight π«Ά
β¨ PROPERTIES β¨
function_: string
The last requested call ποΈ β literally just a string telling you which one you asked for last.
Possible values, in order, no skips, we're listing all of them π :
"open""openat""unlink""rename""stat""lstat""access""eaccess""mkdir""rmdir""connect_unix""connect_inet""connect_inet6""bind_unix""bind_inet""bind_inet6""getaddrinfo"
and that's the whole page, bestie β you ate that ππ
This one was Emilua 0.12.1-nixpkgs1, dated 1980-01-01 (yes the date is a whole vibe, we don't question it π). Go forth and connect_inet6 responsibly, ily β¨ππ¦
π¦ 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