"Fossies" - the Fresh Open Source Software Archive

Member "hdparm-9.65/wiper/wiper.sh" (12 Oct 2016, 27914 Bytes) of package /linux/misc/hdparm-9.65.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Bash source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 #!/bin/bash
    2 #
    3 # SATA SSD free-space TRIM utility, by Mark Lord <mlord@pobox.com>
    4 
    5 VERSION=3.6 
    6 
    7 # Copyright (C) 2009-2010 Mark Lord.  All rights reserved.
    8 #
    9 # Contains hfsplus and ntfs code contributed by Heiko Wegeler <heiko.wegeler@googlemail.com>.
   10 # Package sleuthkit version >=3.1.1 is required for HFS+. Package ntfs-3g and ntfsprogs is required for NTFS.
   11 #
   12 # Requires gawk, a really-recent hdparm, and various other programs.
   13 # This needs to be redone entirely in C, for 64-bit math, someday.
   14 # 
   15 # This program is free software; you can redistribute it and/or
   16 # modify it under the terms of the GNU General Public License Version 2,
   17 # as published by the Free Software Foundation.
   18 # 
   19 # This program is distributed in the hope that it would be useful,
   20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
   21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   22 # GNU General Public License for more details.
   23 #
   24 # You should have received a copy of the GNU General Public License
   25 # along with this program; if not, write to the Free Software Foundation,
   26 # Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   27 #
   28 # Note for OCZ Vertex-LE users:  the drive firmware will error when
   29 # attempting to trim the final sector of the drive.  To avoid this,
   30 # partition the drive such that the final sector is not used.
   31 
   32 export LANG=C
   33 
   34 ## The usual terse usage information:
   35 ##
   36 function usage_error(){
   37     echo >&2
   38     echo "Linux tune-up (TRIM) utility for SATA SSDs"
   39     echo "Usage:  $0 [--verbose] [--commit] <mount_point|block_device>" >&2
   40     echo "   Eg:  $0 /dev/sda1" >&2
   41     echo >&2
   42     exit 1
   43 }
   44 
   45 ## Parameter parsing for the main script.
   46 ## Yeah, we could use getopt here instead, but what fun would that be?
   47 ##
   48 
   49 echo
   50 echo "${0##*/}: Linux SATA SSD TRIM utility, version $VERSION, by Mark Lord."
   51 
   52 export verbose=0
   53 commit=""
   54 destroy_me=""
   55 argc=$#
   56 arg=""
   57 while [ $argc -gt 0 ]; do
   58     if [ "$1" = "--commit" ]; then
   59         commit=yes
   60     elif [ "$1" = "--please-prematurely-wear-out-my-ssd" ]; then
   61         destroy_me=yes
   62     elif [ "$1" = "--verbose" ]; then
   63         verbose=$((verbose + 1))
   64     elif [ "$1" = "" ]; then
   65         usage_error
   66     else
   67         if [ "$arg" != "" ]; then
   68             echo "$1: too many arguments, aborting." >&2
   69             exit 1
   70         fi
   71         arg="$1"
   72     fi
   73     argc=$((argc - 1))
   74     shift
   75 done
   76 [ "$arg" = "" ] && usage_error
   77 
   78 ## Find a required program, or else give a nicer error message than we'd otherwise see:
   79 ##
   80 function find_prog(){
   81     prog="$1"
   82     if [ ! -x "$prog" ]; then
   83         prog="${prog##*/}"
   84         p=`type -f -P "$prog" 2>/dev/null`
   85         if [ "$p" = "" ]; then
   86             [ "$2" != "quiet" ] && echo "$1: needed but not found, aborting." >&2
   87             exit 1
   88         fi
   89         prog="$p"
   90         [ $verbose -gt 0 ] && echo "  --> using $prog instead of $1" >&2
   91     fi
   92     echo "$prog"
   93 }
   94 
   95 ## Ensure we have most of the necessary utilities available before trying to proceed:
   96 ##
   97 hash -r  ## Refresh bash's cached PATH entries
   98 HDPARM=`find_prog /sbin/hdparm` || exit 1
   99 FIND=`find_prog /usr/bin/find`  || exit 1
  100 STAT=`find_prog /usr/bin/stat`  || exit 1
  101 GAWK=`find_prog /usr/bin/gawk`  || exit 1
  102 BLKID=`find_prog /sbin/blkid`   || exit 1
  103 GREP=`find_prog /bin/grep`  || exit 1
  104 ID=`find_prog /usr/bin/id`  || exit 1
  105 LS=`find_prog /bin/ls`      || exit 1
  106 DF=`find_prog /bin/df`      || exit 1
  107 RM=`find_prog /bin/rm`      || exit 1
  108 STAT=`find_prog /usr/bin/stat`  || exit 1
  109 
  110 [ $verbose -gt 1 ] && HDPARM="$HDPARM --verbose"
  111 
  112 ## I suppose this will confuse the three SELinux users out there:
  113 ##
  114 if [ `$ID -u` -ne 0 ]; then
  115     echo "Only the super-user can use this (try \"sudo $0\" instead), aborting." >&2
  116     exit 1
  117 fi
  118 
  119 ## We need a very modern hdparm, for its --fallocate and --trim-sector-ranges-stdin flags:
  120 ## Version 9.25 added automatic determination of safe max-size of TRIM commands.
  121 ##
  122 HDPVER=`$HDPARM -V | $GAWK '{gsub("[^0-9.]","",$2); if ($2 > 0) print ($2 * 100); else print 0; exit(0)}'`
  123 if [ $HDPVER -lt 925 ]; then
  124     echo "$HDPARM: version >= 9.25 is required, aborting." >&2
  125     exit 1
  126 fi
  127 
  128 ## Convert relative path "$1" into an absolute pathname, resolving all symlinks:
  129 ##
  130 function get_realpath(){
  131     iter=0
  132     p="$1"
  133     while [ -e "$p" -a $iter -lt 100 ]; do
  134         ## Strip trailing slashes:
  135         while [ "$p" != "/" -a "$p" != "${p%%/}" ]; do
  136             p="${p%%/}"
  137         done
  138         ## Split into directory:leaf portions:
  139         d="${p%/*}"
  140         t="${p##*/}"
  141         ## If the split worked, then cd into the directory portion:
  142         if [ "$d" != "" -a "$d" != "$p" ]; then
  143             cd -P "$d" || exit
  144             p="$t"
  145         fi
  146         ## If what we have left is a directory, then cd to it and print realpath:
  147         if [ -d "$p" ]; then
  148             cd -P "$p" || exit
  149             pwd -P
  150             exit
  151         ## Otherwise if it is a symlink, read the link and loop again:
  152         elif [ -h "$p" ]; then
  153             p="`$LS -ld "$p" | awk '{sub("^[^>]*-[>] *",""); print}'`"
  154         ## Otherwise, prefix $p with the cwd path and print it:
  155         elif [ -e "$p" ]; then
  156             [ "${p:0:1}" = "/" ] || p="`pwd -P`/$p"
  157             echo "$p"
  158             exit
  159         fi
  160         iter=$((iter + 1))
  161     done
  162 }
  163 
  164 function get_devpath(){
  165     dir="$1"
  166     kdev=`$STAT --format="%04D" "$dir" 2>/dev/null`
  167     [ "$kdev" = "" ] && exit 1
  168     major=$((0x${kdev:0:2}))
  169     minor=$((0x${kdev:2:2}))
  170     $FIND /dev -xdev -type b -exec $LS -ln {} \; | $GAWK -v major="$major," -v minor="$minor" \
  171         '($5 == major && $6 == minor){r=$NF}END{print r}'
  172 }
  173 
  174 ## Convert "$arg" into an absolute pathname target, with no symlinks or embedded blanks:
  175 target="`get_realpath "$arg"`"
  176 if [ "$target" = "" ]; then
  177     [ "$arg" = "/dev/root" ] && target="`get_devpath /`"
  178     if [ "$target" = "" ]; then
  179         echo "$arg: unable to determine full pathname, aborting." >&2
  180         exit 1
  181     fi
  182 fi
  183 if [ "$target" != "${target##* }" ]; then
  184     echo "\"$target\": pathname has embedded blanks, aborting." >&2
  185     exit 1
  186 fi
  187 
  188 ## Take a first cut at online/offline determination, based on the target:
  189 ##
  190 if [ -d "$target" ]; then
  191     method=online
  192 elif [ -b "$target" ]; then
  193     method=offline
  194 else
  195     echo "$target: not a block device or mount point, aborting." >&2
  196     exit 1
  197 fi
  198 
  199 ## Find the active mount-point (fsdir) associated with a device ($1: fsdev).
  200 ## This is complicated, and probably still buggy, because a single
  201 ## device can show up under *multiple* mount points in /proc/mounts.
  202 ##
  203 function get_fsdir(){
  204     rw=""
  205     r=""
  206     while read -a m ; do
  207         pdev="${m[0]}"
  208         [ "$pdev" = "$1" ] || pdev="`get_realpath "$pdev"`"
  209         if [ "$pdev" = "$1" ]; then
  210             if [ "$rw" != "rw" ]; then
  211                 rw="${m[3]:0:2}"
  212                 r="${m[1]}"
  213             fi
  214         fi
  215         #echo "$pdev ${m[1]} ${m[2]} ${m[3]}"
  216     done
  217     echo -n "$r"
  218 }
  219 
  220 ## Find the device (fsdev) associated with a mount point ($1: fsdir).
  221 ## Since mounts can be stacked on top of each other, we return the
  222 ## one from the last occurance in the list from /proc/mounts.
  223 ##
  224 function get_fsdev(){   ## from fsdir
  225     get_realpath "`$GAWK -v p="$1" '{if ($2 == p) r=$1} END{print r}' < /proc/mounts`"
  226 }
  227 
  228 ## Find the r/w or r/o status (fsmode) of a filesystem mount point  ($1: fsdir)
  229 ## We get it from the last occurance of the mount point in the list from /proc/mounts,
  230 ## and convert it to a longer human-readable string.
  231 ##
  232 function get_fsmode(){  ## from fsdir
  233     mode="`$GAWK -v p="$1" '{if ($2 == p) r=substr($4,1,2)} END{print r}' < /proc/mounts`"
  234     if [ "$mode" = "ro" ]; then
  235         echo "read-only"
  236     elif [ "$mode" = "rw" ]; then
  237         echo "read-write"
  238     else
  239         echo "$fsdir: unable to determine mount status, aborting." >&2
  240         exit 1
  241     fi
  242 }
  243 
  244 ## Try and determine the device name associated with the root filesystem.
  245 ## This is nearly impossible to do in any perfect fashion.
  246 ##
  247 ## Redhat/Fedora no longer have an rdev command.  Silly them.
  248 ## So we now implement it internally, below.
  249 ##
  250 ## match_rootdev *should* work, but on some distros it may find only "/dev/root",
  251 ## and "/dev/root" is not usually a real device.  We leave it like that for now,
  252 ## because that's the pattern such systems also use in /proc/mounts.
  253 ## Later, at time of use, we'll try harder to find the real rootdev.
  254 ##
  255 ## FIXME: apparently this doesn't work on SuSE Linux, though.
  256 ## So for there, we'll likely need to read /etc/mtab,
  257 ## or be a lot more clever and get it somehow from statfs or something.
  258 ## FIXME: or use target from /dev/root symlink for Gentoo as well.
  259 ##
  260 function match_rootdev() {
  261     rdev=""
  262     rdevno="$1"
  263     while read bdev ; do
  264         if [ "$rdev" = "" -o "$bdev" != "/dev/root" ]; then
  265             devno=$($STAT -c "0x%t%02T" "$bdev" 2>/dev/null)
  266             [ "$devno" = "$rdevno" ] && rdev="$bdev"
  267         fi
  268     done
  269     echo -n "$rdev"
  270 }
  271 
  272 rootdev=$($FIND /dev/ -type b 2>/dev/null | match_rootdev $($STAT -c "0x%D" '/'))
  273 [ $verbose -gt 0 ] && echo "rootdev=$rootdev"
  274 
  275 ## The user gave us a directory (mount point) to TRIM,
  276 ## which implies that we will be doing an online TRIM
  277 ## using --fallocate and --fibmap to find the free extents.
  278 ## Do some preliminary correctness/feasibility checks on fsdir:
  279 ##
  280 if [ "$method" = "online" ]; then
  281     ## Ensure fsdir exists and is accessible to us:
  282     fsdir="$target"
  283     cd "$fsdir" || exit 1
  284 
  285     if [ "$fsdir" = "/" ]; then
  286         fsdev="$rootdev"
  287     else
  288         ## Figure out what device holds the filesystem.
  289         fsdev="`get_fsdev $fsdir`"
  290         if [ "$fsdev" = "" ]; then
  291             echo "$fsdir: not found in /proc/mounts, aborting." >&2
  292             exit 1
  293         fi
  294     fi
  295 
  296     ## The root filesystem may show up as the phoney "/dev/root" device
  297     ## in /proc/mounts (ugh).  So if we see that, then substitute the rootdev
  298     ## that $DF gave us earlier.  But $DF may have the same problem (double ugh).
  299     ##
  300     [ ! -e "$fsdev" -a "$fsdev" = "/dev/root" ] && fsdev="$rootdev"
  301 
  302     ## Ensure that fsdev exists and is a block device:
  303     if [ ! -e "$fsdev" ]; then
  304         if [ "$fsdev" != "/dev/root" ]; then
  305             echo "$fsdev: not found" >&2
  306             exit 1
  307         fi
  308         if [ "$rootdev" = "" ]; then
  309             echo "$fsdev: not found" >&2
  310             exit 1
  311         fi
  312         fsdev="$rootdev"
  313     fi
  314     if [ ! -b "$fsdev" ]; then
  315         echo "$fsdev: not a block device" >&2
  316         exit 1
  317     fi
  318 
  319     ## If it is mounted read-only, we must switch to doing an "offline" trim of fsdev:
  320     fsmode="`get_fsmode $fsdir`" || exit 1
  321     [ $verbose -gt 0 ] && echo "fsmode1: fsmode=$fsmode"
  322     [ "$fsmode" = "read-only" ] && method=offline
  323 fi
  324 
  325 ## This is not an "else" clause from the above, because "method" may have changed.
  326 ## For offline TRIM, we need the block device, and it cannot be mounted read-write:
  327 ##
  328 if [ "$method" = "offline" ]; then
  329     ## We might already have fsdev/fsdir from above; if not, we need to find them.
  330     if [ "$fsdev" = "" -o "$fsdir" = "" ]; then
  331         fsdev="$target"
  332         fsdir="`get_fsdir "$fsdev" < /proc/mounts`"
  333         ## More weirdness for /dev/root in /proc/mounts:
  334         if [ "$fsdir" = "" -a "$fsdev" = "$rootdev" ]; then
  335             fsdir="`get_fsdir /dev/root < /proc/mounts`"
  336             if [ "$fsdir" = "" ]; then
  337                 rdev="`get_devpath /`"
  338                 [ "$rdev" != "" ] && fsdir="`get_fsdir "$rdev" < /proc/mounts`"
  339             fi
  340         fi
  341     fi
  342 
  343     ## If the filesystem is truly not-mounted, then fsdir will still be empty here.
  344     ## It could be mounted, though.  Read-only is fine, but read-write means we need
  345     ## to switch gears and do an "online" TRIM instead of an "offline" TRIM.
  346     ##
  347     if [ "$fsdir" != "" ]; then
  348         fsmode="`get_fsmode $fsdir`" || exit 1
  349         [ $verbose -gt 0 ] && echo "fsmode2: fsmode=$fsmode"
  350         if [ "$fsmode" = "read-write" ]; then
  351             method=online
  352             cd "$fsdir" || exit 1
  353         fi
  354     fi
  355 fi
  356 
  357 ## Use $LS to find the major number of a block device:
  358 ##
  359 function get_major(){
  360     $LS -ln "$1" | $GAWK '{print gensub(",","",1,$5)}'
  361 }
  362 
  363 ## At this point, we have finalized our selection of online vs. offline,
  364 ## and we definitely know the fsdev, as well as the fsdir (fsdir="" if not-mounted).
  365 ##
  366 ## Now guess at the underlying rawdev name, which could be exactly the same as fsdev.
  367 ## Then determine whether or not rawdev claims support for TRIM commands.
  368 ## Note that some devices lie about support, and later reject the TRIM commands.
  369 ##
  370 rawdev=`echo $fsdev | $GAWK '{print gensub("[0-9]*$","","g")}'`
  371 rawdev="`get_realpath "$rawdev"`"
  372 if [ ! -e "$rawdev" ]; then
  373     rawdev=""
  374 elif [ ! -b "$rawdev" ]; then
  375     rawdev=""
  376 elif [ "`get_major $fsdev`" -ne "`get_major $rawdev`" ]; then  ## sanity check
  377     rawdev=""
  378 else
  379     ## "SCSI" drives only; no LVM confusion for now:
  380     maj="$(get_major $fsdev)"
  381     maj_ok=0
  382     for scsi_major in 8 65 66 67 68 69 70 71 ; do
  383         [ "$maj" = "$scsi_major" ] && maj_ok=1
  384     done
  385     if [ $maj_ok -eq 0 ]; then
  386         echo "$rawdev: does not appear to be a SCSI/SATA SSD, aborting." >&2
  387         exit 1
  388     fi
  389     if ! $HDPARM -I $rawdev | $GREP -i '[   ][*][   ]*Data Set Management TRIM supported' &>/dev/null ; then
  390         if [ "$commit" = "yes" ]; then
  391             echo "$rawdev: DSM/TRIM command not supported, aborting." >&2
  392             exit 1
  393         fi
  394         echo "$rawdev: DSM/TRIM command not supported (continuing with dry-run)." >&2
  395     fi
  396 fi
  397 if [ "$rawdev" = "" ]; then
  398     echo "$fsdev: unable to reliably determine the underlying physical device name, aborting" >&2
  399     exit 1
  400 fi
  401 
  402 ## We also need to know the offset of fsdev from the beginning of rawdev,
  403 ## because TRIM requires absolute sector numbers within rawdev:
  404 ##
  405 fsoffset=`$HDPARM -g "$fsdev" | $GAWK 'END {print $NF}'`
  406 
  407 ## Next step is to determine what type of filesystem we are dealing with (fstype):
  408 ##
  409 if [ "$fsdir" = "" ]; then
  410     ## Not mounted: use $BLKID to determine the fstype of fsdev:
  411     fstype=`$BLKID -w /dev/null -c /dev/null $fsdev 2>/dev/null | \
  412          $GAWK '/ TYPE=".*"/{sub("^.* TYPE=\"",""); sub("[\" ][\" ]*.*$",""); print}'`
  413     [ $verbose -gt 0 ] && echo "$fsdev: fstype=$fstype"
  414 else
  415     ## Mounted: we could just use $BLKID here, too, but it's safer to use /proc/mounts directly:
  416     fstype="`$GAWK -v p="$fsdir" '{if ($2 == p) r=$3} END{print r}' < /proc/mounts`"
  417     [ $verbose -gt 0 ] && echo "$fsdir: fstype=$fstype"
  418 fi
  419 if [ "$fstype" = "" ]; then
  420     echo "$fsdev: unable to determine filesystem type, aborting." >&2
  421     exit 1
  422 fi
  423 
  424 ## Some helper funcs and vars for use with the xfs filesystem tools:
  425 ##
  426 function xfs_abort(){
  427     echo "$fsdev: unable to determine xfs filesystem ${1-parameters}, aborting." >&2
  428     exit 1
  429 }
  430 function xfs_trimlist(){
  431     $XFS_DB -r -c "freesp -d" "$fsdev"  ## couldn't get this to work inline
  432 }
  433 xfs_agoffsets=""
  434 xfs_blksects=0
  435 
  436 ## We used to allow single-drive btrfs here, but it stopped working in linux-2.6.31,
  437 ## and Chris Mason says "unsafe at any speed" really.  So it's been dropped now.
  438 ##
  439 if [ "$fstype" = "btrfs" ]; then  ## hdparm --fibmap fails, due to fake 0:xx device nodes
  440     echo "$target: btrfs filesystem type not supported (cannot determine physical devices), aborting." >&2
  441     exit 1
  442 fi
  443 
  444 ## Now figure out whether we can actually do TRIM on this type of filesystem:
  445 ##
  446 if [ "$method" = "online" ]; then
  447     ## Print sensible error messages for some common situations,
  448     ## rather than failing with more confusing messages later on..
  449     ##
  450     if [ "$fstype" = "ext2" -o "$fstype" = "ext3" ]; then  ## No --fallocate support
  451         echo "$target: cannot TRIM $fstype filesystem when mounted read-write, aborting." >&2
  452         exit 1
  453     fi
  454 
  455     ## Figure out if we have enough free space to even attempt TRIM:
  456     ##
  457     freesize=`$DF -P -B 1024 . | $GAWK '{r=$4}END{print r}'`
  458     if [ "$freesize" = "" ]; then
  459         echo "$fsdev: unknown to '$DF'"
  460         exit 1
  461     fi
  462     if [ $freesize -lt 15000 ]; then
  463         echo "$target: filesystem too full for TRIM, aborting." >&2
  464         exit 1
  465     fi
  466 
  467     ## Figure out how much space to --fallocate (later), keeping in mind
  468     ## that this is a live filesystem, and we need to leave some space for
  469     ## other concurrent activities, as well as for filesystem overhead (metadata).
  470     ## So, reserve at least 1% or 7500 KB, whichever is larger:
  471     ##
  472     reserved=$((freesize / 100))
  473     [ $reserved -lt 7500 ] && reserved=7500
  474     [ $verbose -gt 0 ] && echo "freesize = ${freesize} KB, reserved = ${reserved} KB"
  475     tmpsize=$((freesize - reserved))
  476     tmpfile="WIPER_TMPFILE.$$"
  477     get_trimlist="$HDPARM --fibmap $tmpfile"
  478 else
  479     ## We can only do offline TRIM on filesystems that we "know" about here.
  480     ## Currently, this includes the ext2/3/4 family, xfs, and reiserfs.
  481     ## The first step for any of these is to ensure that the filesystem is "clean",
  482     ## and immediately abort if it is not.
  483     ##
  484     get_trimlist=""
  485     if [ "$fstype" = "ext2" -o "$fstype" = "ext3" -o "$fstype" = "ext4" ]; then
  486         DUMPE2FS=`find_prog /sbin/dumpe2fs` || exit 1
  487         fstate="`$DUMPE2FS $fsdev 2>/dev/null | $GAWK '/^[Ff]ilesystem state:/{print $NF}' 2>/dev/null`"
  488         if [ "$fstate" != "clean" ]; then
  489             echo "$target: filesystem not clean, please run \"e2fsck $fsdev\" first, aborting." >&2
  490             exit 1
  491         fi
  492         get_trimlist="$DUMPE2FS $fsdev"
  493     elif [ "$fstype" = "xfs" ]; then
  494         XFS_DB=`find_prog /sbin/xfs_db` || exit 1
  495         XFS_REPAIR=`find_prog /sbin/xfs_repair` || exit 1
  496         if ! $XFS_REPAIR -n "$fsdev" &>/dev/null ; then
  497             echo "$fsdev: filesystem not clean, please run \"xfs_repair $fsdev\" first, aborting." >&2
  498             exit 1
  499         fi
  500 
  501         ## For xfs, life is more complex than with ext2/3/4 above.
  502         ## The $XFS_DB tool does not return absolute block numbers for freespace,
  503         ## but rather gives them as relative to it's allocation groups (ag's).
  504         ## So, we'll need to interogate it for the offset of each ag within the filesystem.
  505         ## The agoffsets are extracted from $XFS_DB as sector offsets within the fsdev.
  506         ##
  507         agcount=`$XFS_DB -r -c "sb" -c "print agcount" "$fsdev" | $GAWK '{print 0 + $NF}'`
  508         [ "$agcount" = "" -o "$agcount" = "0" ] && xfs_abort "agcount"
  509         xfs_agoffsets=
  510         i=0
  511         while [ $i -lt $agcount ]; do
  512             agoffset=`$XFS_DB -r -c "sb" -c "convert agno $i daddr" "$fsdev" \
  513                 | $GAWK '{print 0 + gensub("[( )]","","g",$2)}'`
  514             [ "$agoffset" = "" ] && xfs_abort "agoffset-$i"
  515             [ $i -gt 0 ] && [ $agoffset -le ${xfs_agoffsets##* } ] && xfs_abort "agoffset[$i]"
  516             xfs_agoffsets="$xfs_agoffsets $agoffset"
  517             i=$((i + 1))
  518         done
  519         xfs_agoffsets="${xfs_agoffsets:1}"  ## strip leading space
  520 
  521         ## We also need xfs_blksects for later, because freespace gets listed as block numbers.
  522         ##
  523         blksize=`$XFS_DB -r -c "sb" -c "print blocksize" "$fsdev" | $GAWK '{print 0 + $NF}'`
  524         [ "$blksize" = "" -o "$blksize" = "0" ] && xfs_abort "block size"
  525         xfs_blksects=$((blksize/512))
  526         get_trimlist="xfs_trimlist"
  527     elif [ "$fstype" = "reiserfs" ]; then
  528         DEBUGREISERFS=`find_prog /sbin/debugreiserfs` || exit 1
  529         ( $DEBUGREISERFS $fsdev | $GREP '^Filesystem state:.consistent' ) &> /dev/null
  530         if [ $? -ne 0 ]; then
  531             echo "Please run fsck.reiserfs first, aborting." >&2
  532             exit 1
  533         fi
  534         get_trimlist="$DEBUGREISERFS -m $fsdev"
  535     elif [ "$fstype" = "hfsplus" ]; then
  536         OD=`find_prog /usr/bin/od` || exit 1
  537         TR=`find_prog /usr/bin/tr` || exit 1
  538         #check sleuthkit
  539         FSSTAT=`find_prog /usr/local/bin/fsstat` 
  540         if [ "$?" = "1" ]; then
  541             echo "fsstat and icat from package sleuthkit >= 3.1.1 is required for hfsplus."
  542             exit 1
  543         fi
  544         ICAT=`find_prog /usr/local/bin/icat` 
  545         if [ "`$ICAT -f list 2>/dev/stdout|$GREP HFS+`" = "" ]; then
  546                         echo "Wrong icat, version from package sleuthkit >= 3.1.1 is required for hfsplus."
  547                         exit 1
  548                 fi
  549         #check for unmounted properly
  550         if [ "`$FSSTAT -f hfs $fsdev | $GREP "Volume Unmounted Properly"`" = ""  ]; then
  551             echo "Hfsplus volume unmounted improperly!"
  552             exit 1
  553         fi
  554         #check $AllocationFile inode
  555         FFIND=`find_prog /usr/local/bin/ffind`
  556         if [ "`$FFIND -f hfs $fsdev 6`" != "/\$AllocationFile" ]; then
  557             echo "Hfsplus bitmap \$AllocationFile is not inode 6!"
  558             exit 1
  559         fi
  560         #get offset for hfsplus with a wrapper
  561         hfsoffset=`$FSSTAT -f hfs $fsdev | $GREP "File system is embedded in an HFS wrapper at offset "|$TR -d "\t"`
  562         if [ -n "$hfsoffset" ]; then
  563             hfsoffset=${hfsoffset:52}
  564             ((fsoffset=fsoffset+hfsoffset))
  565             echo "File system is embedded in an HFS wrapper at offset $hfsoffset"
  566         fi
  567         blksize=`$FSSTAT -f hfs $fsdev | $GREP "Allocation Block Size: "|$TR -d "\t"`
  568         blksize=${blksize:23}
  569         blksects=$((blksize / 512))
  570         #get count of used bytes in $AllocationFile
  571         blkcount=`$FSSTAT -f hfs $fsdev | $GREP "Block Range: 0 - "`
  572         blkcount=${blkcount:17}
  573         bytecount=$((blkcount/blksects))
  574         
  575         method="bitmap_offline"
  576         get_trimlist="echo $blksects hfsplus `$ICAT -f hfs $fsdev 6 | $OD -N $bytecount -An -vtu1 -j0 -w1`"
  577     elif [ "$fstype" = "ntfs" ]; then
  578         NTFSINFO=`find_prog /usr/bin/ntfsinfo` || exit 1
  579         NTFSCAT=`find_prog /usr/bin/ntfscat` || exit 1
  580         NTFSPROBE=`find_prog /usr/bin/ntfs-3g.probe` || exit 1
  581         OD=`find_prog /usr/bin/od` || exit 1
  582         TR=`find_prog /usr/bin/tr` || exit 1
  583         #check for unmounted properly
  584         $NTFSPROBE -w $fsdev 2>/dev/null
  585         if [ $? -ne 0 ]; then
  586             echo "$fsdev contains an unclean file system!"
  587             exit 1
  588         fi
  589         #check for volume version
  590         if [ "`$NTFSINFO -m -f $fsdev | $GREP "Volume Version: 3.1"`" = "" ]; then
  591             echo "NTFS volume version must be 3.1!"
  592             exit 1
  593         fi
  594         blksize=`$NTFSINFO -m -f $fsdev | $GREP "Cluster Size: " | $TR -d "\t"`
  595         blksize=${blksize:14}
  596         blksects=$((blksize / 512))
  597         #get count of used bytes in $Bitmap
  598         blkcount=`$NTFSINFO -m -f $fsdev | $GREP "Volume Size in Clusters: " | $TR -d "\t"`
  599         blkcount=${blkcount:25}
  600         bytecount=$((blkcount/blksects))
  601 
  602         method="bitmap_offline"
  603         get_trimlist="echo $blksects ntfs `$NTFSCAT $fsdev \\\$Bitmap | $OD -N $bytecount -An -vtu1 -j0 -w1`"
  604     fi
  605     if [ "$get_trimlist" = "" ]; then
  606         echo "$target: offline TRIM not supported for $fstype filesystems, aborting." >&2
  607         exit 1
  608     fi
  609 fi
  610 
  611 ## All ready.  Now let the user know exactly what we intend to do:
  612 ##
  613 mountstatus="$fstype non-mounted"
  614 [ "$fsdir" = "" ] || mountstatus="$fstype mounted $fsmode at $fsdir"
  615 echo "Preparing for $method TRIM of free space on $fsdev ($mountstatus)."
  616 
  617 ## If they specified "--commit" on the command line, then prompt for confirmation first:
  618 ##
  619 if [ "$commit" = "yes" ]; then
  620     if [ "$destroy_me" = "" ]; then
  621         echo >/dev/tty
  622         echo -n "This operation could silently destroy your data.  Are you sure (y/N)? " >/dev/tty
  623         read yn < /dev/tty
  624         if [ "$yn" != "y" -a "$yn" != "Y" ]; then
  625             echo "Aborting." >&2
  626             exit 1
  627         fi
  628     fi
  629     TRIM="$HDPARM --please-destroy-my-drive --trim-sector-ranges-stdin $rawdev"
  630 else
  631     echo "This will be a DRY-RUN only.  Use --commit to do it for real."
  632     TRIM="$GAWK {}"
  633 fi
  634 
  635 ## Useful in a few places later on:
  636 ##
  637 function sync_disks(){
  638     echo -n "Syncing disks.. "
  639     sync
  640     echo
  641 }
  642 
  643 ## Clean up tmpfile (if any) and exit:
  644 ##
  645 function do_cleanup(){
  646     if [ "$method" = "online" ]; then
  647         if [ -e $tmpfile ]; then
  648             echo "Removing temporary file.."
  649             $RM -f $tmpfile
  650         fi
  651         sync_disks
  652     fi
  653     [ $1 -eq 0 ] && echo "Done."
  654     [ $1 -eq 0 ] || echo "Aborted." >&2
  655     exit $1
  656 }
  657 
  658 ## Prepare signal handling, in case we get interrupted while $tmpfile exists:
  659 ##
  660 function do_abort(){
  661     echo
  662     do_cleanup 1
  663 }
  664 trap do_abort SIGTERM
  665 trap do_abort SIGQUIT
  666 trap do_abort SIGINT
  667 trap do_abort SIGHUP
  668 trap do_abort SIGPIPE
  669 
  670 ## For online TRIM, go ahead and create the huge temporary file.
  671 ## This is where we finally discover whether the filesystem actually
  672 ## supports --fallocate or not.  Some folks will be disappointed here.
  673 ##
  674 ## Note that --fallocate does not actually write any file data to fsdev,
  675 ## but rather simply allocates formerly-free space to the tmpfile.
  676 ##
  677 if [ "$method" = "online" ]; then
  678     if [ -e "$tmpfile" ]; then
  679         if ! $RM -f "$tmpfile" ; then
  680             echo "$tmpfile: already exists and could not be removed, aborting." >&2
  681             exit 1
  682         fi
  683     fi
  684     echo -n "Allocating temporary file (${tmpsize} KB).. "
  685     if ! $HDPARM --fallocate "${tmpsize}" $tmpfile ; then
  686         echo "$target: this kernel may not support 'fallocate' on a $fstype filesystem, aborting." >&2
  687         exit 1
  688     fi
  689     echo
  690 fi
  691 
  692 ## Finally, we are now ready to TRIM something!
  693 ##
  694 ## Feed the "get_trimlist" output into a gawk program which will
  695 ## extract the trimable lba-ranges (extents) and batch them together
  696 ## into huge --trim-sector-ranges calls.
  697 ##
  698 ## We are limited by at least one thing when doing this:
  699 ##   1. Some device drivers may not support more than 255 sectors
  700 ##      full of lba:count range data per TRIM command.
  701 ## The latest hdparm versions now take care of that automatically.
  702 ##
  703 sync_disks
  704 if [ "$commit" = "yes" ]; then
  705     echo "Beginning TRIM operations.."
  706 else
  707     echo "Simulating TRIM operations.."
  708 fi
  709 [ $verbose -gt 0 ] && echo "get_trimlist=$get_trimlist"
  710 
  711 ## Begin gawk program
  712 GAWKPROG='
  713     BEGIN {
  714         if (xfs_agoffsets != "") {
  715             method = "xfs_offline"
  716             agcount = split(xfs_agoffsets,agoffset," ");
  717         }
  718     }
  719     function append_range (lba,count  ,this_count){
  720         nsectors += count;
  721         while (count > 0) {
  722             this_count  = (count > 65535) ? 65535 : count
  723             printf "%u:%u ", lba, this_count
  724             if (verbose > 1)
  725                 printf "%u:%u ", lba, this_count > "/dev/stderr"
  726             lba        += this_count
  727             count      -= this_count
  728             nranges++;
  729         }
  730     }
  731     (method == "online") {  ## Output from "hdparm --fibmap", in absolute sectors:
  732         if (NF == 4 && $2 ~ "^[1-9][0-9]*$")
  733             append_range($2,$4)
  734         next
  735     }
  736     (method == "xfs_offline") { ## Output from xfs_db:
  737         if (NF == 3 && gensub("[0-9 ]","","g",$0) == "" && $1 < agcount) {
  738             lba   = agoffset[1 + $1] + ($2 * xfs_blksects) + fsoffset
  739             count = $3 * xfs_blksects
  740             append_range(lba,count)
  741         }
  742         next
  743     }
  744     (method == "bitmap_offline") {
  745         n = split($0,f)
  746         blksects = f[1]
  747         fstype = f[2]
  748         bitmap_start = 3
  749         range_first = -1 #clusters
  750         range_last = -1
  751         for (i = bitmap_start; i <= n-1; i++) {
  752             if (f[i] == 0) {
  753                 if (range_first == -1)
  754                     range_first = (i-bitmap_start) * 8 
  755                 range_last = (i-bitmap_start) * 8 + 7
  756             } else if (f[i] == 255 && range_first > -1){
  757                 #printf range_first "-" range_last "\n" > "/dev/stderr"
  758                 lba = (range_first * blksects) + fsoffset
  759                 count = (range_last - range_first + 1) * blksects
  760                 append_range(lba,count)
  761                 range_first = -1
  762                 range_last = -1
  763             } else {
  764                 for (b = 0; b < 8; b++) {
  765                     if (fstype == "ntfs")
  766                         bit = and(f[i], lshift(1, b)) ? 1 : 0
  767                     else #hfsplus
  768                         bit = and(f[i], lshift(1, 7-b)) ? 1 : 0
  769                     if (bit == 0) {
  770                         if (range_first == -1) {
  771                             range_first = (i-bitmap_start) * 8 + b
  772                             range_last = (i-bitmap_start) * 8 + b
  773                         } else
  774                             range_last += 1
  775                     } else if (range_first > -1) {
  776                         #printf range_first "-" range_last " " > "/dev/stderr"
  777                         lba = (range_first * blksects) + fsoffset
  778                         count = (range_last - range_first + 1) * blksects
  779                         if (fstype == "ntfs")
  780                             append_range(lba,count)
  781                         else if (count > (2 * blksects)) #faster for hfsplus
  782                             append_range(lba,count)
  783                         range_first = -1
  784                         range_last = -1
  785                     }
  786                 }
  787             }
  788         }
  789         if (range_first > -1){
  790             #printf range_first "-" range_last " " > "/dev/stderr"
  791             lba = (range_first * blksects) + fsoffset
  792             count = (range_last - range_first + 1) * blksects
  793             append_range(lba,count)
  794         }
  795         next
  796     }
  797     /^Block size: *[1-9]/ { ## First stage output from dumpe2fs:
  798         blksects = $NF / 512
  799         next
  800     }
  801     /^Group [0-9][0-9]*:/ { ## Second stage output from dumpe2fs:
  802         in_groups = 1
  803         next
  804     }
  805     /^ *Free blocks: [0-9]/ { ## Bulk of output from dumpe2fs:
  806         if (blksects && in_groups) {
  807             n = split(substr($0,16),f,",*  *")
  808             for (i = 1; i <= n; ++i) {
  809                 if (f[i] ~ "^[1-9][0-9]*-[1-9][0-9]*$") {
  810                     split(f[i],b,"-")
  811                     lba   = (b[1] * blksects) + fsoffset
  812                     count = (b[2] - b[1] + 1) * blksects
  813                     append_range(lba,count)
  814                 } else if (f[i] ~ "^[1-9][0-9]*$") {
  815                     lba   = (f[i] * blksects) + fsoffset
  816                     count = blksects
  817                     append_range(lba,count)
  818                 }
  819             }
  820             next
  821         }
  822     }
  823     /^Reiserfs super block/ {
  824         method = "reiserfs"
  825         next
  826     }
  827     /^Blocksize: / {
  828         if (method == "reiserfs") {
  829             blksects = $2 / 512
  830             next
  831         }
  832     }
  833     /^#[0-9][0-9]*:.*Free[(]/ { ## debugreiserfs
  834         if (method == "reiserfs" && blksects > 0) {
  835             n = split($0,f)
  836             for (i = 4; i <= n; ++i) {
  837                 if (f[i] ~ "^ *Free[(]") {
  838                     if (2 == split(gensub("[^-0-9]","","g",f[i]),b,"-")) {
  839                         lba = (b[1] * blksects) + fsoffset
  840                         count = (b[2] - b[1] + 1) * blksects
  841                         append_range(lba, count)
  842                     }
  843                 }
  844             }
  845             next
  846         }
  847     }
  848     END {
  849         if (err == 0 && commit != "yes")
  850             printf "(dry-run) trimming %u sectors from %u ranges\n", nsectors, nranges > "/dev/stderr"
  851         exit err
  852     }'
  853 ## End gawk program
  854 
  855 $get_trimlist 2>/dev/null | $GAWK       \
  856     -v commit="$commit"         \
  857     -v method="$method"         \
  858     -v rawdev="$rawdev"         \
  859     -v fsoffset="$fsoffset"         \
  860     -v verbose="$verbose"           \
  861     -v xfs_blksects="$xfs_blksects"     \
  862     -v xfs_agoffsets="$xfs_agoffsets"   \
  863     "$GAWKPROG" | $TRIM
  864 
  865 do_cleanup $?