Add device refresh capability to scandisks command
[blkdevalias.git] / blkdevalias
1 #!/bin/bash
2 #
3 #  blkdevalias - manage aliases and permissions for block devices
4 #
5 #  Bryn M. Reeves <bmr@redhat.com>
6 #
7 #  Copyright (C) Red Hat, Inc. 2012, 2013
8 #
9 #  This program is free software; you can redistribute it and/or modify
10 #  it under the terms of the GNU General Public License as published by
11 #  the Free Software Foundation; either version 2 of the License, or
12 #  (at your option) any later version.
13 #
14 #  This program is distributed in the hope that it will be useful,
15 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
16 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 #  GNU General Public License for more details.
18 #
19 #  You should have received a copy of the GNU General Public License
20 #  along with this program; if not, write to the Free Software
21 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 #
23 declare -A WWIDMAP
24 declare -A NAMEMAP
25 declare -A TYPEMAP
26 declare -A PARTMAP
27
28 # To enable debug output set the BA_DEBUG environment variable to "yes".
29 #BA_DEBUG="yes"
30 BA_DEFAULT_USER="oracle"
31 BA_DEFAULT_GROUP="oracle"
32 BA_CONF_PATH="/etc/blkdevalias"
33 BA_DEV_PATH="oracleasm/disks"
34 BA_MAP_PATH="${BA_CONF_PATH}/blkdevalias.map"
35 BA_CONF="${BA_CONF_PATH}/blkdevalias.conf"
36 BA_USER="$BA_DEFAULT_USER"
37 BA_GROUP="$BA_DEFAULT_GROUP"
38 BA_LOCK="/var/lock/blkdevalias"
39 BA_SCSI_ID="/sbin/scsi_id"
40 BA_SCSI_ID_FLAGS="-g -u"
41 BA_READLINK="/usr/bin/readlink"
42 BA_BASENAME="/bin/basename"
43 BA_DMSETUP="/sbin/dmsetup"
44 BA_GETENT="/usr/bin/getent"
45 BA_CHOWN="/bin/chown"
46 BA_SUDO="/usr/bin/sudo"
47 BA_SED="/bin/sed"
48 BA_ID="/usr/bin/id"
49 BA_SH="/bin/bash"
50 BA_REAL_UID="$($BA_ID -u)"
51 BA_REAL_GID="$($BA_ID -g)"
52
53 # load config
54 . $BA_CONF
55
56 _print () {
57     echo -e "$@"
58 }
59
60 _error () {
61     echo -e "ERROR: $@" 1>&2
62 }
63
64 _warn () {
65     echo -e "WARNING: $@" 1>&2
66 }
67
68 _debug () {
69     [ "$BA_DEBUG" == "yes" ] && echo -e "$@" 1>&2
70 }
71
72 # print usage message
73 # sub-commands may provide their own usage messages
74 ba_usage () {
75     if [ x"$@" == x"" ]; then
76         _print "Usage: $0 <command> [<arg>]..."
77     else
78         _print "Usage: $0 $@"
79     fi
80     exit 2
81 }
82
83 ba_store_wwid_map_header() {
84     local _TMP_MAP="$1"
85     cat > $_TMP_MAP << EOF
86 # THIS FILE IS AUTOMATICALLY GENERATED. MANUAL EDITS
87 # WILL BE LOST WHEN ALIASES ARE ADDED OR REMOVED.
88 # <WWID> <ALIAS> <TYPE> <PARTNUM>
89 EOF
90 }
91
92 # store the WWID map to disk
93 ba_store_wwid_map() {
94     local _WWID _TMP_MAP
95     _TMP_MAP=$(mktemp --tmpdir=$BA_CONF_DIR)
96     if [ ! -f "$_TMP_MAP" ]; then
97         _error "could not write temporary file"
98         exit 1
99     fi
100     ba_store_wwid_map_header $_TMP_MAP
101     for _WWID in ${!WWIDMAP[@]}; do
102         echo "$_WWID ${WWIDMAP[$_WWID]}" \
103                 "${TYPEMAP[$_WWID]} ${PARTMAP[$_WWID]}"
104     done  >> $_TMP_MAP
105     mv $_TMP_MAP $BA_MAP_PATH
106     # we may be running as root - if so re-chown the configs
107     if [ "$BA_REAL_UID" == "0" ] || [ "$BA_REAL_GID" == "0" ]; then
108         ba_chown_configs $BA_USER $BA_GROUP
109     fi
110 }
111
112 # read the WWID map from disk
113 ba_load_wwid_map() {
114     local _WWID _ALIAS _TYPE _PART
115     if [ ! -f "$BA_MAP_PATH" ] || [ ! -r "$BA_MAP_PATH"  ]; then
116         _error "$BA_MAP_PATH does not exist or is not readable"
117         exit 1
118     fi
119     while read _WWID _ALIAS _TYPE _PART; do
120         if [[ $_WWID == \#* ]] || [[ $_WWID == "" ]]; then
121             continue
122         fi
123         WWIDMAP[$_WWID]="$_ALIAS"
124         NAMEMAP[$_ALIAS]="$_WWID"
125         TYPEMAP[$_WWID]="$_TYPE"
126         PARTMAP[$_WWID]="$_PART"
127     done < $BA_MAP_PATH
128 }
129
130 # ba_add_wwid_mapping <wwid> <alias> <sd|mpath> <part>
131 ba_add_wwid_mapping() {
132     local _WWID _ALIAS _TYPE _PART _DEV
133     _WWID="$1"
134     _ALIAS="$2"
135     _TYPE="$3"
136     _PART="$4"
137     _DEV="$5"
138     _debug "ba_add_wwid_mapping: WWID=\"$_WWID\" ALIAS=\"$_ALIAS\"" \
139         "TYPE=\"$_TYPE\" PART=\"$_PART\""
140     if [ "$_WWID" == "" ]; then
141         _warn "no wwid given"
142         return
143     fi
144     if [ "$_ALIAS" == "" ]; then
145         _warn "no alias given"
146         return
147     fi
148     if [ "$_PART" == "" ]; then
149         _warn "no partition number given"
150     fi
151     if [ "${WWIDMAP[$_WWID]}" != "" ]; then
152         _debug "ba_add_wwid_mapping: WWIDMAP[\$WWID]=${WWIDMAP[$_WWID]}"
153         _warn "$DEV already mapped to alias ${WWIDMAP[$_WWID]}; replacing it."
154         ba_del_wwid_mapping "$_WWID"
155     fi
156     if [ "${NAMEMAP[$_ALIAS]}" != "" ]; then
157         _debug "ba_add_wwid_mapping: NAMEMAP[\$ALIAS]=${NAMEMAP[$_ALIAS]}"
158         _warn "$DEV already mapped to disk ${NAMEMAP[$_ALIAS]}; replacing it."
159         ba_del_wwid_mapping ${NAMEMAP[$_ALIAS]}
160     fi
161     WWIDMAP[$_WWID]="$_ALIAS"
162     NAMEMAP[$_ALIAS]="$_WWID"
163     TYPEMAP[$_WWID]=${_TYPE:-"sd"}
164     PARTMAP[$_WWID]="$_PART"
165 }
166
167 # ba_del_wwid_mapping <wwid>
168 ba_del_wwid_mapping() {
169     local _WWID _ALIAS
170     _WWID="$1"
171     if [ "$_WWID" == "" ]; then
172         return 1
173     fi
174     if [ "${WWIDMAP[$_WWID]}" == "" ]; then
175         _warn "WARNING: $_WWID does not exist; doing nothing."
176         return 1
177     fi
178     _debug "\nba_del_wwid_mapping: unsetting WWIDMAP[$_WWID]"
179     _ALIAS=${WWIDMAP[$_WWID]}
180     unset WWIDMAP[$_WWID]
181     unset NAMEMAP[$_ALIAS]
182     unset TYPEMAP[$_WWID]
183     unset PARTMAP[$_WWID]
184 }
185
186 # ba_get_partnum <device>
187 ba_get_partnum () {
188     local _DEV _NUM
189     _DEV="$1"
190     if [[ $_DEV == dm-* ]]; then
191         _DEV=$DM_NAME
192     fi
193     _debug "ba_get_partnum: $_DEV"
194     _debug calling "$BA_DMSETUP info $_DEV"
195     $BA_DMSETUP info $_DEV > /dev/null 2>&1
196     if [ "$?" == "0" ]; then
197         _NUM=$(echo $_DEV | sed -e 's/\/dev.*\///' -e 's/.*p\([0-9]*\).*/\1/')
198         _debug "ba_get_partnum: dm device $_DEV NUM=$_NUM"
199     else
200         _NUM=$(echo $_DEV | sed 's/\(\/dev\/\)*sd[a-z]*//')
201         _debug "ba_get_partnum: non-dm device $_DEV NUM=$_NUM"
202     fi
203     if [ "$_NUM" == "" ]; then
204         _NUM="0"
205     fi
206     _print $_NUM
207 }
208
209 # ba_scsi_id: <device path>
210 ba_scsi_id () {
211     local _DEV
212     _DEV="$1"
213     _debug "ba_scsi_id: getting ID for device $_DEV"
214     $BA_DMSETUP info $_DEV > /dev/null  2>&1
215     if [ $? -eq 0 ]; then
216         _DEV=$(echo $_DEV | sed 's/p[0-9].*//')
217     else
218         _DEV=$(echo $_DEV | sed 's/[0-9].*//')
219     fi
220      _debug "ba_scsi_id: calling \"$BA_SCSI_ID $BA_SCSI_ID_FLAGS $_DEV\""
221     $BA_SUDO $BA_SCSI_ID $BA_SCSI_ID_FLAGS $_DEV
222 }
223
224 # ba_chown_configs <user> <group>
225 ba_chown_configs () {
226     local _USER _GROUP _OUT
227     _USER="$1"
228     _GROUP="$2"
229     $BA_CHOWN -R "$_USER:$_GROUP" "$BA_CONF_PATH"
230     ls -l "$BA_CONF_PATH" | while read _OUT; do
231         _debug "ba_chown_configs: $_OUT"
232     done
233     return 0
234 }
235
236 # ba_refresh_device <dev path>
237 ba_refresh_device () {
238     local _DEVPATH _DEVNAME _LINKDEST
239     _DEVPATH="$1"
240     _debug "ba_refresh_device: refreshing device at path $_DEVPATH"
241     _LINKDEST=$($BA_READLINK $_DEVPATH)
242     if [ "$?" != 0 ]; then
243         _DEVNAME=$($BA_BASENAME $_DEVPATH | $BA_SED 's/p\+[1-9]*$//')
244         _debug "ba_refresh_device: non-symlinked dev at " \
245             "$_DEVPATH has name $_DEVNAME"
246     else
247         _DEVNAME=$($BA_BASENAME $_LINKDEST)
248         _debug "ba_refresh_device: symlinked dev at" \
249             "$_DEVPATH has name $_DEVNAME"
250     fi
251     _debug "ba_refresh_device: requesting change uevent at" \
252             "sysfs path /sys/block/$_DEVNAME"
253     $BA_SUDO $BA_SH -c "echo change > /sys/block/$_DEVNAME/uevent"
254 }
255
256 #ba_configure_store_header: <file>
257 ba_store_conf_header() {
258     local _TMP_CONF="$1"
259     cat > $_TMP_CONF <<EOF
260 # THIS FILE IS AUTOMATICALLY GENERATED. MANUAL EDITS
261 # WILL BE LOST WHEN THE CONFIGURE COMMAND IS ISSUED.
262 # SEE BLKDEVALIAS.CONF(5) FOR CONFIGURATION PARAMETERS.
263 EOF
264 }
265
266 # ba_configure:
267 ba_configure () {
268     local _BA_USER _BA_GROUP _BA_DEV_PATH _TMP_CONF
269     cat <<EOF
270 Configuring the blkdevalias map.
271
272 This will configure persistent aliases and permissions for storage
273 devices. The current values will be shown in brackets ('[]'). Hitting
274 <ENTER> without typing an answer will keep the current value.
275
276 Ctrl-C will abort.
277
278 EOF
279
280     _print -n "Default user to own device nodes [$BA_USER]: "
281     read _BA_USER
282     if [ "$_BA_USER" != "" ]; then
283         BA_USER=$_BA_USER;
284     fi
285     $BA_GETENT passwd "$BA_USER" &>/dev/null
286     if [ "$?" != 0 ]; then
287         _error "user (\"$BA_USER\") must be a valid user account"
288         exit 1
289     fi
290
291     _print -n "Default group to own device nodes [$BA_GROUP]: "
292     read _BA_GROUP
293     if [ "$_BA_GROUP" != "" ]; then
294         BA_GROUP=$_BA_GROUP;
295     fi
296     $BA_GETENT group "$BA_GROUP" &>/dev/null
297     if [ "$?" != 0 ]; then
298         _error "user (\"$BA_GROUP\") must be a valid group account"
299         exit 1
300     fi
301     _print -n "Default device directory [$BA_DEV_PATH]: "
302     read _BA_DEV_PATH
303     if [ "$_BA_DEV_PATH" != "" ]; then
304         BA_DEV_PATH=$_BA_DEV_PATH;
305     fi
306
307     _debug "BA_DEV_PATH=\"$BA_DEV_PATH\""
308     _debug "BA_USER=\"$BA_USER\""
309     _debug "BA_GROUP=\"$BA_GROUP\""
310     _print -n "Writing wwid map configuration: "
311     _TMP_CONF=$(mktemp --tmpdir=$BA_CONF_PATH)
312     if [ ! -f "$_TMP_CONF" ]; then
313         _print
314         _error "could not write temporary file"
315         exit 1
316     fi
317     ba_store_conf_header $_TMP_CONF
318     echo "BA_DEV_PATH=\"$BA_DEV_PATH\"" >> $_TMP_CONF
319     echo "BA_USER=\"$BA_USER\"" >> $_TMP_CONF
320     _print "BA_GROUP=\"$BA_GROUP\"" >> $_TMP_CONF
321     mv $_TMP_CONF $BA_CONF
322     if [ "$?" != 0 ]; then
323         _error "could not create configuration file $BA_CONF"
324         exit
325     fi
326     # set configration ownership to reflect admin users
327     ba_chown_configs "$BA_USER" "$BA_GROUP"
328     _print -e "done\n"
329 }
330
331 # ba_createdisk <alias> <device>
332 ba_createdisk () {
333     local _WWID _DEV _ALIAS _TYPE _PART
334     _ALIAS="$1"
335     _DEV="$2"
336     if [ "$_DEV" == "" ] || [ "$_ALIAS" == "" ]; then
337         ba_usage "createdisk <alias> <device>"
338     fi
339     _debug "ba_createdisk: DEV=\"$_DEV\" ALIAS=\"$_ALIAS\""
340
341     _WWID=$(ba_scsi_id $_DEV)
342     if [ "$_WWID" == "" ]; then
343         _error "could not get SCSI ID for $_DEV"
344         exit 1
345     fi
346     _debug "ba_createdisk: WWID=\"$_WWID\""
347
348     if $BA_DMSETUP info $_DEV > /dev/null  2>&1; then
349         _TYPE="mpath"
350     else
351         _TYPE="sd"
352     fi
353
354     _PART=$(ba_get_partnum $_DEV) 
355     _debug "ba_createdisk: PART=\"$_PART\""
356
357     ba_add_wwid_mapping $_WWID $_ALIAS $_TYPE $_PART $_DEV
358     ba_store_wwid_map
359     ba_refresh_device $_DEV
360 }
361
362 # ba_deletedisk: <alias>
363 ba_deletedisk () {
364     local _WWID _ALIAS _DEVPATH
365     _ALIAS=$1
366     if [ "$_ALIAS" == "" ]; then
367         ba_usage "deletedisk <alias>"
368     fi
369     _WWID=${NAMEMAP[$_ALIAS]}
370     if [ "$_WWID" == "" ]; then
371         _error "\"$_ALIAS\" does not exist"
372         ba_usage "deletedisk <alias>"
373     fi
374     echo -n "Removing wwid map disk \"$_ALIAS\":"
375     # generate device path for refresh call
376     _DEVPATH="/dev/$BA_DEV_PATH/${WWIDMAP[$_WWID]}"
377     ba_del_wwid_mapping $_WWID \
378     && if [ "$BA_DEBUG" != "yes" ]; then
379         echo "                                  [  OK  ]"
380     fi
381     ba_store_wwid_map
382     ba_refresh_device $_DEVPATH
383 }
384
385 # ba_list_disks:
386 ba_list_disks () {
387     local _D
388     for _D in ${WWIDMAP[@]}; do
389         local _DEVPATH
390         _DEVPATH="/dev/$BA_DEV_PATH/${WWIDMAP[${NAMEMAP[$_D]}]}"
391         echo -n $_D " $_DEVPATH -> "
392         echo $(readlink $_DEVPATH)
393     done | sort -V
394 }
395
396 # ba_querydisk: <[-d]>
397 ba_querydisk () {
398     local _LISTDEV _ONDEV _WWID _ALIAS
399     if [ "$1" == "-d" ]; then
400         _LISTDEV=1
401         shift
402     fi
403     _ALIAS=$1
404     if [ "$_ALIAS" == "" ]; then
405         ba_usage "querydisk <alias>"
406         exit 2
407     fi
408     _WWID=${NAMEMAP[$_ALIAS]}
409     _debug "ba_querydisk: ALIAS=\"$_ALIAS\"" \
410         "WWID=\"$_WWID\" LISTDEV=\"$_LISTDEV\""
411     if [ "$_LISTDEV" == "1" ]; then
412         DEV="/dev/$BA_DEV_PATH/$(readlink "/dev/$BA_DEV_PATH/$_ALIAS")"
413         MAJ="$[0x$(stat --format "%t" $DEV)]"
414         MIN="$[0x$(stat --format "%T" $DEV)]"
415         _ONDEV="on device [$MAJ,$MIN]"
416         _debug "ba_querydisk: DEV=\"$DEV\" MAJ=\"$MAJ\" MIN=\"$MIN\""
417     fi
418     if [ "$_WWID" != "" ]; then
419         echo "$_ALIAS is a valid blkdevalias disk $_ONDEV"
420         return
421     fi
422     exit 1
423 }
424
425 ba_scandisks () {
426     local _DEV _TARGET _REFRESH
427     echo $1
428     if [ "x$1" == "x-r" ]; then
429         _REFRESH="yes"
430     else
431         _REFRESH="no"
432     fi
433     _debug "ba_scandisks: device refresh requested: $_REFRESH"
434     for _DEV in /dev/$BA_DEV_PATH/*; do
435         _TARGET=$(readlink $_DEV)
436         # perform refresh on pre-canonicalised device path
437         if [ "$_REFRESH" == "yes" ]; then
438             _debug "ba_scandisks: refreshing device at $_TARGET"
439             ba_refresh_device $_TARGET
440         fi
441         if [[ $_TARGET == *dm-* ]]; then
442             _TARGET=$($BA_SUDO $BA_DMSETUP info \
443                 --noheadings -c /dev/$(basename $_TARGET) -o name)
444         else
445             _TARGET=$(basename $_TARGET)
446         fi
447         _print "$_DEV ${NAMEMAP[$(basename $_DEV)]} $_TARGET"
448     done
449 }
450
451 # ba_map: <wwid>
452 # DEVNAME - kernel name (supplied by udev)
453 # ID_BUS  - bus ID string (supplied by udev)
454 ba_map () {
455     local _WWID _TYPE _PART
456     _WWID="$1"
457     _debug "ba_map: $_WWID -> ${WWIDMAP[$_WWID]}"
458     if [ "$_WWID" == "" ] || [ "${WWIDMAP[$_WWID]}" == "" ]; then
459         ba_usage "map <wwid>"
460         exit 2
461     fi
462     _PART=$(ba_get_partnum $DEVNAME)
463     if [ "$ID_BUS" == "scsi" ]; then
464         _TYPE=sd
465     else
466         _TYPE=mpath
467     fi
468     _debug "ba_map: WWID=$_WWID PART=$_PART TYPE=$_TYPE"
469     if [ "$_PART" != ${PARTMAP[$_WWID]} ] \
470         || [ "$_TYPE" != ${TYPEMAP[$_WWID]} ] ; then
471         exit 1
472     fi
473     echo "BA_WWID=$_WWID"
474     echo "BA_NAME=$BA_DEV_PATH/${WWIDMAP[$_WWID]}"
475     echo "BA_TYPE=${TYPEMAP[$_WWID]}"
476     echo "BA_USER=$BA_USER"
477     echo "BA_GROUP=$BA_GROUP"
478     echo "BA_MODE=$BA_MODE"
479 }
480
481 # no point optimizing this out for configure
482 ba_load_wwid_map
483 case "$1" in
484     configure)
485         ba_configure
486         ;;
487     create*)
488         ba_createdisk $2 $3
489         ;;
490     delete*)
491         ba_deletedisk $2
492         ;;
493     list*)
494         ba_list_disks
495         ;;
496     query*)
497         ba_querydisk $2 $3
498         ;;
499     scan*)
500         ba_scandisks $2
501         ;;
502     map)
503         ba_map $2
504         ;;
505     *)
506         ba_usage "\n{configure|createdisk|deletedisk|listdisks|scandisks|querydisk|map}"
507 esac
508