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.
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
restartByMessage function. Here,
execv is wrong function used, it should be
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
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.
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
Running xautolock without
-noclose still makes it fail. Maybe one day I will look closely into this too. However, it just works now.