1#!/bin/bash -e
2#
3# tapdisk Xen block device hotplug script
4#
5# Author George Dunlap <george.dunlap@eu.citrix.com>
6#
7# Based on block-iscsi by Roger Pau Monné <roger.pau@citrix.com>
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU Lesser General Public License as published
11# by the Free Software Foundation; version 2.1 only. with the special
12# exception on linking described in file LICENSE.
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 Lesser General Public License for more details.
18#
19# Usage:
20#
21# Disks should be specified using the following syntax:
22#
23# For use with tapback (vbd3) (preferred):
24# vdev=xvda,backendtype=tap,format=vhd,target=/srv/target.vhd
25#
26# For use with blkback and the blktap2 kernel module:
27# script=block-tap,vdev=xvda,target=<type>:<file>
28#
29# format/<type> is either "aio" (for raw files), or "vhd"
30
31dir=$(dirname "$0")
32. "$dir/block-common.sh"
33
34remove_label()
35{
36    echo $1 | sed "s/^\("$2"\)//"
37}
38
39check_tools()
40{
41    if ! command -v tap-ctl > /dev/null 2>&1; then
42        fatal "Unable to find tap-ctl tool"
43    fi
44}
45
46# Sets the following global variables based on the params field passed in as
47# a parameter: type file
48parse_target()
49{
50    params=($(echo "$1" | tr ":" "\n"))
51
52    type=${params[0]}
53    file=${params[1]}
54    if [ -z "$type" ] || [ -z "$file" ]; then
55        fatal "Cannot parse required parameters"
56    fi
57}
58
59# Sets $pid and $minor to point to the device associated with the target
60find_device()
61{
62    local info
63    local param
64
65    if [ -z "$type" ] || [ -z "$file" ]; then
66        fatal "required parameters not set"
67    fi
68
69    info=$(tap-ctl list -t $type -f $file)
70
71    for param in $(echo "$info" | tr "," "\n")
72    do
73        case $param in
74        pid=*)
75            pid=$(remove_label $param "pid=")
76            ;;
77        minor=*)
78            minor=$(remove_label $param "minor=")
79            ;;
80        esac
81    done
82
83    if [ -z "$pid" ] || [ -z "$minor" ]; then
84        return 1
85    fi
86
87    return 0
88}
89
90count_using()
91{
92    local file="$1"
93    local dom
94    local dev
95    local f
96
97    local i=0
98    local base_path="$XENBUS_BASE_PATH/$XENBUS_TYPE"
99    for dom in $(xenstore-list "$base_path")
100    do
101        for dev in $(xenstore-list "$base_path/$dom")
102        do
103            f=$(xenstore_read_default "$base_path/$dom/$dev/params" "")
104            f=$(echo "$f" | cut -d ":" -f 2)
105
106            if [ -n "$f" ] && [ "$file" = $f ] ; then
107                i=$(( i + 1 ))
108            fi
109        done
110    done
111
112    echo "$i"
113}
114
115# tap_shared is used to determine if a shared tap can be closed
116# Since a stubdom and a guest both use the same tap, it can only
117# be freed when there is a single one left.
118tap_shared() {
119    [ $( count_using "$file" ) -gt 1 ]
120}
121
122check_tap_sharing()
123{
124    local file="$1"
125    local mode="$2"
126    local dom
127    local dev
128
129    local base_path="$XENBUS_BASE_PATH/$XENBUS_TYPE"
130    for dom in $(xenstore-list "$base_path") ; do
131        for dev in $(xenstore-list "$base_path/$dom") ; do
132            local f=$(xenstore_read_default "$base_path/$dom/$dev/params" "")
133            f=$(echo "$f" | cut -d ":" -f 2)
134
135            if [ -n "$f" ] && [ "$file" = "$f" ] ; then
136                if [ "$mode" = 'w' ] ; then
137                    if ! same_vm $dom ; then
138                        echo "guest $f"
139                        return
140                    fi
141                else
142                    local m=$(xenstore_read_default "$base_path/$dom/$dev/mode"
143                                                    "")
144                    m=$(canonicalise_mode "$m")
145
146                    if [ "$m" = 'w' ] ; then
147                        if ! same_vm $dom ; then
148                            echo "guest $f"
149                            return
150                        fi
151                    fi
152                fi
153            fi
154        done
155    done
156
157    echo 'ok'
158}
159
160tap_create()
161{
162    if ! minor=$( tap-ctl allocate ) ; then
163        fatal "Could not allocate minor"
164    fi
165
166    # Handle with or without kernel blktap
167    minor=${minor#/run/blktap-control/tapdisk/tapdisk-}
168    minor=${minor#/dev/xen/blktap-2/tapdev}
169
170    # tap-ctl is spawning tapdisk which would hold the _lockfd open.
171    # Ensure it is closed before running tap-ctl spawn, which needs to be
172    # done in a subshell to continue holding the lock in the parent.
173    if ! pid=$( ( eval "exec $_lockfd>&-" ; tap-ctl spawn ) ) ; then
174        tap-ctl free -m "$minor"
175        fatal "Could not spawn tapdisk for $minor"
176    fi
177
178    if ! tap-ctl attach -p "$pid" -m "$minor" ; then
179        tap-ctl free -m "$minor"
180        fatal "Could not attach $pid and $minor"
181    fi
182
183    if ! tap-ctl open -p "$pid" -m "$minor" -a "$target" ; then
184        tap-ctl detach -p "$pid" -m "$minor"
185        tap-ctl free -m "$minor"
186        fatal "Could not open \"$target\""
187    fi
188}
189
190# Attaches the device and writes xenstore backend entries to connect
191# the device
192add()
193{
194    local result
195
196    claim_lock "block"
197
198    if find_device; then
199        result=$( check_tap_sharing "$file" "$mode" )
200        if [ "$result" != "ok" ] ; then
201            do_ebusy "tap $type file $file in use " "$mode" "${result%% *}"
202        fi
203    else
204        tap_create
205    fi
206
207    xenstore_write "$XENBUS_PATH/pid" "$pid"
208    xenstore_write "$XENBUS_PATH/minor" "$minor"
209
210    if [ "$XENBUS_TYPE" = "vbd3" ] ; then
211        # Create nbd unix path.  find_device/tap_create set pid & minor
212        dev=$( printf "/run/blktap-control/nbd%ld.%d" "$pid" "$minor" )
213
214        # $dev, as a unix socket, has major:minor 0:0.  If write_dev writes
215        # physical-device, tapback would use that incorrect minor 0.  So don't
216        # write physical-device.
217        xenstore_write "$XENBUS_PATH/physical-device-path" "$dev"
218
219        success
220    else
221        # Construct dev path from minor
222        dev="/dev/xen/blktap-2/tapdev$minor"
223        [ -b "$dev" ] || fatal "blktap \"$dev\" is not a block dev"
224        write_dev "$dev"
225    fi
226
227    release_lock "block"
228}
229
230# Disconnects the device
231remove()
232{
233    local minor
234    local pid
235
236    claim_lock "block"
237
238    if tap_shared ; then
239        return
240    fi
241
242    minor=$( xenstore_read "$XENBUS_PATH/minor" )
243    pid=$( xenstore_read "$XENBUS_PATH/pid" )
244
245    [ -n "$minor" ] || fatal "minor missing"
246    [ -n "$pid" ] || fatal "pid missing"
247    do_or_die tap-ctl destroy -p "$pid" -m "$minor" > /dev/null
248
249    release_lock "block"
250}
251
252command=$1
253target=$(xenstore-read $XENBUS_PATH/params || true)
254if [ -z "$target" ]; then
255    fatal "No information about the target"
256fi
257
258parse_target "$target"
259
260check_tools || exit 1
261
262mode=$( xenstore_read $XENBUS_PATH/mode )
263mode=$( canonicalise_mode $mode )
264
265# needed for same_vm
266FRONTEND_ID=$(xenstore_read "$XENBUS_PATH/frontend-id")
267FRONTEND_UUID=$(xenstore_read_default \
268                    "/local/domain/$FRONTEND_ID/vm" 'unknown')
269
270case $command in
271add)
272    add
273    ;;
274remove)
275    remove
276    ;;
277*)
278    exit 1
279    ;;
280esac
281