~/.xsession-errors Consumed My Hard Drive: My Best Solution

In my last two posts, I described the ~/.xsession-errors problem and proposed an OK solution using logrotate. I also made a case for logrotate not being a good solution. In this post I give my best solution. As before, I’ll post the code for those who just want to cut and paste without knowing what’s going on, and I’ll follow that with a more detailed explanation of the code.
note: <user> should be replaced with your username on your host.

Code Only (assuming you’ve already implemented the logrotate solution)

# apt-get install incron
# echo root >> /etc/incron.allow
# incrontab -e
/home/<user>/.xsession-errors IN_MODIFY sudo logrotate /etc/logrotate.d/xsession-errors
# nano /etc/logrotate.d/xsession-errors
rotate 1
size 64k
# incrond

or restart.

Full Solution
The first thing you need to do is install incron, a program which works like cron, but is triggered by file events rather than time. I did not need to add any new repos to my sources, but you may so I’ve included the code to do that as well. First try to install incron and if it fails due to apt not being able to find the package, add the repo.

# nano /etc/apt/sources.list
# Debian backports, added to install incron
deb http://backports.debian.org/debian-backports squeeze-backports main
# wget -O - http://backports.org/debian/archive.key | apt-key add - 
# apt-get update
# apt-get -t squeeze-backports install incron

The backports repo is inactive by default, so you’ll need that -t flag and argument. Also note that my version of crunchbang is based on debian squeeze. Replace squeeze with your distro’s nickname if it is based on some other version of debian. If you’re not using a debian derivative, you’ll need to search for incron in the repos applicable to your distro.

Now you add root to incron allowed users list.

# echo root >> /etc/incron.allow

The reason you need to allow root and not your user is that the logrotate command requires superuser privileges.

Now you create root’s table for incron.

# incrontab -e
/home/<user>/.xsession-errors IN_MODIFY sudo logrotate /etc/logrotate.d/xsession-errors

I’m not sure if that sudo needs to be there or not, but it’s not hurting anything. Someone could try omitting it and report back the results.
If you read the code only solution, hopefully you noticed that the /etc/logrotate.d/xsession-errors file is different from the previous solution. I changed the rotate number to 1 because I only want one compressed ~/.xsession-errors file not two. The size is obvious. Now for the key: copytruncate. Without it, logrotate just compresses the file to ~/.xsession-errors.1.gz, so we’re left with only the compressed log. Since ~/.xsession-errors doesn’t exist, the file descriptor in the processes that had access to it are now invalid. So why not just create a new ~/.xsession-errors? Processes don’t use file names to access files, they use file descriptors, and creating a file with the same name does not automatically update the file descriptor in the processes file descriptor tables. So, even though ~/.xsession-errors exists, the processes don’t have a way to access it. copytruncate solves this problem by sending the contents of ~/.xsession-errors to ~/.xsession-errors.1.gz without deleting ~/.xsession-errors, so the file descriptor to ~/.xsession-errors in each process’s file descriptor table is still valid! This problem could also be solved by immediately rebooting after logrotate runs since X will create a ~/.xsession-errors on startup if one doesn’t exist, but there are two issues to consider:

  • You may not want to reboot, e.g. the machine is a server
  • The ~/.xsession-errors spam may be occurring on reboot

The first case is obvious. The second case should be obvious. Consider that during boot, X starts up some process foo which immediately spams ~/.xsession-errors with garbage until incron gets the signal, calls logrotate, and then reboots. This results in an infinite loop of frustration! So just put copytruncate in /etc/logrotate.d/xsession-errors and be done with it.
Lastly, you need to start the daemon that services incron. You may either invoke it on the command line or just reboot your system.

# incrond

That’s it. I tested this solution, and all of the non-solutions leading up to it, by manually spamming ~/.xsession-errors from the terminal.

$ for i in {1..256}; do cat <file> ~/.xsession-errors; done

Where <file> is the path to some file with size greater than 256B -> logrotate limits ~/.xsession-errors to 64kB and 256 x 256kB = 64kB.
Yes, this solution is really just using incron as a pass-through to logrotate, and while that seems inefficient, consider that the most time consuming operation here is the compression called from logrotate. Another performance consideration is that incron runs each time ~/.xsession-errors is modified. If you have some broken process spamming ~/.xsession-errors with errors 64B at a time, incron and logrotate will have to run a lot of times before the 64kB limit is reached. I justify the expense of such a low probability situation two ways:

  • If you want to make sure ~/.xsession-errors never grows larger than some limit, you must check it each time it is modified
  • Chances are, the broken process is wreaking more havoc than incron and logrotate

So there you have it. Tomorrow, or um sometime this week, I’ll show how I got the TomTom Multisport Cardio to work on Linux.