Line data Source code
1 : /*
2 : * Copyright (C) 2014 Red Hat, Inc.
3 : *
4 : * This library is free software; you can redistribute it and/or
5 : * modify it under the terms of the GNU Lesser General Public
6 : * License as published by the Free Software Foundation; either
7 : * version 2.1 of the License, or (at your option) any later version.
8 : *
9 : * This library is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 : * Lesser General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU Lesser General Public
15 : * License along with this library; if not, see <http://www.gnu.org/licenses/>.
16 : *
17 : * Author: Vratislav Podzimek <vpodzime@redhat.com>
18 : */
19 :
20 : #include <glib.h>
21 : /* provides major and minor macros */
22 : #include <sys/sysmacros.h>
23 : #include <libdevmapper.h>
24 : #include <unistd.h>
25 : #include <blockdev/utils.h>
26 :
27 : #include "mpath.h"
28 : #include "check_deps.h"
29 :
30 : #define MULTIPATH_MIN_VERSION "0.4.9"
31 :
32 : /**
33 : * SECTION: mpath
34 : * @short_description: plugin for basic operations with multipath devices
35 : * @title: Mpath
36 : * @include: mpath.h
37 : *
38 : * A plugin for basic operations with multipath devices.
39 : */
40 :
41 : /**
42 : * bd_mpath_error_quark: (skip)
43 : */
44 0 : GQuark bd_mpath_error_quark (void)
45 : {
46 0 : return g_quark_from_static_string ("g-bd-mpath-error-quark");
47 : }
48 :
49 : static volatile guint avail_deps = 0;
50 : static GMutex deps_check_lock;
51 :
52 : #define DEPS_MPATH 0
53 : #define DEPS_MPATH_MASK (1 << DEPS_MPATH)
54 : #define DEPS_MPATHCONF 1
55 : #define DEPS_MPATHCONF_MASK (1 << DEPS_MPATHCONF)
56 : #define DEPS_LAST 2
57 :
58 : static const UtilDep deps[DEPS_LAST] = {
59 : {"multipath", MULTIPATH_MIN_VERSION, NULL, "multipath-tools v([\\d\\.]+)"},
60 : {"mpathconf", NULL, NULL, NULL},
61 : };
62 :
63 :
64 : /**
65 : * bd_mpath_init:
66 : *
67 : * Initializes the plugin. **This function is called automatically by the
68 : * library's initialization functions.**
69 : *
70 : */
71 3 : gboolean bd_mpath_init (void) {
72 : /* nothing to do here */
73 3 : return TRUE;
74 : };
75 :
76 : /**
77 : * bd_mpath_close:
78 : *
79 : * Cleans up after the plugin. **This function is called automatically by the
80 : * library's functions that unload it.**
81 : *
82 : */
83 3 : void bd_mpath_close (void) {
84 : /* nothing to do here */
85 3 : }
86 :
87 : /**
88 : * bd_mpath_is_tech_avail:
89 : * @tech: the queried tech
90 : * @mode: a bit mask of queried modes of operation for @tech
91 : * @error: (out) (optional): place to store error (details about why the @tech-@mode combination is not available)
92 : *
93 : * Returns: whether the @tech-@mode combination is available -- supported by the
94 : * plugin implementation and having all the runtime dependencies available
95 : */
96 4 : gboolean bd_mpath_is_tech_avail (BDMpathTech tech, guint64 mode, GError **error) {
97 4 : switch (tech) {
98 3 : case BD_MPATH_TECH_BASE:
99 3 : return check_deps (&avail_deps, DEPS_MPATH_MASK, deps, DEPS_LAST, &deps_check_lock, error);
100 1 : case BD_MPATH_TECH_FRIENDLY_NAMES:
101 1 : if (mode & ~BD_MPATH_TECH_MODE_MODIFY) {
102 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_TECH_UNAVAIL,
103 : "Only 'modify' (setting) supported for friendly names");
104 0 : return FALSE;
105 1 : } else if (mode & BD_MPATH_TECH_MODE_MODIFY)
106 1 : return check_deps (&avail_deps, DEPS_MPATHCONF_MASK, deps, DEPS_LAST, &deps_check_lock, error);
107 : else {
108 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_TECH_UNAVAIL,
109 : "Unknown mode");
110 0 : return FALSE;
111 : }
112 0 : default:
113 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_TECH_UNAVAIL, "Unknown technology");
114 0 : return FALSE;
115 : }
116 : }
117 :
118 :
119 : /**
120 : * bd_mpath_flush_mpaths:
121 : * @error: (out) (optional): place to store error (if any)
122 : *
123 : * Returns: whether multipath device maps were successfully flushed or not
124 : *
125 : * Flushes all unused multipath device maps.
126 : *
127 : * Tech category: %BD_MPATH_TECH_BASE-%BD_MPATH_TECH_MODE_MODIFY
128 : */
129 0 : gboolean bd_mpath_flush_mpaths (GError **error) {
130 0 : const gchar *argv[3] = {"multipath", "-F", NULL};
131 0 : gboolean success = FALSE;
132 0 : gchar *output = NULL;
133 :
134 0 : if (!check_deps (&avail_deps, DEPS_MPATH_MASK, deps, DEPS_LAST, &deps_check_lock, error))
135 0 : return FALSE;
136 :
137 : /* try to flush the device maps */
138 0 : success = bd_utils_exec_and_report_error (argv, NULL, error);
139 0 : if (!success)
140 0 : return FALSE;
141 :
142 : /* list devices (there should be none) */
143 0 : argv[1] = "-ll";
144 0 : success = bd_utils_exec_and_capture_output (argv, NULL, &output, NULL);
145 0 : if (success && output && (g_strcmp0 (output, "") != 0)) {
146 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_FLUSH,
147 : "Some device cannot be flushed: %s", output);
148 0 : g_free (output);
149 0 : return FALSE;
150 : }
151 :
152 0 : g_free (output);
153 0 : return TRUE;
154 : }
155 :
156 0 : static gchar* get_device_name (const gchar *major_minor, GError **error) {
157 0 : gchar *path = NULL;
158 0 : gchar *link = NULL;
159 0 : gchar *ret = NULL;
160 :
161 0 : path = g_strdup_printf ("/dev/block/%s", major_minor);
162 0 : link = g_file_read_link (path, error);
163 0 : g_free (path);
164 0 : if (!link) {
165 0 : g_prefix_error (error, "Failed to determine device name for '%s'",
166 : major_minor);
167 0 : return NULL;
168 : }
169 :
170 : /* 'link' should be something like "../sda" */
171 : /* get the last '/' */
172 0 : ret = strrchr (link, '/');
173 0 : if (!ret) {
174 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_INVAL,
175 : "Failed to determine device name for '%s'",
176 : major_minor);
177 0 : g_free (link);
178 0 : return NULL;
179 : }
180 : /* move right after the last '/' */
181 0 : ret++;
182 :
183 : /* create a new copy and free the whole link path */
184 0 : ret = g_strdup (ret);
185 0 : g_free (link);
186 :
187 0 : return ret;
188 : }
189 :
190 6 : static gboolean map_is_multipath (const gchar *map_name, GError **error) {
191 6 : struct dm_task *task = NULL;
192 : struct dm_info info;
193 6 : guint64 start = 0;
194 6 : guint64 length = 0;
195 6 : gchar *type = NULL;
196 6 : gchar *params = NULL;
197 6 : gboolean ret = FALSE;
198 :
199 6 : if (geteuid () != 0) {
200 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_NOT_ROOT,
201 : "Not running as root, cannot query DM maps");
202 0 : return FALSE;
203 : }
204 :
205 6 : task = dm_task_create (DM_DEVICE_STATUS);
206 6 : if (!task) {
207 0 : bd_utils_log_format (BD_UTILS_LOG_WARNING, "Failed to create DM task");
208 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
209 : "Failed to create DM task");
210 0 : return FALSE;
211 : }
212 :
213 6 : if (dm_task_set_name (task, map_name) == 0) {
214 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
215 : "Failed to create DM task");
216 0 : dm_task_destroy (task);
217 0 : return FALSE;
218 : }
219 :
220 6 : if (dm_task_run (task) == 0) {
221 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
222 : "Failed to run DM task");
223 0 : dm_task_destroy (task);
224 0 : return FALSE;
225 : }
226 :
227 6 : if (dm_task_get_info (task, &info) == 0) {
228 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
229 : "Failed to get task info");
230 0 : dm_task_destroy (task);
231 0 : return FALSE;
232 : }
233 :
234 6 : dm_get_next_target (task, NULL, &start, &length, &type, ¶ms);
235 6 : if (g_strcmp0 (type, "multipath") == 0)
236 0 : ret = TRUE;
237 : else
238 6 : ret = FALSE;
239 6 : dm_task_destroy (task);
240 :
241 6 : return ret;
242 : }
243 :
244 0 : static gchar** get_map_deps (const gchar *map_name, guint64 *n_deps, GError **error) {
245 : struct dm_task *task;
246 : struct dm_deps *deps;
247 0 : guint64 dev_major = 0;
248 0 : guint64 dev_minor = 0;
249 0 : guint64 i = 0;
250 0 : gchar **dep_devs = NULL;
251 0 : gchar *major_minor = NULL;
252 0 : GError *l_error = NULL;
253 :
254 0 : if (geteuid () != 0) {
255 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_NOT_ROOT,
256 : "Not running as root, cannot query DM maps");
257 0 : return NULL;
258 : }
259 :
260 0 : task = dm_task_create (DM_DEVICE_DEPS);
261 0 : if (!task) {
262 0 : bd_utils_log_format (BD_UTILS_LOG_WARNING, "Failed to create DM task");
263 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
264 : "Failed to create DM task");
265 0 : return NULL;
266 : }
267 :
268 0 : if (dm_task_set_name (task, map_name) == 0) {
269 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
270 : "Failed to create DM task");
271 0 : dm_task_destroy (task);
272 0 : return NULL;
273 : }
274 :
275 0 : if (dm_task_run (task) == 0) {
276 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
277 : "Failed to run DM task");
278 0 : dm_task_destroy (task);
279 0 : return NULL;
280 : }
281 :
282 0 : deps = dm_task_get_deps (task);
283 0 : if (!deps) {
284 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
285 : "Failed to device dependencies");
286 0 : dm_task_destroy (task);
287 0 : return NULL;
288 : }
289 :
290 : /* allocate space for the dependencies */
291 0 : dep_devs = g_new0 (gchar*, deps->count + 1);
292 :
293 0 : for (i = 0; i < deps->count; i++) {
294 0 : dev_major = (guint64) major (deps->device[i]);
295 0 : dev_minor = (guint64) minor (deps->device[i]);
296 0 : major_minor = g_strdup_printf ("%"G_GUINT64_FORMAT":%"G_GUINT64_FORMAT, dev_major, dev_minor);
297 0 : dep_devs[i] = get_device_name (major_minor, &l_error);
298 0 : if (l_error) {
299 0 : g_propagate_prefixed_error (error, l_error, "Failed to resolve '%s' to device name",
300 : major_minor);
301 0 : g_free (dep_devs);
302 0 : g_free (major_minor);
303 0 : return NULL;
304 : }
305 0 : g_free (major_minor);
306 : }
307 0 : dep_devs[deps->count] = NULL;
308 0 : if (n_deps)
309 0 : *n_deps = deps->count;
310 :
311 0 : dm_task_destroy (task);
312 0 : return dep_devs;
313 : }
314 :
315 : /**
316 : * bd_mpath_is_mpath_member:
317 : * @device: device to test
318 : * @error: (out) (optional): place to store error (if any)
319 : *
320 : * Returns: %TRUE if the device is a multipath member, %FALSE if not or an error
321 : * appeared when queried (@error is set in those cases)
322 : *
323 : * Tech category: %BD_MPATH_TECH_BASE-%BD_MPATH_TECH_MODE_QUERY
324 : */
325 1 : gboolean bd_mpath_is_mpath_member (const gchar *device, GError **error) {
326 1 : struct dm_task *task_names = NULL;
327 1 : struct dm_names *names = NULL;
328 1 : gchar *dev_path = NULL;
329 1 : guint64 next = 0;
330 1 : gchar **deps = NULL;
331 1 : gchar **dev_name = NULL;
332 1 : gboolean ret = FALSE;
333 1 : GError *l_error = NULL;
334 :
335 1 : if (geteuid () != 0) {
336 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_NOT_ROOT,
337 : "Not running as root, cannot query DM maps");
338 0 : return FALSE;
339 : }
340 :
341 : /* we check if the 'device' is a dependency of any multipath map */
342 : /* get maps */
343 1 : task_names = dm_task_create (DM_DEVICE_LIST);
344 1 : if (!task_names) {
345 0 : bd_utils_log_format (BD_UTILS_LOG_WARNING, "Failed to create DM task");
346 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
347 : "Failed to create DM task");
348 0 : return FALSE;
349 : }
350 :
351 1 : dm_task_run (task_names);
352 1 : names = dm_task_get_names (task_names);
353 :
354 1 : if (!names || !names->dev)
355 0 : return FALSE;
356 :
357 : /* in case the device is dev_path, we need to resolve it because maps's deps
358 : are devices and not their dev_paths */
359 1 : if (g_str_has_prefix (device, "/dev/mapper/") || g_str_has_prefix (device, "/dev/md/")) {
360 0 : dev_path = bd_utils_resolve_device (device, NULL);
361 0 : if (!dev_path) {
362 : /* the device doesn't exist and thus is not an mpath member */
363 0 : dm_task_destroy (task_names);
364 0 : return FALSE;
365 : }
366 :
367 : /* the dev_path starts with "../" */
368 0 : device = dev_path + 3;
369 : }
370 :
371 1 : if (g_str_has_prefix (device, "/dev/"))
372 1 : device += 5;
373 :
374 : /* check all maps */
375 : do {
376 3 : names = (void *)names + next;
377 3 : next = names->next;
378 :
379 : /* we are only interested in multipath maps */
380 3 : if (map_is_multipath (names->name, &l_error)) {
381 0 : deps = get_map_deps (names->name, NULL, &l_error);
382 0 : if (!deps) {
383 0 : if (l_error)
384 0 : g_propagate_prefixed_error (error, l_error, "Failed to determine deps for '%s'",
385 0 : names->name);
386 : else
387 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_NOT_ROOT,
388 0 : "No deps found for '%s'", names->name);
389 0 : g_free (dev_path);
390 0 : dm_task_destroy (task_names);
391 0 : g_strfreev (deps);
392 0 : return FALSE;
393 : }
394 0 : for (dev_name = deps; !ret && *dev_name; dev_name++)
395 0 : ret = (g_strcmp0 (*dev_name, device) == 0);
396 0 : g_strfreev (deps);
397 3 : } else if (l_error) {
398 0 : g_propagate_prefixed_error (error, l_error, "Failed to determine map's target for '%s'",
399 0 : names->name);
400 0 : g_free (dev_path);
401 0 : dm_task_destroy (task_names);
402 0 : return FALSE;
403 : }
404 3 : } while (!ret && next);
405 :
406 1 : g_free (dev_path);
407 1 : dm_task_destroy (task_names);
408 1 : return ret;
409 : }
410 :
411 : /**
412 : * bd_mpath_get_mpath_members:
413 : * @error: (out) (optional): place to store error (if any)
414 : *
415 : * Returns: (transfer full) (array zero-terminated=1): list of names of all devices that are
416 : * members of the mpath mappings
417 : * (or %NULL in case of error)
418 : *
419 : * Tech category: %BD_MPATH_TECH_BASE-%BD_MPATH_TECH_MODE_QUERY
420 : */
421 1 : gchar** bd_mpath_get_mpath_members (GError **error) {
422 1 : struct dm_task *task_names = NULL;
423 1 : struct dm_names *names = NULL;
424 1 : guint64 next = 0;
425 1 : gchar **deps = NULL;
426 1 : gchar **dev_name = NULL;
427 1 : guint64 n_deps = 0;
428 1 : guint64 n_devs = 0;
429 1 : guint64 top_dev = 0;
430 1 : gchar **ret = NULL;
431 1 : guint64 progress_id = 0;
432 1 : GError *l_error = NULL;
433 :
434 1 : progress_id = bd_utils_report_started ("Started getting mpath members");
435 :
436 1 : if (geteuid () != 0) {
437 0 : g_set_error (&l_error, BD_MPATH_ERROR, BD_MPATH_ERROR_NOT_ROOT,
438 : "Not running as root, cannot query DM maps");
439 0 : bd_utils_report_finished (progress_id, l_error->message);
440 0 : g_propagate_error (error, l_error);
441 0 : return NULL;
442 : }
443 :
444 : /* we check if the 'device' is a dependency of any multipath map */
445 : /* get maps */
446 1 : task_names = dm_task_create (DM_DEVICE_LIST);
447 1 : if (!task_names) {
448 0 : bd_utils_log_format (BD_UTILS_LOG_WARNING, "Failed to create DM task");
449 0 : g_set_error (&l_error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
450 : "Failed to create DM task");
451 0 : bd_utils_report_finished (progress_id, l_error->message);
452 0 : g_propagate_error (error, l_error);
453 0 : return NULL;
454 : }
455 :
456 1 : dm_task_run (task_names);
457 1 : names = dm_task_get_names (task_names);
458 :
459 1 : if (!names || !names->dev) {
460 0 : bd_utils_report_finished (progress_id, "Completed");
461 0 : return NULL;
462 : }
463 :
464 1 : ret = g_new0 (gchar*, 1);
465 1 : n_devs = 1;
466 :
467 : /* check all maps */
468 : do {
469 3 : names = (void *)names + next;
470 3 : next = names->next;
471 :
472 : /* we are only interested in multipath maps */
473 3 : if (map_is_multipath (names->name, NULL)) {
474 0 : deps = get_map_deps (names->name, &n_deps, &l_error);
475 0 : if (l_error) {
476 0 : g_prefix_error (&l_error, "Failed to determine deps for '%s'", names->name);
477 0 : dm_task_destroy (task_names);
478 0 : bd_utils_report_finished (progress_id, l_error->message);
479 0 : g_propagate_error (error, l_error);
480 0 : g_free (deps);
481 0 : g_free (ret);
482 0 : return NULL;
483 : }
484 0 : if (deps) {
485 0 : n_devs += n_deps;
486 0 : ret = g_renew (gchar*, ret, n_devs);
487 0 : for (dev_name=deps; *dev_name; dev_name++) {
488 0 : ret[top_dev] = *dev_name;
489 0 : top_dev += 1;
490 : }
491 0 : g_free (deps);
492 : }
493 : }
494 3 : } while (next);
495 :
496 1 : ret[top_dev] = NULL;
497 1 : bd_utils_report_finished (progress_id, "Completed");
498 :
499 1 : return ret;
500 : }
501 :
502 :
503 : /**
504 : * bd_mpath_set_friendly_names:
505 : * @enabled: whether friendly names should be enabled or not
506 : * @error: (out) (optional): place to store error (if any)
507 : *
508 : * Returns: if successfully set or not
509 : *
510 : * Tech category: %BD_MPATH_TECH_FRIENDLY_NAMES-%BD_MPATH_TECH_MODE_MODIFY
511 : */
512 1 : gboolean bd_mpath_set_friendly_names (gboolean enabled, GError **error) {
513 1 : const gchar *argv[8] = {"mpathconf", "--find_multipaths", "y", "--user_friendly_names", NULL, "--with_multipathd", "y", NULL};
514 1 : argv[4] = enabled ? "y" : "n";
515 :
516 1 : if (!check_deps (&avail_deps, DEPS_MPATHCONF_MASK, deps, DEPS_LAST, &deps_check_lock, error))
517 0 : return FALSE;
518 :
519 1 : return bd_utils_exec_and_report_error (argv, NULL, error);
520 : }
|