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 1 : gboolean bd_mpath_flush_mpaths (GError **error) {
130 1 : const gchar *argv[3] = {"multipath", "-F", NULL};
131 1 : gboolean success = FALSE;
132 1 : gchar *output = NULL;
133 :
134 1 : 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 1 : success = bd_utils_exec_and_report_error (argv, NULL, error);
139 1 : if (!success)
140 0 : return FALSE;
141 :
142 : /* list devices (there should be none) */
143 1 : argv[1] = "-ll";
144 1 : success = bd_utils_exec_and_capture_output (argv, NULL, &output, NULL);
145 1 : 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 1 : g_free (output);
153 1 : 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 : task = dm_task_create (DM_DEVICE_STATUS);
200 6 : if (!task) {
201 0 : bd_utils_log_format (BD_UTILS_LOG_WARNING, "Failed to create DM task");
202 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
203 : "Failed to create DM task");
204 0 : return FALSE;
205 : }
206 :
207 6 : if (dm_task_set_name (task, map_name) == 0) {
208 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
209 : "Failed to create DM task");
210 0 : dm_task_destroy (task);
211 0 : return FALSE;
212 : }
213 :
214 6 : if (dm_task_run (task) == 0) {
215 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
216 : "Failed to run DM task");
217 0 : dm_task_destroy (task);
218 0 : return FALSE;
219 : }
220 :
221 6 : if (dm_task_get_info (task, &info) == 0) {
222 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
223 : "Failed to get task info");
224 0 : dm_task_destroy (task);
225 0 : return FALSE;
226 : }
227 :
228 6 : dm_get_next_target (task, NULL, &start, &length, &type, ¶ms);
229 6 : if (g_strcmp0 (type, "multipath") == 0)
230 0 : ret = TRUE;
231 : else
232 6 : ret = FALSE;
233 6 : dm_task_destroy (task);
234 :
235 6 : return ret;
236 : }
237 :
238 0 : static gchar** get_map_deps (const gchar *map_name, guint64 *n_deps, GError **error) {
239 : struct dm_task *task;
240 : struct dm_deps *deps;
241 0 : guint64 dev_major = 0;
242 0 : guint64 dev_minor = 0;
243 0 : guint64 i = 0;
244 0 : gchar **dep_devs = NULL;
245 0 : gchar *major_minor = NULL;
246 0 : GError *l_error = NULL;
247 :
248 0 : task = dm_task_create (DM_DEVICE_DEPS);
249 0 : if (!task) {
250 0 : bd_utils_log_format (BD_UTILS_LOG_WARNING, "Failed to create DM task");
251 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
252 : "Failed to create DM task");
253 0 : return NULL;
254 : }
255 :
256 0 : if (dm_task_set_name (task, map_name) == 0) {
257 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
258 : "Failed to create DM task");
259 0 : dm_task_destroy (task);
260 0 : return NULL;
261 : }
262 :
263 0 : if (dm_task_run (task) == 0) {
264 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
265 : "Failed to run DM task");
266 0 : dm_task_destroy (task);
267 0 : return NULL;
268 : }
269 :
270 0 : deps = dm_task_get_deps (task);
271 0 : if (!deps) {
272 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
273 : "Failed to device dependencies");
274 0 : dm_task_destroy (task);
275 0 : return NULL;
276 : }
277 :
278 : /* allocate space for the dependencies */
279 0 : dep_devs = g_new0 (gchar*, deps->count + 1);
280 :
281 0 : for (i = 0; i < deps->count; i++) {
282 0 : dev_major = (guint64) major (deps->device[i]);
283 0 : dev_minor = (guint64) minor (deps->device[i]);
284 0 : major_minor = g_strdup_printf ("%"G_GUINT64_FORMAT":%"G_GUINT64_FORMAT, dev_major, dev_minor);
285 0 : dep_devs[i] = get_device_name (major_minor, &l_error);
286 0 : if (l_error) {
287 0 : g_propagate_prefixed_error (error, l_error, "Failed to resolve '%s' to device name",
288 : major_minor);
289 0 : g_free (dep_devs);
290 0 : g_free (major_minor);
291 0 : return NULL;
292 : }
293 0 : g_free (major_minor);
294 : }
295 0 : dep_devs[deps->count] = NULL;
296 0 : if (n_deps)
297 0 : *n_deps = deps->count;
298 :
299 0 : dm_task_destroy (task);
300 0 : return dep_devs;
301 : }
302 :
303 : /**
304 : * bd_mpath_is_mpath_member:
305 : * @device: device to test
306 : * @error: (out) (optional): place to store error (if any)
307 : *
308 : * Returns: %TRUE if the device is a multipath member, %FALSE if not or an error
309 : * appeared when queried (@error is set in those cases)
310 : *
311 : * Tech category: %BD_MPATH_TECH_BASE-%BD_MPATH_TECH_MODE_QUERY
312 : */
313 1 : gboolean bd_mpath_is_mpath_member (const gchar *device, GError **error) {
314 1 : struct dm_task *task_names = NULL;
315 1 : struct dm_names *names = NULL;
316 1 : gchar *dev_path = NULL;
317 1 : guint64 next = 0;
318 1 : gchar **deps = NULL;
319 1 : gchar **dev_name = NULL;
320 1 : gboolean ret = FALSE;
321 1 : GError *l_error = NULL;
322 :
323 : /* we check if the 'device' is a dependency of any multipath map */
324 : /* get maps */
325 1 : task_names = dm_task_create (DM_DEVICE_LIST);
326 1 : if (!task_names) {
327 0 : bd_utils_log_format (BD_UTILS_LOG_WARNING, "Failed to create DM task");
328 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
329 : "Failed to create DM task");
330 0 : return FALSE;
331 : }
332 :
333 1 : dm_task_run (task_names);
334 1 : names = dm_task_get_names (task_names);
335 :
336 1 : if (!names || !names->dev)
337 0 : return FALSE;
338 :
339 : /* in case the device is dev_path, we need to resolve it because maps's deps
340 : are devices and not their dev_paths */
341 1 : if (g_str_has_prefix (device, "/dev/mapper/") || g_str_has_prefix (device, "/dev/md/")) {
342 0 : dev_path = bd_utils_resolve_device (device, NULL);
343 0 : if (!dev_path) {
344 : /* the device doesn't exist and thus is not an mpath member */
345 0 : dm_task_destroy (task_names);
346 0 : return FALSE;
347 : }
348 :
349 : /* the dev_path starts with "../" */
350 0 : device = dev_path + 3;
351 : }
352 :
353 1 : if (g_str_has_prefix (device, "/dev/"))
354 1 : device += 5;
355 :
356 : /* check all maps */
357 : do {
358 3 : names = (void *)names + next;
359 3 : next = names->next;
360 :
361 : /* we are only interested in multipath maps */
362 3 : if (map_is_multipath (names->name, &l_error)) {
363 0 : deps = get_map_deps (names->name, NULL, &l_error);
364 0 : if (!deps) {
365 0 : if (l_error)
366 0 : g_propagate_prefixed_error (error, l_error, "Failed to determine deps for '%s'",
367 0 : names->name);
368 : else
369 0 : g_set_error (error, BD_MPATH_ERROR, BD_MPATH_ERROR_NOT_ROOT,
370 0 : "No deps found for '%s'", names->name);
371 0 : g_free (dev_path);
372 0 : dm_task_destroy (task_names);
373 0 : g_strfreev (deps);
374 0 : return FALSE;
375 : }
376 0 : for (dev_name = deps; !ret && *dev_name; dev_name++)
377 0 : ret = (g_strcmp0 (*dev_name, device) == 0);
378 0 : g_strfreev (deps);
379 3 : } else if (l_error) {
380 0 : g_propagate_prefixed_error (error, l_error, "Failed to determine map's target for '%s'",
381 0 : names->name);
382 0 : g_free (dev_path);
383 0 : dm_task_destroy (task_names);
384 0 : return FALSE;
385 : }
386 3 : } while (!ret && next);
387 :
388 1 : g_free (dev_path);
389 1 : dm_task_destroy (task_names);
390 1 : return ret;
391 : }
392 :
393 : /**
394 : * bd_mpath_get_mpath_members:
395 : * @error: (out) (optional): place to store error (if any)
396 : *
397 : * Returns: (transfer full) (array zero-terminated=1): list of names of all devices that are
398 : * members of the mpath mappings
399 : * (or %NULL in case of error)
400 : *
401 : * Tech category: %BD_MPATH_TECH_BASE-%BD_MPATH_TECH_MODE_QUERY
402 : */
403 1 : gchar** bd_mpath_get_mpath_members (GError **error) {
404 1 : struct dm_task *task_names = NULL;
405 1 : struct dm_names *names = NULL;
406 1 : guint64 next = 0;
407 1 : gchar **deps = NULL;
408 1 : gchar **dev_name = NULL;
409 1 : guint64 n_deps = 0;
410 1 : guint64 n_devs = 0;
411 1 : guint64 top_dev = 0;
412 1 : gchar **ret = NULL;
413 1 : guint64 progress_id = 0;
414 1 : GError *l_error = NULL;
415 :
416 1 : progress_id = bd_utils_report_started ("Started getting mpath members");
417 :
418 : /* we check if the 'device' is a dependency of any multipath map */
419 : /* get maps */
420 1 : task_names = dm_task_create (DM_DEVICE_LIST);
421 1 : if (!task_names) {
422 0 : bd_utils_log_format (BD_UTILS_LOG_WARNING, "Failed to create DM task");
423 0 : g_set_error (&l_error, BD_MPATH_ERROR, BD_MPATH_ERROR_DM_ERROR,
424 : "Failed to create DM task");
425 0 : bd_utils_report_finished (progress_id, l_error->message);
426 0 : g_propagate_error (error, l_error);
427 0 : return NULL;
428 : }
429 :
430 1 : dm_task_run (task_names);
431 1 : names = dm_task_get_names (task_names);
432 :
433 1 : if (!names || !names->dev) {
434 0 : bd_utils_report_finished (progress_id, "Completed");
435 0 : return NULL;
436 : }
437 :
438 1 : ret = g_new0 (gchar*, 1);
439 1 : n_devs = 1;
440 :
441 : /* check all maps */
442 : do {
443 3 : names = (void *)names + next;
444 3 : next = names->next;
445 :
446 : /* we are only interested in multipath maps */
447 3 : if (map_is_multipath (names->name, NULL)) {
448 0 : deps = get_map_deps (names->name, &n_deps, &l_error);
449 0 : if (l_error) {
450 0 : g_prefix_error (&l_error, "Failed to determine deps for '%s'", names->name);
451 0 : dm_task_destroy (task_names);
452 0 : bd_utils_report_finished (progress_id, l_error->message);
453 0 : g_propagate_error (error, l_error);
454 0 : g_free (deps);
455 0 : g_free (ret);
456 0 : return NULL;
457 : }
458 0 : if (deps) {
459 0 : n_devs += n_deps;
460 0 : ret = g_renew (gchar*, ret, n_devs);
461 0 : for (dev_name=deps; *dev_name; dev_name++) {
462 0 : ret[top_dev] = *dev_name;
463 0 : top_dev += 1;
464 : }
465 0 : g_free (deps);
466 : }
467 : }
468 3 : } while (next);
469 :
470 1 : ret[top_dev] = NULL;
471 1 : bd_utils_report_finished (progress_id, "Completed");
472 :
473 1 : return ret;
474 : }
475 :
476 :
477 : /**
478 : * bd_mpath_set_friendly_names:
479 : * @enabled: whether friendly names should be enabled or not
480 : * @error: (out) (optional): place to store error (if any)
481 : *
482 : * Returns: if successfully set or not
483 : *
484 : * Tech category: %BD_MPATH_TECH_FRIENDLY_NAMES-%BD_MPATH_TECH_MODE_MODIFY
485 : */
486 1 : gboolean bd_mpath_set_friendly_names (gboolean enabled, GError **error) {
487 1 : const gchar *argv[8] = {"mpathconf", "--find_multipaths", "y", "--user_friendly_names", NULL, "--with_multipathd", "y", NULL};
488 1 : argv[4] = enabled ? "y" : "n";
489 :
490 1 : if (!check_deps (&avail_deps, DEPS_MPATHCONF_MASK, deps, DEPS_LAST, &deps_check_lock, error))
491 0 : return FALSE;
492 :
493 1 : return bd_utils_exec_and_report_error (argv, NULL, error);
494 : }
|