~ubuntu-branches/ubuntu/saucy/hdparm/saucy

« back to all changes in this revision

Viewing changes to wiper/contrib/raid1ext4trim.sh-1.5

  • Committer: Steve Langasek
  • Date: 2012-10-25 03:08:04 UTC
  • mfrom: (1.1.16 upstream)
  • Revision ID: steve.langasek@canonical.com-20121025030804-l50mshoma6jtp6kg
Merging shared upstream rev into target branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/bin/bash
 
2
#
 
3
# SSD TRIM utility for live RAID1 mirrored ext4 drives.
 
4
#
 
5
# By Chris Caputo.  Adapted from wiper.sh (ver 2.6) by Mark Lord.
 
6
 
 
7
VERSION=1.5
 
8
 
 
9
# Copyright (C) 2010-2012 Chris Caputo.  All rights reserved.
 
10
#
 
11
# This program is free software; you can redistribute it and/or
 
12
# modify it under the terms of the GNU General Public License Version 2,
 
13
# as published by the Free Software Foundation.
 
14
#
 
15
# This program is distributed in the hope that it would be useful,
 
16
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
18
# GNU General Public License for more details.
 
19
#
 
20
# You should have received a copy of the GNU General Public License
 
21
# along with this program; if not, write to the Free Software Foundation,
 
22
# Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
23
 
 
24
function usage_error(){
 
25
        echo "Usage:"
 
26
        echo " ${0##*/} [--verbose] [--commit] [--reserve=#megs] [--max-ranges=#ranges] <raid_dev> <fsdir>"
 
27
        echo "Examples:"
 
28
        echo " ${0##*/} --verbose --commit --reserve=100 --max-ranges=512 md0 /"
 
29
        echo " ${0##*/} --verbose --verbose md1 /boot"
 
30
        echo 
 
31
        echo "Note: For best results, this script should be run on each ext4-based filesystem present on a RAID1 array."
 
32
        echo
 
33
        exit 1
 
34
}
 
35
 
 
36
echo "${0##*/}: TRIM utility for live RAID1 ext4 SATA SSDs, version $VERSION, by Chris Caputo, based on Mark Lord's wiper.sh."
 
37
echo
 
38
 
 
39
## Parameter parsing for the main script.
 
40
##
 
41
 
 
42
export verbose=0
 
43
commit=""
 
44
reservemegs=0
 
45
max_ranges=0
 
46
argc=$#
 
47
raiddev=""
 
48
fsdir=""
 
49
while [ $argc -gt 0 ]; do
 
50
        if [ "$1" = "--commit" ]; then
 
51
                commit=yes
 
52
        elif [ "$1" = "--verbose" ]; then
 
53
                verbose=$((verbose + 1))
 
54
        elif [[ "$1" =~ --reserve= ]]; then
 
55
                reservemegs=${1##--reserve=}
 
56
        elif [[ "$1" =~ --max-ranges= ]]; then
 
57
                max_ranges=${1##--max-ranges=}
 
58
        elif [ "$1" = "" ]; then
 
59
                usage_error
 
60
        else
 
61
                if [ "$raiddev" = "" ]; then
 
62
                        raiddev=${1##*/}
 
63
                elif [ "$fsdir" = "" ]; then
 
64
                        fsdir=$1
 
65
                else
 
66
                        echo "$1: too many arguments, aborting."
 
67
                        exit 1
 
68
                fi
 
69
        fi
 
70
        argc=$((argc - 1))
 
71
        shift
 
72
done
 
73
[ "$raiddev" = "" ] && usage_error
 
74
[ "$fsdir" = "" ] && usage_error
 
75
 
 
76
## Check --reserve number.
 
77
##
 
78
 
 
79
isdigit ()    # Tests whether *entire string* is numerical.
 
80
{             # In other words, tests for integer variable.
 
81
        [ $# -eq 1 ] || return 1
 
82
 
 
83
        case $1 in
 
84
                *[!0-9]*|"") return 1;;
 
85
                *) return 0;;
 
86
        esac
 
87
}
 
88
 
 
89
if ! isdigit "$reservemegs" ; then
 
90
        echo "'$reservemegs' is not numerical"
 
91
        exit 1
 
92
fi
 
93
if ! isdigit "$max_ranges" ; then
 
94
        echo "'$max_ranges' is not numerical"
 
95
        exit 1
 
96
fi
 
97
 
 
98
if [ $reservemegs -eq 0 ]; then
 
99
        echo "Reserve defaulting to 10 megabytes."
 
100
        reservemegs=10
 
101
fi
 
102
reservekilos=$((reservemegs * 1024))
 
103
 
 
104
## Find a required program, or else give a nicer error message than we'd otherwise see:
 
105
##
 
106
function find_prog(){
 
107
        prog="$1"
 
108
        if [ ! -x "$prog" ]; then
 
109
                prog="${prog##*/}"
 
110
                p=`type -f -P "$prog" 2>/dev/null`
 
111
                if [ "$p" = "" ]; then
 
112
                        echo "$1: needed but not found, aborting."
 
113
                        exit 1
 
114
                fi
 
115
                prog="$p"
 
116
                [ $verbose -gt 0 ] && echo "  --> using $prog instead of $1"
 
117
        fi
 
118
        echo "$prog"
 
119
}
 
120
 
 
121
## Ensure we have most of the necessary utilities available before trying to proceed:
 
122
##
 
123
hash -r  ## Refresh bash's cached PATH entries
 
124
HDPARM=`find_prog /sbin/hdparm` || exit 1
 
125
GAWK=`find_prog /usr/bin/gawk`  || exit 1
 
126
GREP=`find_prog /bin/grep`      || exit 1
 
127
ID=`find_prog /usr/bin/id`      || exit 1
 
128
LS=`find_prog /bin/ls`          || exit 1
 
129
DF=`find_prog /bin/df`          || exit 1
 
130
RM=`find_prog /bin/rm`          || exit 1
 
131
 
 
132
[ $verbose -gt 1 ] && HDPARM="$HDPARM --verbose"
 
133
 
 
134
## I suppose this will confuse the three SELinux users out there:
 
135
##
 
136
if [ `$ID -u` -ne 0 ]; then
 
137
        echo "Only the super-user can use this (try \"sudo $0\" instead), aborting."
 
138
        exit 1
 
139
fi
 
140
 
 
141
## We need a very modern hdparm, for its --fallocate and --trim-sector-ranges-stdin flags:
 
142
## Version 9.25 added automatic determination of safe max-size of TRIM commands.
 
143
##
 
144
HDPVER=`$HDPARM -V | $GAWK '{gsub("[^0-9.]","",$2); if ($2 > 0) print ($2 * 100); else print 0; exit(0)}'`
 
145
if [ $HDPVER -lt 925 ]; then
 
146
        echo "$HDPARM: version >= 9.25 is required, aborting."
 
147
        exit 1
 
148
fi
 
149
 
 
150
## Check that this is a RAID1 device.
 
151
##
 
152
if ! $GREP raid1 /sys/block/$raiddev/md/level 1>/dev/null ; then
 
153
        echo "$raiddev is not a RAID1 array."
 
154
        exit 1
 
155
fi
 
156
 
 
157
## Get list of slave devices in the RAID1 mirror.
 
158
##
 
159
slaves=(`$LS /sys/block/$raiddev/slaves`)
 
160
#slaves=(sda sdb sdc)
 
161
#slaves=(md0)
 
162
 
 
163
## Check for DEVTYPE disk and TRIM support on each slave.
 
164
##
 
165
index=0
 
166
for slave in "${slaves[@]}"
 
167
do
 
168
        # Check that slave is of DEVTYPE disk.
 
169
        if ! $GREP "DEVTYPE=disk" /sys/block/$slave/uevent 1>/dev/null ; then
 
170
                echo "$slave is not a whole disk. This program only works with full-disk RAID1, not RAID1 partitions."
 
171
                exit 1
 
172
        fi
 
173
 
 
174
        # Check that slave has TRIM support.  Exclude if not.
 
175
        if ! $HDPARM -I /dev/$slave | $GREP -i '[       ][*][   ]*Data Set Management TRIM supported' &>/dev/null ; then
 
176
                echo "$slave doesn't appear to support TRIM, per $HDPARM. Excluding."
 
177
                unset slaves[index]
 
178
        fi
 
179
                
 
180
        let "index = $index + 1"
 
181
done
 
182
if [ "${slaves[0]}" = "" ]; then
 
183
        echo "No constituent of $raiddev array supports TRIM.  Aborting."
 
184
        exit 1
 
185
fi
 
186
 
 
187
## Check that fsdir is on an ext4 volume.
 
188
##
 
189
lines=`$DF --type=ext4 $fsdir 2>/dev/null | $GREP -v ^Filesystem | wc -l`
 
190
if [ $lines -ne 1 ]; then
 
191
        echo "'$fsdir' does not appear to be on an ext4 filesystem.  Aborting."
 
192
        exit 1
 
193
fi
 
194
 
 
195
## Check that fsdir is a directory.
 
196
##
 
197
if [ ! -d $fsdir ]; then
 
198
        echo "'$fsdir' is not a directory.  Aborting."
 
199
        exit 1
 
200
fi
 
201
 
 
202
## Check free space & calculate tmpfile size.
 
203
##
 
204
freesize=`$DF -P -B 1024 $fsdir | $GAWK '{r=$4}END{print r}'`
 
205
if [ "$freesize" = "" ]; then
 
206
        echo "'$fsdir' is unknown to '$DF'.  Aborting."
 
207
        exit 1
 
208
fi
 
209
if [ $freesize -lt $reservekilos ]; then
 
210
        echo "'$fsdir' available space of $freesize KB is less than the $reservekilos KB to be reserved for the TRIM operation.  Aborting." >&2
 
211
        exit 1
 
212
fi
 
213
tmpsize=$((freesize - reservekilos)) 
 
214
tmpfile="$fsdir/${0##*/}_TMPFILE.$$"
 
215
 
 
216
## Clean up tmpfile (if any) and exit:
 
217
##
 
218
function do_cleanup(){
 
219
        if [ -e $tmpfile ]; then
 
220
                echo "Removing temporary file '$tmpfile'..."
 
221
                $RM -f $tmpfile
 
222
                if [ -e $tmpfile ]; then
 
223
                        echo "Failed to remove '$tmpfile'!!!"
 
224
                fi
 
225
        fi
 
226
        [ $1 -eq 0 ] && echo "Done."
 
227
        [ $1 -eq 0 ] || echo "Aborted." >&2
 
228
        exit $1
 
229
}
 
230
 
 
231
## Prepare signal handling, in case we get interrupted while $tmpfile exists:
 
232
##
 
233
function do_abort(){
 
234
        echo
 
235
        do_cleanup 1
 
236
}
 
237
trap do_abort SIGTERM
 
238
trap do_abort SIGQUIT
 
239
trap do_abort SIGINT
 
240
trap do_abort SIGHUP
 
241
trap do_abort SIGPIPE
 
242
 
 
243
## Do the fallocate.
 
244
## This is where we finally discover whether the filesystem actually
 
245
## supports --fallocate or not.  Some folks will be disappointed here.
 
246
##
 
247
## Note that --fallocate does not actually write any file data to fsdev,
 
248
## but rather simply allocates formerly-free space to the tmpfile.
 
249
##
 
250
echo -n "Creating temporary file (${tmpsize} KB '$tmpfile') ... "
 
251
if ! $HDPARM --fallocate "${tmpsize}" $tmpfile ; then
 
252
        echo "This kernel may not support 'fallocate'.  Aborting."
 
253
        exit 1
 
254
fi
 
255
echo
 
256
 
 
257
## Verify that slaves and RAID1 mirror have same base LBA.  First add a test
 
258
## string to the tmpfile.  "date" is used since it is ever changing.
 
259
##
 
260
TESTSTR=`date`
 
261
echo "$TESTSTR" >> $tmpfile
 
262
sync    # this is critical
 
263
SECTOR_BYTES=`$HDPARM --fibmap $tmpfile | \
 
264
                $GREP "byte sectors"    | \
 
265
                $GAWK '{print $9}'`
 
266
LAST_EXTENT_SECTOR_COUNT=`$HDPARM --fibmap $tmpfile | \
 
267
                                tail -1              | \
 
268
                                $GAWK '{print $4}'`
 
269
LAST_EXTENT_LBA=`$HDPARM --fibmap $tmpfile | tail -1 | $GAWK '{print $2}'`
 
270
 
 
271
## Verify the test string is in the extent read.
 
272
if ! dd iflag=direct status=noxfer bs=$SECTOR_BYTES count=$LAST_EXTENT_SECTOR_COUNT skip=$LAST_EXTENT_LBA if=/dev/$raiddev 2>/dev/null | $GREP "$TESTSTR" &>/dev/null ; then
 
273
        echo "Test string was not found in last extent of tmpfile, as it should have been.  Aborting."
 
274
        do_cleanup 1
 
275
fi
 
276
 
 
277
## Now compare the mirror and the slaves to make sure they have the same data at the same LBA.
 
278
##
 
279
refchksum=`dd iflag=direct status=noxfer bs=$SECTOR_BYTES count=$LAST_EXTENT_SECTOR_COUNT skip=$LAST_EXTENT_LBA if=/dev/$raiddev 2>/dev/null | sha1sum`
 
280
index=0
 
281
for slave in "${slaves[@]}"
 
282
do
 
283
        chksum=`dd iflag=direct status=noxfer bs=$SECTOR_BYTES count=$LAST_EXTENT_SECTOR_COUNT skip=$LAST_EXTENT_LBA if=/dev/$slave 2>/dev/null | sha1sum`
 
284
 
 
285
        if [ "$chksum" != "$refchksum" ]; then
 
286
                echo "Direct I/O of last extent of tmpfile on $slave doesn't match that of $raiddev.  Excluding."
 
287
                unset slaves[index]
 
288
        fi
 
289
                
 
290
        let "index = $index + 1"
 
291
done
 
292
if [ "${slaves[0]}" = "" ]; then
 
293
        echo "No constituent of $raiddev array has a matching checksum.  Aborting."
 
294
        do_cleanup 1
 
295
fi
 
296
 
 
297
echo "TRIMable constituents of $raiddev: ${slaves[@]}"
 
298
 
 
299
## If they specified "--commit" on the command line, then prompt for confirmation first:
 
300
##
 
301
if [ "$commit" = "yes" ]; then
 
302
        echo "Beginning TRIM operations..."
 
303
else
 
304
        echo "This will be a DRY-RUN only.  Use --commit to do it for real."
 
305
        echo "Simulating TRIM operations..."
 
306
fi
 
307
get_trimlist="$HDPARM --fibmap $tmpfile"
 
308
[ $verbose -gt 0 ] && echo "get_trimlist=$get_trimlist"
 
309
 
 
310
 
 
311
## Begin gawk program
 
312
GAWKPROG='
 
313
        function append_range (lba,count  ,this_count){
 
314
                nsectors += count;
 
315
                while (count > 0) {
 
316
                        this_count  = (count > 65535) ? 65535 : count
 
317
                        printf "%u:%u \n", lba, this_count
 
318
                        if (verbose > 1)
 
319
                                printf "%u:%u ", lba, this_count > "/dev/stderr"
 
320
                        lba        += this_count
 
321
                        count      -= this_count
 
322
                        nranges++;
 
323
                }
 
324
        }
 
325
        {  ## Output from "hdparm --fibmap", in absolute sectors:
 
326
                if (NF == 4 && $2 ~ "^[1-9][0-9]*$")
 
327
                append_range($2,$4)
 
328
                next
 
329
        }
 
330
        END {
 
331
                if (verbose > 1)
 
332
                        printf "\n" > "/dev/stderr"
 
333
                if (err == 0 && commit != "yes")
 
334
                        printf "(dry-run) trimming %u sectors from %u ranges\n", nsectors, nranges > "/dev/stderr"
 
335
                exit err
 
336
        }'
 
337
## End gawk program
 
338
 
 
339
## Run TRIM on each slave.  Batch as requested.
 
340
sync
 
341
index=0
 
342
for slave in "${slaves[@]}"
 
343
do
 
344
        echo "TRIM beginning on $slave..."
 
345
 
 
346
        if [ "$commit" = "yes" ]; then
 
347
                TRIM="$HDPARM --please-destroy-my-drive \
 
348
                                --trim-sector-ranges-stdin /dev/$slave"
 
349
        else
 
350
                TRIM="$GAWK {}"
 
351
        fi
 
352
 
 
353
        ## Different SSD's have a different maximum number of ranges they'll
 
354
        ## accept in a single TRIM command.
 
355
        if [ $max_ranges -eq 0 ] ; then
 
356
                model=`$HDPARM -I /dev/$slave | $GAWK '/Model Number/ { print $NF }'`
 
357
                case "$model" in
 
358
                        SSDSA[12]*)  slave_max_range=512 ;; # Intel X18-M/X25-M
 
359
                        OCZ-VERTEX2) slave_max_range=64 ;; # OCZ Vertex2
 
360
                        *)           slave_max_range=65535
 
361
                esac
 
362
        else
 
363
                slave_max_range=$max_ranges
 
364
        fi
 
365
        [ $verbose -gt 0 ] && echo "$slave: max-ranges = $slave_max_range"
 
366
 
 
367
        $get_trimlist 2>/dev/null | $GAWK \
 
368
                -v commit="$commit"       \
 
369
                -v verbose="$verbose"     \
 
370
                "$GAWKPROG" |             \
 
371
                if true; then
 
372
                        i=0
 
373
                        while read range; do
 
374
                                (( i++ ))
 
375
                                if (( i <= $slave_max_range )); then
 
376
                                        ranges=$ranges" "$range
 
377
                                else
 
378
                                        [ $verbose -gt 0 ] && echo -e "Trim ranges:" $ranges
 
379
                                        echo $ranges | $TRIM
 
380
                                        ret=$?
 
381
                                        if [ $ret -ne 0 ] ; then
 
382
                                                do_cleanup $ret
 
383
                                        fi
 
384
                                        ranges=$range
 
385
                                        i=1
 
386
                                fi
 
387
                        done
 
388
                        [ $verbose -gt 0 ] && echo -e "Trim ranges:" $ranges
 
389
                        echo $ranges | $TRIM
 
390
                        ret=$?
 
391
                        if [ $ret -ne 0 ] ; then
 
392
                                do_cleanup $ret
 
393
                        fi
 
394
                        ranges=""
 
395
                fi
 
396
                                
 
397
        ret=$?
 
398
        if [ $ret -ne 0 ] ; then
 
399
                echo "TRIM failed on $slave.  Aborting."
 
400
                do_cleanup $ret
 
401
        else
 
402
                echo "TRIM finished successfully on $slave."
 
403
        fi
 
404
done
 
405
 
 
406
do_cleanup 0
 
407