Ivan N. Zlatev wrote: > Hi again, > > It appears that the storage-fixup rule [1] doesn't work. Verbose > output below[2]. Also I saw that you use hdparm -i and sg_inq so > output from those for the HDD too. I badly failed to understand this > brainf*** called bash :-), so may be you can advice what's going > wrong? Thanks in advance. Can you please the attached updated script? The previous version failed match if dmi string contains leading or trailing blanks. -- tejun
#! /bin/bash # # storage-fixup - Tejun Heo <teheo@xxxxxxx> # # Script to issue fix up commands for weird disks. This is primarily # to adjust ATA APM setting. Some laptop BIOSen set this value too # aggressively causing frequent head unloads which can kill the drive # quickly. This script should be called during boot and resume. It # examines rules from /etc/stroage-fixup.conf and executes matching # commands. # # In stroage-fixup.conf, empty lines and lines starting w/ # are # ignored. Each line starts with rule, dmi, ata or act. # # rule RULENAME # Starts a rule. $RULENAME can't contain whitespaces. # # dmi KEY PATTERN # Checks whether DMI value for KEY matches PATTERN. If not, the # rule is skipped. # # ata KEY PATTERN # Checks whether ATA value for KEY matches PATTERN. If not, the # rule is skipped. KEY can be one of model, rev and serial. # # act ACTION # Executes ACTION on matched devices. ACTION can contain $DEV # which will be substituted with device file of matching device. # # sact ACTION # Silent version of "act". Useful when the action to take is # printing out a warning message. # # PATTERN is bash extglob pattern. # # For example, the following (useless) rule disables APM on the first # harddrive of my machine. # # rule p5w64 # dmi baseboard-product-name P5W64 WS Pro # dmi baseboard-manufacturer ASUSTeK Computer INC. # ata model WDC WD5000YS-01M # ata serial *WMANU1217262 # act hdparm -B 255 $DEV # # Release under BSD license. See LICENSE. # declare usage=" Usage: storage-fixup [-h] [-V] [-v] [-b] [-c config_file] [-m max_devs] -h Print this help message and exit -V Print version and exit -v Verbose -d Dry run, don't actually execute action -c Use config_file instead of /etc/storage-fixup.conf -m Maximum number of allowed devices (default=64, 0 for unlimited) " declare dmidecode=${DMIDECODE:-dmidecode} declare hdparm=${HDPARM:-hdparm} declare sg_inq=${HDPARM:-sg_inq} declare sed=${SED:-sed} declare version=0.2 declare conf_file=/etc/storage-fixup.conf declare max_devs=64 declare newline=$'\n' declare dry_run=0 verbose=0 lineno=0 skip=0 rule_name="" reply declare -a storage_devs declare -a match_cache declare -a matches log() { echo "storage-fixup: $@" } warn() { log "$@" 1>&2 } debug() { if [ $verbose -ne 0 ]; then warn "$@" fi } trim() { local str="$1" str="${str##*([[:blank:]])}" str="${str%%*([[:blank:]])}" echo -n "$str" } # # search_match_cache - search match cache # @type: type of match # @key: key of property to search # @idx: index of device to search for # # Searches match cache and returns 0 if found, 1 if @type:@key # properties are cached but matching entry is not found, 2 if # @type:@key properties are not cached yet. On success, the matched # property is returned in $reply. # search_match_cache() { local type="$1" key=$(trim "$2") idx="$3" local i key_found=0 cache len match reply= for ((i=0;i<${#match_cache[@]};i++)); do cache=${match_cache[i]} len=${#cache} match="${cache#$type:$key:?(-)+([0-9]) }" if [ ${#match} -ne $len ]; then key_found=1 fi match="${cache#$type:$key:$idx }" if [ ${#match} -ne $len ]; then reply="$match" return 0 fi done if [ $key_found -eq 1 ]; then return 1 else return 2 fi } # # add_to_match_cache - add entry to match cache # @type: type of match # @key: key of the entry to be added # @idx: index of device to add entry for # @property: property of the entry to be added # # Add $property for $type:$key:$idx. # add_to_match_cache() { local type="$1" key=$(trim "$2") idx="$3" property=$(trim "$4") match_cache+=("$type:$key:$idx $property") debug "C $type:$key:$idx $property" reply="$property" return 0 } # # do_dmi - perform DMI match # @key: DMI key to be passed as --string argument to dmidecode # @pattern: glob pattern to match # # Returns 0 on match, 1 on mismatch, 2 on invalid match (triggers # warning). # do_dmi() { local key="$1" pattern="$2" local ret val if [ -z "$key" -o -z "$pattern" ]; then return 1 fi search_match_cache dmi "$key" 0 ret=$? if [ $ret -eq 2 ]; then val=$($dmidecode --string "$key") if [ "$?" -ne 0 ]; then add_to_match_cache dmi "$key" -1 return 2 fi add_to_match_cache dmi "$key" 0 "$val" elif [ $ret -eq 1 ]; then return 2 fi if [ -z "${reply##$pattern}" ]; then debug "Y $lineno $rule_name dmi $key=$pattern" return 0 fi debug "N $lineno $rule_name dmi $key=$pattern" return 1 } # # do_storage - perform storage match # @type: ata or scsi # @key: ata key - model, rev or serial for both ata and scsi or vendor for scsi # @pattern: glob pattern to match # # Returns 0 on match, 1 on mismatch, 2 on invalid match (triggers # warning). # do_storage() { local type="$1" key="$2" pattern="$3" idx local -a old_matches=("${matches[@]}") if [ -z "$key" -o -z "$pattern" ]; then return 1 fi matches=() for idx in ${old_matches[@]}; do if search_match_cache $type "$key" $idx; then if [ $? -eq 0 -a -z "${reply##$pattern}" ]; then matches+=($idx) fi fi done if [ ${#matches[@]} -eq 0 ]; then debug "N $lineno $rule_name $type:$key=$pattern" return 1 fi debug "Y $lineno $rule_name $type nr_devs=${#matches[@]} $type:$key" return 0 } # # do_act - execute action # @act: action to execute # # Execute @act for each device in $matches. "$DEV" in @act is # substituted with the /dev node of each match. If $dry_run is set, # the action is logged but not actually executed. # # Returns 0. # do_act() { local act="$1" verbose="$2" local id dev for idx in ${matches[@]}; do DEV=${storage_devs[idx]} if [ $dry_run -eq 0 ]; then if [ $verbose -ne 0 ]; then eval log "$rule_name: executing \"$act\"" fi eval "$act" else eval log "$rule_name: dry-run \"$act\"" fi done return 0 } # # Execution starts here # shopt -s extglob while getopts "dvVc:m:h" option; do case $option in d) dry_run=1;; v) verbose=1;; V) echo "$version" exit 0;; c) conf_file=$OPTARG;; m) max_devs=$((OPTARG+0));; *) echo "$usage" 2>&1 exit 1;; esac done # what storage devices do we have? storage_devs=($(ls /dev/[sh]d+([a-z]) /dev/sr+([0-9]) 2> /dev/null)) debug "I ${#storage_devs[@]} storage devices" if [ $max_devs -ne 0 -a ${#storage_devs[@]} -gt $max_devs ]; then warn "nr_storage_devs=${#storage_devs[@]} > limit=$max_devs, skipping" exit 1 fi # populate storage info for ((i=0;i<${#storage_devs};i++)); do output=$($hdparm -i ${storage_devs[i]} 2> /dev/null) if [ $? -eq 0 ]; then MODEL= REV= SERIAL= eval $(echo "$output" | $sed -nr 's/^\s*Model=\s*(.*\S|\s*)\s*,\s*FwRev=\s*(.*\S|\s*)\s*,\s*SerialNo=\s*(.*\S|\s*)\s*$/MODEL=\"\1\"\nREV=\"\2\"\nSERIAL=\"\3\"/p') add_to_match_cache ata model $i "$MODEL" add_to_match_cache ata rev $i "$REV" add_to_match_cache ata serial $i "$SERIAL" fi output=$($sg_inq ${storage_devs[i]} 2> /dev/null) if [ $? -eq 0 ]; then VENDOR= MODEL= REV= SERIAL= eval $(echo "$output" | $sed -nr 's/^\s*Vendor identification:\s*(.*\S|\s*)\s*$/VENDOR=\"\1\"/p') eval $(echo "$output" | $sed -nr 's/^\s*Product identification:\s*(.*\S|\s*)\s*$/MODEL=\"\1\"/p') eval $(echo "$output" | $sed -nr 's/^\s*Product revision level:\s*(.*\S|\s*)\s*$/REV=\"\1\"/p') eval $(echo "$output" | $sed -nr 's/^\s*Unit serial number:\s*(.*\S|\s*)\s*$/SERIAL=\"\1\"/p') add_to_match_cache scsi vendor $i "$VENDOR" add_to_match_cache scsi model $i "$MODEL" add_to_match_cache scsi rev $i "$REV" add_to_match_cache scsi serial $i "$SERIAL" fi done while read f0 f1 f2; do true $((lineno++)) if [ -z ${f0###*} ]; then continue fi if [ "$f0" = rule ]; then rule_name=$f1 skip=0 matches=($(seq 0 $((${#storage_devs[@]}-1)))) continue fi if [ $skip -ne 0 ]; then continue fi case "$f0" in dmi) do_dmi "$f1" "$f2" ;; ata) do_storage ata "$f1" "$f2" ;; scsi) do_storage scsi "$f1" "$f2" ;; act) do_act "$f1 $f2" 1 ;; sact) do_act "$f1 $f2" 0 ;; *) false ;; esac ret=$? if [ $ret -ne 0 ]; then if [ $ret -eq 2 ]; then warn "malformed line $lineno \"$f0 $f1 $f2\","\ "skipping rule $rule_name" 2>&1 fi skip=1 fi done < $conf_file