[linux-l] Benutzerwechsel im Shellscript

Steffen Dettmer steffen at dett.de
Mi Apr 11 21:11:26 CEST 2007


* Volker Grabsch wrote on Fri, Apr 06, 2007 at 12:33 +0200:
> Trotzdem klingt das mit syslog erstmal interessant und wäre auf jeden
> Fall professioneller. Jedoch muss ich dann "logger" innerhalb des
> Daemons aufrufen, richtig? Ist das ratsam?
> 
> Das ist grundsätzlich kein Problem, weil der Daemon ein Shellscript
> ist. Alternativ könnte ich die Separierung aufrecht erhalten per:

Script-Daemons sind bissel albern aber haben eigenen Charme. Hab sowas
mal in Perl gemacht. Auch mit bash kommt man schnell recht weit. Bei
init.d schon wieder schlecht, dass nohup und setsid in /usr/bin sind,
also nicht "gleich am Anfang" verfügbar sein müssen.

Was brauchen wir für'n Daemon...
- kein controlling terminal + unix process session --> setsid
- kein normales stdio (insbesondere kein stdin :))
- kontrolliertes Environment
- drop root
- pid file

Vom controlling terminal wird über setsid(2) abgekoppelt, das gibts als
Kommandozeilentool setsid. Da hat man dann ne unix process session.
Inzwischen hat meine bash auch ein kill builtin, was negative PIDs
akzeptiert, kill(2) so aufruft und damit die Prozesssession killen kann.
Damit müsste ein "kill -`cat x.pid`" oder sowas gehen.

stdio wird man in bash los, in dem man einfach /dev/null "wie ein
terminal zuweist". Das heisst "[n]<>word" aka "Opening File Descriptors
for Reading and Writing". Kann ich mir nicht merken, nenn das "terminal
zuweisen", weil es eigentlich nur da Sinn macht. Hier halt das null
terminal :) Sonst macht man vielleicht mal sowas wie "<> /dev/tty" oder
so. Hier geht "0<> /dev/null 1>&0 2>&0". Kann man gut an ein exec
basteln.

exec -c schmeisst das environment weg, passt.

Für drop root ist mir nur "/usr/bin/sudo -u user" eingefallen, aber das
ist gefällt mir nicht so wirklich. Natürlich nur machen, wenn root. Das
erwarte ich von deamons so. Das droppen macht man ja zur "Sicherheit",
weil man soviel Rechte nicht braucht/will. Wenn man schon user ist, um
so besser.

Das zusammen muss aber ein Script aufrufen (oder?), leider geht
sudo nicht innerhalb eines Bashscripts. In Perl geht das besser, aber in
bash wohl nicht. Na ja. Also $0 aufrufen und merken, z.B. über
environment oder $1. 

Beispiel für all das zusammen wäre:

if [ "$1" != "daemon" ] ; then
	exec 0<> /dev/null 1>&0 2>&0 -c -a "($BASENAME)" \
	$SUDO /usr/bin/nohup /usr/bin/setsid "$0" "daemon" "$@"
else
	.... daemon
fi

Das "$@" ist eine Art Trick, alle Parameter 1:1, selbst wenn
Leerzeichen. Bin mir nicht 100% sicher, ob das so stimmt, aber
zuversichtlich :)

Das pid file sollte der Daemon auch selbst schreiben. Aus irgendwelchen
Gründen (die mir gerade entfallen sind), soll man vor setsid immer
fork()en. Ach so, muss man, damit setsid auch klappt, wenn der Aufrufer
setsid + exec gemacht hat... glaub ich. Na egal. Jedenfalls muss
natürlich die richtige pid drin stehen. Bei crash das pid file löschen.
Das geht in bash mit "trap atexit EXIT" (das ist eh ein schönes Pattern
zum Aufräumen) und sowas wie test "$pidfile" && echo "$$" > "$pidfile".

Logging via syslog ist oft prima. Da man stdout und stderr im Deamon
"frei" hat, kann man auf die Idee kommen, die beiden Files für zwei
Levels von Meldungen zu verwenden (STDOUT: debug/info, STDERR: Warnungen
und Fehler). Das mit stderr ist wohl intuitiv, find ich. Kostet leider
so ca. vier prozesse (je channel ne Shell mit logger), aber wer in bash
deamon macht, der darf das, weil man wohl keine bash auf armer Hardware
(embedded) hat :) Das mit dem logging kriegt man so hin:

	( main | $LOGGER ) 2>&1 | $LOGGERWARN

Für das loggen kann man sich ne Funktion log_error machen:

function log_notive()
{
	echo "$@"
}
function log_error()
{
	echo "$@" >&2
}

Alles zusammen könnte so aussehen:

=====8<----------------------------------------------------------

#!/bin/bash
# This is a deamon example, surely not portable.
BASENAME=`basename $0`
LOGGER="/bin/logger -t $BASENAME[$$] --"
LOGGERWARN="/bin/logger -t $BASENAME[$$] -p warn --"
pidfile="x.pid"

# The deamon function usually should not terminate :)
function main() 
{
	echo "I am a deamon `id -u`"
	echo "warning, I sleep" >&2
	sleep 5
	echo "done, exit (0)"
	exit 0
}

function atexit()
{
	echo "deamon terminating" | $LOGGER
	# cleanups
	test "$pidfile" && test -e "$pidfile" && rm "$pidfile"
}


# hier unten der Code zum Starten:
if [ "$1" != "daemon" ] ; then
	SUDO=""
	if [ `id -u` -eq 0 ]; then
		#drop root first
		SUDO="/usr/bin/sudo -u steffen"
	fi
	# -a geht bei bash scripts scheinbar nicht, aber egal.
	exec 0<> /dev/null 1>&0 2>&0 -c -a "($BASENAME)" \
		$SUDO \
		/usr/bin/nohup \
		/usr/bin/setsid \
		"$0" "daemon" "$@"
else
	echo "Starting at `date`" | $LOGGER
	trap atexit EXIT
	test "$pidfile" && echo "$$" > "$pidfile"
	export PATH=/bin:/usr/bin
	export HOME=/
	# stdout als debug, stderr als warnung --> syslog
	# Nachteil von pipe: logger prozess leben die ganze Zeit weiter
	( main | $LOGGER ) 2>&1 | $LOGGERWARN
fi
--------------------------------------------------------->8======




oki,

Steffen

-- 
Dieses Schreiben wurde maschinell erstellt,
es trägt daher weder Unterschrift noch Siegel.





Mehr Informationen über die Mailingliste linux-l