[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

shell kung-fu with set -e, return codes and i/o redirection in while read ... loop



Hello,

in the cryptsetup init and cryptdists_{start|stop} scripts we use some
shell magic with set -e, while read ... done and input/output
redirection. i have to admit that this magic demonstrated me the limits
of my shell scripting skills.

the code in question is:

set -e
...
egrep -v "^[[:space:]]*(#|$)" "$TABFILE" | while read dst src key opts; do
	...
		handle_crypttab_line_start "$dst" "$src" "$key" "$opts" <&3
	...
done 3<&1

and the bugs that I try to fix are:

#524173
 cryptdisk_start target doesn't return a sensible exit code on failure.
#524485
 cryptdisks_start incorrectly reports failure

now let me try to explain the problem:

cryptdisks_start is a script that takes a target name as single argument
and starts this cryptdisks target with the options from /etc/crypttab.
many functions are shared in /lib/cryptsetup/cryptdisks.functions for
cryptdisks_{start|stop} and the cryptdisks initscripts.

cryptdisks_start at the moment always exits with error code 0 (success),
even if errors occured (target not found in /etc/crypttab, failed to
start, ...), and even worse, it doesn't even detect correctly whether
the target was found or not.

so the script needs to be changed to exit the while loop with
information about whether
 - the target was found in crypttab
 - the target setup succeeded or not

for the first one I only see one solution: add a counter to the while
loop which checks for $count -ge $valid_crypttab_lines.

for the second one I first thought that wrapping the complete while
loop with ret=$(while read ... done) would be an option, but
unfortunately that doesn't work due to input redirection to/from
filedescripter 3 (<&3, 3<&1) ...

so second thought was to use return codes inside the while loop and
check for them after the while loop. but that failed due to 'set -e'
being set. therefore any return code != 0 caused the script to stop
immediately instead of just exiting the while loop.

now I see the option to set -e only inside the while loop, but I would
like to have other opinions about that idea before actually implementing
it. tests on my system gave me the impression that it works as expected:

count=0
tablen="$(egrep -v "^[[:space:]]*(#|$)" "$TABFILE" | /usr/bin/wc -l)"
egrep -v "^[[:space:]]*(#|$)" "$TABFILE" | while read dst src key opts; do
	set -e
	count=$(( $count + 1 ))
	if [ "$1" = "$dst" ]; then
		handle_crypttab_line_start "$dst" "$src" "$key" "$opts" <&3
		exit $?
	fi
	if [ $count -ge $tablen ]; then
		device_msg "$1" "failed, not found in crypttab"
		exit 1
	fi
done 3<&1
ret=$?
...
log_action_end_msg $ret

only one issue remains that still doesn't work as expected:
if handle_crypttab_line_start fails with a warning, it still returns 0
(success) as return code. unfortunately I don't see an option t change
that one.
in initscripts it would be fatal for handle_crypttab_line_start to exit
with any return code != 0 as that would cause the whole initscript to
stop instead of processing the remaining crypt targets.

maybe the best solution would be to rewrite the whole story, but I don't
even have an idea how to do it better.

thanks in advance for any comments.

greetings,
 jonas


Reply to: