📜 ⬆️ ⬇️

Bash free space monitoring service

Good day! I would like to tell you about the next cycling. Looking through Habr, I came across a wonderful article: Bash: run a daemon with child processes . After reading, the idea arose to write something useful, with preference and courtesans, where without it.

Introductory:

OS: Astra Linux 1.2 (1.3)

From the input, two conclusions follow:
')
  1. You can not install non-certified software, otherwise we will catch a fierce popabole from two directions (Customer and Manual).
  2. Since Since we are real pioneers and are not looking for easy ways, then the output of the df command does not interest us.

I will not tell the main points of building a demon on bash, it is beautifully described in the article mentioned above, so let's go straight to the working body :).

To begin, let us indicate the variables that we will use:

#        PID_FILE="/run/ac_check_disk_space.pid" LOG_FILE="/var/log/ac_check_disk_space.log" #   #       : # s -  # m -  # h -  # d -  #    ,      CHECK_PERIOD="1m" #  : #  :      #    2 : # CHECK_DISKS=('/dev/sda1:10G' '/dev/sda3:10G') #       : # K -  # M -  # G -  #    ,      CHECK_DISKS=('/dev/sda1:10G' '/dev/sda3:10G') #  : # :host: -   # :disk: -   # :mount_point: -   # :disk_total: -    # :disk_avaiable: -     # :disk_checked_size: -    MAIL_SUBJECT_TEMPLATE="ACHTUNG: :host: low disk space on :disk: mounted to :mount_point:!" MAIL_BODY_TEMPLATE="Details: Total disk size :disk_total:, Avaiable size: :disk_avaiable:, Trigger size: :disk_checked_size:" MAIL_RCPT=('somebody@domain.ru') 

Since the df output does not interest us, then it is possible to get information about the state of the file system through stat . but for this you need to know the directory where this file system is mounted. This data is stored in the / proc / mounts file, but there is a small catch, where the disk name can be represented either by the usual device name (for example / dev / sda1) or the UUID (ohm) of the device (for example / dev / disk / by-uuid / xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx). To bring all this into a divine form, the blkid utility (locale / print block device attributes) will help us.

So, we’ll start filling in the start () function, we’ll start the check from the root and check for a second copy of the process, we’ll go straight to compiling a dictionary that matches the device name to the mount point

 #       UUID disks=$(blkid | grep -v swap | awk '{print $1}' | sed -es/://) uuids=$(blkid | grep -v swap | awk '{print $1}' | sed -es/UUID=// | sed -es/\*//g) #        mounts=() #      for (( i=0; i<${#disks[*]}; i++ )); do mount_point=( `cat /proc/mounts | grep ${disks[$i]} | awk '{print $2}'` ) if [[ ! -z $mount_point ]]; then mounts=("${mounts[@]}" "${disks[$i]}:$mount_point") fi done #    UUID for (( i=0; i<${#uuids[*]}; i++ )); do mount_point=( `cat /proc/mounts | grep ${uuids[$i]} | awk '{print $2}'` ) if [[ ! -z $mount_point ]]; then disk=`blkid -U ${uuids[$i]}` mounts=("${mounts[@]}" "$disk:$mount_point") fi done # ,              exists=0 checked_disks=() for mount in "${mounts[@]}"; do mount_disk="${mount%%:*}" for check in "${CHECK_DISKS[@]}"; do check_disk="${check%%:*}" if [ $check_disk == $mount_disk ]; then check_size="${check##*:}" size=$(calculate_space_prefix $check_size) checked_disks=("${checked_disks[@]}" "$check_disk:$size") exists=1 fi done done if [ $exists -eq 0 ]; then echo "Can not find disks, please check your configuration file" exit 1 fi 

As you can see in the configuration file there is a variable CHECK_DISKS which is an array of checked disk partitions. The size at which it is necessary to arrange a panic is specified in a form that is understandable by a human, for translation we use the function calculate_space_prefix . The function receives the size and prefix, and translates this economy into bytes.

 function calculate_space_prefix() { local value=$1 local result=$2 local size=0 local prefix="" prefix="${value: -1}" len="${#value}" len=$(($len - 1)) size="${value:0:$len}" case $prefix in "K") size=$(($size * 1024)) ;; "M") size=$(($size * 1048576)) ;; "G") size=$(($size * 1073741824)) ;; *) #size=$(($size * 1073741824)) ;; esac echo $size } 

Now consider the main loop. In it we pass through the array checked_disks , in which a section is indicated and the threshold of free space less than which must be hit hard. As mentioned above, the stat command is used to obtain information about the partition; we need the following syntax.

 stat -f < > -c "%b %a %s" # : # %b -        # %a -   ,     # %s -   

If we do not want the user to receive a letter of happiness that he is running out of space on a section, sitting with a calculator and recounting bytes into a readable form, then we will write another function.

 function calculate_return_space_prefix() { local value=$1 local space=$2 local size=0 prefix="${value: -1}" case $prefix in "K") size=$(($space / 1024)) ;; "M") size=$(($space / 1048576)) ;; "G") size=$(($space / 1073741824)) ;; *) ;; esac echo $size } 

As you can see, this is the same function calculate_space_prefix , just the opposite.

So, now everything is ready for the main service cycle. There are few comments, but I think the main principle is clear without them: check, check and check again, and then write letters.

 #   while [ 1 ]; do for checked in "${checked_disks[@]}"; do checked_disk="${checked%%:*}" checked_size="${checked##*:}" for mount in "${mounts[@]}"; do mount_disk="${mount%%:*}" mount_point="${mount##*:}" if [ $mount_disk == $checked_disk ]; then disk_all=( `stat -f $mount_point -c "%b"` ) disk_avaiable=( `stat -f $mount_point -c "%a"` ) disk_block_size=( `stat -f $mount_point -c "%s"` ) disk_all=$(($disk_all * $disk_block_size)) disk_avaiable=$(($disk_avaiable * $disk_block_size)) if [ $disk_avaiable -le $checked_size ]; then _log "Low disk size on $checked_disk mounted to $mount_point. Total size: $disk_all, avaiable size: $disk_avaiable, trigger size: $checked_size." #      for check in "${CHECK_DISKS[@]}"; do check_disk="${check%%:*}" check_size="${check##*:}" if [ $check_disk == $checked_disk ]; then disk_all=$(calculate_return_space_prefix $check_size $disk_all) disk_avaiable=$(calculate_return_space_prefix $check_size $disk_avaiable) checked_size=$(calculate_return_space_prefix $check_size $checked_size) prefix="${check_size: -1}" fi done subject=`echo -e ${MAIL_SUBJECT_TEMPLATE} | sed -e "s|:host:|$host|g" | sed -e "s|:disk:|$checked_disk|g" | sed -e "s|:mount_point:|$mount_point|g" | sed -e "s|:disk_total:|${disk_all}${prefix}|g" | sed -e "s|:disk_avaiable:|${disk_avaiable}${prefix}|g" | sed -e "s|:disk_checked_size:|${checked_size}${prefix}|g"` body=`echo -e ${MAIL_BODY_TEMPLATE} | sed -e "s|:host:|$host|g" | sed -e "s|:disk:|$checked_disk|g" | sed -e "s|:mount_point:|$mount_point|g" | sed -e "s|:disk_total:|${disk_all}${prefix}|g" | sed -e "s|:disk_avaiable:|${disk_avaiable}${prefix}|g" | sed -e "s|:disk_checked_size:|${checked_size}${prefix}|g"` for rcpt in "${MAIL_RCPT[@]}"; do echo "$body" | mail -s "$subject" "$rcpt" done fi fi done done sleep "${CHECK_PERIOD}" done 

If anyone is interested, then the full listing of the service under the spoiler
Full listing
 #!/usr/bin/env bash set -e set -m ### BEGIN INIT SCRIPT # Provides: ac_check_disk_space # Required-Start: $local_fs $syslog # Required-Stop: $local_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: ac_check_disk_space # Description: Service to monitoring disk space for Astra Linux ### END INIT SCRIPT usage() { echo -e "Usage:\n$0 (start|stop|restart)" } _log() { #     #shift ts=`date +"%b %d %Y %H:%M:%S"` hn=`cat /etc/hostname` echo "$ts $hn ac_check_disk_space[${BASHPID}]: $*" } check_conf_file() { if [ -e "/etc/ac/check_disk_space.conf" ]; then source "/etc/ac/check_disk_space.conf" else echo "Can not find configuration file (/etc/ac/check_disk_space.conf)" exit 0 fi } function calculate_space_prefix() { local value=$1 local result=$2 local size=0 local prefix="" prefix="${value: -1}" len="${#value}" len=$(($len - 1)) size="${value:0:$len}" case $prefix in "K") size=$(($size * 1024)) ;; "M") size=$(($size * 1048576)) ;; "G") size=$(($size * 1073741824)) ;; *) #size=$(($size * 1073741824)) ;; esac echo $size } function calculate_return_space_prefix() { local value=$1 local space=$2 local size=0 prefix="${value: -1}" case $prefix in "K") size=$(($space / 1024)) ;; "M") size=$(($space / 1048576)) ;; "G") size=$(($space / 1073741824)) ;; *) ;; esac echo $size } start() { #trap 'echo "1" >> /tmp/test' 1 2 3 15 #     if [ $UID -ne 0 ]; then echo "Root privileges required" exit 0 fi #    check_conf_file #     if [ -e ${PID_FILE} ]; then _pid=( `cat ${PID_FILE}` ) if [ -e "/proc/${_pid}" ]; then echo "Daemon already running with pid = $_pid" exit 0 fi fi touch ${LOG_FILE} #       UUID disks=( `blkid | grep -v swap | awk '{print $1}' | sed -es/://` ) uuids=( `blkid | grep -v swap | awk '{print $2}' | sed -es/UUID=// | sed -es/\"//g` ) #        mounts=() #      for (( i=0; i<${#disks[*]}; i++ )); do mount_point=( `cat /proc/mounts | grep ${disks[$i]} | awk '{print $2}'` ) if [[ ! -z $mount_point ]]; then mounts=("${mounts[@]}" "${disks[$i]}:$mount_point") fi done #    UUID for (( i=0; i<${#uuids[*]}; i++ )); do mount_point=( `cat /proc/mounts | grep ${uuids[$i]} | awk '{print $2}'` ) if [[ ! -z $mount_point ]]; then disk=`blkid -U ${uuids[$i]}` mounts=("${mounts[@]}" "$disk:$mount_point") fi done # ,              exists=0 checked_disks=() for mount in "${mounts[@]}"; do mount_disk="${mount%%:*}" for check in "${CHECK_DISKS[@]}"; do check_disk="${check%%:*}" if [ $check_disk == $mount_disk ]; then check_size="${check##*:}" size=$(calculate_space_prefix $check_size) checked_disks=("${checked_disks[@]}" "$check_disk:$size") exists=1 fi done done if [ $exists -eq 0 ]; then echo "Can not find disks, please check your configuration file" exit 1 fi #    cp -f ${LOG_FILE} ${LOG_FILE}.prev #   host=( `cat /etc/hostname` ) #   =) cd / exec > ${LOG_FILE} exec 2> /dev/null exec < /dev/null #  ( # ; rm -f ${PID_FILE}; exit 255; # SIGHUP SIGINT SIGQUIT SIGTERM #trap '_log "Daemon stop"; rm -f ${PID_FILE}; cp ${LOG_FILE} ${LOG_FILE}.prev; exit 0;' 1 2 3 15 _log "Daemon started" #   while [ 1 ]; do for checked in "${checked_disks[@]}"; do checked_disk="${checked%%:*}" checked_size="${checked##*:}" for mount in "${mounts[@]}"; do mount_disk="${mount%%:*}" mount_point="${mount##*:}" if [ $mount_disk == $checked_disk ]; then disk_all=( `stat -f $mount_point -c "%b"` ) disk_avaiable=( `stat -f $mount_point -c "%a"` ) disk_block_size=( `stat -f $mount_point -c "%s"` ) disk_all=$(($disk_all * $disk_block_size)) disk_avaiable=$(($disk_avaiable * $disk_block_size)) if [ $disk_avaiable -le $checked_size ]; then _log "Low disk size on $checked_disk mounted to $mount_point. Total size: $disk_all, avaiable size: $disk_avaiable, trigger size: $checked_size." #      for check in "${CHECK_DISKS[@]}"; do check_disk="${check%%:*}" check_size="${check##*:}" if [ $check_disk == $checked_disk ]; then disk_all=$(calculate_return_space_prefix $check_size $disk_all) disk_avaiable=$(calculate_return_space_prefix $check_size $disk_avaiable) checked_size=$(calculate_return_space_prefix $check_size $checked_size) prefix="${check_size: -1}" fi done subject=`echo -e ${MAIL_SUBJECT_TEMPLATE} | sed -e "s|:host:|$host|g" | sed -e "s|:disk:|$checked_disk|g" | sed -e "s|:mount_point:|$mount_point|g" | sed -e "s|:disk_total:|${disk_all}${prefix}|g" | sed -e "s|:disk_avaiable:|${disk_avaiable}${prefix}|g" | sed -e "s|:disk_checked_size:|${checked_size}${prefix}|g"` body=`echo -e ${MAIL_BODY_TEMPLATE} | sed -e "s|:host:|$host|g" | sed -e "s|:disk:|$checked_disk|g" | sed -e "s|:mount_point:|$mount_point|g" | sed -e "s|:disk_total:|${disk_all}${prefix}|g" | sed -e "s|:disk_avaiable:|${disk_avaiable}${prefix}|g" | sed -e "s|:disk_checked_size:|${checked_size}${prefix}|g"` for rcpt in "${MAIL_RCPT[@]}"; do echo "$body" | mail -s "$subject" "$rcpt" done fi fi done done sleep "${CHECK_PERIOD}" done )& #  pid    echo $! > ${PID_FILE} } stop() { check_conf_file if [ -e ${PID_FILE} ]; then _pid=( `cat ${PID_FILE}` ) if [ -e "/proc/${_pid}" ]; then kill -9 $_pid result=$? if [ $result -eq 0 ]; then echo "Daemon stop." else echo "Error stop daemon" fi else echo "Daemon is not run" fi else echo "Daemon is not run" fi } restart() { stop start } case $1 in "start") start ;; "stop") stop ;; "restart") restart ;; *) usage ;; esac exit 0 


Now about the jamb (with which too lazy to understand and fix):

  1. The script processes the signals sent to it with a delay specified in the variable CHECK_PERIOD , and not instantly. Unfortunately, I can’t remember how it is called, but it depends on the cycle.

That's all about what I wanted to tell. All beaver!

Source: https://habr.com/ru/post/245107/


All Articles