/* [ʞ] bgrd.c * ~ lexi hale * $ cc -Ofast bgread.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 and ; * 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-pront * 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 ` 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 #include #include #include #include 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 child; master: { close(wr); char buf[256]; size_t sz; while(sz = read(rd, buf, 255)) { buf[sz]=0; for (size_t i=0;i