/* [ʞ] bgrd.c * ~ lexi hale * $ 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 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-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 ` 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