LCOV - code coverage report
Current view: top level - plugins - mpath.c (source / functions) Coverage Total Hit
Test: libblockdev Coverage Report Lines: 35.3 % 252 89
Test Date: 2026-01-23 09:12:16 Functions: 63.6 % 11 7
Legend: Lines: hit not hit

            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, &params);
     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              : }
        

Generated by: LCOV version 2.0-1