/* [ʞ] bgrd.c
* ~ lexi hale <lexi@hale.su>
* $ cc -Ofast bgrd.c -lutil -obgrd
* © affero general public license 3.0
* i am angry beyond words that this program had to be
* written
*
* bgrd "background read" is a tool for launching an
* application and retrieving the first line of output.
* this is a nontrivial task for a number of reasons,
* all of which are incredibly stupid, however, most
* saliently: buffered io.
*
* the suckmore web browser `surf` has an option '-x'
* which is intended to print the X window id of the
* window it opens, so it can be captured and used in
* scripts. so far so good.
*
* except unix has two distinctly different concepts of
* IO. there's POSIX IO, and then there's libc IO.
*
* POSIX IO uses the shit in <fcntl.h> and <unistd.h>;
* syscalls like read(2), write(2), and pipe(2) - the
* good, simple shit God made unix for. this is really
* bare-metal; these are basically C wrappers over kernel
* syscalls. POSIX IO uses plain old ints as file
* descriptors, and it doesn't fuck around. when you say
* "write," god dammit, it WRITES.
*
* libc is a very different beast. libc has opinions.
* libc has abstractions. libc has its own entire goddamn
* DSL by which to specify format strings, because
* apparently someone felt called to reinvent FORTRAN
* except worse. printf(), you know, the first function
* they ever teach you in C 101? (more like CS 403
* these days, but aghhh) it's actually a heinously
* complicated, dangerous, slow, insecure mess that drags
* a ridiculous amount of background bullshit into the
* frame. such as BUFFERING.
*
* libc, you see, is too good to just wrap write() and
* read(). no, it also has to decide when to SEND them.
* this is responsible for that behavior every c coder
* trips over eventually - you know the one, where if you
* don't end your format string in '\n' it isn't printed,
* sometimes even if the program exits. this is not
* actually a POSIX thing; it's a libc thing.
*
* libc has a couple different kinds of buffering tactics
* (set with setvbuf(), a function nobody seems to know
* exists) that it uses in different circumstances. the
* printf \n gotcha behavior is what's known as "line
* buffering." however, because libc thinks it's fucking
* smart or something, it's not content to just pick one
* predictable behavior and stick to it. oh no.
*
* ever noticed how programs can seem to tell whether
* they're connected to a terminal (and thus can output
* all those fancy ansi formatting codes), or whether
* you're redirecting their stdout to a file? that's
* because there's more than one kind of pipe. the kind
* you create with pipe(2) - and the kind you create with
* openpty(2).
*
* a pty is, essentially, a kind of pipe that carries
* extra information around, the information you access
* via ioctl and termios. it's designed to imitate a TTY,
* so that a shell can create one, aim a process at it,
* and then that process can draw TUIs and shit on it.
* and some programs are designed to behave differently
* depending on whether they're hooked up to a pipe or a
* pty.
*
* libc, tragically, is among them.
*
* if libc notices it's hooked up to a pipe instead of a
* pty, it will change its default buffering strategy.
* newlines will suddenly cease flushing stdout, and
* libc will only print its buffer in one of two
* circumstances: the buffer is filled up, or the program
* exits.
*
* this is a problem if you are, say, trying to output a
* handle that scripts can use to control the running
* program.
*
* the `surf` developers had a couple of options. they
* could have simply broken out the POSIX headers and
* sent the X id to stdout with a call to write(2), the
* correct thing to do. they could have thrown in a call
* to setvbuf(3) to explicitly pick a buffering strategy
* compatible with their usecase, the sensibly wrong
* thing to do. they could have explicitly flushed stdout
* after printf(3)'ing to it, the dumb and error-prone
* thing to do.
*
* instead, they did *nothing.*
*
* so if you run `surf -x` from a terminal, great!
* you'll see it print the x window id first thing.
* you'll then try to capture it via any number of
* increasing desperate means, all of which will fail
* hilariously. finally, you'll spend four goddamn hours
* after midnight reading source code and manpages and
* frantically googling around and digging up unix lore
* and finally, FINALLY figure out what the batshit FUCK
* is going on, and write this goddamn utility to hack
* around the suckmore crowd's suckitude.
*
* so i figured i'd save you some time.
*
* i am probably going to submit a PR eventually because
* holy hell this is just so monumentally bozotic.
*
* in the mean time, you can use this extremely
* no-bullshit wrapper by running `set surfwin (bgrd
* (which surf) surf -x <params>` or whatever the bash
* equivalent is and it will immediately launch surf in
* the background, printing the X window and exiting as
* soon as it hits a newline. it should be adaptable to
* similar scenarios if you find yourself dealing with
* similarly broken software tho.
*
* in conclusion, read lenin. */
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <pty.h>
int main(int argc, char** argv) {
// pipe fd handles
int wr, rd;
// populate the pipe
openpty(&wr, &rd, NULL, NULL, NULL);
// note ptys and pipes use basically the same
// interface; you can use one as a drop-in
// replacement for the other, tho the creation
// function syntax is a bit different.
if(fork()) goto master;
else goto slave;
master: {
close(wr);
char buf[256]; size_t sz;
while(sz = read(rd, buf, 255)) {
buf[sz]=0;
for (size_t i=0;i<sz;++i) {
if (buf[i] == '\n') {
write(1,buf,i);
// got what we came for, time to blow
// this popsicle stand.
goto leave; // bite me, dijkstra
}
}
write(1,buf,sz);
}
}
leave: {
close(rd);
return 0;
}
slave: {
close(rd);
// redirect stdout (NOT stderr) to our pty master
dup2(wr, 1);
close(wr);
execv(argv[1],argv+2);
return 1; // THIS SHOULD NEVER HAPPEN
}
}