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 back-end XenStore path gets
31 : : * modified.
32 : : */
33 : :
34 : : #include "config.h"
35 : :
36 : : #include "tapback.h"
37 : : #include "xenstore.h"
38 : : #include <xen/io/blkif.h>
39 : : #include <unistd.h>
40 : : #include <signal.h>
41 : : #include <stdlib.h>
42 : : #include "xen_blkif.h"
43 : :
44 : : extern int tapdev_major;
45 : :
46 : : /**
47 : : * Removes the XenStore watch from the front-end.
48 : : *
49 : : * @param device the VBD whose front-end XenStore path should stop being
50 : : * watched
51 : : */
52 : : static void
53 : 0 : tapback_device_unwatch_frontend_state(vbd_t * const device)
54 : : {
55 : 0 : ASSERT(device);
56 : :
57 : 0 : if (device->frontend_state_path)
58 : : /* TODO check return code */
59 : 0 : xs_unwatch(device->backend->xs, device->frontend_state_path,
60 : 0 : device->backend->frontend_token);
61 : :
62 : 0 : free(device->frontend_state_path);
63 : 0 : device->frontend_state_path = NULL;
64 : 0 : }
65 : :
66 : : /**
67 : : * Destroys and deallocates the back-end part of a VBD.
68 : : *
69 : : * @param device the VBD to destroy
70 : : * @returns 0 on success, -errno on failure.
71 : : */
72 : : static int
73 : 0 : tapback_backend_destroy_device(vbd_t * const device)
74 : : {
75 : : int err;
76 : :
77 : 0 : ASSERT(device);
78 : :
79 : 0 : DBG(device, "removing VBD\n");
80 : :
81 : 0 : if (device->tap && device->connected) {
82 : :
83 : 0 : DBG(device, "implicitly disconnecting tapdisk[%d] minor=%d from the "
84 : : "ring\n", device->tap->pid, device->minor);
85 : :
86 : 0 : err = tap_ctl_disconnect_xenblkif(device->tap->pid, device->domid,
87 : : device->devid, NULL);
88 : 0 : if (err) {
89 : 0 : if (err == -ESRCH || err == -ENOENT) {
90 : : /*
91 : : * TODO tapdisk might have died without cleaning up, in which
92 : : * case we'll receive an I/O error when trying to talk to it
93 : : * through the socket, maybe search for a process with that
94 : : * PID? Alternatively, we can spawn tapdisks through a daemon
95 : : * which will monitor tapdisks for abrupt deaths and clean up
96 : : * after them (e.g. remove the socket).
97 : : */
98 : 0 : INFO(device, "tapdisk[%d] not running\n", device->tap->pid);
99 : : err = 0;
100 : : } else {
101 : 0 : WARN(device, "cannot disconnect tapdisk[%d] minor=%d from the "
102 : : "ring: %s\n", device->tap->pid, device->minor,
103 : : strerror(-err));
104 : : }
105 : 0 : return err;
106 : : }
107 : : }
108 : :
109 : 0 : list_del(&device->backend_entry);
110 : :
111 : 0 : tapback_device_unwatch_frontend_state(device);
112 : :
113 : 0 : free(device->tap);
114 : 0 : free(device->frontend_path);
115 : 0 : free(device->name);
116 : 0 : free(device);
117 : :
118 : 0 : return 0;
119 : : }
120 : :
121 : : /**
122 : : * Retrieves the tapdisk designated to serve this device, storing this
123 : : * information in the supplied VBD handle.
124 : : *
125 : : * @param minor
126 : : * @param tap output parameter that receives the tapdisk process information.
127 : : * The parameter is undefined when the function returns a non-zero value.
128 : : * @returns 0 if a suitable tapdisk is found, ESRCH if no suitable tapdisk is
129 : : * found, and a negative error code in case of error
130 : : *
131 : : * TODO rename function
132 : : *
133 : : * XXX Only called by blkback_probe.
134 : : */
135 : : static inline int
136 : 0 : find_tapdisk(const int minor, tap_list_t *tap)
137 : : {
138 : 0 : struct list_head list = LIST_HEAD_INIT(list);
139 : : tap_list_t *_tap;
140 : : int err;
141 : :
142 : 0 : err = tap_ctl_list(&list);
143 : 0 : if (err) {
144 : 0 : WARN(NULL, "error listing tapdisks: %s\n", strerror(-err));
145 : : goto out;
146 : : }
147 : :
148 : 0 : err = -ESRCH;
149 : 0 : if (!list_empty(&list)) {
150 : 0 : tap_list_for_each_entry(_tap, &list) {
151 : 0 : if (_tap->minor == minor) {
152 : 0 : err = 0;
153 : : memcpy(tap, _tap, sizeof(*tap));
154 : : break;
155 : : }
156 : : }
157 : 0 : tap_ctl_list_free(&list);
158 : : } else
159 : 0 : DBG(NULL, "no tapdisks\n");
160 : :
161 : : out:
162 : 0 : return err;
163 : : }
164 : :
165 : : static inline int
166 : 0 : find_tapdisk_by_pid(const pid_t pid, const int minor, tap_list_t *tap)
167 : : {
168 : 0 : struct list_head list = LIST_HEAD_INIT(list);
169 : : tap_list_t *entry;
170 : : int err;
171 : :
172 : 0 : err = tap_ctl_list_pid(pid, &list);
173 : 0 : if (err) {
174 : 0 : WARN(NULL, "error listing tapdisks: %s\n", strerror(-err));
175 : : goto out;
176 : : }
177 : :
178 : 0 : err = -ESRCH;
179 : 0 : if (!list_empty(&list)) {
180 : 0 : tap_list_for_each_entry(entry, &list) {
181 : 0 : if (entry->minor == minor && entry->pid == pid) {
182 : 0 : err = 0;
183 : : memcpy(tap, entry, sizeof(*tap));
184 : : break;
185 : : }
186 : : }
187 : 0 : tap_ctl_list_free(&list);
188 : : } else {
189 : 0 : DBG(NULL, "no tapdisks\n");
190 : : }
191 : :
192 : : out:
193 : 0 : return err;
194 : : }
195 : :
196 : : /**
197 : : * Creates a device and adds it to the list of devices.
198 : : *
199 : : * Creating the device implies initializing the handle and retrieving all the
200 : : * information of the tapdisk serving this VBD.
201 : : *
202 : : * @param domid the ID of the domain where the VBD is created
203 : : * @param name the name of the device
204 : : * @returns the created device, NULL on error, sets errno
205 : : *
206 : : * FIXME If the transaction fails with any error code other than EAGAIN, we're
207 : : * not undoing everything we did in this function.
208 : : */
209 : : static inline vbd_t*
210 : 0 : tapback_backend_create_device(backend_t *backend,
211 : : const domid_t domid, const char * const name)
212 : : {
213 : 0 : vbd_t *device = NULL;
214 : 0 : int err = 0;
215 : :
216 : 0 : ASSERT(backend);
217 : 0 : ASSERT(name);
218 : :
219 : 0 : DBG(NULL, "%s creating device\n", name);
220 : :
221 : 0 : if (!(device = calloc(1, sizeof(*device)))) {
222 : 0 : WARN(NULL, "error allocating memory\n");
223 : 0 : err = errno;
224 : 0 : goto out;
225 : : }
226 : :
227 : 0 : device->backend = backend;
228 : 0 : list_add_tail(&device->backend_entry,
229 : : &device->backend->slave.slave.devices);
230 : :
231 : 0 : device->major = -1;
232 : 0 : device->minor = -1;
233 : :
234 : 0 : device->mode = false;
235 : 0 : device->cdrom = false;
236 : 0 : device->info = 0;
237 : 0 : device->polling_duration = 0;
238 : 0 : device->polling_idle_threshold = 0;
239 : :
240 : : /* TODO check for errors */
241 : 0 : device->devid = atoi(name);
242 : :
243 : 0 : device->domid = domid;
244 : :
245 : 0 : device->state = XenbusStateUnknown;
246 : 0 : device->frontend_state = XenbusStateUnknown;
247 : :
248 : 0 : if (!(device->name = strdup(name))) {
249 : 0 : err = -errno;
250 : 0 : goto out;
251 : : }
252 : :
253 : : /* Enable multi-page rings */
254 : 0 : err = tapback_device_printf(device, XBT_NULL, "max-ring-page-order",
255 : : true, "%d", MAX_RING_PAGE_ORDER);
256 : 0 : if (unlikely(err)) {
257 : 0 : WARN(device, "failed to write max-ring-page-order: %s\n",
258 : : strerror(-err));
259 : : goto out;
260 : : }
261 : :
262 : : out:
263 : 0 : if (err) {
264 : 0 : WARN(NULL, "%s: error creating device: %s\n", name, strerror(-err));
265 : 0 : if (device) {
266 : 0 : int err2 = tapback_backend_destroy_device(device);
267 : 0 : if (err2)
268 : 0 : WARN(device, "error cleaning up: failed to destroy the "
269 : : "device: %s\n", strerror(-err2));
270 : : }
271 : 0 : device = NULL;
272 : 0 : errno = err;
273 : : }
274 : 0 : return device;
275 : : }
276 : :
277 : : static int
278 : 0 : device_set_mode(vbd_t *device, const char *mode) {
279 : :
280 : 0 : ASSERT(device);
281 : 0 : ASSERT(mode);
282 : :
283 : 0 : if (!strcmp(mode, "r"))
284 : 0 : device->mode = false;
285 : 0 : else if (!strcmp(mode, "w"))
286 : 0 : device->mode = true;
287 : : else {
288 : 0 : WARN(device, "invalid value %s for XenStore key %s\n",
289 : : mode, MODE_KEY);
290 : : return -EINVAL;
291 : : }
292 : :
293 : : return 0;
294 : : }
295 : :
296 : : int
297 : 0 : physical_device_path_changed(vbd_t *device) {
298 : 0 : int err = 0, minor = -1;
299 : 0 : pid_t pid = -1;
300 : 0 : char *s = NULL;
301 : :
302 : : /*
303 : : * Assume there will never be an OS with pid_t larger than long.
304 : : */
305 : 0 : const char *nbd_pattern = "/run/blktap-control/nbd%ld.%d";
306 : 0 : device->tap = NULL;
307 : :
308 : 0 : s = tapback_device_read(device, XBT_NULL, PHYS_DEV_PATH_KEY);
309 : 0 : if (!s) {
310 : 0 : err = -errno;
311 : 0 : WARN(device, "Failed to read the %s: %s\n",
312 : : PHYS_DEV_PATH_KEY, strerror(-err));
313 : : goto done;
314 : : }
315 : :
316 : 0 : if (sscanf(s, nbd_pattern, &pid, &minor) != 2) {
317 : 0 : err = -EINVAL;
318 : 0 : WARN(device, "Malformed %s: '%s'\n", PHYS_DEV_PATH_KEY, s);
319 : : goto out;
320 : : }
321 : :
322 : 0 : device->tap = malloc(sizeof(*device->tap));
323 : 0 : if (!device->tap) {
324 : : err = -ENOMEM;
325 : : goto out;
326 : : }
327 : :
328 : 0 : err = find_tapdisk_by_pid(pid, minor, device->tap);
329 : 0 : if (err) {
330 : 0 : WARN(device, "Error looking for tapdisk: %s\n", strerror(-err));
331 : : goto out;
332 : : }
333 : 0 : INFO(device, "Found tapdisk[%d]\n", device->tap->pid);
334 : 0 : device->minor = minor;
335 : :
336 : : /*
337 : : * get the VBD parameters from the tapdisk
338 : : */
339 : 0 : if ((err = tap_ctl_info(device->tap->pid, &device->sectors,
340 : : &device->sector_size, &device->info,
341 : : device->minor))) {
342 : 0 : WARN(device, "error retrieving disk characteristics: %s\n",
343 : : strerror(-err));
344 : : goto out;
345 : : }
346 : :
347 : 0 : err = tapback_device_printf(device, XBT_NULL, "kthread-pid", false, "%d",
348 : 0 : device->tap->pid);
349 : 0 : if (unlikely(err)) {
350 : 0 : WARN(device, "warning: failed to write kthread-pid: %s\n",
351 : : strerror(-err));
352 : : goto out;
353 : : }
354 : :
355 : 0 : if (device->sector_size & 0x1ff || device->sectors <= 0) {
356 : 0 : WARN(device, "warning: unexpected device characteristics: sector "
357 : : "size=%d, sectors=%llu\n", device->sector_size,
358 : : device->sectors);
359 : : }
360 : :
361 : : /*
362 : : * The front-end might have switched to state Connected before
363 : : * physical-device is written. Check it's state and connect if necessary.
364 : : *
365 : : * TODO blkback ignores connection errors, let's do the same until we
366 : : * know better.
367 : : */
368 : 0 : err = -frontend_changed(device, device->frontend_state);
369 : 0 : if (err) {
370 : 0 : WARN(device, "failed to switch state: %s (error ignored)\n",
371 : : strerror(-err));
372 : : }
373 : : err = 0;
374 : : out:
375 : 0 : if (err) {
376 : 0 : free(device->tap);
377 : 0 : device->tap = NULL;
378 : 0 : device->sector_size = device->sectors = device->info = 0;
379 : : }
380 : 0 : free(s);
381 : : done:
382 : 0 : return err;
383 : : }
384 : :
385 : : /**
386 : : * Retrieves the minor number and locates the corresponding tapdisk, storing
387 : : * all relevant information in @device. Then, it attempts to advance the Xenbus
388 : : * state as it might be that everything is ready and all that was missing was
389 : : * the physical device.
390 : : *
391 : : * We might already have read the major/minor, but if the physical-device
392 : : * key triggered we need to make sure it hasn't changed. This also protects us
393 : : * against restarted transactions.
394 : : *
395 : : * Returns 0 on success, a negative error code otherwise.
396 : : */
397 : : static int
398 : 0 : physical_device_changed(vbd_t *device) {
399 : :
400 : 0 : char * s = NULL, *p = NULL, *end = NULL;
401 : 0 : int err = 0, major = 0, minor = 0;
402 : : unsigned int info;
403 : :
404 : 0 : ASSERT(device);
405 : :
406 : 0 : INFO(device, "physical_device_changed\n");
407 : :
408 : : /*
409 : : * Get the minor.
410 : : */
411 : 0 : s = tapback_device_read(device, XBT_NULL, PHYS_DEV_KEY);
412 : 0 : if (!s) {
413 : 0 : err = -errno;
414 : 0 : if (err != -ENOENT)
415 : 0 : WARN(device, "failed to read the physical-device: %s\n",
416 : : strerror(-err));
417 : : goto out;
418 : : }
419 : :
420 : 0 : if (strlen(s) == 0) {
421 : : /* empty string is a missing device */
422 : : err = -ENOENT;
423 : : goto out;
424 : : }
425 : :
426 : : /*
427 : : * The XenStore key physical-device contains "major:minor" in hex.
428 : : */
429 : 0 : p = strtok(s, ":");
430 : 0 : if (!p) {
431 : 0 : WARN(device, "malformed physical device '%s'\n", s);
432 : : err = -EINVAL;
433 : : goto out;
434 : : }
435 : 0 : major = strtol(p, &end, 16);
436 : 0 : if (*end != 0 || end == p) {
437 : 0 : WARN(device, "malformed physical device '%s'\n", s);
438 : : err = -EINVAL;
439 : : goto out;
440 : : }
441 : 0 : p = strtok(NULL, ":");
442 : 0 : if (unlikely(!p)) {
443 : 0 : WARN(device, "malformed physical device '%s'\n", s);
444 : : err = -EINVAL;
445 : : goto out;
446 : : }
447 : 0 : minor = strtol(p, &end, 16);
448 : 0 : if (*end != 0 || end == p) {
449 : 0 : WARN(device, "malformed physical device '%s'\n", s);
450 : : err = -EINVAL;
451 : : goto out;
452 : : }
453 : :
454 : 0 : if ((device->major >= 0 || device->minor >= 0) &&
455 : 0 : (major != device->major || minor != device->minor)) {
456 : 0 : WARN(device, "changing physical device from %x:%x to %x:%x not "
457 : : "supported\n", device->major, device->minor, major, minor);
458 : : err = -ENOSYS;
459 : : goto out;
460 : : }
461 : :
462 : 0 : if (major != tapdev_major) {
463 : 0 : WARN(device, "ignoring non-blktap2 physical device: %d\n", major);
464 : : err = -EINVAL;
465 : : goto out;
466 : : }
467 : :
468 : 0 : device->major = major;
469 : 0 : device->minor = minor;
470 : :
471 : 0 : device->tap = malloc(sizeof(*device->tap));
472 : 0 : if (!device->tap) {
473 : : err = -ENOMEM;
474 : : goto out;
475 : : }
476 : :
477 : : /*
478 : : * XXX If the physical-device key has been written we expect a tapdisk to
479 : : * have been created. If tapdisk is created after the physical-device key
480 : : * is written we have no way of being notified, so we will not be able to
481 : : * advance the back-end state.
482 : : */
483 : :
484 : 0 : DBG(device, "need to find tapdisk serving minor=%d\n", device->minor);
485 : :
486 : 0 : err = find_tapdisk(device->minor, device->tap);
487 : 0 : if (err) {
488 : 0 : WARN(device, "error looking for tapdisk: %s\n", strerror(-err));
489 : : goto out;
490 : : }
491 : :
492 : 0 : INFO(device, "found tapdisk[%d], for %d:%d\n", device->tap->pid, device->major, device->minor);
493 : :
494 : : /*
495 : : * get the VBD parameters from the tapdisk
496 : : */
497 : 0 : if ((err = tap_ctl_info(device->tap->pid, &device->sectors,
498 : : &device->sector_size, &info,
499 : : device->minor))) {
500 : 0 : WARN(device, "error retrieving disk characteristics: %s\n",
501 : : strerror(-err));
502 : : goto out;
503 : : }
504 : :
505 : 0 : err = tapback_device_printf(device, XBT_NULL, "kthread-pid", false, "%d",
506 : 0 : device->tap->pid);
507 : 0 : if (unlikely(err)) {
508 : 0 : WARN(device, "warning: failed to write kthread-pid: %s\n",
509 : : strerror(-err));
510 : : goto out;
511 : : }
512 : :
513 : 0 : if (device->sector_size & 0x1ff || device->sectors <= 0) {
514 : 0 : WARN(device, "warning: unexpected device characteristics: sector "
515 : : "size=%d, sectors=%llu\n", device->sector_size,
516 : : device->sectors);
517 : : }
518 : :
519 : : /*
520 : : * The front-end might have switched to state Connected before
521 : : * physical-device is written. Check it's state and connect if necessary.
522 : : *
523 : : * TODO blkback ignores connection errors, let's do the same until we
524 : : * know better.
525 : : */
526 : 0 : err = -frontend_changed(device, device->frontend_state);
527 : 0 : if (err)
528 : 0 : WARN(device, "failed to switch state: %s (error ignored)\n",
529 : : strerror(-err));
530 : : err = 0;
531 : : out:
532 : 0 : if (err) {
533 : 0 : free(device->tap);
534 : 0 : device->tap = NULL;
535 : 0 : device->sector_size = device->sectors = device->info = 0;
536 : : }
537 : 0 : free(s);
538 : 0 : return err;
539 : : }
540 : :
541 : : /**
542 : : * Start watching the front-end state path.
543 : : */
544 : : static int
545 : 0 : frontend(vbd_t *device) {
546 : :
547 : 0 : int err = 0;
548 : :
549 : 0 : ASSERT(device);
550 : :
551 : 0 : if (device->frontend_path)
552 : : return 0;
553 : :
554 : : /*
555 : : * Get the front-end path from XenStore. We need this to talk to
556 : : * the front-end.
557 : : */
558 : 0 : if (!(device->frontend_path = tapback_device_read(device, XBT_NULL,
559 : : FRONTEND_KEY))) {
560 : 0 : err = -errno;
561 : 0 : if (err != -ENOENT)
562 : 0 : WARN(device, "failed to read front-end path: %s\n", strerror(-err));
563 : : goto out;
564 : : }
565 : :
566 : : /*
567 : : * Watch the front-end path in XenStore for changes, i.e.
568 : : * /local/domain/<domid>/device/vbd/<devname>/state After this, we
569 : : * wait for the front-end to switch state to continue with the
570 : : * initialisation.
571 : : */
572 : 0 : if (asprintf(&device->frontend_state_path, "%s/state",
573 : : device->frontend_path) == -1) {
574 : 0 : err = -errno;
575 : 0 : WARN(device, "failed to asprintf: %s\n", strerror(-err));
576 : : goto out;
577 : : }
578 : :
579 : : /*
580 : : * We use the same token for all front-end watches. We don't have
581 : : * to use a unique token for each front-end watch because when a
582 : : * front-end watch fires we are given the XenStore path that
583 : : * changed.
584 : : */
585 : 0 : if (!xs_watch(device->backend->xs, device->frontend_state_path,
586 : 0 : device->backend->frontend_token)) {
587 : 0 : err = -errno;
588 : 0 : WARN(device, "failed to watch the front-end path (%s): %s\n",
589 : : device->frontend_state_path, strerror(-err));
590 : : goto out;
591 : : }
592 : :
593 : : out:
594 : 0 : if (err) {
595 : 0 : free(device->frontend_path);
596 : 0 : device->frontend_path = NULL;
597 : 0 : free(device->frontend_state_path);
598 : 0 : device->frontend_state_path = NULL;
599 : : }
600 : 0 : return err;
601 : : }
602 : :
603 : : /**
604 : : * Reads the hotplug-status XenStore key from the back-end and attemps to
605 : : * connect to the front-end.
606 : : *
607 : : * FIXME This function REQUIRES the device->frontend_path member to be
608 : : * populated, and this is done by frontend().
609 : : *
610 : : * Connecting to the front-end requires the physical-device key to have been
611 : : * written. This function will attempt to connect anyway, and connecting will
612 : : * fail half-way through. This is expected.
613 : : *
614 : : * Returns 0 in success, -errno on failure.
615 : : */
616 : : static int
617 : 0 : hotplug_status_changed(vbd_t * const device) {
618 : :
619 : 0 : int err = 0;
620 : 0 : char *hotplug_status = NULL, *device_type = NULL, *mode = NULL, *polling_duration = NULL, *polling_idle_threshold = NULL;
621 : :
622 : 0 : ASSERT(device);
623 : :
624 : 0 : if (unlikely(!device->frontend_path)) {
625 : : /*
626 : : * Something has gone horribly wrong.
627 : : */
628 : 0 : WARN(device, "hotplug scripts ran but front-end does not exist\n");
629 : : err = -EINVAL;
630 : : goto out;
631 : : }
632 : :
633 : 0 : hotplug_status = tapback_device_read(device, XBT_NULL, HOTPLUG_STATUS_KEY);
634 : 0 : if (!hotplug_status) {
635 : 0 : err = -errno;
636 : 0 : goto out;
637 : : }
638 : 0 : if (!strcmp(hotplug_status, "connected")) {
639 : :
640 : 0 : DBG(device, "physical device available (hotplug scripts ran)\n");
641 : :
642 : 0 : device->hotplug_status_connected = true;
643 : :
644 : 0 : device_type = tapback_device_read_otherend(device, XBT_NULL,
645 : : "device-type");
646 : 0 : if (!device_type) {
647 : 0 : err = -errno;
648 : 0 : WARN(device, "failed to read device-type: %s\n", strerror(-err));
649 : : goto out;
650 : : }
651 : 0 : if (!strcmp(device_type, "cdrom"))
652 : 0 : device->cdrom = true;
653 : 0 : else if (!strcmp(device_type, "disk"))
654 : 0 : device->cdrom = false;
655 : : else {
656 : 0 : WARN(device, "unsupported device type %s\n", device_type);
657 : : err = -EOPNOTSUPP;
658 : : goto out;
659 : : }
660 : :
661 : 0 : mode = tapback_device_read(device, XBT_NULL, MODE_KEY);
662 : 0 : if (!mode) {
663 : 0 : err = -errno;
664 : 0 : WARN(device, "failed to read XenStore key %s: %s\n",
665 : : MODE_KEY, strerror(-err));
666 : : goto out;
667 : : }
668 : 0 : err = device_set_mode(device, mode);
669 : 0 : if (err) {
670 : 0 : WARN(device, "failed to set device R/W mode: %s\n",
671 : : strerror(-err));
672 : : goto out;
673 : : }
674 : :
675 : 0 : if (device->cdrom)
676 : 0 : device->info |= VDISK_CDROM;
677 : 0 : if (!device->mode)
678 : 0 : device->info |= VDISK_READONLY;
679 : :
680 : : /* Set polling duration if the key exists. Otherwise polling duration remains 0, i.e. polling is disabled. */
681 : 0 : polling_duration = tapback_device_read(device, XBT_NULL, POLLING_DURATION);
682 : 0 : if (polling_duration)
683 : 0 : device->polling_duration = atoi(polling_duration);
684 : :
685 : : /* Set polling idle threshold if the key exists. Otherwise threshold remains 0, i.e. always permit polling */
686 : 0 : polling_idle_threshold = tapback_device_read(device, XBT_NULL, POLLING_IDLE_THRESHOLD);
687 : 0 : if (polling_idle_threshold)
688 : 0 : device->polling_idle_threshold = atoi(polling_idle_threshold);
689 : :
690 : : /*
691 : : * Attempt to connect as everything may be ready and the only thing the
692 : : * back-end is waiting for is this XenStore key to be written.
693 : : */
694 : 0 : err = frontend_changed(device, device->frontend_state);
695 : 0 : if (err) {
696 : : /*
697 : : * Ignore connection errors as the front-end might not yet be
698 : : * ready. blkback doesn't wait for this XenStore key to be written,
699 : : * so we choose to handle this the same way we do with
700 : : * physical-device.
701 : : */
702 : 0 : INFO(device, "failed to connect to the front-end: %s "
703 : : "(error ignored)\n", strerror(err));
704 : : }
705 : : err = 0;
706 : : }
707 : : else {
708 : 0 : WARN(device, "invalid hotplug-status value %s\n", hotplug_status);
709 : : err = -EINVAL;
710 : : }
711 : :
712 : : out:
713 : 0 : free(hotplug_status);
714 : 0 : free(device_type);
715 : 0 : free(mode);
716 : :
717 : 0 : return err;
718 : : }
719 : :
720 : : /**
721 : : * Attempts to reconnected the back-end to the fornt-end if possible (e.g.
722 : : * after a tapback restart), or after the slave tapback has started.
723 : : *
724 : : * The order we call the functions is not important, apart from
725 : : * tapback_backend_handle_otherend_watch that MUST be at the end, because each
726 : : * function attemps to reconnect but won't do so because the front-end state
727 : : * won't have been read.
728 : : *
729 : : * returns 0 on success, an error code otherwise
730 : : *
731 : : * NB that 0 is also returned when a reconnection is not yet feasible
732 : : */
733 : : static inline int
734 : 0 : reconnect(vbd_t *device) {
735 : :
736 : : int err;
737 : :
738 : 0 : DBG(device, "attempting reconnect\n");
739 : :
740 : : /*
741 : : * frontend() must be called before all other functions.
742 : : */
743 : 0 : err = frontend(device);
744 : 0 : if (err) {
745 : : /*
746 : : * tapdisk or the front-end state path are not available.
747 : : */
748 : 0 : if (err == -ENOENT) {
749 : 0 : DBG(device, "front-end XenStore sub-tree does not yet exist\n");
750 : : err = 0;
751 : 0 : } else if (err == -ESRCH) {
752 : 0 : DBG(device, "tapdisk not yet available\n");
753 : : err = 0;
754 : : } else
755 : 0 : WARN(device, "failed to watch front-end path: %s\n",
756 : : strerror(-err));
757 : : goto out;
758 : : }
759 : :
760 : 0 : err = physical_device_changed(device);
761 : 0 : if (err) {
762 : 0 : if (err == -ENOENT) {
763 : 0 : DBG(device, "no physical device yet\n");
764 : : err = 0;
765 : : } else {
766 : 0 : WARN(device, "failed to retrieve physical device information: "
767 : : "%s\n", strerror(-err));
768 : : goto out;
769 : : }
770 : : }
771 : :
772 : 0 : err = hotplug_status_changed(device);
773 : 0 : if (err) {
774 : 0 : if (err == -ENOENT) {
775 : 0 : DBG(device, "udev scripts haven't yet run\n");
776 : : err = 0;
777 : : } else {
778 : 0 : WARN(device, "failed to retrieve hotplug information: %s\n",
779 : : strerror(-err));
780 : : goto out;
781 : : }
782 : : }
783 : :
784 : 0 : err = -tapback_backend_handle_otherend_watch(device->backend,
785 : 0 : device->frontend_state_path);
786 : 0 : if (err) {
787 : 0 : if (err == -ENOENT) {
788 : 0 : DBG(device, "front-end not yet ready\n");
789 : : err = 0;
790 : : } else
791 : 0 : WARN(device, "error running the Xenbus protocol: %s\n",
792 : : strerror(-err));
793 : : }
794 : : out:
795 : 0 : return err;
796 : : }
797 : :
798 : : /**
799 : : * Creates (removes) a device depending on the existence (non-existence) of the
800 : : * "backend/<backend name>/@domid/@devname" XenStore path.
801 : : * Also handles some device status change behaviour which is neither create nor
802 : : * remove.
803 : : *
804 : : * @param domid the ID of the domain where the VBD is created
805 : : * @param devname device name
806 : : * @param comp the XenStore component after the
807 : : * backend/<backend name>/@domid/@devname/. Might be NULL if the device just
808 : : * got created.
809 : : * @returns 0 on success, a negative error code otherwise
810 : : */
811 : : static int
812 : 0 : tapback_backend_probe_device(backend_t *backend,
813 : : const domid_t domid, const char * const devname, const char *comp)
814 : : {
815 : 0 : bool should_exist = false, create = false, remove = false;
816 : 0 : int err = 0;
817 : 0 : vbd_t *device = NULL;
818 : 0 : char * s = NULL;
819 : :
820 : 0 : ASSERT(backend);
821 : 0 : ASSERT(devname);
822 : :
823 : 0 : ASSERT(!tapback_is_master(backend));
824 : :
825 : 0 : DBG(NULL, "%s probing device\n", devname);
826 : :
827 : : /*
828 : : * Ask XenStore if the device _should_ exist.
829 : : */
830 : 0 : s = tapback_xs_read(backend->xs, XBT_NULL, "%s/%s",
831 : : backend->path, devname);
832 : 0 : should_exist = s != NULL;
833 : 0 : free(s);
834 : :
835 : : /*
836 : : * Search the device list for this specific device.
837 : : */
838 : 0 : tapback_backend_find_device(backend, device,
839 : : device->domid == domid && !strcmp(device->name, devname));
840 : :
841 : : /*
842 : : * If XenStore says that the device should exist but it's not in our device
843 : : * list, we must create it. If it's the other way round, this is a removal.
844 : : *
845 : : * It *is* possible for a device to be not in our device list *and* XenStore
846 : : * does not say it should exist, in which case neither create nor remove
847 : : * will be true. This needs to fall through to some additional behaviour
848 : : * which records status changes.
849 : : */
850 : 0 : remove = device && !should_exist;
851 : 0 : create = !device && should_exist;
852 : :
853 : : /*
854 : : * Remember that remove and create may both be true at the same time, as
855 : : * this indicates that the device has been removed and re-created too fast.
856 : : * (FIXME Is this really true?) In this case, we too need to remove and
857 : : * re-create the device, respectively.
858 : : */
859 : :
860 : 0 : if (remove) {
861 : 0 : err = tapback_backend_destroy_device(device);
862 : 0 : if (err) {
863 : 0 : WARN(device, "failed to destroy the device: %s\n", strerror(-err));
864 : 0 : return err;
865 : : }
866 : : device = NULL;
867 : : }
868 : :
869 : 0 : if (create) {
870 : 0 : device = tapback_backend_create_device(backend, domid, devname);
871 : 0 : if (!device) {
872 : 0 : err = errno;
873 : 0 : WARN(NULL, "%s error creating device: %s\n", devname,
874 : : strerror(err));
875 : 0 : return err;
876 : : }
877 : :
878 : 0 : err = reconnect(device);
879 : 0 : if (err)
880 : 0 : WARN(device, "failed to reconnect: %s\n", strerror(-err));
881 : : }
882 : :
883 : : /*
884 : : * Examine what happened to the XenStore component on which the watch
885 : : * triggered.
886 : : *
887 : : * We don't set a XenStore watch on these paths in order to limit the
888 : : * number of watches for performance reasons.
889 : : */
890 : 0 : if (device && !remove && comp) {
891 : : /*
892 : : * TODO Replace this with a despatch table mapping XenStore keys to
893 : : * callbacks.
894 : : *
895 : : * XXX physical_device_changed() and hotplug_status_changed() require
896 : : * frontend() to have been called beforehand. This is achieved by
897 : : * calling reconnect by calling reconnect() when the VBD is created.
898 : : */
899 : 0 : if (!strcmp(PHYS_DEV_KEY, comp))
900 : 0 : err = physical_device_changed(device);
901 : 0 : if (!strcmp(PHYS_DEV_PATH_KEY, comp))
902 : 0 : err = physical_device_path_changed(device);
903 : 0 : else if (!strcmp(FRONTEND_KEY, comp))
904 : 0 : err = frontend(device);
905 : 0 : else if (!strcmp(HOTPLUG_STATUS_KEY, comp))
906 : 0 : err = hotplug_status_changed(device);
907 : : else
908 : 0 : DBG(device, "ignoring '%s'\n", comp);
909 : : }
910 : :
911 : 0 : if (err && create && device) {
912 : 0 : int err2 = tapback_backend_destroy_device(device);
913 : 0 : if (err2)
914 : 0 : WARN(device, "error cleaning up: failed to destroy the device: "
915 : : "%s\n", strerror(-err2));
916 : : }
917 : 0 : return err;
918 : : }
919 : :
920 : : /**
921 : : * Scans XenStore and probes any device found.
922 : : */
923 : : static int
924 : 0 : tapback_domain_scan(backend_t *backend, const domid_t domid)
925 : : {
926 : 0 : char *path = NULL, **sub = NULL;
927 : 0 : int err = 0;
928 : 0 : unsigned i, n = 0;
929 : :
930 : 0 : ASSERT(backend);
931 : :
932 : 0 : if (tapback_is_master(backend)) {
933 : : /*
934 : : * FIXME The master tapback can reach this only if it has been
935 : : * restarted. We need to figure out which slaves are running and
936 : : * reconstruct state.
937 : : */
938 : 0 : WARN(NULL, "master restart not yet implemented, ignoring domain %d\n",
939 : : domid);
940 : : } else {
941 : : /*
942 : : * Read the devices of this domain.
943 : : */
944 : 0 : sub = xs_directory(backend->xs, XBT_NULL, backend->path, &n);
945 : 0 : err = -errno;
946 : : free(path);
947 : :
948 : 0 : if (!sub)
949 : : goto out;
950 : :
951 : : /*
952 : : * Probe each device.
953 : : */
954 : 0 : for (i = 0; i < n; i++) {
955 : 0 : err = tapback_backend_probe_device(backend, domid, sub[i], NULL);
956 : 0 : if (unlikely(err))
957 : : /*
958 : : * Keep probing other devices.
959 : : */
960 : 0 : WARN(NULL, "%s error probing device: %s\n",
961 : : sub[i], strerror(-err));
962 : : }
963 : : }
964 : :
965 : : out:
966 : 0 : free(sub);
967 : 0 : return err;
968 : : }
969 : :
970 : : /**
971 : : * Compares the devices between XenStore and the device list, and
972 : : * creates/destroys devices accordingly.
973 : : */
974 : : static int
975 : 0 : tapback_probe_domain(backend_t *backend, const domid_t domid)
976 : : {
977 : 0 : vbd_t *device = NULL, *next = NULL;
978 : : int err;
979 : :
980 : 0 : ASSERT(backend);
981 : :
982 : : /*
983 : : * scrap all non-existent devices
984 : : */
985 : 0 : tapback_backend_for_each_device(backend, device, next) {
986 : 0 : if (device->domid == domid) {
987 : 0 : err = tapback_backend_probe_device(backend, device->domid,
988 : 0 : device->name, NULL);
989 : 0 : if (unlikely(err))
990 : : /*
991 : : * Keep probing other devices.
992 : : */
993 : 0 : WARN(device, "error probing device: %s\n", strerror(-err));
994 : : }
995 : : }
996 : :
997 : 0 : err = tapback_domain_scan(backend, domid);
998 : 0 : if (err == -ENOENT)
999 : 0 : err = 0;
1000 : 0 : return err;
1001 : : }
1002 : :
1003 : : /**
1004 : : * Scans XenStore for all blktap3 devices and probes each one of them.
1005 : : *
1006 : : * @returns 0 on success, a negative error code otherwise
1007 : : *
1008 : : * XXX Only called by tapback_backend_handle_backend_watch.
1009 : : */
1010 : : static int
1011 : 0 : tapback_backend_scan(backend_t *backend)
1012 : : {
1013 : 0 : vbd_t *device = NULL, *next = NULL;
1014 : 0 : unsigned int i = 0, n = 0;
1015 : 0 : char **dir = NULL;
1016 : 0 : int err = 0;
1017 : :
1018 : 0 : ASSERT(backend);
1019 : :
1020 : 0 : DBG(NULL, "scanning entire back-end\n");
1021 : :
1022 : 0 : if (!tapback_is_master(backend)) {
1023 : : /*
1024 : : * scrap all non-existent devices
1025 : : * TODO Why do we do this? Is this costly?
1026 : : */
1027 : 0 : tapback_backend_for_each_device(backend, device, next) {
1028 : : /*
1029 : : * FIXME check that there is no compoment.
1030 : : */
1031 : 0 : err = tapback_backend_probe_device(backend, device->domid,
1032 : 0 : device->name, NULL);
1033 : 0 : if (unlikely(err))
1034 : : /*
1035 : : * Keep probing other devices.
1036 : : */
1037 : 0 : WARN(device, "error probing device: %s\n", strerror(-err));
1038 : : }
1039 : : }
1040 : :
1041 : : /*
1042 : : * probe the new ones
1043 : : *
1044 : : * TODO We're checking each and every device in each and every domain,
1045 : : * could there be a performance issue in the presence of many VMs/VBDs?
1046 : : * (e.g. boot-storm)
1047 : : */
1048 : 0 : if (!(dir = xs_directory(backend->xs, XBT_NULL,
1049 : 0 : backend->path, &n))) {
1050 : 0 : err = -errno;
1051 : 0 : if (err == -ENOENT)
1052 : : err = 0;
1053 : : else
1054 : 0 : WARN(NULL, "error listing %s: %s\n", backend->path,
1055 : : strerror(err));
1056 : : goto out;
1057 : : }
1058 : :
1059 : 0 : DBG(NULL, "probing %d domains\n", n);
1060 : :
1061 : 0 : for (i = 0; i < n; i++) { /* for each domain */
1062 : 0 : char *end = NULL;
1063 : 0 : domid_t domid = 0;
1064 : :
1065 : : /*
1066 : : * Get the domain ID.
1067 : : */
1068 : 0 : domid = strtoul(dir[i], &end, 0);
1069 : 0 : if (*end != 0 || end == dir[i])
1070 : 0 : continue;
1071 : :
1072 : 0 : err = tapback_domain_scan(backend, domid);
1073 : 0 : if (err)
1074 : 0 : WARN(NULL, "error scanning domain %d: %s\n", domid,
1075 : : strerror(-err));
1076 : : }
1077 : :
1078 : : out:
1079 : 0 : free(dir);
1080 : 0 : return err;
1081 : : }
1082 : :
1083 : : int
1084 : 0 : tapback_backend_handle_backend_watch(backend_t *backend,
1085 : : char * const path)
1086 : : {
1087 : 0 : char *s = NULL, *end = NULL, *_path = NULL;
1088 : 0 : domid_t domid = 0;
1089 : 0 : int err = 0;
1090 : 0 : bool exists = false;
1091 : 0 : int n = 0;
1092 : :
1093 : 0 : ASSERT(backend);
1094 : 0 : ASSERT(path);
1095 : :
1096 : 0 : _path = strdup(path);
1097 : 0 : if (!_path) {
1098 : : err = -ENOMEM;
1099 : : goto out;
1100 : : }
1101 : :
1102 : : /*
1103 : : * path is something like "backend/vbd/domid/devid"
1104 : : */
1105 : :
1106 : 0 : s = strtok(_path, "/");
1107 : 0 : if (unlikely(!s)) {
1108 : 0 : WARN(NULL, "invalid path %s\n", _path);
1109 : : err = -EINVAL;
1110 : : goto out;
1111 : : }
1112 : 0 : ASSERT(!strcmp(s, XENSTORE_BACKEND));
1113 : 0 : if (!(s = strtok(NULL, "/"))) {
1114 : 0 : err = tapback_backend_scan(backend);
1115 : : goto out;
1116 : : }
1117 : :
1118 : 0 : ASSERT(!strcmp(s, backend->name));
1119 : 0 : if (!(s = strtok(NULL, "/"))) {
1120 : 0 : err = tapback_backend_scan(backend);
1121 : : goto out;
1122 : : }
1123 : :
1124 : 0 : domid = strtoul(s, &end, 0);
1125 : 0 : if (*end != 0 || end == s) {
1126 : 0 : WARN(NULL, "invalid domain ID \'%s\'\n", s);
1127 : : err = -EINVAL;
1128 : : goto out;
1129 : : }
1130 : :
1131 : : /*
1132 : : * The backend/vbd3/<domain ID> path was either created or removed.
1133 : : */
1134 : 0 : n = s - _path + strlen(s);
1135 : 0 : err = tapback_xs_exists(backend->xs, XBT_NULL, path, &n);
1136 : 0 : if (err < 0) {
1137 : 0 : WARN(NULL, "failed to read XenStore key %s: %s\n",
1138 : : &path[(s - _path)], strerror(-err));
1139 : : goto out;
1140 : : }
1141 : 0 : if (err == 0)
1142 : : exists = false;
1143 : : else
1144 : 0 : exists = true;
1145 : 0 : err = 0;
1146 : :
1147 : : /*
1148 : : * Master tapback: check if there's tapback for this domain. If there isn't
1149 : : * one, create it, otherwise ignore this event, the per-domain tapback will
1150 : : * take care of it.
1151 : : */
1152 : 0 : if (tapback_is_master(backend)) {
1153 : 0 : struct backend_slave *slave = tapback_find_slave(backend, domid),
1154 : 0 : **_slave = NULL;
1155 : :
1156 : 0 : if (!exists && slave) {
1157 : 0 : DBG(NULL, "de-register slave[%d]\n", slave->master.pid);
1158 : : /*
1159 : : * remove the slave
1160 : : */
1161 : 0 : tdelete(slave, &backend->master.slaves, compare);
1162 : 0 : free(slave);
1163 : : }
1164 : 0 : else if (exists && !slave) {
1165 : : pid_t pid;
1166 : :
1167 : 0 : DBG(NULL, "need to create slave for domain %d\n", domid);
1168 : :
1169 : 0 : pid = fork();
1170 : 0 : if (pid == -1) {
1171 : 0 : err = -errno;
1172 : 0 : WARN(NULL, "failed to fork: %s\n", strerror(-err));
1173 : : goto out;
1174 : 0 : } else if (pid != 0) { /* parent */
1175 : 0 : slave = malloc(sizeof(*slave));
1176 : 0 : if (!slave) {
1177 : : int err2;
1178 : 0 : WARN(NULL, "failed to allocate memory\n");
1179 : 0 : err = -ENOMEM;
1180 : 0 : err2 = kill(pid, SIGKILL);
1181 : 0 : if (err2 != 0) {
1182 : 0 : err2 = errno;
1183 : 0 : WARN(NULL, "failed to kill process %d: %s "
1184 : : "(error ignored)\n", pid, strerror(err2));
1185 : : }
1186 : : goto out;
1187 : : }
1188 : 0 : slave->master.pid = pid;
1189 : 0 : slave->master.domid = domid;
1190 : 0 : _slave = tsearch(slave, &backend->master.slaves, compare);
1191 : 0 : ASSERT(slave == *_slave);
1192 : :
1193 : 0 : DBG(NULL, "created slave[%d] for domain %d\n",
1194 : : slave->master.pid, slave->master.domid);
1195 : :
1196 : : /*
1197 : : * FIXME Shall we watch the child process?
1198 : : */
1199 : : } else { /* child */
1200 : : char *args[7];
1201 : 0 : int i = 0;
1202 : :
1203 : 0 : args[i++] = (char*)tapback_name;
1204 : 0 : args[i++] = "-d";
1205 : 0 : args[i++] = "-x";
1206 : 0 : err = asprintf(&args[i++], "%d", domid);
1207 : 0 : if (err == -1) {
1208 : 0 : err = -errno;
1209 : 0 : WARN(NULL, "failed to asprintf: %s\n", strerror(-err));
1210 : 0 : abort();
1211 : : }
1212 : 0 : if (log_level == LOG_DEBUG)
1213 : 0 : args[i++] = "-v";
1214 : 0 : if (!backend->barrier)
1215 : 0 : args[i++] = "-b";
1216 : 0 : args[i] = NULL;
1217 : : /*
1218 : : * TODO we're hard-coding the name of the binary, better let
1219 : : * the build system supply it.
1220 : : */
1221 : 0 : execvp(tapback_name, args);
1222 : 0 : err = -errno;
1223 : 0 : WARN(NULL, "failed to replace master process with slave: %s\n",
1224 : : strerror(-err));
1225 : 0 : abort();
1226 : : }
1227 : : }
1228 : : err = 0;
1229 : : } else {
1230 : 0 : char *device = NULL;
1231 : :
1232 : 0 : ASSERT(domid == backend->slave_domid);
1233 : :
1234 : 0 : if (!exists) {
1235 : : /*
1236 : : * The entire domain may be removed in one go, so we need to tear
1237 : : * down all devices.
1238 : : */
1239 : 0 : err = tapback_probe_domain(backend, domid);
1240 : 0 : if (err)
1241 : 0 : WARN(NULL, "failed to probe domain: %s\n", strerror(-err));
1242 : :
1243 : : /*
1244 : : * Time to go.
1245 : : */
1246 : 0 : INFO(NULL, "domain removed, exit\n");
1247 : 0 : tapback_backend_destroy(backend);
1248 : 0 : exit(EXIT_SUCCESS);
1249 : :
1250 : : /*
1251 : : * R.I.P.
1252 : : */
1253 : : }
1254 : :
1255 : : /*
1256 : : * There's no device yet, the domain just got created, nothing to do
1257 : : * just yet. However, the entire sub-tree might have gotten created
1258 : : * before the slave so we still need to check whether there are any
1259 : : * devices.
1260 : : */
1261 : 0 : device = strtok(NULL, "/");
1262 : 0 : if (!device) {
1263 : 0 : err = tapback_probe_domain(backend, domid);
1264 : : goto out;
1265 : : }
1266 : :
1267 : : /*
1268 : : * Create or remove a specific device.
1269 : : *
1270 : : * TODO tapback_backend_probe_device reads xenstore again to see if the
1271 : : * device should exist, but we already know that in the current
1272 : : * function.
1273 : : *
1274 : : * Optimise this case.
1275 : : */
1276 : 0 : err = tapback_backend_probe_device(backend, domid, device,
1277 : 0 : strtok(NULL, "/"));
1278 : : }
1279 : : out:
1280 : 0 : free(_path);
1281 : 0 : return err;
1282 : : }
1283 : :
1284 : : bool
1285 : 0 : verbose(void)
1286 : : {
1287 : 0 : return log_level >= LOG_DEBUG;
1288 : : }
|