Release new version 1.04.1
[grml-shlib.git] / sh-lib
1 #!/bin/sh
2 # Filename:      sh-lib
3 # Purpose:       Shellscript library
4 # Authors:       grml-team (grml.org), (c) Michael Gebetsroither <gebi@grml.org>
5 # Bug-Reports:   see http://grml.org/bugs/
6 # License:       This file is licensed under the GPL v2.
7 ################################################################################
8
9 # VARIABLES {{{
10 VERBOSE__=0
11 VERBOSE_TMP__=0
12
13 # FIXME maybe PROG_PATH__ for better error reporting?
14 PROG_NAME__=""    # initialised within init section
15
16 # directory for init scripts
17 INITD_DIR__="/etc/init.d/"
18
19 # >= level and the function will print the message
20 EPRINT__=1    # eprint (error print)
21 EEPRINT__=2   # 2print (intern error print)
22 DPRINT__=3    # dprint (debug print)
23
24 EXIT_FUNCTION__="_syslog"    # function to call upon die (can be set by user)
25
26 SYSLOG__="YES"
27
28 CMD_LINE__=""   # /proc/cmdline
29
30 LANG__="$LANG"
31 LC_ALL__="$LC_ALL"
32 LANGUAGE__="$LANGUAGE"
33 # }}}
34
35 # CONFIG FUNCTIONS  {{{
36
37 setProgName() { PROG_NAME__="$1"; }
38
39 setExitFunction() { EXIT_FUNCTION__="$1"; }
40
41 disableSyslog() { SYSLOG__="NO";  }
42 enableSyslog() { SYSLOG__="YES"; }
43
44 saveLang() { LANG__="$LANG"; LC_ALL__="$LC_ALL"; LANGUAGE__="$LANGUAGE"; }
45 restoreLang() { LANG="$LANG__"; LC_ALL="$LC_ALL__"; LANGUAGE="$LANGUAGE__"; }
46 setCLang() { saveLang; LANG="C"; LC_ALL="C"; LANGUAGE="C"; }
47 # }}}
48
49 # DEBUG FRAMEWORK  {{{
50
51 setVerbose()     { VERBOSE__=${1:-1}; }
52 unsetVerbose()   { VERBOSE_TMP__=$VERBOSE__; VERBOSE__=0; }
53 restoreVerbose() { VERBOSE__=$VERBOSE_TMP__; }
54 getVerbose()     { echo "$VERBOSE__"; }
55
56 setDebug()       { setVerbose "$DPRINT__"; }
57 unsetDebug()     { restoreVerbose; }
58
59 setExitFunction()    { EXIT_FUNCTION__="$1"; }
60 resetExitFunction()  { EXIT_FUNCTION__="_syslog"; }
61 # }}}
62
63 # ERROR REPORTING FUNCTIONS  {{{
64
65 # default print backend (there may be other functions)
66 vprint()
67 {
68   local level_="$1"
69   local type_="$2"
70   local message_="$3"
71
72   if [ "$VERBOSE__" -ge "$level_" -a -n "$message_" ]; then
73     echo -n "$type_" >&2
74     echo "$message_" >&2
75   fi
76 }
77
78 # print error output
79 eprint()
80 {
81   # FIXME vprint should be a var, because we want to call user-defined functions
82   # global var (there should be a syslog, and vprint + syslog function)
83   vprint $EPRINT__ "Error - " "$1"
84 }
85
86 # should be used for intern silentExecutes
87 eeprint()
88 {
89   vprint $EEPRINT__ "  Error2 - " "$1"
90 }
91
92 # print debug output (function intern errors)
93 dprint()
94 {
95   vprint $DPRINT__ "Debug - " "$1"
96 }
97
98 # for program notice messages
99 notice()
100 {
101   vprint $EPRINT__ "Notice - " "$1"
102 }
103
104 die()
105 {
106   local error_message_="$1"   # print this error message
107   local exit_code_="$2"  # command exited with this exit code
108
109   echo -n "PANIC: $error_message_" >&2
110   if [ -n "$2" ]; then
111     echo "; ret($exit_code_)" >&2
112   else
113     echo >&2
114   fi
115
116   if [ -n "$EXIT_FUNCTION__" ]; then
117     $EXIT_FUNCTION__ "$error_message_" "$exit_code_" >&2
118   fi
119   kill $$
120 }
121
122 warn()
123 {
124   local error_message_="$1"   # print this error message
125   local exit_code_="$2"  # command exits with this exit code
126
127   echo -n "WARN: $error_message_" >&2
128   if [ -n "$exit_code_" ]; then
129     echo "; ret($exit_code_)" >&2
130   else
131     echo >&2
132   fi
133 }
134
135 _syslog()
136 {
137   local message_="$1"   # error message
138   local exit_code_="$2"
139
140   if [ "$SYSLOG__" = "YES" ]; then
141     if [ -n "$exit_code_" ]; then
142       logger -p user.alert -t "$PROG_NAME__" -i "$message_ ret($exit_code_)" >&2
143     else
144       logger -p user.alert -t "$PROG_NAME__" -i "$message_" >&2
145     fi
146   fi
147 }
148
149 syslog()
150 {
151   local message_="$1"   # error message
152   local exit_code_="$2"
153
154   if [ -n "$exit_code_" ]; then
155     logger -p user.alert -t "$PROG_NAME__" -i "$message_ ret($exit_code_)" >&2
156   else
157     logger -p user.alert -t "$PROG_NAME__" -i "$message_" >&2
158   fi
159 }
160
161 warnLog()
162 {
163   local error_message_="$1"   # print this error message
164   local exit_code_="$2"  # command exits with this exit code
165
166   warn "$error_message_" "$exit_code_"
167   syslog "$error_message_" "$exit_code_"
168 }
169 # }}}
170
171 # CORE FUNCTIONS {{{
172
173 ##
174 # ATTENTION... THIS FUNCTION IS A BIG SECURITY HOLE
175 # this function will be changed in future release
176 ##
177 # I don't want to write exit status control stuff every time
178 execute()
179 {
180   local to_exec_="$1"   # command to execute
181   local error_function_=${2:-"eprint"}    # function to call on error
182   local message_="$3"   # user supplied error message
183
184   local ret_=''
185
186   # NOT A GOOD IDEA
187   eval "$to_exec_"
188   ret_=$?
189
190   if [ $ret_ -eq 127 ]; then
191     syslog "problems executing ( $to_exec_ )" $ret_
192   fi
193   if [ $ret_ -ne 0 ]; then
194     if [ -z "$message_" ]; then
195       $error_function_ "problems executing ( $to_exec_ )" "$ret_"
196     else
197       $error_function_ "$message_" "$ret_"
198     fi
199   fi
200   dprint "exec-$error_function_: ( $to_exec_ ) ret($ret_)"
201   return $ret_
202 }
203
204 silentExecute()
205 {
206   unsetVerbose
207   execute "$@"
208   local ret_=$?
209   restoreVerbose
210   return $ret_
211 }
212
213
214 ###
215 #
216 # TEST FUNCTIONS
217 #
218 ###
219
220 # if the file DOES exist, everything is fine
221 isExistent()
222 {
223   local file_to_test_="$1"    # file to test
224   local error_function_=${2:-"eprint"}    # function to call on error
225   local message_="$3"    # user supplied error message
226
227   if [ ! -e "$file_to_test_" ]; then
228     if [ -z "$message_" ]; then
229       $error_function_ "file does not exist \"$file_to_test_\"" 66
230     else
231       $error_function_ "$message_"
232     fi
233     return 1
234   fi
235   dprint "isExistent(): file \"$1\" does exist => ready to go"
236   return 0
237 }
238
239 isNotExistent()
240 {
241   local file_to_test_="$1"    # file to test
242   local error_function_=${2:-"eprint"}    # function to call on error
243   local message_="$3"    # user supplied error message
244
245   if [ -e "$file_to_test_" ]; then
246     if [ -z "$message_" ]; then
247       $error_function_ "file does already exist \"$file_to_test_\"" 67
248     else
249       $error_function_ "$message_"
250     fi
251     return 1
252   fi
253   dprint "isNotExistent(): file \"$1\" does not exist => ready to go"
254   return 0
255 }
256
257
258 checkUser()
259 {
260   local to_check_="$1"    # username to check against running process
261   local error_function_=${2:-"eprint"}    # function to call on error
262   local message_="$3"    # user supplied error message
263
264   local user_=''
265
266   user_=`id -un`
267   if [ $user_ != "$to_check_" ]; then
268     if [ -z "$message_" ]; then
269       $error_function_ "username \"$user_\" is not \"$to_check_\"" 77 $exit_function_
270     else
271       $error_function_ "$message_"
272     fi
273     return 1
274   else
275     dprint "checkUser(): accepted, username matches \"$to_check_\""
276     return 0
277   fi
278 }
279
280 checkId()
281 {
282   local to_check_="$1"    # user-id to check against running process
283   local error_function_=${2:-"eprint"}    # function to call on error
284   local message_="$3"    # user supplied error message
285
286   local user_id_=''
287
288   user_id_=$(id -u)
289   if [ "$user_id_" != "$to_check_" ]; then
290     if [ -z "$message_" ]; then
291       $error_function_ "UID \"$user_id_\" is not \"$to_check_\"" 77
292     else
293       $error_function_ "$message_"
294     fi
295     return 1
296   else
297     dprint "checkId(): accepted, UID matches \"$to_check_\""
298     return 0
299   fi
300 }
301
302 checkRoot()
303 {
304   checkId 0 "$1" "$2"
305 }
306
307 isGrml()
308 {
309   if [ -f /etc/grml_version ] ; then
310     dprint "isGrml(): this seems to be a grml system"
311     return 0
312   else
313     dprint "isGrml(): this is not a grml system"
314     return 1
315   fi
316 }
317
318 runsFromHd()
319 {
320   if [ -e "/etc/grml_cd" ]; then
321     dprint "runsFromHd(): grml is on CD"
322     return 1
323   else
324     dprint "runsFromHd(): grml is on HD"
325     return 0
326   fi
327 }
328
329 runsFromCd()
330 {
331   if [ -e "/etc/grml_cd" ]; then
332     dprint "runsFromCd(): grml is on CD"
333     return 0
334   else
335     dprint "runsFromCd(): grml is on HD"
336     return 1
337   fi
338 }
339
340
341 # secure input from console
342 secureInput()
343 {
344   local to_secure_="$1"
345
346   local secured_=''
347
348   secured_=`echo -n "$to_secure_" |tr -c '[:alnum:]/.\-,\(\)' '_'`
349   dprint "secureInput(): \"$to_secure_\" => \"$secured_\""
350   echo "$secured_"
351 }
352
353
354 # convert all possible path formats to absolute paths
355 relToAbs()
356 {
357   local relpath_="$1"
358   local abspath_=''
359
360   abspath_="`readlink -f \"$relpath_\"`" || \
361     warn "relToAbs(): Problems getting absolute path" "$?" || return 1
362   dprint "relToAbs(): \"$relpath_\" => \"$abspath_\""
363   echo "$abspath_"
364 }
365
366
367 # Trim off white-space characters
368 # white-space in the "C" and "POSIX" locales are:
369 #   space
370 #   form-feed ('\f')
371 #   newline ('\n')
372 #   carriage return ('\r')
373 #   horizontal tab ('\t')
374 #   vertical tab ('\v')
375 stringTrim()
376 {
377   local str_="$1"
378   local result_=""
379
380   result_="`echo "$str_" | sed -e 's/^\s*//' -e 's/\s*$//'`" || \
381     warn "stringTrim(): Problems stripping of blanks" || return 1
382   dprint "stringTrim(): \"$str_\" => \"$result_\""
383   echo "$result_"
384 }
385
386 # Simple shell grep
387 stringInFile()
388 {
389   local to_test_="$1"   # matching pattern
390   local source_="$2"    # source-file to grep
391
392   if [ ! -e "$source_" ]; then
393     eprint "stringInFile(): \"$source_\" does not exist"
394     return 1
395   fi
396
397   case "$(cat $source_)" in *$to_test_*) return 0;; esac
398   return 1
399 }
400
401 # same for strings
402 stringInString()
403 {
404   local to_test_="$1"   # matching pattern
405   local source_="$2"    # string to search in
406
407   case "$source_" in *$to_test_*) return 0;; esac
408   return 1
409 }
410
411 # get value for bootparam given as first param
412 getBootParam()
413 {
414   local param_to_search_="$1"
415   local result_=''
416
417   stringInString " $param_to_search_=" "$CMD_LINE__" || return 1
418   result_="${CMD_LINE__##*$param_to_search_=}"
419   result_="${result_%%[   ]*}"
420   echo "$result_"
421   return 0
422 }
423
424 # Check boot commandline for specified option
425 checkBootParam()
426 {
427   stringInString " $1" "$CMD_LINE__"
428   return "$?"
429 }
430 # }}}
431
432 # NETWORK  {{{
433
434 # validates an IP FIXME
435 netValidIp()
436 {
437   local ip_="$1"    # ip address to validate
438   local error_function_=${2:-"eprint"}    # function to call on error
439   local message_="$3"    # user supplied error message
440
441   local ret_=''
442
443   echo "$ip_" | grep -E -q -e '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}.[0-9]{1,3}' \
444     1>/dev/null 2>&1
445   ret_=$?
446   if [ $ret_ -ne 0 ]; then
447     if [ -z "$message_" ]; then
448       "$error_function_" "ip-address \"$ip_\" is NOT valid" $ret_
449     else
450       "$error_function_" "$message_" $ret_
451     fi
452     return 1
453   fi
454
455   dprint "ip-address \"$ip_\" is valid" $ret_
456   return $ret_
457 }
458
459 netGetIfaces()
460 {
461   local error_function_=${1:-"eprint"}    # function to call on error
462   local message_="$2"    # user supplied error message
463   local if_=''
464   local ret_=''
465
466   #ip a|grep 'inet ' |awk '$NF !~ /lo/{print $NF}'
467   if_="`ip a|grep 'inet ' |awk '{print $NF}'`"
468   ret_=$?
469   if [ -z "$if_" ]; then
470     if [ -z "$message_" ]; then
471       "$error_function_" "no interfaces found" $ret_
472     else
473       "$error_function_" "$message_" $ret_
474     fi
475     return 1
476   fi
477   dprint "interfaces found" $ret_
478   echo "$if_"
479 }
480
481 # FIXME
482 netGetDefaultGateway()
483 {
484   local error_function_=${1:-"eprint"}    # function to call on error
485   local message_="$2"    # user supplied error message
486
487   local ip_=''
488   local ret_=''
489
490   setCLang
491   ip_=`route -n | awk '/^0\.0\.0\.0/{print $2; exit}'`
492   ret_=$?
493   restoreLang
494   if [ -z "$ip_" ]; then
495     if [ -z "$message_" ]; then
496       "$error_function_" "no default gateway found" $ret_
497     else
498       "$error_function_" "$message_" $ret_
499     fi
500     return 1
501   fi
502   dprint "default gateway is \"$ip_\"" $ret_
503   echo "$ip_"
504   return 0
505 }
506
507 # FIXME
508 netGetNetmask()
509 {
510   local iface_="$1"
511   local error_function_=${2:-"eprint"}    # function to call on error
512   local message_="$3"    # user supplied error message
513
514   local nm_=''
515   local ret_=''
516
517   setCLang
518   if ifconfig "$iface_" | grep -qi 'Mask:' ; then # old ifconfig output:
519     nm_=$(ifconfig "$iface_" | awk '/[Mm]ask/{FS="[:   ]*"; $0=$0; print $8; exit}')
520   else # new ifconfig output (net-tools >1.60-27):
521     nm_=$(ifconfig "$iface_" | awk '/netmask/{print $4}')
522   fi
523   ret_=$?
524   restoreLang
525   if [ -z "$nm_" ]; then
526     if [ -z "$message_" ]; then
527       "$error_function_" "could not find a netmask for \"$iface_\"" $ret_
528     else
529       "$error_function_" "$message_" $ret_
530     fi
531     return 1
532   fi
533   dprint "netmask on \"$iface_\" is \"$nm_\"" $ret_
534   echo "$nm_"
535   return 0
536 }
537
538 # FIXME
539 netGetIp()
540 {
541   local iface_="$1"
542   local error_function_=${2:-"eprint"}    # function to call on error
543   local message_="$3"    # user supplied error message
544
545   local ip_=""
546   local ret_=""
547
548   setCLang
549   ip_=$(ip addr show dev "$iface_" | awk '/inet /{split($2,a,"/"); print a[1]}')
550   ret_=$?
551   restoreLang
552   if [ -z "$ip_" ]; then
553     if [ -z "$message_" ]; then
554       "$error_function_" "no ip for \"$iface_\" found" $ret_
555     else
556       "$error_function_" "$message_" $ret_
557     fi
558     return 1
559   fi
560   dprint "address for \"$iface_\" is \"$ip_\"" $ret_
561   echo "$ip_"
562   return 0
563 }
564
565 netGetNameservers()
566 {
567   local error_function_=${1:-"eprint"}    # function to call on error
568   local message_="$2"    # user supplied error message
569
570   local file_="/etc/resolv.conf"
571   local ns_=""
572
573   if [ ! -e $file_ ]; then
574     warn "file \"$file_\" does not exist, could not get nameservers"
575     return 1
576   fi
577
578   setCLang
579   ns_=`awk '/^nameserver/{printf "%s ",$2}' $file_ |xargs echo`
580   restoreLang
581   if [ -z "$ns_" ]; then
582     if [ -z "$message_" ]; then
583       "$error_function_" "no nameservers found" $ret_
584     else
585       "$error_function_" "$message_" $ret_
586     fi
587     return 1
588   fi
589   dprint "nameservers: \"$ns_\"" $ret_
590   echo "$ns_"
591   return 0
592 }
593
594 # }}}
595
596 # SERVICES {{{
597 _touchService()
598 {
599   local action_="${1:-"start"}"
600   local service_="$2"
601   local error_function_=${3:-"eprint"}    # function to call on error
602   local message_="$4"     # user supplied error message
603
604   local i=""
605   local known_action_='false'
606   for i in "start" "stop" "restart" "reload" "force-reload"; do
607     if [ "$i" = "$action_" ]; then
608       known_action_='true'
609       break
610     fi
611   done
612   $known_action_ || warn "_touchService(): unknown action \"$action_\""
613
614
615   local service_path_=""
616   service_path_="${INITD_DIR__}/$service_"
617   if [ ! -e "$service_path_" ]; then
618     warn "_touchService(): service does not exist: \"$service_\""
619     return 1
620   fi
621   if [ ! -x "$service_path_" ]; then
622     warn "_touchService(): service is not executable: \"$service_\""
623   fi
624
625   local ret_=""
626   "$service_path_" "$action_"
627   ret_=$?
628   if [ "$ret_" != "0" ]; then
629     if [ -z "$message_" ]; then
630       "$error_function_" "Problems ${action_}ing service \"$service_\"" $ret_
631     else
632       "$error_function_" "$message_" $ret_
633     fi
634     return 1
635   fi
636   dprint "_touchService(): successfully started service \"$service_\""
637   return 0
638 }
639
640 _createServiceFunctions()
641 {
642   for i in "start" "stop" "restart" "reload"; do
643     eval "${i}Service() { _touchService ${i} \"\$1\" \"\$2\" \"\$3\"; }"
644   done
645   eval "forceReloadService() { _touchService force-reload \"\$1\" \"\$2\" \"\$3\"; }"
646 }
647 _createServiceFunctions
648 # }}}
649
650 # LOSETUP HELPER FUNCTION {{{
651 # print next free /dev/loop* to stdout
652 findNextFreeLoop()
653 {
654   local error_function_=${1:-"eprint"}    # function to call on error
655   local message_="$2"    # user supplied error message
656
657   local tmp_=''   # tmp
658   local i=''      # counter
659   local ret_=''   # saved return value
660
661   for i in 'losetup' 'losetup.orig'; do
662     tmp_=`$i -f 2>/dev/null`
663     if [ $? -eq 0 ]; then
664       echo $tmp_
665       return 0
666     fi
667   done
668
669   # we have to search
670   dprint 'findNextFreeLoop(): losetup does not recognice option -f, searching next free loop device'
671   for i in `seq 0 100`; do
672     test -e /dev/loop$i || continue
673     losetup /dev/loop$i >/dev/null 2>&1
674     ret_=$?
675     case "$ret_" in
676       2) continue ;;  # losetup could not get status of loopdevice (EPERM)
677       0) continue ;;  # device exist
678       1) echo "/dev/loop$i"; return 0 ;;  # device does not exist and no error
679       ?) continue ;;  # return value not available in 'man losetup'
680     esac
681   done
682
683   # hmm... could not find a loopdevice
684   if [ -z "$message_" ]; then
685     $error_function_ "could not find a free loop device"
686   else
687     $error_function_ "$message_"
688   fi
689   return 1
690 }
691 # }}}
692
693 # INIT {{{
694
695 _initProgName()
696 {
697   local name_="$1"    # program name
698
699   local tmp_name_=`basename "$name_"` || \
700     logger -p user.alert -i "Init-initProgName: problems executing ( basename \"$name_\" ) ret($?)" >/dev/null
701
702   secureInput "$tmp_name_"
703 }
704 PROG_NAME__=`_initProgName "$0"`
705
706
707 _checkExecutables()
708 {
709   local tmp_=""
710   for i in tr dirname basename id logger kill cat grep route awk ifconfig; do
711     which $i >/dev/null 2>&1 || tmp_="${tmp_}$i "
712   done
713   if [ -n "$tmp_" ]; then
714     eprint "Init-checkExecutables: following executables not found or not executable:\n$tmp_"
715     #syslog "Init-checkExecutables: following executables not found or not executable: $tmp_"
716   fi
717 }
718 _checkExecutables
719
720
721 _checkBootParam()
722 {
723   local path_="/proc/cmdline"
724   if [ -e "$path_" ]; then
725     CMD_LINE__=`execute "cat $path_" warnLog`
726     return 0
727   fi
728   warnLog "$path_ does not exist, thus sh-lib may not work reliable!"
729   return 1
730 }
731 _checkBootParam
732
733 _setDebugLevel()
734 {
735   # accept only integer as arguments
736   if echo "$DEBUG" | grep -E -q '^[0-9]+$' ; then
737     local debug_="${DEBUG:-0}"
738   else
739     local debug_="0"
740   fi
741   VERBOSE__="$debug_"
742 }
743 _setDebugLevel
744 # }}}
745
746 # END OF FILE
747 ################################################################################
748 # vim:foldmethod=marker expandtab shiftwidth=2 tabstop=2