1aadd7a00fcedd4b665b5f955093c38117bce9ae
[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 # store the WWID map to disk
84 ba_store_wwid_map() {
85     local _WWID _TMP_MAP
86     _TMP_MAP=$(mktemp --tmpdir=$BA_CONF_DIR)
87     if [ ! -f "$_TMP_MAP" ]; then
88         _error "could not write temporary file"
89         exit 1
90     fi
91     for _WWID in ${!WWIDMAP[@]}; do
92         _print "$_WWID ${WWIDMAP[$_WWID]}" \
93                 "${TYPEMAP[$_WWID]} ${PARTMAP[$_WWID]}"
94     done  >> $_TMP_MAP
95     mv $_TMP_MAP $BA_MAP_PATH
96     # we may be running as root - if so re-chown the configs
97     if [ "$BA_REAL_UID" == "0" ] || [ "$BA_REAL_GID" == "0" ]; then
98         ba_chown_configs $BA_USER $BA_GROUP
99     fi
100 }
101
102 # read the WWID map from disk
103 ba_load_wwid_map() {
104     local _WWID _ALIAS _TYPE _PART
105     if [ ! -f "$BA_MAP_PATH" ] || [ ! -r "$BA_MAP_PATH"  ]; then
106         _error "$BA_MAP_PATH does not exist or is not readable"
107         exit 1
108     fi
109     while read _WWID _ALIAS _TYPE _PART; do
110         if [[ $_WWID == \#* ]] || [[ $_WWID == "" ]]; then
111             continue
112         fi
113         WWIDMAP[$_WWID]="$_ALIAS"
114         NAMEMAP[$_ALIAS]="$_WWID"
115         TYPEMAP[$_WWID]="$_TYPE"
116         PARTMAP[$_WWID]="$_PART"
117     done < $BA_MAP_PATH
118 }
119
120 # ba_add_wwid_mapping <wwid> <alias> <sd|mpath> <part>
121 ba_add_wwid_mapping() {
122     local _WWID _ALIAS _TYPE _PART _DEV
123     _WWID="$1"
124     _ALIAS="$2"
125     _TYPE="$3"
126     _PART="$4"
127     _DEV="$5"
128     _debug "ba_add_wwid_mapping: WWID=\"$_WWID\" ALIAS=\"$_ALIAS\"" \
129         "TYPE=\"$_TYPE\" PART=\"$_PART\""
130     if [ "$_WWID" == "" ]; then
131         _warn "no wwid given"
132         return
133     fi
134     if [ "$_ALIAS" == "" ]; then
135         _warn "no alias given"
136         return
137     fi
138     if [ "$_PART" == "" ]; then
139         _warn "no partition number given"
140     fi
141     if [ "${WWIDMAP[$_WWID]}" != "" ]; then
142         _debug "ba_add_wwid_mapping: WWIDMAP[\$WWID]=${WWIDMAP[$_WWID]}"
143         _warn "$DEV already mapped to alias ${WWIDMAP[$_WWID]}; replacing it."
144         ba_del_wwid_mapping "$_WWID"
145     fi
146     if [ "${NAMEMAP[$_ALIAS]}" != "" ]; then
147         _debug "ba_add_wwid_mapping: NAMEMAP[\$ALIAS]=${NAMEMAP[$_ALIAS]}"
148         _warn "$DEV already mapped to disk ${NAMEMAP[$_ALIAS]}; replacing it."
149         ba_del_wwid_mapping ${NAMEMAP[$_ALIAS]}
150     fi
151     WWIDMAP[$_WWID]="$_ALIAS"
152     NAMEMAP[$_ALIAS]="$_WWID"
153     TYPEMAP[$_WWID]=${_TYPE:-"sd"}
154     PARTMAP[$_WWID]="$_PART"
155 }
156
157 # ba_del_wwid_mapping <wwid>
158 ba_del_wwid_mapping() {
159     local _WWID _ALIAS
160     _WWID="$1"
161     if [ "$_WWID" == "" ]; then
162         return 1
163     fi
164     if [ "${WWIDMAP[$_WWID]}" == "" ]; then
165         _warn "WARNING: $_WWID does not exist; doing nothing."
166         return 1
167     fi
168     _debug "\nba_del_wwid_mapping: unsetting WWIDMAP[$_WWID]"
169     _ALIAS=${WWIDMAP[$_WWID]}
170     unset WWIDMAP[$_WWID]
171     unset NAMEMAP[$_ALIAS]
172     unset TYPEMAP[$_WWID]
173     unset PARTMAP[$_WWID]
174 }
175
176 # ba_get_partnum <device>
177 ba_get_partnum () {
178     local _DEV _NUM
179     _DEV="$1"
180     if [[ $_DEV == dm-* ]]; then
181         _DEV=$DM_NAME
182     fi
183     _debug "ba_get_partnum: $_DEV"
184     _debug calling "$BA_DMSETUP info $_DEV"
185     $BA_DMSETUP info $_DEV > /dev/null 2>&1
186     if [ "$?" == "0" ]; then
187         _NUM=$(echo $_DEV | sed -e 's/\/dev.*\///' -e 's/.*p\([0-9]*\).*/\1/')
188         _debug "ba_get_partnum: dm device $_DEV NUM=$_NUM"
189     else
190         _NUM=$(echo $_DEV | sed 's/\(\/dev\/\)*sd[a-z]*//')
191         _debug "ba_get_partnum: non-dm device $_DEV NUM=$_NUM"
192     fi
193     if [ "$_NUM" == "" ]; then
194         _NUM="0"
195     fi
196     _print $_NUM
197 }
198
199 # ba_scsi_id: <device path>
200 ba_scsi_id () {
201     local _DEV
202     _DEV="$1"
203     _debug "ba_scsi_id: getting ID for device $_DEV"
204     $BA_DMSETUP info $_DEV > /dev/null  2>&1
205     if [ $? -eq 0 ]; then
206         _DEV=$(echo $_DEV | sed 's/p[0-9].*//')
207     else
208         _DEV=$(echo $_DEV | sed 's/[0-9].*//')
209     fi
210      _debug "ba_scsi_id: calling \"$BA_SCSI_ID $BA_SCSI_ID_FLAGS $_DEV\""
211     $BA_SUDO $BA_SCSI_ID $BA_SCSI_ID_FLAGS $_DEV
212 }
213
214 # ba_chown_configs <user> <group>
215 ba_chown_configs () {
216     local _USER _GROUP _OUT
217     _USER="$1"
218     _GROUP="$2"
219     $BA_CHOWN -R "$_USER:$_GROUP" "$BA_CONF_PATH"
220     ls -l "$BA_CONF_PATH" | while read _OUT; do
221         _debug "ba_chown_configs: $_OUT"
222     done
223     return 0
224 }
225
226 # ba_refresh_device <dev path>
227 ba_refresh_device () {
228     local _DEVPATH _DEVNAME _LINKDEST
229     _DEVPATH="$1"
230     _debug "ba_refresh_device: refreshing device at path $_DEVPATH"
231     _LINKDEST=$($BA_READLINK $_DEVPATH)
232     if [ "$?" != 0 ]; then
233         _DEVNAME=$($BA_BASENAME $_DEVPATH | $BA_SED 's/p\+[1-9]*$//')
234         _debug "ba_refresh_device: non-symlinked dev at " \
235             "$_DEVPATH has name $_DEVNAME"
236     else
237         _DEVNAME=$($BA_BASENAME $_LINKDEST)
238         _debug "ba_refresh_device: symlinked dev at" \
239             "$_DEVPATH has name $_DEVNAME"
240     fi
241     _debug "ba_refresh_device: requesting change uevent at" \
242             "sysfs path /sys/block/$_DEVNAME"
243     $BA_SUDO $BA_SH -c "echo change > /sys/block/$_DEVNAME/uevent"
244 }
245
246 # ba_configure:
247 ba_configure () {
248     local _BA_USER _BA_GROUP _BA_DEV_PATH _TMP_CONF
249     cat <<EOF
250 Configuring the blkdevalias map.
251
252 This will configure persistent aliases and permissions for storage
253 devices. The current values will be shown in brackets ('[]'). Hitting
254 <ENTER> without typing an answer will keep the current value.
255
256 Ctrl-C will abort.
257
258 EOF
259
260     _print -n "Default user to own device nodes [$BA_USER]: "
261     read _BA_USER
262     if [ "$_BA_USER" != "" ]; then
263         BA_USER=$_BA_USER;
264     fi
265     $BA_GETENT passwd "$BA_USER" &>/dev/null
266     if [ "$?" != 0 ]; then
267         _error "user (\"$BA_USER\") must be a valid user account"
268         exit 1
269     fi
270
271     _print -n "Default group to own device nodes [$BA_GROUP]: "
272     read _BA_GROUP
273     if [ "$_BA_GROUP" != "" ]; then
274         BA_GROUP=$_BA_GROUP;
275     fi
276     $BA_GETENT group "$BA_GROUP" &>/dev/null
277     if [ "$?" != 0 ]; then
278         _error "user (\"$BA_GROUP\") must be a valid group account"
279         exit 1
280     fi
281     _print -n "Default device directory [$BA_DEV_PATH]: "
282     read _BA_DEV_PATH
283     if [ "$_BA_DEV_PATH" != "" ]; then
284         BA_DEV_PATH=$_BA_DEV_PATH;
285     fi
286
287     _debug "BA_DEV_PATH=\"$BA_DEV_PATH\""
288     _debug "BA_USER=\"$BA_USER\""
289     _debug "BA_GROUP=\"$BA_GROUP\""
290     _print -n "Writing wwid map configuration: "
291     TMP_CONF=$(mktemp --tmpdir=$BA_CONF_PATH)
292     if [ ! -f "$TMP_CONF" ]; then
293         _print
294         _error "could not write temporary file"
295         exit 1
296     fi
297     echo "BA_DEV_PATH=\"$BA_DEV_PATH\"" >> $TMP_CONF
298     echo "BA_USER=\"$BA_USER\"" >> $TMP_CONF
299     _print "BA_GROUP=\"$BA_GROUP\"" >> $TMP_CONF
300     mv $TMP_CONF $BA_CONF
301     if [ "$?" != 0 ]; then
302         _error "could not create configuration file $BA_CONF"
303         exit
304     fi
305     # set configration ownership to reflect admin users
306     ba_chown_configs "$BA_USER" "$BA_GROUP"
307     _print -e "done\n"
308 }
309
310 # ba_createdisk <alias> <device>
311 ba_createdisk () {
312     local _WWID _DEV _ALIAS _TYPE _PART
313     _ALIAS="$1"
314     _DEV="$2"
315     if [ "$_DEV" == "" ] || [ "$_ALIAS" == "" ]; then
316         ba_usage "createdisk <alias> <device>"
317     fi
318     _debug "ba_createdisk: DEV=\"$_DEV\" ALIAS=\"$_ALIAS\""
319
320     _WWID=$(ba_scsi_id $_DEV)
321     if [ "$_WWID" == "" ]; then
322         _error "could not get SCSI ID for $_DEV"
323         exit 1
324     fi
325     _debug "ba_createdisk: WWID=\"$_WWID\""
326
327     if $BA_DMSETUP info $_DEV > /dev/null  2>&1; then
328         _TYPE="mpath"
329     else
330         _TYPE="sd"
331     fi
332
333     _PART=$(ba_get_partnum $_DEV) 
334     _debug "ba_createdisk: PART=\"$_PART\""
335
336     ba_add_wwid_mapping $_WWID $_ALIAS $_TYPE $_PART $_DEV
337     ba_store_wwid_map
338     ba_refresh_device $_DEV
339 }
340
341 # ba_deletedisk: <alias>
342 ba_deletedisk () {
343     local _WWID _ALIAS _DEVPATH
344     _ALIAS=$1
345     if [ "$_ALIAS" == "" ]; then
346         ba_usage "deletedisk <alias>"
347     fi
348     _WWID=${NAMEMAP[$_ALIAS]}
349     if [ "$_WWID" == "" ]; then
350         _error "\"$_ALIAS\" does not exist"
351         ba_usage "deletedisk <alias>"
352     fi
353     echo -n "Removing wwid map disk \"$_ALIAS\":"
354     # generate device path for refresh call
355     _DEVPATH="/dev/$BA_DEV_PATH/${WWIDMAP[$_WWID]}"
356     ba_del_wwid_mapping $_WWID \
357     && if [ "$BA_DEBUG" != "yes" ]; then
358         echo "                                  [  OK  ]"
359     fi
360     ba_store_wwid_map
361     ba_refresh_device $_DEVPATH
362 }
363
364 # ba_list_disks:
365 ba_list_disks () {
366     local _D
367     for _D in ${WWIDMAP[@]}; do
368         local _DEVPATH
369         _DEVPATH="/dev/$BA_DEV_PATH/${WWIDMAP[${NAMEMAP[$_D]}]}"
370         echo -n $_D " $_DEVPATH -> "
371         echo $(readlink $_DEVPATH)
372     done | sort -V
373 }
374
375 # ba_querydisk: <[-d]>
376 ba_querydisk () {
377     local _LISTDEV _ONDEV _WWID _ALIAS
378     if [ "$1" == "-d" ]; then
379         _LISTDEV=1
380         shift
381     fi
382     _ALIAS=$1
383     if [ "$_ALIAS" == "" ]; then
384         ba_usage "querydisk <alias>"
385         exit 2
386     fi
387     _WWID=${NAMEMAP[$_ALIAS]}
388     _debug "ba_querydisk: ALIAS=\"$_ALIAS\"" \
389         "WWID=\"$_WWID\" LISTDEV=\"$_LISTDEV\""
390     if [ "$_LISTDEV" == "1" ]; then
391         DEV="/dev/$BA_DEV_PATH/$(readlink "/dev/$BA_DEV_PATH/$_ALIAS")"
392         MAJ="$[0x$(stat --format "%t" $DEV)]"
393         MIN="$[0x$(stat --format "%T" $DEV)]"
394         _ONDEV="on device [$MAJ,$MIN]"
395         _debug "ba_querydisk: DEV=\"$DEV\" MAJ=\"$MAJ\" MIN=\"$MIN\""
396     fi
397     if [ "$_WWID" != "" ]; then
398         echo "$_ALIAS is a valid blkdevalias disk $_ONDEV"
399         return
400     fi
401     exit 1
402 }
403
404 ba_scandisks () {
405     local _DEV _TARGET
406     for _DEV in /dev/$BA_DEV_PATH/*; do
407         _TARGET=$(readlink $_DEV)
408         if [[ $_TARGET == *dm-* ]]; then
409             _TARGET=$($BA_SUDO $BA_DMSETUP info \
410                 --noheadings -c /dev/$(basename $_TARGET) -o name)
411         else
412             _TARGET=$(basename $_TARGET)
413         fi
414         _print "$_DEV ${NAMEMAP[$(basename $_DEV)]} $_TARGET"
415     done
416 }
417
418 # ba_map: <wwid>
419 # DEVNAME - kernel name (supplied by udev)
420 # ID_BUS  - bus ID string (supplied by udev)
421 ba_map () {
422     local _WWID _TYPE _PART
423     _WWID="$1"
424     _debug "ba_map: $_WWID -> ${WWIDMAP[$_WWID]}"
425     if [ "$_WWID" == "" ] || [ "${WWIDMAP[$_WWID]}" == "" ]; then
426         ba_usage "map <wwid>"
427         exit 2
428     fi
429     _PART=$(ba_get_partnum $DEVNAME)
430     if [ "$ID_BUS" == "scsi" ]; then
431         _TYPE=sd
432     else
433         _TYPE=mpath
434     fi
435     _debug "ba_map: WWID=$_WWID PART=$_PART TYPE=$_TYPE"
436     if [ "$_PART" != ${PARTMAP[$_WWID]} ] \
437         || [ "$_TYPE" != ${TYPEMAP[$_WWID]} ] ; then
438         exit 1
439     fi
440     echo "BA_WWID=$_WWID"
441     echo "BA_NAME=$BA_DEV_PATH/${WWIDMAP[$_WWID]}"
442     echo "BA_TYPE=${TYPEMAP[$_WWID]}"
443     echo "BA_USER=$BA_USER"
444     echo "BA_GROUP=$BA_GROUP"
445     echo "BA_MODE=$BA_MODE"
446 }
447
448 # no point optimizing this out for configure
449 ba_load_wwid_map
450 case "$1" in
451     configure)
452         ba_configure
453         ;;
454     create*)
455         ba_createdisk $2 $3
456         ;;
457     delete*)
458         ba_deletedisk $2
459         ;;
460     list*)
461         ba_list_disks
462         ;;
463     query*)
464         ba_querydisk $2 $3
465         ;;
466     scan*)
467         ba_scandisks
468         ;;
469     map)
470         ba_map $2
471         ;;
472     *)
473         ba_usage "\n{configure|createdisk|deletedisk|listdisks|scandisks|querydisk|map}"
474 esac
475