A user logs in, opens some applications, plays a game, launches a browser and visits a webpage. Processes process. The user logs out. What happens to application processes when a user logs out? Do the processes die? And if they do, do they do so willingly or unwillingly? Or perhaps they just lay dormant until next user session?
I don’t want to get overly poetic or philosophical, but the answer does have some relevance and is not always obvious. Usually we expect processes to just die (or otherwise ”become nonexistant”) when we log out from a computer system, and this is what typically happens (if you are interested: most often processes die willingly, if we allow some antropomorphism here). But some software may instead daemonize itself, meaning that they detach themselves from terminal sessions and keep running after a user has logged out. A user may intentionally, perhaps even maliciously, leave processes running in the background when he logs out to perform some tasks. Some programs may have bugs and will not always exit as a user logs out or a terminal connection is broken. Even worse, a program may end up using resources pointlessly, for example stay in an endless loop using most available CPU time, or keep trying to write to nonwritable filesystem mounts or doing something else nonsensical.
These may be problems and while they may appear elsewhere, they are probably most prevalent in server environments such as in LTSP systems, because badly behaving software spends resources that could be used by other software. With servers problems also persist long times, because servers are rebooted relatively infrequently.
In some environments, of course, it may be desirable that users can leave processes open to do some processing after a user has logged out. For example, this might be the case when servers are used for scientific calculations. But even in these cases it may be useful to have some limits to what particular programs should be left running after the actual desktop session is closed.
One possible solution is xexit, which may be used to trigger scripts as users log out or their session is abruptly broken by a network disconnection or some other such an event. See the xexit project website at https://code.launchpad.net/~sbalneav/ltsp/xexit. The xexit program is written by Scott Balneaves, an LTSP developer, and it is created primarily for LTSP environments even though it can be used in other settings.
For those who use Ubuntu (or perhaps Debian or some other of its derivatives), the easiest way to install xexit is from the launchpad ppa by Scott:
sudo add-apt-repository ppa:sbalneav/ppa sudo apt-get update sudo apt-get install xexit
Those who use some other Unix can compile the source and install the xexit binary in the following way. First install autoconf and bzr, and then do (this should work for version 0.2):
bzr branch lp:~sbalneav/ltsp/xexit cd xexit touch ChangeLog sh autogen.sh ./configure make sudo install -o root -g root -m 755 src/xexit /usr/local/bin/xexit
Using xexit from the Ubuntu package is easy. The package installs a startup snippet into /etc/X11/Xsession.d/90xexit
so that xexit is started with parameter /usr/bin/EndXSession
when user logs in. The xexit binary periodically pings the X display and if a connection to X server is broken or does not answer to ping checks in some time, xexit runs the session exit script it was given as a parameter. With thin clients, if a thin client is turned off uncleanly from a power button or if a network cable is unplugged, xexit normally waits about 20-30 seconds before the exit script is run on the server.
The default exit session script (/usr/bin/EndXSession
) runs all the scripts in directory /etc/Xexit.d
. With the default script the scripts under /etc/Xexit.d
must be bourne shell fragments. The default configuration kills evolution-data-server (that daemonizes itself when started), pulseaudio, requests gconfd to shut down and then kills all user processes forcibly with KILL-signal. This should work quite well for many configurations.
One can change the script that is run at session exit by editing /etc/X11/Xsession.d/90xexit
. This also allows to use some other language than bourne shell. For example, we might want to trigger /usr/local/bin/kill_desktop_session
instead of executing the scripts in /etc/Xexit.d
.
Edit /etc/X11/Xsession.d/90xexit
:
xexit /usr/local/bin/kill_desktop_session || true
One possible problem with default configuration is that if a user has two desktop sessions to the same LTSP server, exiting one session will end up forcing an exit to the other one as well. To kill only the processes of the session where X connection is lost, one can only kill processes that have the same LTSP_CLIENT_HOSTNAME environment variable set that xexit has. This can be done by having the following script at /usr/local/bin/kill_desktop_session
(do remember to set the executable bits for this script):
#!/bin/sh TERMINAL="$LTSP_CLIENT_HOSTNAME" lookup_processes() { ps eww -u "$USER" | grep -w "LTSP_CLIENT_HOSTNAME=$LTSP_CLIENT_HOSTNAME" | awk '{ print $1 }' | fgrep -vw $$ | xargs } logger "kill_desktop_session: $$ killing all session processes of user $USER on $TERMINAL" for signal in CONT TERM KILL; do if [ "$signal" = "KILL" ]; then sleep 3 fi PROCESSES="$(lookup_processes)" if [ -n "$PROCESSES" ]; then logger "kill_desktop_session: $$ sending $signal to $USER processes $PROCESSES" kill -$signal $PROCESSES fi done logger "kill_desktop_session: $$ finished killing"
This works, because for each desktop session an xexit process is started, and it has the same environment variable LTSP_CLIENT_HOSTNAME as other processes in that session. In function lookup_processes
we lookup the pids of those processes that match this environment variable. Then we send them CONT (continue) signal (in case some of them have been stopped), request them to terminate (by sending TERM), and if they did not comply after three seconds, we forcibly kill them. (In process termination, it is nice to ask politely before making demands, in case some programs want to do some operations (such as writing their internal state to files) before they are killed).
In a non-LTSP case we could replace the TERMINAL and lookup_processes() definitions with this:
TERMINAL="$(hostname)" lookup_processes() { pgrep -u "$USER" | fgrep -vw $$ | xargs }
This should work for laptops, workstations and other such cases, in case xexit is wanted or needed for those. Xexit also works properly with the ”change user” feature in GNOME session (and probably others), that is, it will not kill processes when user session is still open, even if it is not active.
Note that xexit is run with the privileges of the user that logs in. This means that if a user wants to bypass this mechanism and not have her processes killed when she logs out, she can do so by killing the xexit process when her desktop session is still open. While this may be an issue, on the other hand the choice to run xexit with user privileges makes xexit simple to implement, and it safeguards against some bad session exit scripts, because xexit only has the same limited privileges that the user has.
What is nice about xexit is that it is simple, effective and mostly desktop environment agnostic. The default configuration targets a typical Ubuntu installation, but it may be adapted with ease to other systems and configurations.
Try it out, and keep your servers clean!
Juha Erkkilä
Thanks for the writeup, Juha! In celebration(!) of this, I’ve done a minor update to xexit, adding a manpage. Perhaps a Debianista or Fedora-er out there can include xexit in the archives. It’s a handy little program, and as you note, I went out of my way to make it not care too much about what desktop or distro it gets launched under.
In an ideal world, xexit wouldn’t be needed because RUDDY X PROGRAMS SHOULD EXIT PROPERLY ON THEIR OWN, DAMMIT!, but that not being the case *cough*evolution-dataserver*cough*, xexit takes care of the problem nicely without consuming a huge amount of resources on your system.
Cheers,
Scott
Man, you really saved my day! LTSP is a very good product but sometimes absolutely mindblowing poorly documented! I found xexit after a long seerch and various dead ends and it works perfect. Thank you very much for your documentation!
2 things:
1) http://surfnet.dl.sourceforge.net/project/ltsp/Docs-Admin-Guide/LTSPManual.pdf We’ve got an 84 page PDF. We always accept submissions if you feel up to adding to what’s already there.
2) Programs not exiting isn’t really LTSP’s fault, as I’ve maintained for years. Try logging out of a regular desktop, CTL-ALT-F1’ing to a text console, and check out what gets left behind. A lot of desktop programs don’t bother cleaning themselves up because they assume you’ll just be shutting down. If things cleaned up properly after themselves, xexit wouldn’t need to be here.
I updated the kill_desktop_session script, because it turned out to have an embarrassing bug: the script terminated itself before it could ever send KILL signal to any processes. It is not perfect yet but better 😉
Here is my nice and cute script called K99_ltsp_cleanup:
#! /bin/sh
# Program: /etc/Xexit.d/K99-ltsp-cleanup
# Purpose: terminate all processes and remove temp files of a user on logout
# Executed via: xexit -> /usr/share/Xexit/EndXSession -> /etc/Xexit.d/*
# History:
#110801-MSe: 0.01 — fast and dirty hack
# Dieses Script wird vom xexit aufgerufen, wenn sich ein User ausloggt.
#
# a) Es wird getestet, ob es sich um einen regulären User handelt.
# Das ist der Fall, wenn die UID existiert und zwischen
# uid_low und uid_high liegt.
#
# b) Es wird getestet, ob es sich um die letzte Session des Users handelt.
# Das können wir über pam_mount machen (beim Einloggen werden
# Verzeichnisse gemountet und dabei führt pam_mount für jeden User
# einen Zähler in der Datei /var/run/pam_mount/$USER.
#
# c) Nun werden alle Prozesse des Users terminiert (erst HUP, dann KILL).
#
# d) Anschließend werden alle temporären Dateien und Verzeichnisse des Users gelöscht.
#
#===== Variables
ver=”/etc/Xexit.d/K99-ltsp-cleanup, v0.02″ # Name and version of this script
lognormal=”daemon.info” # facility and level for normal logfile entries
logerr=”daemon.notice” # facility and level for error logfile entries
# The UID of the user has to be in the range [uid_min … uid_max]
uid_min=10000
uid_max=19999
#—– external programs
find=”/usr/bin/find”
# — absolut unsecure: the good (?) old (!) rm
#rm=”/bin/rm”
# — more secure file deletion: wipe data with zeros
# You have to insall secure-delete in order to use this
# rm=”/usr/bin/srm -l -l -z”
# — much more secure file deletion: wipe data twice (1x random, 1x zero)
# You have to insall secure-delete in order to use this
#rm=”/usr/bin/srm -l -z”
# — paranoid secure file deletion – but it consumes lot of time: wipe 36 times (see man srm for details)
# You have to insall secure-delete in order to use this
#rm=”/usr/bin/srm”
rmdir=”/bin/rmdir”
logger=”/usr/bin/logger”
# — DEBUG —
#find=”/bin/echo”
#rm=”/bin/echo”
#rmdir=”/bin/echo”
#logger=”/bin/echo”
#===== Main Code
#—– Exit if variable ”$USER” is not set (yes, we are paranoid)
if [ ”x$USER” = ”x” ] ; then
echo ”$ver: Variable ”$USER” is not set. Exit.”
$logger -p $logerr ”$ver: Variable ”$USER” is not set. Exit.”
exit 1
fi
#—– Check, if we are allowed to process this user
# check if $USER can be found in the output of getent
uid=`getent passwd | grep ^$USER | awk -F : ’{print $3}’`
if [ ”x$uid” = ”x” ] ; then
echo ”$ver: User ”$USER” logs out but is unknown. Exit.”
$logger -p $logerr ”$ver: User ”$USER” logs out but is unknown. Exit.”
exit 1
fi
# check, if it is a regular user
if [ $uid -lt $uid_min -o $uid -gt $uid_max ]; then
echo ”$ver: User ”$USER” logs out but is not a normal user. Exit.”
$logger -p $lognormal ”$ver: User ”$USER” logs out but is not a normal user. Exit.”
exit 2
fi
# give some information
echo ”$ver: Processing user ”$USER””
$logger -p $lognormal ”$ver: Processing user ”$USER””
#—– Check, if the user exits his/her last session (using pam_mount)
# !!
# !! This is very specific, we use pam_mount in order to mount various shares during the login of a user.
# !!
# if [ ! -d /var/run/pam_mount ]; then
# echo ”$ver: Directory /var/run/pam_mount does not exist. Exit.”
# $logger -p $logerr ”$ver: Directory /var/run/pam_mount does not exist. Exit.”
# exit 3
# fi
#
# if [ -e /var/run/pam_mount/$USER ]; then
# echo ”$ver: The pam_mount file ”/var/run/pam_mount/$USER” for user ”$USER” is still present. Exit.”
# $logger -p $logerr ”$ver: The pam_mount file ”/var/run/pam_mount/$USER” for user ”$USER” is still present. Exit.”
# exit 4
# fi
#—– Kill users processes
# We want to compute the output line by line
# Therefore we have to set the IFS (Internal Field Separator)
# to ”Newline” (please notice the single ” after the line >IFS=”/dev/null | wc -l`
$find $i -user $USER -not -type d -exec $rm {} ; > /dev/null 2>&1
nrdirs=`$find $i -user $USER -type d 2>/dev/null | wc -l`
$find $i -user $USER -type d -exec $rmdir -p {} ; > /dev/null 2>&1
lstr=”$lstr $i ($nrfiles/$nrdirs)”
done;
echo ”$ver: Cleaned temp (special)files/dirs of user $USER:$lstr”
$logger -p $lognormal ”$ver: Cleaned temp (special)files/dirs of user $USER:$lstr”
#—– Goodbye
echo ”$ver: Exit.”
$logger -p $lognormal ”$ver: Exit.”
exit 0
# this is the last line 🙂
If you like top get a copy of the script, just write a mail to s.i.c.h.e.r.h.e.i.t @ antago.info (remove the dots and the blank).
xexit is fine. But may not work for me wherein my requirement at times is that a user process must remain live. I understand that xexit has the option to be killed itself. But there is something else in my ltsp-cluster installation which auto-kills all user processes and nothing stale is left behind. And this is random. Have been observing it for over a month. Hence the need to search for xexit.
Nothing special that I have installed – no gnome-watchdog. Looks like LDM exit script is doing something. Here the need is to nail the random ”worker”.
Any pointers in this regard shall be helpful.