irssi and AFS

For the vast majority of my IRC needs, I used to use a Quassel core. But for a couple of more lighthearted channels I hang out in, I instead started using (and have since folded the rest of my IRC setup into) what on its face appears to be a traditional irssi-in-screen setup on a JHU ACM system so I could have scripts loaded to implement some fun little bits of functionality that others in those channels can invoke.

The traditionality ends there, though.

This is because the ACM uses AFS, so my homedir is in AFS. And there are some channels I am archiving, and to do that, irssi needs to be able to access my homedir for as long as it continues to run. So I need something to maintain an AFS token, which also requires maintaining a Kerberos ticket, but I don’t want this to give irssi access to everything my Kerberos identity is able to do.

So here’s what I did:

First, I made a new Kerberos principal stump/irc and a keytab and pts entry for it and stored the keytab in my homedir. (As part of the JHU ACM sysadmin team I could just do this myself; we’d be happy to do the same thing for any user who asks.) Since my own realm is cross-realmed with the ACM, I could have made this principal here in STUMP.IO-land instead (and then gotten a ticket and run aklog to create the pts entry), but I chose not to. I saved the keytab as ~/irc.keytab.

Then I granted the pts entry the minimum permissions it needed on my homedir to meaningfully run irssi with my configuration and archiving. For me, this is l on the root level of my homedir, rlidw recursively on ~/irclogs, rl recursively on ~/perl5 (because I used the wonderful local::lib to install some Perl packages to my homedir that are used by my scripts but aren’t installed system-wide), and rlidw recursively on ~/.irssi.

Then I wrote a wrapper script to do the runtime setup for starting irssi, as follows:

set -e
TMPDIR=`mktemp -d /tmp/stump_irc_XXXXXX`
trap 'rm -rf "$TMPDIR"' 0 1 2 15
cp -a ~/irc.keytab "$TMPDIR"/keytab
k5start -U -f "$TMPDIR"/keytab -k "$TMPDIR"/krb5cc -t -- irssi "$@"

(The reason for copying the keytab into the temp dir is that k5start sets up a PAG, and thereby loses access to the AFS tokens it was started with, before it reads the keytab. And if, like me, you’re in the [very good!] habit of exec-ing the real program when you write a wrapper script, note that you can’t do this here due to the trap.)

Now I can just run that script in a screen and get an irssi that will continue working indefinitely.


…or it would, but because AFS only writes back file contents on close or fsync, each channel’s archive since the last time it was (re-)joined cannot be viewed from other systems. And worse, if the machine loses power or otherwise shuts down ungracefully (which semi-regularly happens, due to the state of constant flux the ACM systems are in, though things are much better now than they were when I first set this up), those spans of archives fall on the floor, which is very sad.

So I wrote a quick script to call fsync on every file descriptor irssi has open (because there was no obvious way to figure out which ones were the logfiles, I just took the brute force approach) every so often. Here it is:

use strict;
use warnings;

use Irssi;
use IO::Handle;
use POSIX;

my $VERSION = '0.1';
my %IRSSI = (
  'authors' => 'John Stumpo',
  'name' => '',
  'description' => 'Periodically fsync()s all file descriptors irssi has open',
  'license' => 'Public domain (CC0)',

our $FSYNC_MSECS = 300000; # every 5 minutes

sub fsync_everything {
  my $io = IO::Handle->new();
  for (my $fd = 0; $fd < 1024; $fd++) {
    # Sadly, modern Perls don't let you just call POSIX::fsync on an int,
    # insisting that you do it through an IO::Handle, which has the highly
    # undesirable (for us) behavior of closing the file descriptor when the
    # object goes away without any documented-reliable way to override this.
    # Therefore, dup the fd first, and fsync and close the duplicate. The dup
    # also serves as a check that $fd is actually an open file descriptor.
    my $duped_fd = POSIX::dup($fd);
    next unless defined($duped_fd);
    $io->fdopen($duped_fd, 'r');

Irssi::timeout_add($FSYNC_MSECS, \&fsync_everything, undef);

As the comment says, it would have been nice to just call POSIX::fsync on each int from 0 to some reasonable value and ignore errors (as I could by using fsync from C or os.fsync from Python), but Perl doesn’t let you do that anymore without going through a handle object that closes the file descriptor when it goes away and doesn’t give you a choice about that, hence the dance with POSIX::dup. (And closing a dup‘d file descriptor that goes into AFS doesn’t flush cached writes – only when the last file descriptor closes does that happen, aside from fsync being called.)

Happy IRCing on AFS!

One thought on “irssi and AFS

Leave a Reply

Your email address will not be published. Required fields are marked *