xautolock mystery.

xautolock is a rusty piece of software. Last release was in 2007. I used it together with slock, because I hate when my monitor wastes power just to display cute (although useless) colorful patterns. I prefer my hardware to eat little power when I am away.

This piece of security code had failed, leaving my desktop unlocked for a few hours. This is bad, and should be fixed ASAP.

When moving from generic musl system back to Slackware with glibc, I noticed that xautolock is now broken:

rys@rys:~$ xautolock -locknow # works
rys@rys:~$ xautolock -restart # works
rys@rys:~$ xautolock -locknow
Could not locate a running xautolock.

An interesting find about that is it breaks after I watch videos with mpv.

Indeed, quick googling reveal the call chain:

mpv -> xdg-screensaver -> xautolock

Here, mpv invokes xdg-screensaver which detects that xautolock is used and tells it to disable the screen lock trigger. After mpv had finished playing a video, it invokes xdg-screensaver again to restore the trigger.

I do not know why xdg-screensaver is designed to reset xautolock instead of toggling it with -toggle, but this does not matter. The culprit is in xautolock itself, as strace(1) had revealed.

Using strace to debug things

strace(1) is a cool tool, and every Linux sysadmin should learn how to briefly debug things with it. It reveals a syscalls which a program does. While it cannot show what library functions get invoked, it still provides the hints about when and where the program did a bad move and went to wrong way.

My preferred strace options are: -f -v -s2048 -tt.

This time, strace detected that xautolock misused exec functions. A relevant strace line:

20:14:52.978833 execve("xautolock", ["xautolock", "-time", "5", "-locker", "slock"], [/* 35 vars */]) = -1 ENOENT (No such file or directory)

An error is in src/message.c:107, there is the only one call to execv in restartByMessage function. Here, execv is wrong function used, it should be execvp, because execv takes only absolute paths. This is an xautolock bug: an author’s mistake.

When called with -restart, original xautolock tried to execute just "xautolock" program (without a path such as "/usr/bin/xautolock"), and libc happily passed it directly to Linux kernel. The execve call without a full path specified failed, and xautolock even does not check the return value from such a bad execv, continuing the execution, but not responding to commands anymore.

File descriptors mess mystery

Replacing execv with execvp may solve the troubles, however this is not only one problem. xautolock still did not want to work after this fix. Now, after -restart command xautolock simply exits with exit status 1. Doing second strace on a running process, it shows that it fails after close(2), trying to close stderr which is already closed:

20:16:33.718362 close(2) = -1 EBADF (Bad file descriptor)
20:16:33.718819 exit_group(1) = ?

Looking into source, there is no any mention of close(2), however, in src/xautolock.c:123, there is a call to fclose on stderr. Interestingly, there is no any error checking performed, but program still exits with failure.

Since there is a condition depending on noCloseErr variable, I wondered there is an option that disables this call. And here it is: running xautolock with -noclose option solves this second trouble, and now xautolock survives restarts!

Indeed, watched youtube video with mpv again, then tried to lock the screen - it worked!

Mystery gone unsolved

What about buggy fclose call? Dunno. I suspect glibc does internal error checking and aborts program if an inconsistency between a living objects and current state was found: here, raw fd number 2 ceased to exist but glibc was not aware of that with stderr object still allocated and living.

EDIT: Of course not, glibc cannot impose such a nonconformant behavior, and it is easily confirmed. Still, I did not investigated xautolock code more, unwanted close sits somewhere in it.

The only raw close call in xautolock is in src/engine.c:332. It’s a bit obscure and I did not traced how it works. Maybe xautolock simply messes with file descriptors very badly.

However, anyhow, with execvp fix and running xautolock with -noclose solves everything, and for now I am not going to investigate it deeper.

Patches?

No any. The fixes are trivial to make them by hand just in few minutes, and they are explained above. While looking around, I also replaced vfork call just with fork one in src/engine.c:325.

Running xautolock without -noclose still makes it fail. Maybe one day I will look closely into this too. However, it just works now.

Written on February 21, 2019