afunix-polyfill is a complete linux IPC solution, compared to dbus it is:
- just a socket
- serverless
- single header file
- data format agnostic
- dependency-free
- using unix permissions instead of custom policies
- theoretically posix compliant
- bullshit and NIH free
A word of warning: this is an experiment or proof-of-concept or RFC. If you need something usable, check back 2017.
The backstory originates from ubus (aep-ubus, not openwrt-ubus), which supports aproximatly the same messaging directives as dbus without a server.
But when you think really hard,
almost every IPC problem people have in linux can be reduced to:
- send message to a group identified by name
- optionally respond to that message
which could easily be implemented with DGRAM on AF_UNIX plus SO_REUSEPORT and maybe SO_BROADCAST.
but none of these things work in linux.
So here is a 'polyfill' for that, which emulates it, until it gets fixed in linux.
As soon as it's fixed, you can seemlessly transition to the proper posix api, by just removing the afunix_ prefix.
here's some pseudo code of how to implement any of the dbus
functionality in just plain posix.
At least you could, if linux didn't suck.
- "remote procedure call returning void"
//server
int sock = socket(AF_UNIX, SOCK_DGRAM, 0);
bind(sock, "/var/bus/myservice/dostuff");
for(;;) { char buf[]; recv(socket, buf); printf("let's do stuff"); }
//client
int sock = socket(AF_UNIX, SOCK_DGRAM, 0);
connect(sock, "/var/bus/myservice/dostuff");
send(sock, "do it!");
- "publish/subscribe" or "notification"
//server
int sock = socket(AF_UNIX, SOCK_DGRAM, 0);
bind(sock, "/var/bus/myservice/event");
sendto(sock, "hey everyone, things happened!", BROADCAST);
//client
int sock = socket(AF_UNIX, SOCK_DGRAM, 0);
connect(sock, "/var/bus/myservice/event");
recv(socket, buf);
- "remote procedure call returning string"
//server
int sock = socket(AF_UNIX, SOCK_DGRAM, 0);
bind(sock, "/var/bus/myservice/dostuff");
for(;;) {
char buf[];
address sender;
recv_from(socket, buf, &sender);
send_to(socket, "ok i did it!", sender);
//client
int sock = socket(AF_UNIX, SOCK_DGRAM, 0);
connect(sock, "/var/bus/myservice/dostuff");
send(sock, "do it!");
recv(socket, buf);
And poof, the whole thing is solved. The best part, this is already how AF_INET behaves!
All it takes is copying the exact behaviour over to AF_UNIX.
But while we wait for hell to freeze over, let's hop over to using afunix_polyfill
should be pretty self explaining, since it just does exactly what the posix api is supposed to do.
// creates all the polyfill backends and wraps them in a single fd,
// which behaves exactly like a socket
int fd = afunix_socket();
// creates the file, and listens on it
// valid options are:
// - SO_REUSEPORT round robin messages between all active binds
// without that option, you can only have one bind
int ret = afunix_bind(int fd, const char *name, int options);
//receive a datagram
char buf[128];
uint32_t address
afunix_recvfrom(fd, &buf, AFUNIX_MAX_PACKAGE_SIZE, 0, &address);
//respond
afunix_sendto(fd, &buf, AFUNIX_MAX_PACKAGE_SIZE, 0, address);
//broadcast
afunix_sendto(fd, &buf, AFUNIX_MAX_PACKAGE_SIZE, 0, 0);
//connects to a file
int ret = afunix_connect(int fd, const char *name, int options);
//send to connected service
send(fd, &buf, AFUNIX_MAX_PACKAGE_SIZE, 0);
write(fd, &buf, AFUNIX_MAX_PACKAGE_SIZE, 0);
//receive response
recv(fd, &buf, AFUNIX_MAX_PACKAGE_SIZE, 0);
//close and clean up the polyfill backend
afunix_close(fd);
Instead of handling a policy of "who may open which service" in some framework, you can simply rely on file permissions.
chmod 600 /var/0chan/
mkdir /var/0chan/cupsd/
chown cups /var/0chan/cupsd/
exactly the same for "who may talk to this service"
chgrp cups /var/0chan/cupsd/something.cmd
everything is in a single header file 'afunix_polyfill.h', prefferably add a git submodule to your project and just include that header.
- using any of the standard posix functions instead of the prefixed afunix ones is undefined behaviour. there is no way to detect that either.
- two threads doing afunix_recvfrom() on the same chan at the same time, is undefined behaviour, because passing the address is thread unaware.
- same for afunix_send()
- mixing afunix_recvfrom and recv on the same socket is undefined behaviour
- the whole thing isnt thread safe yet, but that can be done later. its more in proof of concept state
- unlike with real DGRAM, there is no way to transport a 0 bytes package. to prevent inconsistent behaviour, sending 0 bytes will close the channel
In addition to just fixing af_unix, this polyfill has optional high level apis, as options to connect and bind:A
- AFUNIX_PATH_CONVENTION in addition to an absolute file path, take a shorter service name that is matched to an absolute path according to conventions described below
- AFUNIX_MAKE_PATH when using service names, create the real paths automatically
there's a system bus and a session bus. system is in /var/run/unixbus//.seqpacket service is in $XDG_RUNTIME_DIR/unixbus//.seqpacket
short service names are identified as <system|session>::, for example:
afunix_bind(fd, "service:xlock:lock", AFUNIX_PATH_CONVENTION | AFUNIX_MAKE_PATH);
will create $XDG_RUNTIME_DIR/unixbus/xlock/lock.seqpacket
The 'unixbus' commandline tool is in cmd.c and built with 'make' by default. The main use cases are covered from a cient perspective:
- "remote procedure call returning void"
$ unixbus invoke session:myservice:dostuff
$
- "publish/subscribe" or "notification"
$ unixbus listen session:myservice:thatevent
hey everyone, things happened!
hey everyone, things happened again!
wow su much happen!
- "remote procedure call returning string"
$ unixbus call session:myservice:do these things
i did these things!
$
There's also sever commands, although they're a bit excotic:
$ unixbus listen session:myservice:do
derp
merp
other shell:
$ unixbus call session:myservice:do derp
$ unixbus call session:myservice:do merp
xargs is pretty fun
$ echo 'echo $1 | tr a e' > /tmp/foo
$ unixbus xargs session:myservice:echo sh /tmp/foo
other shell:
$ unbixbus call session:myservice:echo amazing
emezing
$