/* * Copyright (C) 2017 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, see . * * Author: Vratislav Podzimek */ #include #include #include "dev_utils.h" /** * bd_utils_dev_utils_error_quark: (skip) */ GQuark bd_utils_dev_utils_error_quark (void) { return g_quark_from_static_string ("g-bd-utils-dev_utils-error-quark"); } /** * bd_utils_resolve_device: * @dev_spec: specification of the device (e.g. "/dev/sda", any symlink, or the name of a file * under "/dev") * @error: (out): place to store error (if any) * * Returns: (transfer full): the full real path of the device (e.g. "/dev/md126" * for "/dev/md/my_raid") or %NULL in case of error */ gchar* bd_utils_resolve_device (const gchar *dev_spec, GError **error) { gchar *path = NULL; gchar *symlink = NULL; /* TODO: check that the resulting path is a block device? */ if (!g_str_has_prefix (dev_spec, "/dev/")) path = g_strdup_printf ("/dev/%s", dev_spec); else path = g_strdup (dev_spec); symlink = g_file_read_link (path, error); if (!symlink) { if (g_error_matches (*error, G_FILE_ERROR, G_FILE_ERROR_INVAL)) { /* invalid argument -> not a symlink -> nothing to resolve */ g_clear_error (error); return path; } else { /* some other error, just report it */ g_free (path); return NULL; } } g_free (path); if (g_str_has_prefix (symlink, "../")) path = g_strdup_printf ("/dev/%s", symlink + 3); else path = g_strdup_printf ("/dev/%s", symlink); g_free (symlink); return path; } /** * bd_utils_get_device_symlinks: * @dev_spec: specification of the device (e.g. "/dev/sda", any symlink, or the name of a file * under "/dev") * @error: (out): place to store error (if any) * * Returns: (transfer full) (array zero-terminated=1): a list of all symlinks (known to udev) for the * device specified with @dev_spec or %NULL in * case of error */ gchar** bd_utils_get_device_symlinks (const gchar *dev_spec, GError **error) { gchar *dev_path; struct udev *context; struct udev_device *device; struct udev_list_entry *entry = NULL; struct udev_list_entry *ent_it = NULL; guint64 n_links = 0; guint64 i = 0; gchar **ret = NULL; dev_path = bd_utils_resolve_device (dev_spec, error); if (!dev_path) return NULL; context = udev_new (); /* dev_path is the full path like "/dev/sda", we only need the device name ("sda") */ device = udev_device_new_from_subsystem_sysname (context, "block", dev_path + 5); if (!device) { g_set_error (error, BD_UTILS_DEV_UTILS_ERROR, BD_UTILS_DEV_UTILS_ERROR_FAILED, "Failed to get information about the device '%s' from udev database", dev_path); g_free (dev_path); udev_unref (context); return NULL; } entry = udev_device_get_devlinks_list_entry (device); if (!entry) { g_set_error (error, BD_UTILS_DEV_UTILS_ERROR, BD_UTILS_DEV_UTILS_ERROR_FAILED, "Failed to get symlinks for the device '%s'", dev_path); g_free (dev_path); udev_device_unref (device); udev_unref (context); return NULL; } g_free (dev_path); ent_it = entry; while (ent_it) { n_links++; ent_it = udev_list_entry_get_next (ent_it); } ret = g_new0 (gchar*, n_links + 1); ent_it = entry; while (ent_it) { ret[i++] = g_strdup (udev_list_entry_get_name (ent_it)); ent_it = udev_list_entry_get_next (ent_it); } ret[i] = NULL; udev_device_unref (device); udev_unref (context); return ret; }