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