simont: A picture of me in 2016 (Default)
simont ([personal profile] simont) wrote2008-05-21 12:02 pm

Unexpectedly useful Unix utility

A few years ago, I wrote a little framework for a class of Unix filter program. The framework deals with allocating a new pseudo-terminal device and running a subprocess which has that device as its controlling terminal (or, if you prefer, simply running the subprocess in pipes instead); it then handles the fiddly select() work to get input and output to and from the subprocess, and then it passes all of that input and output to a pair of translation functions, to be modified on their way past. So, essentially, you get to run a full Unix terminal session and have its input and output adjusted in some unspecified but useful way; and the point was that I then expected to write several actual programs using this framework, with different translation functions for different purposes.

Over the past couple of years I have indeed accumulated several small filter programs written using this framework:

  • a character-set translator I wrote by bolting the above filter framework to my charset translation library (similar in concept to luit, except that mine isn't tied in a hideous and confusing way into the Unix locale system so I can actually remember how to use it)
  • a session recording tool (combining the functionality of script and ttyrec and fixing some minor irritations of both)
  • a wrapper for nethack to convert PuTTY's (more generally useful) Meta key behaviour into the (rather limited) one that Nethack accepts
  • an anti-idle wrapper which will terminate a program if it performs no I/O for longer than a given period.

(Amusingly, I never actually ended up writing the filter that originally inspired me to set up the framework, which was going to be a filter that massaged ECMA-48 terminal escape sequences subtly to compensate for the shortcomings of a terminal emulator I was forced to use at the time. I instead solved the problem by ditching that terminal emulator and running pterm instead :-)

In addition to these, in the spirit of ‘hello, world’, the first filter program I wrote using the framework was called ‘nullfilter’. As you might expect from the name, it has trivial translation functions which never modify the sub-session's input or output; so its sole function is to run a subprocess inside a freshly allocated pty and funnel its input and output back to the terminal it was invoked from.

I had believed when I wrote nullfilter that it was a completely useless program, and that all of its reasons for existence were higher-level: to act as an example source file and a starting point for writing new filter programs, or as a test program to aid debugging by distinguishing failures at the translation layer from failures in the underlying framework, that sort of thing.

However, recently I've been finding nullfilter to be a startlingly useful program in its own right, because it has the great virtue that it can construct a pty where none previously existed: if you have a program which expects to have access to a terminal device, and in fact you want to run it in pipes for some reason of scripting or automation, then nullfilter is just the tool to insert between the pipes and the program to prevent the latter getting confused by the former. Also, if you have a program that does accidental violence to its controlling terminal, nullfilter can box it up so that that terminal isn't the one you wanted to carry on using afterwards.

For example, I've recently been playing with UML (not to be confused with UML), for running less-than-trusted binaries in a sandbox and also being able to massage those binaries' view of the VFS into what they expect to see. So my first job was to write some scripts that wrap up all the fiddly details of starting a UML kernel and arranging to pass information to the processes inside it about what I wanted them to do. I rapidly found out that running a user shell process inside UML on the main console device causes shell job control to fail for some irritating permissions reason; solution, use nullfilter to run the shell process on a different terminal and then everything is fine. I also found that the UML kernel sets its outer controlling terminal device into non-blocking mode, which can cause the next program I run from my real shell to get thoroughly confused. (Not sure why that's a property of the device itself rather than of a specific process's file handle on it, but whatever.) Solution, use another nullfilter to protect my shell from UML.

And today I discovered that one system I use has an SSH client which appears to get thoroughly confused if its standard input isn't a terminal: as far as I could tell via strace, it attempts some termios ioctls on its standard input and if they fail never tries to so much as read from it. No idea what's going on there – and really, I can't be bothered to debug it, when I can just wrap the offending invocation of ssh in nullfilter, and the problem is solved.

So nullfilter, despite my having originally assumed it would be a completely useless program for actually using, has proved its worth repeatedly in the last month and in fact might well end up being the one of my filter programs from which I get the most ongoing use!

I almost feel there ought to be a moral here. Perhaps it should be a comment I recall once seeing posted on a newsgroup by (I think) [livejournal.com profile] pm215: ‘never neglect the trivial case’.

fanf: (Default)

[personal profile] fanf 2008-05-21 01:34 pm (UTC)(link)
I have a similar program called pty. I wrote it to work around a bug in xterm, which thinks it knows the best way to initialize a pty, but is wrong, and has many options for tweaking its pty handling, which are also wrong. In order to work out WTF it was doing, I even re-wrote unifdef so I could see what code xterm ran on FreeBSD - this was not immediately obvious because xterm's pty handling code has about the highest ifdef density I've seen.

It turns out that the easiest way to discourage xterm from screwing up the pty settings is to ensure it has a /dev/tty whose settings it can copy. Unfortunately X sessions usually don't have a /dev/tty. So I wrote my pty program so I could run it in my .xsession script as a wrapper around fvwm, so that my window manager and all the programs it spawns (especially xterm) have a /dev/tty to use as a template when creating ptys.

[identity profile] cartesiandaemon.livejournal.com 2008-05-21 02:51 pm (UTC)(link)
:)

Yes, I am convinced by your moral, even though I don't know what it is :)

(Anonymous) 2008-06-10 12:27 pm (UTC)(link)
I could have done with a script like this just now. I was helping someone out with just this problem, but I couldn't find nullfilter, and Dan Bernstein's stuff is a bit, um, legacy, in the build process and I didn't hold out much hope of it working on MacOSX. In the end I used script, not least because the intermingling of stdout, stderr, and the tty is actually what we wanted (for once).

Strange how you read something thinking I've never wanted one of those "all these years", and then within a month or so you do! :)