 |
Linux |
|
Run scripts when session starts en when it ends.
INTRODUCTION
Other projects I work on, like fuse-workspace and automounting with md5keys,
require a construction which launch them when a session begins, and terminates when the session ends. Now ConsoleKit offers this,
at documented in the source of ConsoleKit (at least 0.3.1), and Upstart.
GENERAL SETUP
To make this work create the following, to have a basic setup to run session scripts anyway:
install --directory /etc/session.d/scripts/start
install --directory /etc/session.d/scripts/stop
The general place to put start scripts in, is /etc/session.d/scripts/start.
Just simular for the stop scripts.
For example a script to mount the fuse module fuse-workspace for a user goes in the start directory,
and to unmount it again in the stop directory.
Helper scripts and special functions (for example logging) go in the functions map.
Running directly by ConsoleKit
ConsoleKit offers the ability to run scripts when a session is changed, added or removed,
at documented in the source of ConsoleKit (at least 0.3.1):
-
Piece of ChangeLog of ConsoleKit:
commit a90ecd3544f03c16f6c5a97aafb1c0b33b58101c
Author: David Zeuthen
Date: Thu Oct 4 20:39:09 2007 -0400
add a way to synchronously run programs on session
add/remove/activity_change
This patch adds support for running programs when
1. A session is added
2. A session is removed
3. The activity of a session changes
Executables with the suffix .ck in the directories
$sysconfdir/ConsoleKit/run-session.d
(typically /etc/ConsoleKit/run-session.d)
and
$libdir/ConsoleKit/run-session.d
(typically /usr/lib/ConsoleKit/run-session.d)
will be run on each event. The former directory is meant to be used
for the system administrator and the latter is meant to be used by
programs.
Only when all programs in these directories have run, ConsoleKit will
resume event processing and e.g. broadcast the event on the system
message bus. Hence, this new mechanism can be used to safely
(e.g. without race conditions) perform operations on certain resources
before programs in the desktop session are told they may use them. The
obvious example here is managing ACL's on /dev such that certain
device nodes are only available to users in local and active sessions.
The environment of the program launched is the environment that the
ConsoleKit daemon was launched with and also the following variables
(variables tagged with [*] may not be set).
CK_SESSION_ID
CK_SESSION_TYPE
CK_SESSION_SEAT_ID
CK_SESSION_USER_UID
CK_SESSION_DISPLAY_DEVICE [*]
CK_SESSION_X11_DISPLAY_DEVICE [*]
CK_SESSION_X11_DISPLAY [*]
CK_SESSION_REMOTE_HOST_NAME [*]
CK_SESSION_IS_ACTIVE
CK_SESSION_IS_LOCAL
corresponding to the properties of a Session object in question. Each
program is passed exactly one parameter that can assume one of the
following values:
- session_active_changed: is_active changed
- session_added: the session was added
- session_removed: the session was removed
As a safety hatch, there is a timeout of 15 seconds for each program;
if it hasn't exited within 15 seconds, the daemon will send it a
SIGTERM signal, and move on to the next program. The daemon is still
responsive when the program is running - this is to ensure that the
program itself can call into the org.freedesktop.ConsoleKit service.
Now my idea is to use a script in /etc/ConsoleKit/run-session.d which on his turn runs scripts it find in /etc/session.d/CK-added
when a session is added, and scripts it finds in /etc/session.d/CK-removed when a session is removed.
So create this directories:
install --directory /etc/session.d/CK-added
install --directory /etc/session.d/CK-removed
and make a symlink to the desired script in /etc/session.d/scripts :
ln --symbolic --force ../scripts/start/example_start_script.sh /etc/session.d/CK-addded/
ln --symbolic --force ../scripts/stop/example_stop_script.sh /etc/session.d/CK-removed/
This start script can for example mount shares, and the stop script unmounts them.
Important to note is that there is no construction here to wait for the script to finish, and/or to check the
exit status and do something with that. Scripts are just started.
Upstart is a better alternative for this.
-
Run scripts
#!/bin/bash
# This script is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License.
# If not, see http://www.gnu.org/licenses/.
# credits:
#
# Stef Bon. the Netherlands
#
manage_consolekit_data()
{
local laction="$1"
if [ -n "$CK_SESSION_SHORT" ]; then
if [ "$laction" = "session_added" ]; then
if [ ! -d "/tmp/ConsoleKit/sessions/$CK_SESSION_SHORT" ]; then
install --directory "/tmp/ConsoleKit/sessions/$CK_SESSION_SHORT"
fi
echo $userid > "/tmp/ConsoleKit/sessions/$CK_SESSION_SHORT"/unixuser
echo $CK_SESSION_SHORT > "/tmp/ConsoleKit/sessions/$CK_SESSION_SHORT"/session
if [ -n "$userid" ]; then
if [ ! -d "/tmp/ConsoleKit/users/$userid" ]; then
install --directory "/tmp/ConsoleKit/users/$userid"
fi
if [ -h "/tmp/ConsoleKit/users/$userid/$CK_SESSION_SHORT" ]; then
rm --force "/tmp/ConsoleKit/users/$userid/$CK_SESSION_SHORT"
fi
ln --symbolic --force ../../sessions/$CK_SESSION_SHORT "/tmp/ConsoleKit/users/$userid/$CK_SESSION_SHORT"
fi
elif [ "$laction" = "session_removed" ]; then
if [ -d "/tmp/ConsoleKit/sessions/$CK_SESSION_SHORT" ]; then
rm --recursive --force "/tmp/ConsoleKit/sessions/$CK_SESSION_SHORT"
fi
if [ -n "$userid" ]; then
if [ -h "/tmp/ConsoleKit/users/$userid/$CK_SESSION_SHORT" ]; then
rm --force "/tmp/ConsoleKit/users/$userid/$CK_SESSION_SHORT"
if [ $(ls -v "/tmp/ConsoleKit/users/$userid/" | wc --words) -eq 0 ]; then
rm --force "/tmp/ConsoleKit/users/$userid/"
fi
fi
fi
fi
fi
}
function count_sessions()
{
local luserid="$1"
local ncount=0
if [ -n "$luserid" ]; then
if [ -d "/tmp/ConsoleKit/users/$luserid" ]; then
ncount=$(ls -v "/tmp/ConsoleKit/users/$luserid" | wc --words)
fi
fi
echo $ncount
}
# defaults
logpriority="daemon.info"
if [ -f /etc/session-scripts.conf ]; then
. /etc/session-scripts.conf
elif [ -f /etc/session.d/session-scripts.conf ]; then
. /etc/session.d/session-scripts.conf
else
echo "Configurationfile session-scripts.conf not found."
fi
if [ -z "$BASE_SESSION_SCRIPTS_DIR" ]; then
echo "Parameter SESSION_FUNCTIONS_DIR not set. Taking default."
BASE_SESSION_SCRIPTS_DIR=/etc/session.d
fi
if [ -z "$SESSION_FUNCTIONS_FILE" ]; then
echo "Parameter SESSION_FUNCTIONS_DIR not set."
SESSION_FUNCTIONS_FILE=${BASE_SESSION_SCRIPTS_DIR}/scripts/functions
elif [ -z "$SESSION_CK_ADDED_DIR" ]; then
echo "Parameter SESSION_CK_ADDED_DIR not set. Taking default."
SESSION_CK_ADDED_DIR=${BASE_SESSION_SCRIPTS_DIR}/CK-added
elif [ -z "$SESSION_CK_REMOVED_DIR" ]; then
echo "Parameter SESSION_CK_REMOVED_DIR not set. Taking default."
SESSION_CK_REMOVED_DIR=${BASE_SESSION_SCRIPTS_DIR}/CK-removed
fi
if [ -f "$SESSION_FUNCTIONS_FILE" ]; then
. "$SESSION_FUNCTIONS_FILE"
fi
if [ -z "$(type -t do_log)" ]; then
echo "Function do_log not defined."
exit
fi
if [ -z "$(type -t find_command)" ]; then
echo "Function find_command not defined."
exit
fi
if [ -z "$(type -t manage_consolekit_data)" ]; then
echo "Function manage_consolekit_data not defined."
exit
fi
if [ -z "$SESSION_USE_ATD" ]; then
SESSION_USE_ATD=yes
fi
CK_SESSION_SHORT=""
if [ -n "$CK_SESSION_USER_UID" -a -n "$CK_SESSION_ID" ]; then
# called through ConsoleKit
do_log "Starting $0 through CK with parameters $@."
# determine the session
CK_SESSION_SHORT=$(echo "$CK_SESSION_ID" | rev | sed 's@^/*@@' | awk -F '/' '{ print $1 }' | rev)
else
# cannot determine how called: exit
do_log "Called $0 without the required CK parameters. Quitting."
exit
fi
# lookup user
userproperties=$(getent passwd $CK_SESSION_USER_UID | grep --max-count=1 ":$CK_SESSION_USER_UID:")
userid=$(echo $userproperties | cut --delimiter=":" --fields=1)
if [ -z "$userproperties" ]; then
# something wrong : the basic properties for this user are not found!
do_log "User $userid not found. Quitting."
exit 1
else
do_log "User $userid found."
fi
# determine TMPDIR
if [ -z "$TMPDIR" ]; then
if [ ! -d /tmp ]; then
exit 1
else
TMPDIR=/tmp/run-scripts-with-CK
fi
fi
if [ ! -d $TMPDIR ]; then
install --directory $TMPDIR
fi
set >> $TMPDIR/set.ck.log
if [ "$1" = "session_added" ]; then
# maintain a simple tree containing information about sessions and users
# which is easy accessible to shellscripts
manage_consolekit_data "session_added"
# only run scripts when this is the first session for this user
if [ $(count_sessions $userid) -gt 1 ]; then
# not the first session
do_log "This is not the first session for this user."
exit
fi
# a session is added
SCRIPTS_DIRECTORY="$SESSION_CK_ADDED_DIR"
LOCK_FILE="$TMPDIR/run-add-scripts-for-$userid"
elif [ "$1" = "session_removed" ]; then
# maintain a simple tree containing information about sessions and users
manage_consolekit_data "session_removed"
# only run scripts when this is the last session for this user
if [ $(count_sessions $userid) -gt 0 ]; then
# there are still other sessions
do_log "This is not the last session for this user."
exit
fi
# a session is removed
SCRIPTS_DIRECTORY="$SESSION_CK_REMOVED_DIR"
LOCK_FILE="$TMPDIR/run-remove-scripts-for-$userid"
else
do_log "Parameter $1 not handled here."
exit
fi
# try to run scripts via the at daemon; if not possible run these directly
if [ "$SESSION_USE_ATD" = "yes" ]; then
AT_COMMAND=$(find_command at)
if [ -z "$AT_COMMAND" ]; then
do_log "At command not found. Running scripts directy."
SESSION_USE_ATD="no"
else
do_log "At command found. Check the daemon is running."
if [ -z "$(ps --no-headers -C atd)" ]; then
do_log "The at daemon not running. Running scripts directly."
SESSION_USE_ATD="no"
fi
fi
fi
# create the lockfile
if [ -f "$LOCK_FILE" ]; then
sleep 1
if [ -f "$LOCK_FILE" ]; then
do_log "Existing lockfile $LOCK_FILE found. Cannot continue."
exit
fi
else
touch "$LOCK_FILE"
fi
if [ -d "$SCRIPTS_DIRECTORY" ]; then
for at_script in $(ls -v "$SCRIPTS_DIRECTORY"); do
if [ -x "$SCRIPTS_DIRECTORY/$at_script" ]; then
if [ "$SESSION_USE_ATD" = "yes" ]; then
# run commands through at daemon
# create a sh script file and feed it to the at daemon
AT_SH_FILE="$TMPDIR/$at_script"
if [ -f "$AT_SH_FILE" ]; then
rm --force "$AT_SH_FILE"
fi
echo "sleep 1" >> "$AT_SH_FILE"
echo "$SCRIPTS_DIRECTORY/$at_script $userid" >> "$AT_SH_FILE"
$AT_COMMAND -q "b" -f "$AT_SH_FILE" NOW >> /dev/null
do_log "created script $AT_SH_FILE with $at_script... and running it with at $AT_COMMAND."
else
# run commands directly
$SCRIPTS_DIRECTORY/$at_script $userid &
fi
else
# script is not executable
do_log "Script $at_script found in $SCRIPT_DIRECTORY not executable."
fi
done
else
# this should not happen
do_log "Cannot find directory $SCRIPTS_DIRECTORY where the scripts should be. Cannot continue."
fi
rm --force "$LOCK_FILE"
Basically, this scripts determines the user for which the session is changed, added or removed,
by looking at the environment variable CK_SESSION_USER_UID. Next it maintains a simple table
in /tmp/ConsoleKit with all available sessions and users logged in. The reason for this is that I
find the data presented by the CK tools not so usable for scripts, and then I mean how many users are
logged in, which sessions there are, and which session belong to a user.
Next it does look if scripts should run via the at daemon, or directly. Finally, it runs all the scripts,
in /etc/session.d/CK-added when a session is added and in /etc/session.d/scripts/CK-removed when
a session is removed. It passes one parameter to these scripts, the userid.
When the parameter SESSION_USE_ATD is set to yes, and when the utility at is found and when the atd daemon is running, and only then,
the scripts are run by the at daemon like:
echo "/etc/session.d/scripts/start/testscript $userid" >> /tmp/atscript
at -q "b" -f /tmp/atscript NOW
If not run by the at daemon, it runs them directly like:
/etc/session.d/scripts/start/testscript $userid &
Note the last & is very important, it makes run_script.ck not wait for the script to finish.
About running scripts with Upstart
I've developed this construction before I knew Upstart. Upstart is created to run scripts on specific moments,
meant as replacement of the traditional init. It's also possible to make it session aware (very easy!!).
I'm planning this for future, I'm not in a situation I can test Upstart now. Look at the website:
http://upstart.ubuntu.com/
and a topic about starting and stopping a session at the maillist:
https://lists.ubuntu.com/archives/upstart-devel/2009-October/001091.html
Licence
This software and the construction is distributed under the terms of the GNU General Public License.
A copy you'll find at the src directory.