util  Artifact Content

Artifact 930615166d05aced80a89d26bea453b6124fed0cb84cf061cf8798abf05c404a:

  • File bgrd.c — part of check-in [7a21b1f19e] at 2019-04-23 02:01:14 on branch trunk — fix bug where bgrd also scooped stderr, thereby breaking my `surf` setup in neovim (user: lexi size: 6485)

/* [ʞ] 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-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  <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 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<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;
	}

	child: {
		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
	}
}