• Home
  • History
  • Annotate
  • Raw
  • Download
  • only in /netgear-R7000-V1.0.7.12_1.2.5/ap/gpl/hdparm-9.43/wiper/

Lines Matching defs:*

0 #!/bin/bash
3 # SATA SSD free-space TRIM utility, by Mark Lord <mlord@pobox.com>
5 VERSION=3.5
7 # Copyright (C) 2009-2010 Mark Lord. All rights reserved.
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.
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.
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.
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.
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
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.
32 export LANG=C
34 ## The usual terse usage information:
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
45 ## Parameter parsing for the main script.
46 ## Yeah, we could use getopt here instead, but what fun would that be?
49 echo
50 echo "${0##*/}: Linux SATA SSD TRIM utility, version $VERSION, by Mark Lord."
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
78 ## Find a required program, or else give a nicer error message than we'd otherwise see:
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"
95 ## Ensure we have most of the necessary utilities available before trying to proceed:
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
110 [ $verbose -gt 1 ] && HDPARM="$HDPARM --verbose"
112 ## I suppose this will confuse the three SELinux users out there:
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
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.
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
128 ## Convert relative path "$1" into an absolute pathname, resolving all symlinks:
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
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}'
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
188 ## Take a first cut at online/offline determination, based on the target:
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
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.
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"
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.
224 function get_fsdev(){ ## from fsdir
225 get_realpath "`$GAWK -v p="$1" '{if ($2 == p) r=$1} END{print r}' < /proc/mounts`"
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.
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
244 ## Try and determine the device name associated with the root filesystem.
245 ## This is nearly impossible to do in any perfect fashion.
247 ## Redhat/Fedora no longer have an rdev command. Silly them.
248 ## So we now implement it internally, below.
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.
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.
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"
272 rootdev=$($FIND /dev/ -type b 2>/dev/null | match_rootdev $($STAT -c "0x%D" '/'))
273 [ $verbose -gt 0 ] && echo "rootdev=$rootdev"
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:
280 if [ "$method" = "online" ]; then
281 ## Ensure fsdir exists and is accessible to us:
282 fsdir="$target"
283 cd "$fsdir" || exit 1
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
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).
300 [ ! -e "$fsdev" -a "$fsdev" = "/dev/root" ] && fsdev="$rootdev"
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
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
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:
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
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.
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
357 ## Use $LS to find the major number of a block device:
359 function get_major(){
360 $LS -ln "$1" | $GAWK '{print gensub(",","",1,$5)}'
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).
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.
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
402 ## We also need to know the offset of fsdev from the beginning of rawdev,
403 ## because TRIM requires absolute sector numbers within rawdev:
405 fsoffset=`$HDPARM -g "$fsdev" | $GAWK 'END {print $NF}'`
407 ## Next step is to determine what type of filesystem we are dealing with (fstype):
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
424 ## Some helper funcs and vars for use with the xfs filesystem tools:
426 function xfs_abort(){
427 echo "$fsdev: unable to determine xfs filesystem ${1-parameters}, aborting." >&2
428 exit 1
430 function xfs_trimlist(){
431 $XFS_DB -r -c "freesp -d" "$fsdev" ## couldn't get this to work inline
433 xfs_agoffsets=""
434 xfs_blksects=0
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.
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
444 ## Now figure out whether we can actually do TRIM on this type of filesystem:
446 if [ "$method" = "online" ]; then
447 ## Print sensible error messages for some common situations,
448 ## rather than failing with more confusing messages later on..
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
455 ## Figure out if we have enough free space to even attempt TRIM:
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
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:
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.
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
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.
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
521 ## We also need xfs_blksects for later, because freespace gets listed as block numbers.
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))
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))
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
611 ## All ready. Now let the user know exactly what we intend to do:
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)."
617 ## If they specified "--commit" on the command line, then prompt for confirmation first:
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
635 ## Useful in a few places later on:
637 function sync_disks(){
638 echo -n "Syncing disks.. "
639 sync
640 echo
643 ## Clean up tmpfile (if any) and exit:
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
658 ## Prepare signal handling, in case we get interrupted while $tmpfile exists:
660 function do_abort(){
661 echo
662 do_cleanup 1
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
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.
674 ## Note that --fallocate does not actually write any file data to fsdev,
675 ## but rather simply allocates formerly-free space to the tmpfile.
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 "Creating 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
692 ## Finally, we are now ready to TRIM something!
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.
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.
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"
711 ## Begin gawk program
712 GAWKPROG='
713 BEGIN {
714 if (xfs_agoffsets != "") {
715 method = "xfs_offline"
716 agcount = split(xfs_agoffsets,agoffset," ");
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++;
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
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)
742 next
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
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)
795 next
797 /^Block size: *[1-9]/ { ## First stage output from dumpe2fs:
798 blksects = $NF / 512
799 next
801 /^Group [0-9][0-9]*:/ { ## Second stage output from dumpe2fs:
802 in_groups = 1
803 next
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)
820 next
823 /^Reiserfs super block/ {
824 method = "reiserfs"
825 next
827 /^Blocksize: / {
828 if (method == "reiserfs") {
829 blksects = $2 / 512
830 next
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)
845 next
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
853 ## End gawk program
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
865 do_cleanup $?