1#!/bin/bash
2
3dir=$(dirname "$0")
4. "$dir/block-common.sh"
5
6expand_dev() {
7  local dev
8  case $1 in
9  /*)
10    dev=$1
11    ;;
12  *)
13    dev=/dev/$1
14    ;;
15  esac
16  echo -n $dev
17}
18
19find_free_loopback_helper() {
20  local next_devnum=0
21  local busy_devnum
22  while read busy_devnum; do
23    if [ "$next_devnum" != "$busy_devnum" ]; then
24      break
25    fi
26    let next_devnum=$next_devnum+1
27  done
28  echo "/dev/loop${next_devnum}"
29}
30
31# Not all distros have "losetup -f"
32find_free_loopback_dev() {
33  local loopdev
34  loopdev=$(losetup -a | sed -e 's+^/dev/loop++' -e 's/:.*//' | find_free_loopback_helper)
35  if [ -n "$loopdev" ] && [ -b "$loopdev" ]; then
36    echo "$loopdev"
37  fi
38}
39
40##
41# check_sharing devtype device mode [inode]
42#
43# Check whether the device requested is already in use.  To use the device in
44# read-only mode, it may be in use in read-only mode, but may not be in use in
45# read-write anywhere at all.  To use the device in read-write mode, it must
46# not be in use anywhere at all.
47#
48# Prints one of
49#
50#    'local $d': the device ($d) may not be used because it is mounted in the
51#                current (i.e. the privileged domain) in a way incompatible
52#                with the requested mode;
53#    'guest $d': the device may not be used because it is already mounted
54#                through device $d by a guest in a way incompatible with the
55#                requested mode; or
56#    'ok':       the device may be used.
57#
58check_sharing()
59{
60  local devtype=$1
61  local dev="$2"
62  local mode="$3"
63  local devmm=","
64
65  if [ "$devtype" = "file" ];
66  then
67    local inode="$4"
68
69    shared_list=$(losetup -a |
70          sed -n -e "s@^\([^:]\+\)\(:[[:blank:]]\[0*${dev}\]:${inode}[[:blank:]](.*)\)@\1@p" )
71    for dev in $shared_list
72    do
73      if [ -n "$dev" ]
74      then
75        devmm="${devmm}$(device_major_minor $dev),"
76      fi
77    done
78    # if $devmm is unchanged, file being checked is not a shared loopback device
79    if [ "$devmm" = "," ];
80    then
81      echo 'ok'
82      return
83    fi
84  else
85    devmm=${devmm}$(device_major_minor "$dev")","
86  fi
87
88  local file
89
90  if [ "$mode" = 'w' ]
91  then
92    toskip="^$"
93  else
94    toskip="^[^ ]* [^ ]* [^ ]* ro[, ]"
95  fi
96
97  for file in $(cat /proc/mounts | grep -v "$toskip" | cut -f 1 -d ' ')
98  do
99    if [ -e "$file" ]
100    then
101      local d=$(device_major_minor "$file")
102
103      # checking for $d in $devmm is best through the [[...]] bashism
104      if [[ "$devmm" == *",$d,"* ]]
105      then
106        echo "local $d"
107        return
108      fi
109    fi
110  done
111
112  local base_path="$XENBUS_BASE_PATH/$XENBUS_TYPE"
113  for dom in $(xenstore-list "$base_path")
114  do
115    for dev in $(xenstore-list "$base_path/$dom")
116    do
117      d=$(xenstore_read_default "$base_path/$dom/$dev/physical-device" "")
118
119      # checking for $d in $devmm is best through the [[...]] bashism
120      if [ -n "$d" ] && [[ "$devmm" == *",$d,"* ]]
121      then
122        if [ "$mode" = 'w' ]
123        then
124          if ! same_vm $dom
125          then
126            echo "guest $d"
127            return
128          fi
129        else
130          local m=$(xenstore_read_default "$base_path/$dom/$dev/mode" "")
131          m=$(canonicalise_mode "$m")
132
133          if [ "$m" = 'w' ]
134          then
135            if ! same_vm $dom
136            then
137              echo "guest $d"
138              return
139            fi
140          fi
141        fi
142      fi
143    done
144  done
145
146  echo 'ok'
147}
148
149
150##
151# check_device_sharing dev mode
152#
153# Perform the sharing check for the given physical device and mode.
154#
155check_device_sharing()
156{
157  local dev="$1"
158  local mode=$(canonicalise_mode "$2")
159  local type="device"
160  local result
161
162  if [ "x$mode" = 'x!' ]
163  then
164    return 0
165  fi
166
167  result=$(check_sharing "$type" "$dev" "$mode")
168
169  if [ "$result" != 'ok' ]
170  then
171    do_ebusy "Device $dev is mounted " "$mode" "${result%% *}"
172  fi
173}
174
175
176##
177# check_device_sharing file dev mode inode
178#
179# Perform the sharing check for the given file, with its corresponding
180# device, inode and mode. As the file can be mounted multiple times,
181# the inode is passed through to check_sharing for all instances to be
182# checked.
183#
184check_file_sharing()
185{
186  local file="$1"
187  local dev="$2"
188  local mode="$3"
189  local inode="$4"
190  local type="file"
191  local result
192
193  result=$(check_sharing "$type" "$dev" "$mode" "$inode")
194
195  if [ "$result" != 'ok' ]
196  then
197    do_ebusy "File $file is loopback-mounted through ${result#* },
198which is mounted " "$mode" "${result%% *}"
199  fi
200}
201
202
203##
204# do_ebusy prefix mode result
205#
206# Helper function for check_device_sharing check_file_sharing, calling ebusy
207# with an error message constructed from the given prefix, mode, and result
208# from a call to check_sharing.
209#
210do_ebusy()
211{
212  local prefix="$1"
213  local mode="$2"
214  local result="$3"
215
216  if [ "$result" = 'guest' ]
217  then
218    dom='a guest '
219    when='now'
220  else
221    dom='the privileged '
222    when='by a guest'
223  fi
224
225  if [ "$mode" = 'w' ]
226  then
227    m1=''
228    m2=''
229  else
230    m1='read-write '
231    m2='read-only '
232  fi
233
234  release_lock "block"
235  ebusy \
236"${prefix}${m1}in ${dom}domain,
237and so cannot be mounted ${m2}${when}."
238}
239
240
241t=$(xenstore_read_default "$XENBUS_PATH/type" 'MISSING')
242p=$(xenstore_read "$XENBUS_PATH/params")
243mode=$(xenstore_read "$XENBUS_PATH/mode")
244if [ -b "$p" ]; then
245    truetype="phy"
246elif [ -f "$p" ]; then
247    truetype="file"
248fi
249
250case "$command" in
251  add)
252    phys=$(xenstore_read_default "$XENBUS_PATH/physical-device" 'MISSING')
253    if [ "$phys" != 'MISSING' ]
254    then
255      # Depending upon the hotplug configuration, it is possible for this
256      # script to be called twice, so just bail.
257      exit 0
258    fi
259
260    FRONTEND_ID=$(xenstore_read "$XENBUS_PATH/frontend-id")
261    FRONTEND_UUID=$(xenstore_read_default \
262            "/local/domain/$FRONTEND_ID/vm" 'unknown')
263
264    case $truetype in
265      phy)
266        dev=$(expand_dev $p)
267
268        if [ -L "$dev" ]
269        then
270          dev=$(readlink -f "$dev") || fatal "$dev link does not exist."
271        fi
272        test -e "$dev" || fatal "$dev does not exist."
273        test -b "$dev" || fatal "$dev is not a block device."
274
275        claim_lock "block"
276        check_device_sharing "$dev" "$mode"
277	write_dev "$dev"
278        release_lock "block"
279	exit 0
280	;;
281
282      file)
283        # Canonicalise the file, for sharing check comparison, and the mode
284        # for ease of use here.
285        file=$(readlink -f "$p") || fatal "$p does not exist."
286        test -f "$file" || fatal "$file does not exist."
287        mode=$(canonicalise_mode "$mode")
288
289        claim_lock "block"
290
291        # Avoid a race with the remove if the path has been deleted, or
292	# otherwise changed from "InitWait" state e.g. due to a timeout
293        xenbus_state=$(xenstore_read_default "$XENBUS_PATH/state" 'unknown')
294        if [ "$xenbus_state" != '2' ]
295        then
296          release_lock "block"
297          fatal "Path closed or removed during hotplug add: $XENBUS_PATH state: $xenbus_state"
298        fi
299
300        if [ "$mode" = 'w' ] && ! stat "$file" -c %A | grep -q w
301        then
302          release_lock "block"
303          ebusy \
304"File $file is read-only, and so I will not
305mount it read-write in a guest domain."
306        fi
307
308        if [ "x$mode" != 'x!' ]
309        then
310          inode=$(stat -c '%i' "$file")
311          dev=$(stat -c '%D' "$file")
312          if [ -z "$inode" ] || [ -z "$dev" ]
313          then
314            fatal "Unable to lookup $file: dev: $dev inode: $inode"
315          fi
316
317          check_file_sharing "$file" "$dev" "$mode" "$inode"
318        fi
319
320        loopdev=$(losetup -f 2>/dev/null || find_free_loopback_dev)
321        if [ "$loopdev" = '' ]
322        then
323          release_lock "block"
324          fatal 'Failed to find an unused loop device'
325        fi
326
327        if LANG=C losetup -h 2>&1 | grep read-only >/dev/null
328        then
329          roflag="-$mode"; roflag="${roflag#-w}"; roflag="${roflag#-!}"
330        else
331          roflag=''
332        fi
333        do_or_die losetup $roflag "$loopdev" "$file"
334        xenstore_write "$XENBUS_PATH/node" "$loopdev"
335        write_dev "$loopdev"
336        release_lock "block"
337        exit 0
338	;;
339
340      "")
341        claim_lock "block"
342        success
343        release_lock "block"
344	;;
345    esac
346    ;;
347
348  remove)
349    case $truetype in
350      phy)
351	exit 0
352	;;
353
354      file)
355        claim_lock "block"
356        node=$(xenstore_read "$XENBUS_PATH/node")
357	losetup -d "$node"
358        release_lock "block"
359	exit 0
360	;;
361
362      "")
363        exit 0
364	;;
365    esac
366    ;;
367
368esac
369
370# If we've reached here, $t is neither phy nor file, so fire a helper script.
371[ -x ${XEN_SCRIPT_DIR}/block-"$t" ] && \
372  ${XEN_SCRIPT_DIR}/block-"$t" "$command"
373