#!/bin/sh # Script for thin volume space reclamation on AIX, HP-UX, Linux, Solaris, # and VMware # # Inspiration from basic script for HP-UX and Linux written by # J. Thompkins in 2010 # # Last Update: 16 June 2014 (added support for VMware) # Coded by: Dusan U. Baljevic (dusan.baljevic@ieee.org) # # Originally, I wanted to use fully POSIX-compliant Shell # but the standards vary between operating systems # and I realized it would NOT be easy to do it all in one # script: # # POSIX Shells: # /usr/xpg4/bin/sh shell on Solaris 10 # /bin/bash, /bin/ksh on Solaris 10 # /bin/sh on AIX # /bin/sh, /bin/bash on Linux # /bin/sh on HP-UX # ... # To make things worse, GNU Bash is not fully compliant: # # http://www.gnu.org/software/bash/manual/html_node/Bash-POSIX-Mode.html # states: # "Starting Bash with the --posix command-line option or # executing .set -o posix. while Bash is running will # cause Bash to conform more closely to the posix standard # by changing the behavior to match that specified by posix # in areas where the Bash default differs. Unixos=`/bin/uname -s` case $Unixos in AIX|aix) PATH=/usr/bin:/sbin:/opt/VRTS/bin; export PATH DF=df AWK=awk ;; Linux|linux) PATH=/usr/bin:/bin:/sbin:/opt/VRTS/bin; export PATH DF=df AWK=awk ;; HP-UX) PATH=/usr/bin:/sbin:/usr/sbin:/opt/VRTS/bin; export PATH DF=bdf AWK=awk ;; SunOS|SUNOS|sunos) PATH=/usr/bin:/sbin:/opt/VRTS/bin; export PATH DF=df AWK=nawk ;; *) echo "ERROR: Unsupported operating system" ;; esac umask 022 ARGC="$#" SCRIPT="`basename $0`" USAGE="INFO: Script for thin volume space reclamation on AIX, HP-UX, Linux, Solaris, and VMware USAGE: $SCRIPT -f filesystem [ -p percent-fill] [-r] [-h] [-v] -h Print help file -f filesystem Filesystem in which to reclaim thin-provisioned space or VMware VMFS volumename -p percent-fill Threshold to fill filesystem (default 90%) -r Run batch discard via fstrim command on Linux -v Run VMware space reclamation " # Check the volume manager type # VXDCTL="`vxdctl list 2>/dev/null`" VGDISP="`vgdisplay 2>/dev/null`" VMWAREFLAG=0 FSTRIM=0 # Process command line arguments. # while getopts hrvf:p: c do case $c in f) FS="$OPTARG" ;; h) echo "$USAGE"; exit 0 ;; p) PCTFILL="$OPTARG" ;; r) FSTRIM=1;; v) VMWAREFLAG=1;; \?) echo "$USAGE"; exit 1 ;; esac done shift `expr $OPTIND - 1` if [ ! "$PCTFILL" ]; then PCTFILL=90 fi # Tunables # zfile="zerorecl.dd" SLEEPTIME=5 if [ ! "$FS" ]; then printf "ERROR: Filesystem argument not supplied\n" echo "$USAGE" exit 1 fi if [ "$ARGC" -lt 2 ]; then printf "ERROR: Must supply one filesystem mount point\n"; exit 1 fi # Check the operating system version and set up environment variables # case $Unixos in AIX|aix) DFCMD="df -P" VXOPT="" MOUNT=`$DFCMD $FS 2>/dev/null | $AWK '!/Filesystem/ {print $NF}'` FSTYPE=`/usr/sysv/bin/df -n $FS | $AWK '! /Type/ {print $3}'` ;; Linux|linux) DFCMD="df -Pk" VXOPT="" MOUNT=`$DFCMD $FS 2>/dev/null | $AWK '!/Filesystem/ {print $NF}'` FSTYPE=`df -TP $FS 2>/dev/null | $AWK '! /Type/ {print $2}'` ;; HP-UX) DFCMD="df -P" VXOPT="-F vxfs" MOUNT=`$DFCMD $FS 2>/dev/null | $AWK '!/Filesystem/ {print $NF}'` FSTYPE=`df -n $FS | $AWK '{print $5}'` ;; SunOS|SUNOS|sunos) DFCMD="df -k" VXOPT="" MOUNT=`$DFCMD $FS 2>/dev/null | $AWK '!/Filesystem/ {print $NF}'` FSTYPE=`df -n $FS | $AWK '{print $3}'` # # UFS does not support SCSI UNMAP # Steps for enabling the SCSI UNMAP with ZFS. # 1. Enable this feature by adding the following entries to the # /etc/system file: # set zfs:zfs_unmap_ignore_size=0x100000 # set zfs:zfs_log_unmap_ignore_size=0x100000 # 2. Reboot the system. # # A 1MB (0x100000) size is recommended so that smaller # areas are not reclaimed. # # ZFS does it automaticaly freeing blocks when the # underlying device advertises the functionality but be aware # of various bugs or performance issues, like Solaris 11.1 ZFS # Write Performance Degradation on Storage Devices That Support # the SCSI UNMAP Command (Doc ID 1503167.1)... # if [ "$FSTYPE" == "ufs" ] then printf "ERROR: \"$FS\" is UFS and does not support SCSI UNMAP\n" exit 1 fi if [ "$FSTYPE" == "zfs" ] then ZFSsysfile=`$AWK '! /^#/ && /zfs:zfs_unmap_ignore_size|zfs:zfs_log_unmap_ignore_size/' /etc/system | wc -l` if [ "$ZFSsysfile" -ne 2 ] then printf "ERROR: \"$FS\" is ZFS and does not support SCSI UNMAP because \"/etc/system\" does not have features enabled for it\n" printf "INFO: For ZFS to support SCSI UNMAP \"/etc/system\" must have \"zfs:zfs_unmap_ignore_size\" and \"zfs:zfs_log_unmap_ignore_size\"\n" exit 1 else printf "INFO: For ZFS to support SCSI UNMAP \"/etc/system\" must have \"zfs:zfs_unmap_ignore_size\" and \"zfs:zfs_log_unmap_ignore_size\"\n" printf "INFO: \"$FS\" is ZFS and supports SCSI UNMAP\n" exit 0 fi fi ;; esac ####### SETUP SIGNAL HANDLING ############## # trap "INTERRUPT_HANDLER" 1 2 3 9 15 # Catch interrupts and handle them # INTERRUPT_HANDLER() { trap "" 1 2 3 9 15 printf "\n\nWARN: Interrupt received... Terminating program and cleaning up\n" # killing child processes # for PID in `ps -f | $AWK -v PPID=$$ '{if ( $3 == PPID ) print $2}'` ; do if [ "$PID" != "PID" -a $PID -ne $$ ] ; then echo "INFO: Executing termination of process ID $PID 2>/dev/null" kill $PID 2>/dev/null fi done cleanup $FS exit 0 } status() { PCT=`$DFCMD $1 | $AWK '!/Filesystem/ {print $5}' | sed 's/%//g'` printf "INFO: Initial filesystem \"$1\" utilization: $PCT percent\n\n" while true do PCT=`$DFCMD $1 | $AWK '!/Filesystem/ {print $5}' | sed 's/%//g'` printf "INFO: Current filesystem \"$1\" utilization: $PCT percent...\n" if [ $PCT -gt `expr $PCTFILL + 1 ` ]; then printf "\nINFO: Filesystem \"$1\" exceeded the threshold of $PCTFILL percent\n" printf "INFO: Stopping space reclamation process and continuing with cleanup\n" #Send term signal to myself kill -1 $$ fi printf "INFO: Running filesystem space reclamation process...\n" ps -ef | egrep -v grep | grep `cat $1/$zfile.pid` if [ $? -eq 0 ]; then sleep $SLEEPTIME else return fi done } cleanup() { printf "\nINFO: Calling sync...\n" sync sleep $SLEEPTIME printf "INFO: Completed Successfully... removing temporary files...\n" rm $1/${zfile} $1/${zfile}.pid 2>/dev/null 2>&1 sleep $SLEEPTIME $DFCMD $1 } if [ "$VMWAREFLAG" -eq 1 ] then VMWARE=`lspci | grep VMware` VMWARE2=`vmware -v 2>/dev/null | grep "ESX 5"` if [ "$VMWARE" -o "$VMWARE2" ] then cd /vmfs/volumes/$FS if [ $? -eq 0 ]; then vmkfstools -y $PCTFILL 2>/dev/null if [ $? -eq 0 ]; then printf "INFO: Completed \"vmkfstools -y $FS\" successfully...\n" exit 0 else printf "INFO: \"vmkfstools -y $FS\" failed or not supported on this platform\n" exit 1 fi else printf "ERROR: Cannot change directory to \"/vmfs/volumes/$FS\"\n" fi # In ESX 5.5 vmkfstools was replaced by the following command # esxcli storage vmfs unmap -l $FS 2>/dev/null if [ $? -eq 0 ]; then printf "INFO: Completed \"esxcli storage vmfs unmap -l $FS\" successfully...\n" exit 0 else printf "ERROR: \"esxcli storage vmfs unmap -l $FS\" failed or not supported on this platform\n" exit 1 fi else printf "ERROR: VMware module not installed in kernel\n" exit 1 fi else if [ "$MOUNT" != "$FS" ]; then printf "INFO: Filesystem argument supplied \"$FS\" is not a mount point\n" printf "INFO: It is contained within \"$MOUNT\"\n" exit 1 fi fi # On Linux, check if file system mounted with "discard" option: # case $Unixos in Linux|linux) MNTFLAG=`mount | awk '/discard/ { if ( $3 == "'$FS'" ) print}'` if [ "$MNTFLAG" ] then printf "INFO: \"$FS\" supports periodic discard (\"discard\" mount option)\n" printf "INFO: No action required manually\n" printf "INFO: If you wish, you can issue batch discard via fstrim(8) command\n" fi # If periodic discard is not used, then check if batch discard via fstrim(8) works # if [ "$FSTRIM" == "1" ] then fstrim -v $FS >/dev/null 2>&1 if [ $? -eq 0 ]; then printf "INFO: Completed \"fstrim -v $FS\" successfully...\n" exit 0 else printf "INFO: \"fstrim -v $FS\" failed or not supported on this platform\n" fi fi ;; *) ;; esac # Veritas File System 5.x with VxVM has its own option for ThP space reclamation # As per http://www.symantec.com/business/support/index?page=content&id=HOWTO59591: # # "Thin Reclamation is only supported on file systems mounted on a VxVM volume." # if [ "$VXDCTL" ] then case $FSTYPE in vxfs|VXFS) printf "INFO: Attempting to run \"fsadm $VXOPT -R\" ThP reclamation for VxFS file system \"$FS\"...\n" fsadm -R $FS >/dev/null 2>&1 if [ $? -eq 0 ]; then printf "INFO: Completed \"fsadm $VXOPT -R $FS\" successfully...\n" exit 0 else continue fi ;; esac fi if [ -f $FS/$zfile ]; then printf "ERROR: \"$FS/$zfile\" exists (previous run did not complete?)... exiting\n" exit 1 fi line=`$DFCMD $FS | $AWK '!/Filesystem/ {print}'` CURCAP=`echo $line | $AWK '{print $5}' | sed 's/%//g'` CAPKB=`echo $line | $AWK '{print $2}'` CAPUSEDKB=`echo $line | $AWK '{print $3}'` # As a last resort, run dd command # if [ $CURCAP -ge `expr $PCTFILL + 1` ]; then printf "INFO: Capacity of \"$FS\" equals or exceeds threshold of fill capacity currently set at $PCTFILL\n" printf "INFO: No operation will be performend on \"$FS\"\n" else printf "INFO: Filesystem \"$FS\" has capacity `expr $CAPKB / 1024` MB\n" printf "INFO: Filling filesystem \"$FS\" utilization to $PCTFILL percent\n\n" # Thanks to John Tompkins for uncovering bug with expr(1) overflow with large arguments # An example in Shell: # z=`expr 28880458 \* 90 / 100` # echo $z # -16957260 # # I replaced expr(1) with bc(1) command # # FSEND=`expr $CAPKB \* $PCTFILL / 100` # FSEND=`echo "$CAPKB * $PCTFILL / 100" | bc` KB=`expr $FSEND - $CAPUSEDKB` COUNT=`echo "$KB / 1024" | bc` echo "INFO: Running dd if=/dev/zero of=$FS/$zfile bs=1024k count=$COUNT > /dev/null 2>&1 &" dd if=/dev/zero of=$FS/$zfile bs=1024k count=$COUNT > /dev/null 2>&1 & echo $! > $FS/$zfile.pid status $FS cleanup $FS fi exit 0