LCOV - code coverage report
Current view: top level - plugins - mpath.c (source / functions) Coverage Total Hit
Test: libblockdev Coverage Report Lines: 41.2 % 238 98
Test Date: 2026-01-26 13:19:28 Functions: 72.7 % 11 8
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            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, &params);
     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              : }
        

Generated by: LCOV version 2.0-1