Branch data Line data Source code
1 : : /*
2 : : * Copyright (c) 2016, Citrix Systems, Inc.
3 : : *
4 : : * All rights reserved.
5 : : *
6 : : * Redistribution and use in source and binary forms, with or without
7 : : * modification, are permitted provided that the following conditions are met:
8 : : *
9 : : * 1. Redistributions of source code must retain the above copyright
10 : : * notice, this list of conditions and the following disclaimer.
11 : : * 2. Redistributions in binary form must reproduce the above copyright
12 : : * notice, this list of conditions and the following disclaimer in the
13 : : * documentation and/or other materials provided with the distribution.
14 : : * 3. Neither the name of the copyright holder nor the names of its
15 : : * contributors may be used to endorse or promote products derived from
16 : : * this software without specific prior written permission.
17 : : *
18 : : * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 : : * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 : : * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 : : * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
22 : : * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 : : * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 : : * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 : : * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 : : * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 : : * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 : : * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 : : *
30 : : * This file contains the handler executed when the front-end XenStore path of
31 : : * a VBD gets modified.
32 : : */
33 : :
34 : : #include "tapback.h"
35 : :
36 : : #include <xen/io/protocols.h>
37 : : #include "xen_blkif.h"
38 : :
39 : : /**
40 : : * Switches the back-end state of the device by writing to XenStore.
41 : : *
42 : : * @param device the VBD
43 : : * @param state the state to switch to
44 : : * @returns 0 on success, an error code otherwise
45 : : */
46 : : int
47 : 0 : xenbus_switch_state(vbd_t * const device,
48 : : const XenbusState state)
49 : : {
50 : : int err;
51 : :
52 : 0 : ASSERT(device);
53 : :
54 : : /*
55 : : * TODO Ensure @state contains a legitimate XenbusState value.
56 : : * TODO Check for valid state transitions?
57 : : */
58 : :
59 : 0 : err = -tapback_device_printf(device, XBT_NULL, "state", false, "%u",
60 : : state);
61 : 0 : if (err)
62 : 0 : WARN(device, "failed to switch back-end state to %s: %s\n",
63 : : xenbus_strstate(state), strerror(err));
64 : : else {
65 : 0 : DBG(device, "switched back-end state to %s\n", xenbus_strstate(state));
66 : 0 : device->state = state;
67 : : }
68 : 0 : return err;
69 : : }
70 : :
71 : : /**
72 : : * Core functions that instructs the tapdisk to connect to the shared ring (if
73 : : * not already connected).
74 : : *
75 : : * If the tapdisk is not already connected, all the necessary information is
76 : : * read from XenStore and the tapdisk gets connected using this information.
77 : : * This function is idempotent: if the tapback daemon gets restarted this
78 : : * function will be called again but it won't really do anything.
79 : : *
80 : : * @param device the VBD the tapdisk should connect to
81 : : * @returns (a) 0 on success, (b) ESRCH if the tapdisk is not available, and
82 : : * (c) an error code otherwise
83 : : */
84 : : static inline int
85 : 0 : connect_tap(vbd_t * const device)
86 : : {
87 : 0 : evtchn_port_t port = 0;
88 : 0 : grant_ref_t *gref = NULL;
89 : 0 : int err = 0;
90 : 0 : char *proto_str = NULL;
91 : 0 : char *persistent_grants_str = NULL;
92 : 0 : int nr_pages = 0, proto = 0, order = 0;
93 : 0 : bool persistent_grants = false;
94 : :
95 : 0 : ASSERT(device);
96 : :
97 : : /*
98 : : * FIXME disconnect if already connected and then reconnect, this is how
99 : : * blkback does.
100 : : * FIXME If we're already connected, why did we end up here in the first
101 : : * place?
102 : : */
103 : 0 : ASSERT(!device->connected);
104 : :
105 : : /*
106 : : * The physical-device XenStore key has not been written yet.
107 : : */
108 : 0 : if (!device->tap) {
109 : 0 : DBG(device, "no tapdisk yet\n");
110 : : err = ESRCH;
111 : : goto out;
112 : : }
113 : : /*
114 : : * TODO How can we make sure we're not missing a node written by the
115 : : * front-end? Use xs_directory?
116 : : */
117 : :
118 : 0 : if (1 != tapback_device_scanf_otherend(device, XBT_NULL, RING_PAGE_ORDER,
119 : : "%d", &order))
120 : 0 : order = 0;
121 : :
122 : 0 : nr_pages = 1 << order;
123 : :
124 : 0 : if (!(gref = calloc(nr_pages, sizeof(grant_ref_t)))) {
125 : 0 : WARN(device, "failed to allocate memory for grant refs.\n");
126 : : err = ENOMEM;
127 : : goto out;
128 : : }
129 : :
130 : : /*
131 : : * Read the grant references.
132 : : */
133 : 0 : if (order) {
134 : 0 : int i = 0;
135 : : /*
136 : : * +10 is for INT_MAX, +1 for NULL termination
137 : : */
138 : :
139 : : static const size_t len = sizeof(RING_REF) + 10 + 1;
140 : 0 : char ring_ref[len];
141 : 0 : for (i = 0; i < nr_pages; i++) {
142 : 0 : if (snprintf(ring_ref, len, "%s%d", RING_REF, i) >= (int)len) {
143 : 0 : DBG(device, "error printing to buffer\n");
144 : : err = EINVAL;
145 : 0 : goto out;
146 : : }
147 : 0 : if (1 != tapback_device_scanf_otherend(device, XBT_NULL, ring_ref,
148 : 0 : "%u", &gref[i])) {
149 : 0 : WARN(device, "failed to read grant ref 0x%x\n", i);
150 : : err = ENOENT;
151 : : goto out;
152 : : }
153 : : }
154 : : } else {
155 : 0 : if (1 != tapback_device_scanf_otherend(device, XBT_NULL, RING_REF,
156 : : "%u", &gref[0])) {
157 : 0 : WARN(device, "failed to read grant ref\n");
158 : : err = ENOENT;
159 : : goto out;
160 : : }
161 : : }
162 : :
163 : : /*
164 : : * Read the event channel.
165 : : */
166 : 0 : if (1 != tapback_device_scanf_otherend(device, XBT_NULL, EVENT_CHANNEL,
167 : : "%u", &port)) {
168 : 0 : WARN(device, "failed to read event channel\n");
169 : : err = ENOENT;
170 : : goto out;
171 : : }
172 : :
173 : : /*
174 : : * Read the guest VM's ABI.
175 : : */
176 : 0 : if (!(proto_str = tapback_device_read_otherend(device, XBT_NULL, PROTO)))
177 : : proto = BLKIF_PROTOCOL_X86_32;
178 : 0 : else if (!strcmp(proto_str, XEN_IO_PROTO_ABI_NATIVE))
179 : : proto = BLKIF_PROTOCOL_NATIVE;
180 : 0 : else if (!strcmp(proto_str, XEN_IO_PROTO_ABI_X86_32))
181 : : proto = BLKIF_PROTOCOL_X86_32;
182 : 0 : else if (!strcmp(proto_str, XEN_IO_PROTO_ABI_X86_64))
183 : : proto = BLKIF_PROTOCOL_X86_64;
184 : : else {
185 : 0 : WARN(device, "unsupported protocol %s\n", proto_str);
186 : : err = EINVAL;
187 : : goto out;
188 : : }
189 : :
190 : 0 : DBG(device, "protocol=%d\n", proto);
191 : :
192 : : /*
193 : : * Does the front-end support persistent grants?
194 : : */
195 : 0 : persistent_grants_str = tapback_device_read_otherend(device, XBT_NULL,
196 : : FEAT_PERSIST);
197 : 0 : if (persistent_grants_str) {
198 : 0 : if (!strcmp(persistent_grants_str, "0"))
199 : : persistent_grants = false;
200 : 0 : else if (!strcmp(persistent_grants_str, "1"))
201 : : persistent_grants = true;
202 : : else {
203 : 0 : WARN(device, "invalid %s value: %s\n", FEAT_PERSIST,
204 : : persistent_grants_str);
205 : : err = EINVAL;
206 : : goto out;
207 : : }
208 : : }
209 : : else
210 : 0 : DBG(device, "front-end doesn't support persistent grants\n");
211 : :
212 : : /*
213 : : * persistent grants are not yet supported
214 : : */
215 : 0 : if (persistent_grants)
216 : 0 : WARN(device, "front-end supports persistent grants but we don't\n");
217 : :
218 : : /*
219 : : * Create the shared ring and ask the tapdisk to connect to it.
220 : : */
221 : 0 : if ((err = -tap_ctl_connect_xenblkif(device->tap->pid, device->domid,
222 : : device->devid, device->polling_duration, device->polling_idle_threshold,
223 : : gref, order, port, proto, NULL,
224 : : device->minor))) {
225 : : /*
226 : : * This happens if the tapback dameon gets restarted while there are
227 : : * active VBDs.
228 : : */
229 : 0 : if (err == EALREADY) {
230 : 0 : INFO(device, "tapdisk[%d] minor=%d already connected to the "
231 : : "shared ring\n", device->tap->pid, device->tap->minor);
232 : : err = 0;
233 : : } else {
234 : 0 : WARN(device, "tapdisk[%d] failed to connect to the shared "
235 : : "ring: %s\n", device->tap->pid, strerror(err));
236 : : goto out;
237 : : }
238 : : }
239 : :
240 : 0 : device->connected = true;
241 : :
242 : 0 : DBG(device, "tapdisk[%d] connected to shared ring\n", device->tap->pid);
243 : :
244 : : out:
245 : 0 : if (err && device->connected) {
246 : 0 : const int err2 = -tap_ctl_disconnect_xenblkif(device->tap->pid,
247 : 0 : device->domid, device->devid, NULL);
248 : 0 : if (err2) {
249 : 0 : WARN(device, "error disconnecting tapdisk[%d] from the shared "
250 : : "ring (error ignored): %s\n", device->tap->pid,
251 : : strerror(err2));
252 : : }
253 : :
254 : 0 : device->connected = false;
255 : : }
256 : :
257 : 0 : free(gref);
258 : 0 : free(proto_str);
259 : 0 : free(persistent_grants_str);
260 : :
261 : 0 : return err;
262 : : }
263 : :
264 : :
265 : : /**
266 : : * Returns 0 on success, a negative error code otherwise.
267 : : */
268 : : static inline int
269 : 0 : connect_frontend(vbd_t *device) {
270 : :
271 : 0 : int err = 0;
272 : 0 : xs_transaction_t xst = XBT_NULL;
273 : 0 : bool abort_transaction = false;
274 : :
275 : 0 : ASSERT(device);
276 : :
277 : : do {
278 : 0 : if (!(xst = xs_transaction_start(device->backend->xs))) {
279 : 0 : err = -errno;
280 : 0 : WARN(device, "failed to start transaction: %s\n", strerror(err));
281 : : goto out;
282 : : }
283 : :
284 : 0 : abort_transaction = true;
285 : :
286 : : /*
287 : : * FIXME blkback writes discard-granularity, discard-alignment,
288 : : * discard-secure, feature-discard but we don't.
289 : : */
290 : :
291 : : /*
292 : : * Write the number of sectors, sector size, info, and barrier support
293 : : * to the back-end path in XenStore so that the front-end creates a VBD
294 : : * with the appropriate characteristics.
295 : : */
296 : 0 : if ((err = tapback_device_printf(device, xst, "feature-barrier", true,
297 : 0 : "%d", device->backend->barrier ? 1 : 0))) {
298 : 0 : WARN(device, "failed to write feature-barrier: %s\n",
299 : : strerror(-err));
300 : : break;
301 : : }
302 : :
303 : 0 : if ((err = tapback_device_printf(device, xst, "sector-size", true,
304 : : "%u", device->sector_size))) {
305 : 0 : WARN(device, "failed to write sector-size: %s\n", strerror(-err));
306 : : break;
307 : : }
308 : :
309 : 0 : if ((err = tapback_device_printf(device, xst, "sectors", true, "%llu",
310 : : device->sectors))) {
311 : 0 : WARN(device, "failed to write sectors: %s\n", strerror(-err));
312 : : break;
313 : : }
314 : :
315 : 0 : if ((err = tapback_device_printf(device, xst, "info", true, "%u",
316 : : device->info))) {
317 : 0 : WARN(device, "failed to write info: %s\n", strerror(-err));
318 : : break;
319 : : }
320 : :
321 : 0 : abort_transaction = false;
322 : 0 : if (!xs_transaction_end(device->backend->xs, xst, 0)) {
323 : 0 : err = -errno;
324 : 0 : ASSERT(err);
325 : : }
326 : 0 : } while (err == -EAGAIN);
327 : :
328 : 0 : if (abort_transaction) {
329 : 0 : if (!xs_transaction_end(device->backend->xs, xst, 1)) {
330 : 0 : int err2 = errno;
331 : 0 : WARN(device, "failed to abort transaction: %s\n", strerror(err2));
332 : : }
333 : : goto out;
334 : : }
335 : :
336 : 0 : if (err) {
337 : 0 : WARN(device, "failed to end transaction: %s\n", strerror(-err));
338 : : goto out;
339 : : }
340 : :
341 : 0 : err = -xenbus_switch_state(device, XenbusStateConnected);
342 : 0 : if (err)
343 : 0 : WARN(device, "failed to switch back-end state to connected: %s\n",
344 : : strerror(-err));
345 : : out:
346 : 0 : return err;
347 : : }
348 : :
349 : : /*
350 : : * Returns 0 on success, a positive error code otherwise.
351 : : *
352 : : * If tapdisk is not yet available (the physical-device key has not yet been
353 : : * written), ESRCH is returned.
354 : : */
355 : : static inline int
356 : 0 : xenbus_connect(vbd_t *device) {
357 : : int err;
358 : :
359 : 0 : ASSERT(device);
360 : :
361 : 0 : err = connect_tap(device);
362 : : /*
363 : : * No tapdisk yet (the physical-device XenStore key has not been written).
364 : : */
365 : 0 : if (err == ESRCH)
366 : : goto out;
367 : : /*
368 : : * Even if tapdisk is already connected to the shared ring, we continue
369 : : * connecting since we don't know how far the connection process had gone
370 : : * before the tapback daemon was restarted.
371 : : */
372 : 0 : if (err && err != -EALREADY)
373 : : goto out;
374 : 0 : err = -connect_frontend(device);
375 : : out:
376 : 0 : return err;
377 : : }
378 : :
379 : : /**
380 : : * Callback that is executed when the front-end goes to StateClosed.
381 : : *
382 : : * Instructs the tapdisk to disconnect itself from the shared ring and switches
383 : : * the back-end state to StateClosed.
384 : : *
385 : : * @param xdevice the VBD whose tapdisk should be disconnected
386 : : * @param state unused
387 : : * @returns 0 on success, a +errno otherwise
388 : : *
389 : : * XXX Only called by frontend_changed.
390 : : */
391 : : static inline int
392 : 0 : backend_close(vbd_t * const device)
393 : : {
394 : 0 : int err = 0;
395 : :
396 : 0 : ASSERT(device);
397 : :
398 : 0 : if (!device->connected) {
399 : : /*
400 : : * This VBD might be a CD-ROM device, or a disk device that never went
401 : : * to state Connected.
402 : : */
403 : 0 : if (device->tap)
404 : 0 : DBG(device, "tapdisk[%d] not connected\n", device->tap->pid);
405 : : else
406 : 0 : DBG(device, "no tapdisk connected\n");
407 : : } else {
408 : 0 : ASSERT(device->tap);
409 : :
410 : 0 : DBG(device, "disconnecting tapdisk[%d] minor=%d from the ring\n",
411 : : device->tap->pid, device->minor);
412 : :
413 : 0 : err = -tap_ctl_disconnect_xenblkif(device->tap->pid, device->domid,
414 : : device->devid, NULL);
415 : 0 : if (err) {
416 : 0 : if (err == ESRCH) {/* tapdisk might have died :-( */
417 : 0 : WARN(device, "tapdisk[%d] not running\n", device->tap->pid);
418 : : } else {
419 : 0 : WARN(device, "error disconnecting tapdisk[%d] minor=%d from "
420 : : "the ring: %s\n", device->tap->pid, device->minor,
421 : : strerror(err));
422 : 0 : return err;
423 : : }
424 : : }
425 : :
426 : 0 : device->connected = false;
427 : : }
428 : :
429 : 0 : return xenbus_switch_state(device, XenbusStateClosed);
430 : : }
431 : :
432 : : int
433 : 0 : frontend_changed(vbd_t * const device, const XenbusState state)
434 : : {
435 : 0 : int err = 0;
436 : :
437 : 0 : DBG(device, "front-end switched to state %s\n", xenbus_strstate(state));
438 : 0 : device->frontend_state = state;
439 : :
440 : 0 : switch (state) {
441 : : case XenbusStateInitialising:
442 : 0 : if (device->hotplug_status_connected)
443 : 0 : err = xenbus_switch_state(device, XenbusStateInitWait);
444 : : break;
445 : : case XenbusStateInitialised:
446 : : case XenbusStateConnected:
447 : 0 : if (!device->hotplug_status_connected)
448 : 0 : DBG(device, "udev scripts haven't yet run\n");
449 : : else {
450 : 0 : if (device->state != XenbusStateConnected) {
451 : 0 : DBG(device, "connecting to front-end\n");
452 : 0 : err = xenbus_connect(device);
453 : : } else
454 : 0 : DBG(device, "already connected\n");
455 : : }
456 : : break;
457 : : case XenbusStateClosing:
458 : 0 : err = xenbus_switch_state(device, XenbusStateClosing);
459 : 0 : break;
460 : : case XenbusStateClosed:
461 : 0 : err = backend_close(device);
462 : 0 : break;
463 : : case XenbusStateUnknown:
464 : : err = 0;
465 : : break;
466 : : default:
467 : 0 : err = EINVAL;
468 : 0 : WARN(device, "invalid front-end state %d\n", state);
469 : : break;
470 : : }
471 : 0 : return err;
472 : : }
473 : :
474 : : int
475 : 0 : tapback_backend_handle_otherend_watch(backend_t *backend,
476 : : const char * const path)
477 : : {
478 : 0 : vbd_t *device = NULL;
479 : 0 : int err = 0, state = 0;
480 : 0 : char *s = NULL, *end = NULL, *_path = NULL;
481 : :
482 : 0 : ASSERT(backend);
483 : 0 : ASSERT(path);
484 : :
485 : : /*
486 : : * Find the device that has the same front-end state path.
487 : : *
488 : : * There should definitely be such a device in our list, otherwise this
489 : : * function would not have executed at all, since we would not be waiting
490 : : * on that XenStore path. The XenStore path we wait for is:
491 : : * /local/domain/<domid>/device/vbd/<devname>/state. In order to watch this
492 : : * path, it means that we have received a device create request, so the
493 : : * device will be there.
494 : : *
495 : : * TODO Instead of this linear search we could do better (hash table etc).
496 : : */
497 : 0 : tapback_backend_find_device(backend, device,
498 : : device->frontend_state_path &&
499 : : !strcmp(device->frontend_state_path, path));
500 : 0 : if (!device) {
501 : 0 : WARN(NULL, "path \'%s\' does not correspond to a known device\n",
502 : : path);
503 : 0 : return ENODEV;
504 : : }
505 : :
506 : : /*
507 : : * Read the new front-end's state.
508 : : */
509 : 0 : s = tapback_xs_read(device->backend->xs, XBT_NULL, "%s",
510 : : device->frontend_state_path);
511 : 0 : if (!s) {
512 : 0 : err = errno;
513 : : /*
514 : : * If the front-end XenBus node is missing, the XenBus device has been
515 : : * removed: remove the XenBus back-end node.
516 : : */
517 : 0 : if (err == ENOENT) {
518 : 0 : err = asprintf(&_path, "%s/%s/%d/%d", XENSTORE_BACKEND,
519 : 0 : device->backend->name, device->domid, device->devid);
520 : 0 : if (err == -1) {
521 : 0 : err = errno;
522 : 0 : WARN(device, "failed to asprintf: %s\n", strerror(err));
523 : : goto out;
524 : : }
525 : 0 : err = 0;
526 : 0 : if (!xs_rm(device->backend->xs, XBT_NULL, _path)) {
527 : 0 : if (errno != ENOENT) {
528 : 0 : err = errno;
529 : 0 : WARN(device, "failed to remove %s: %s\n", path,
530 : : strerror(err));
531 : : }
532 : : }
533 : : }
534 : : } else {
535 : 0 : state = strtol(s, &end, 0);
536 : 0 : if (*end != 0 || end == s) {
537 : 0 : WARN(device, "invalid XenBus state '%s'\n", s);
538 : : err = EINVAL;
539 : : } else
540 : 0 : err = frontend_changed(device, state);
541 : : }
542 : :
543 : : out:
544 : 0 : free(s);
545 : 0 : free(_path);
546 : 0 : return err;
547 : : }
548 : :
549 : : struct backend_slave*
550 : 0 : tapback_find_slave(const backend_t *master, const domid_t domid) {
551 : :
552 : 0 : struct backend_slave _slave, **__slave = NULL;
553 : :
554 : 0 : ASSERT(master);
555 : :
556 : 0 : _slave.master.domid = domid;
557 : :
558 : 0 : __slave = tfind(&_slave, &master->master.slaves, compare);
559 : 0 : if (!__slave)
560 : 0 : return NULL;
561 : 0 : return *__slave;
562 : : }
|