PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Bash: Wie Prozess zur PID finden?



Linus
31-07-2007, 22:37
In einigen meiner Bash-Skripte habe ich drei Funktionen zum sauberen Locken, die ich aus dem Buch "Raffinierte Shell Scripts" habe:



sleeptime="1" # sleeptime for creating the lockfile
retries="10" # default number of retries of creating the lockfile: 10 (should be > locktimeout*sleeptime)
locktimeout="5" # default timeout : 5 s. The lockfile will be removed
# by force after locktimeout seconds have passed
lockdir="/var/tmp"
lockfile="$lockdir/lockfile.beispiel" # lockfile name

# ascertain whether we have lockf or lockfile system apps
check ()
{
if [ -z "$(which lockfile | grep -v '^no ')" ] ; then
echo "$0 failed: 'lockfile' utility not found in PATH." >&2
exit 1
fi
}

# make lockifle
lock ()
{
if [ -f $lockfile ]; then
if kill -0 $(cat $lockfile) 2> /dev/null; then
echo "The locking executable with pid $(cat $lockfile) appears to be already running."
echo "Please check $lockfile if you think this is an error."
exit 1
else
echo "The locking executable with pid $(cat $lockfile) appears to have completed without cleaning up its lockfile."
echo "Removing that lockfile "$lockfile"."
rm -f "$lockfile"
fi
fi
# create the lockfile; wait
if ! lockfile -$sleeptime -r $retries -l $locktimeout "$lockfile" 2> /dev/null; then
echo "$0: Failed: Couldn't create lockfile in time" >&2
exit 1
fi
chmod u+rw "$lockfile"
# store the pid
echo $$ > "$lockfile"
chmod u-wx "$lockfile"
}

# cleanup
unlock ()
{
rm -f "$lockfile"
}


Allerdings gibt's damit das Problem, das beim lock() zufällig ein ganz anderer Prozess laufen kann, der die PID hat, die im Lockfile steht.
Und Gemäß Murphy's Law wird mir das sicherlich irgendwann passieren.
Man kann das Problem aber beseitigen, indem man den Namen zu der PID überprüft.
Dabei gibt's aber das Problem, das bei Bash-Skripten am Anfang der Kommandozeile, die ps anzeigt, erstmal /bin/bash steht; ein einfacher String-Vergleich reicht also nicht. Dazu kommt, das am Ende auch etwas stehen kann; beispielsweise
/dev/ttyS0 | tee data ; } 3>&2 2>&1 1>&3 | tee log.err
.
Wie also kann ich beim lock() überprüfen, ob der Prozess, der zu der PID im Lockfile gehört, einen bestimmten Namen hat (enthält); beispielsweise $0 (Name des aktuellen Shellskripts) oder ein String wie "beispiel.sh"? :confused:

jan61
01-08-2007, 12:36
...Wie also kann ich beim lock() überprüfen, ob der Prozess, der zu der PID im Lockfile gehört, einen bestimmten Namen hat (enthält); beispielsweise $0 (Name des aktuellen Shellskripts) oder ein String wie "beispiel.sh"?

Schau Dir mal pidof an, damit kannst Du mit dem Namen des Programms die PID-Liste gegen Deine ermittelte PID prüfen.

HTH
Jan

Linus
01-08-2007, 22:22
Ja, sowas habe ich gesucht; danke :)
Inzwischen hat aber auch die Manpage von ps weitergeholfen:

Print only the name of PID 42:
ps -p 42 -o comm=

Damit wird anscheinend immer das Kommando ohne Optionen ausgegeben.
Allerdings funktioniert das nur ohne vorangestelltes /bin/bash, aber das sollte egal sein, oder? :confused:

Eigentlich fehlt noch die Überprüfung des Users zu der PID um DOS-Atacken lokaler User zu blocken, denn das Skript/Programm kann ja terminieren und ein lokaler User kann ja ein gleichnamiges Skript/Programm laufen lassen und mehr oder minder zufällig unter der PID, die im Lockfile steht; die PID bekommt er ja mit ps oder top oder pidof angezeigt, wenn das ursprüngliche Skript/Programm läuft.

Mittels

ps -p 42 -o user=

überprüfe ich auch das mal.
Ich poste das Ergebnis (lock()), wenn es fertig ist.

jan61
01-08-2007, 23:12
...Ich poste das Ergebnis (lock()), wenn es fertig ist.

Ja, gute Idee - man baut sich zwar immer wieder solche Locks, aber eine gute, allgemeingültige Lösung kann man gut brauchen.

Jan

P.S.: Und wenn es dann noch auf sh/ksh und unter Solaris, AIX ... funktioniert, das wäre dann sozusagen das Creme-Häufchen obendrauf ;-)

Linus
06-08-2007, 01:04
Also ich hab es "fast" fertig, aber es scheitert noch daran, das $0 den Pfad enthält und $(ps -p $(cat $lockfile) -o comm=) nicht. Beispielsweise erhalte ich für $0 und $(ps -p $(cat $lockfile) -o comm=)
./testlock.sh und testlock.sh die natürlich verschieden sind :mad:

Wie kann ich $0 und $(ps -p $(cat $lockfile) -o comm=) denn ohne Pfad vergleichen; wie bekomme ich den Pfad bei $0 abgeschnitten? :confused:
Oder hat jemand einen besseren Vorschlag? :confused:

jan61
06-08-2007, 15:56
...wie bekomme ich den Pfad bei $0 abgeschnitten?...

prog_name=`basename $0`

Jan

Linus
07-08-2007, 00:18
Also des Rätsels Lösung ist die Prozssnamen in beiden Fällen mittels ps zu ermitteln.
Die Lösung ist nun:



#!/bin/bash
# Testscript (testlock.sh) for locking with lockfile and several checks.
# Based on scripts from "Linux Server Hacks" and "Wicked Cool Shell Scripts".
# It checks if the lockfile a) contains a PID of a runnig process, b) if the
# name of that process is the name of this script ($0) and c) if the locking
# process has been started by the same user ($UID).
# With these checks it should work without problem.
# For locking from multiple users or scripts it has to be modified and renamed.
#
# This script MUST be run without the explicit shell (so NOT /bin/bash testlock.sh).
#
# License: GPL
# Author: verinder at pop.ms (Dr. R. F.)
# 2007-08-06
# Version 0.9

########## Lockfile Part #####################

# parameters
sleeptime="1" # sleeptime for creating the lockfile
retries="10" # default number of retries of creating the lockfile: 10 (should be > locktimeout*sleeptime)
locktimeout="5" # default timeout : 5 s. The lockfile will be removed
# by force after locktimeout seconds have passed since the lock-
# file was last modified/created. Lockfile is clock skew immune.
lockdir="/var/tmp"
this_process="$(ps -p $$ -o comm=)"
lockfile="$lockdir/lockfile.$this_process" # lockfile name

# ascertain whether we have lockf or lockfile system apps
check ()
{
if [ -z "$(which lockfile | grep -v '^no ')" ] ; then
echo "$0 failed: 'lockfile' utility not found in PATH." >&2
exit 1
fi
}


# make lockifle
lock ()
{
# check if a lockfile is present
if [ -f $lockfile ]; then
# check the PID in the lockfile
if kill -0 $(cat $lockfile) 2> /dev/null; then
echo "The locking executable with pid $(cat $lockfile) appears to be already running."
locking_process=$(ps -p $(cat $lockfile) -o comm=)
# check if the process with the found PID has the name of this skript (run this skript always without /bin/bash)
#if [ "$locking_process" == "$0" ] ; then
if [ "$locking_process" == $(ps -p $$ -o comm=) ] ; then
echo "The locking executable has the same name (without the path) as this script"
echo "$this_process"
echo "."
# check if the process with the found UID
if [ $(ps -p $(cat $lockfile) -o uid=) == $UID ] ; then
echo "The locking process has been created from the same user $UID which is running this script; exiting."
exit 1
else
echo "The locking process has been created from the different user"
echo $(ps -p $(cat $lockfile) -o uid=)
echo "; the user (UID) of this script is $UID."
# If you want to (try to) kill the blocking process, uncomment the following line.
kill -9 "$(cat $lockfile)"
# Maybe in the line befor fi you should send an email to root@localhost that a user tried (or maybe caused)
# a DOS attack and that the blocking process (here undocumented because already killed) was killed.
fi
else
echo "The locking executable"
echo "$locking_process"
echo "DOES NOT has the same name as this script,"
echo $(ps -p $$ -o comm=)
echo "."
echo "Removing that lockfile $lockfile."
rm -f "$lockfile"
fi
else
echo "The locking executable with pid $(cat $lockfile) has completed or was killed without cleaning up its lockfile"
echo "or the locking executable has another name than this script or it is run by an other user;"
echo "removing that lockfile $lockfile."
rm -f "$lockfile"
fi
fi
# create the lockfile; wait
if ! lockfile -$sleeptime -r $retries -l $locktimeout "$lockfile" 2> /dev/null; then
echo "$0: Failed: Couldn't create lockfile in time" >&2
exit 1
fi
chmod u+rw "$lockfile"
# store the pid
echo $$ > "$lockfile"
chmod u-wx "$lockfile"
# A trap to delete the lockfile when the script gets killed by SIGHUP SIGINT or SIGTERM.
# In many cases, e. g. a kernel hangup, this does not work and the checks above are necessary.
trap "rm $lockfile; exit" SIGHUP SIGINT SIGTERM
}


# cleanup
unlock ()
{
rm -f "$lockfile"
}


#################### "main" ##############################

echo "Start of main part at"
date | xargs echo

# variable i
typeset -i i=0

# lockfile: first check, then set for locking
check
lock

echo "lock set; waiting 20 seconds"
echo "now you, or another user can test with starting this script twice etc."
sleep 20

unlock

exit 0

Linus
07-08-2007, 00:29
prog_name=`basename $0`


Ok, thx, funktioniert auch, aber ich finde es konsistenter, beide Prozessnamen mittels ps zu ermitteln ;)

Linus
19-08-2007, 23:41
Jetzt habe ich noch das Problem, das Skripte, die per inittab gestartet werden, immer explizit mit /bin/bash aufgerufen werden und das das bisherige Skript deshalb als Skript-Namen bash meldet :mad:
Wie bekomme ich den Skript-Namen auch in diesem Fall heraus? :confused:

jan61
20-08-2007, 19:31
...Wie bekomme ich den Skript-Namen auch in diesem Fall heraus? :confused:

Bei mir gehts so:
jan@jack:~/tmp> ps -p 8206 -o cmd=
/bin/bash ./bash_test.sh
jan@jack:~/tmp> ps -p 8206 -o cmd= | cut -f2- -d" "
./bash_test.sh
Jan

EDIT: Flexibler ist es, wenn nur dann bearbeitet wird, wenn auch tatsächlich /bin/bash am Anfang steht:
jan@jack:~/tmp> ps -p 8306 -o cmd= | sed 's/^\/bin\/bash //'
./bash_test.sh

Linus
21-08-2007, 18:21
Aha, danke, aber das funktioniert nicht, wenn statt
/bin/bash
nur
bash
aufgerufen wird.

jan61
22-08-2007, 22:37
Dann nimm halt die 1. Variante. Oder stell besser die sed-Expression auf eine etwas genügsamere Variante um - man sollte schon prüfen, ob man nicht gerade einen "vi DeinProgramm" am Wickel hat. Das ginge z. B. so:

ps -p 8306 -o cmd= | sed 's/^[^ ]*bash //'Damit wird abgeschnitten, wenn am Anfang der Kommandozeile irgendwas außer Leerzeichen (vielleicht, muss nicht sein) mit der Zeichenkette "bash" am Ende steht. Das ist auch noch nicht 100% sicher (/pfad/zur/ganz_anderen_bash), aber IMHO hinreichend genau. Auf jeden Fall werden /bin/bash, /usr/bin/bash, bash, ... umweltfreundlich entsorgt.

Jan

Linus
22-08-2007, 23:42
Ok, werde ich mal testen :)

Linus
26-08-2007, 12:15
Ok, nun ist das Beispiel zum Locken so:



#!/bin/bash
# Testscript (testlock.sh) for locking with lockfile and several checks.
# Based on scripts from "Linux Server Hacks" and "Wicked Cool Shell Scripts".
# It checks if the lockfile a) contains a PID of a runnig process, b) if the
# name of that process is the name of this script ($0) and c) if the locking
# process has been started by the same user ($UID).
# With these checks it should work without problem.
# For locking from multiple users or scripts it has to be modified and renamed.
#
# License: GPL
# Author: verinder at pop.ms (Dr. R. F.)
# 2007-08-26
# Version 0.91

# show every action
#set -x

########## Lockfile Part #####################

# parameters
sleeptime="1" # sleeptime for creating the lockfile
retries="10" # default number of retries of creating the lockfile: 10 (should be > locktimeout*sleeptime)
locktimeout="5" # default timeout : 5 s. The lockfile will be removed
# by force after locktimeout seconds have passed since the lock-
# file was last modified/created. Lockfile is clock skew immune.
lockdir="/var/tmp"

# Eleminate the optional bash call with sed and get this process name from basename.
this_process="$(basename "$(ps -p $$ -o cmd= | sed 's/^[^ ]*bash //')")"
lockfile="$lockdir/lockfile.$this_process" # lockfile name

# ascertain whether we have lockf or lockfile system apps
check ()
{
if [ -z "$(which lockfile | grep -v '^no ')" ] ; then
echo "$0 failed: 'lockfile' utility not found in PATH." >&2
exit 1
fi
}


# make lockifle
lock ()
{
typeset -i pid=0
# check if a lockfile is present
if [ -f "$lockfile" ]; then
# check the PID in the lockfile
pid="$(cat "$lockfile")"
if [ $pid -eq 0 ]; then
echo "Could not read a valid PID from the lockfile."
echo "Trying to remove that lockfile"
echo "$lockfile"
echo "."
rm -f "$lockfile"
else
if kill -0 $pid 2> /dev/null; then
echo "The locking executable with pid $pid appears to be already running."
locking_process="$(basename "$(ps -p $pid -o cmd= | sed 's/^[^ ]*bash //')")"
# check if the process with the found PID has the name of this skript (run this skript always without /bin/bash)
#if [ "$locking_process" == "$0" ] ; then
if [ "$locking_process" == "$this_process" ] ; then
echo "The locking executable has the same name (without the path) as this script"
echo "$this_process"
echo "."
# check if the process with the found UID
if [ $(ps -p $pid -o uid=) == $UID ] ; then
echo "The locking process has been created from the same user $UID which is running this script; exiting."
exit 1
else
echo "The locking process has been created from the different user"
echo $(ps -p $pid -o uid=)
echo "; the user (UID) of this script is $UID."
# If you want to (try to) kill the blocking process, uncomment the following 3 lines.
echo "Try to kill this locking process."
kill -9 $pid
rm -f "$lockfile"
echo "Done killing and lockfile deletion."
# Maybe in the line before the next fi you should send an email to root@localhost that a user tried (or maybe caused)
# a DOS attack and that the blocking process (here undocumented because already killed) was killed.
fi
else
echo "The locking executable"
echo "$locking_process"
echo "DOES NOT has the same name as this script,"
echo "$this_process"
echo "."
echo "Trying to remove that lockfile $lockfile."
rm -f "$lockfile"
fi
else
echo "The locking executable with pid $pid has completed or was killed without cleaning up its lockfile"
echo "or the locking executable has another name than this script or it is run by an other user;"
echo "removing that lockfile"
echo "$lockfile"
echo "."
rm -f "$lockfile"
fi
fi
fi
# (try to) create the lockfile; wait
if ! lockfile -$sleeptime -r $retries -l $locktimeout "$lockfile" 2> /dev/null; then
echo "$0: Failed: Couldn't create lockfile in time" >&2
exit 1
fi
chmod u+rw "$lockfile"
# store the pid
echo $$ > "$lockfile"
chmod u-wx "$lockfile"
# A trap to delete the lockfile when the script gets killed by SIGHUP SIGINT or SIGTERM.
# In many cases, e. g. a kernel hangup, this does not work and the checks above are necessary.
trap "rm $lockfile; exit" SIGHUP SIGINT SIGTERM
}


# cleanup
unlock ()
{
rm -f "$lockfile"
}


#################### "main" ##############################

echo "Start of main part at"
date | xargs echo
echo "pid=$$"

# lockfile: first check, then set for locking
check
lock

echo "lock set; waiting 20 seconds"
echo "now you, or another user can test with starting this script twice etc."
sleep 20

unlock

exit 0