From b666e69490955c90c7d4cd8abc2b385b325281d6 Mon Sep 17 00:00:00 2001 From: Valentyn Pavliuchenko Date: Sun, 20 Apr 2025 00:39:51 +0300 Subject: [PATCH] Fix creating dm devices when there are mounted partitions on the disk. The idea is to use LDM protective partition block devices for dm calls instead of the whole disk block device. This allows scenarios like booting Linux from LDM, when a separate protective partition is created exactly over the rootfs LDM partition, which allows mounting it directly, i.e. without ldmtool. Originally, it then prevents using ldmtool after such direct mount. This change fixes that. --- src/ldm.c | 204 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 196 insertions(+), 8 deletions(-) diff --git a/src/ldm.c b/src/ldm.c index baa66bf..de932c2 100644 --- a/src/ldm.c +++ b/src/ldm.c @@ -17,9 +17,11 @@ #include +#include #include #include #include +#include #include #include #include @@ -39,6 +41,15 @@ #include "gpt.h" #include "ldm.h" +#ifndef G_DEFINE_AUTOPTR_CLEANUP_FUNC_FILE_CLOSER +#define G_DEFINE_AUTOPTR_CLEANUP_FUNC_FILE_CLOSER +G_DEFINE_AUTOPTR_CLEANUP_FUNC(FILE, fclose) +#endif +#ifndef G_DEFINE_AUTOPTR_CLEANUP_FUNC_DIR_CLOSER +#define G_DEFINE_AUTOPTR_CLEANUP_FUNC_DIR_CLOSER +G_DEFINE_AUTOPTR_CLEANUP_FUNC(DIR, closedir) +#endif + #define DM_UUID_PREFIX "LDM-" #define UUID_FMT "%02x%02x%02x%02x-%02x%02x-%02x%02x-" \ @@ -2729,6 +2740,187 @@ _dm_remove(const gchar * const name, uint32_t udev_cookie, GError ** const err) return r; } +static int _find_partition(GString * target_params, const gchar * dev_name, + guint64 abs_part_start, guint64 part_size, + GError ** const err) +{ + g_autofree gchar * base_name = g_path_get_basename(dev_name); + if (!base_name || strlen(base_name) == 0) { + g_set_error(err, LDM_ERROR, LDM_ERROR_INTERNAL, + "Unable to obtain base name for %s", dev_name); + return -1; + } + + g_autofree gchar * sysfs_path = g_strdup_printf("/sys/block/%s", + base_name); + if (!sysfs_path) { + g_set_error(err, LDM_ERROR, LDM_ERROR_INTERNAL, + "Unable to create sysfs path for %s", base_name); + return -1; + } + + g_autoptr(DIR) dir = opendir(sysfs_path); + if (!dir) { + g_set_error(err, LDM_ERROR, LDM_ERROR_IO, + "Unable to open dir %s", sysfs_path); + return -1; + } + + struct dirent * entry; + int found = 0; + while (errno = 0, (entry = readdir(dir)) != NULL && !found) { + if (strcmp(entry->d_name, ".") == 0 || + strcmp(entry->d_name, "..") == 0) { + continue; + } + + g_autofree gchar * partition_dir_path = g_build_filename(sysfs_path, + entry->d_name, + NULL); + if (!partition_dir_path) + { + g_warning("Unable to build partition dir for %s/%s", sysfs_path, + entry->d_name); + continue; + } + + g_autofree gchar * partition_file_path = g_build_filename( + partition_dir_path, "partition", NULL); + g_autofree gchar * start_file_path = g_build_filename( + partition_dir_path, "start", NULL); + g_autofree gchar * size_file_path = g_build_filename( + partition_dir_path, "size", NULL); + + if (!partition_file_path) + { + g_warning("Unable to build partition file path for %s", + partition_dir_path); + continue; + } + if (!start_file_path) + { + g_warning("Unable to build start file path for %s", + partition_dir_path); + continue; + } + if (!size_file_path) + { + g_warning("Unable to build size file path for %s", + partition_dir_path); + continue; + } + + if (g_access(partition_file_path, F_OK) != 0) { + /* no warning - this is a check that filters out non-partition + subdirs */ + continue; + } + if (g_access(start_file_path, F_OK) != 0) { + g_warning("Unable to access %s", start_file_path); + continue; + } + if (g_access(size_file_path, F_OK) != 0) { + g_warning("Unable to access %s", size_file_path); + continue; + } + + /* Check if the 'partition' file exists within this subdirectory + This is a filter against other subdirectories that do not + refer to partitions */ + + g_autoptr(FILE) fstart = fopen(start_file_path, "r"); + if (!fstart) + { + g_warning("Unable to open %s", start_file_path); + continue; + } + + guint64 curr_part_start = 0; + if (fscanf(fstart, "%" PRIu64, &curr_part_start) != 1) { + g_warning("Unable to parse %s", start_file_path); + continue; + } + + g_autoptr(FILE) fsize = fopen(size_file_path, "r"); + if (!fsize) + { + g_warning("Unable to open %s", size_file_path); + continue; + } + + guint64 curr_part_size = 0; + if (fscanf(fsize, "%" PRIu64, &curr_part_size) != 1) { + g_warning("Unable to parse %s", size_file_path); + continue; + } + + guint64 curr_part_end = curr_part_start + curr_part_size; + guint64 target_part_end = abs_part_start + part_size; + + if (abs_part_start >= curr_part_start && + target_part_end <= curr_part_end) + { + g_autofree gchar * dev_dir = g_path_get_dirname(dev_name); + if (!dev_dir) + { + g_set_error(err, LDM_ERROR, LDM_ERROR_INTERNAL, + "Unable to extract dir from %s", dev_name); + return -1; + } + + g_autofree gchar * full_part_path = g_build_filename(dev_dir, entry->d_name, NULL); + if (!full_part_path) + { + g_set_error(err, LDM_ERROR, LDM_ERROR_INTERNAL, + "Unable to build file path for %s/%s", dev_dir, + entry->d_name); + return -1; + } + + g_string_printf(target_params, "%s %" PRIu64, + full_part_path, + abs_part_start - curr_part_start); + found = 1; + } + } + + if (errno != 0) { + g_set_error(err, LDM_ERROR, LDM_ERROR_IO, + "Unable to enumerate drive dir %s", sysfs_path); + } + + return found ? 0 : -1; +} + +static void _refine_partition(GString* target_params, + const LDMDiskPrivate * const disk, + guint64 part_start, guint64 part_size, + GError ** const err) +{ + /* The goal here is to try to use partition's block device instead + of whole disk's device. The problem of using the whole disk is this: + If LDM disk has at least one partition mounted directly, then it won't + be possible to create *any* LDM devmapper device using that disk. + + Mounting LDM partition directly may make sense for cases like Linux + system booting from LDM. The approach is the following: + 1. First, you need to split GPT/MBR's LDM protective partition into a few + separate protective partitions, so that your target LDM partition is + exactly matched by one protective partition. + 2. Then you'll be able to mount it as usual, boot from it and so on. + Windows actually does this for its C:\ partition is the respective + disk is converted to LDM. */ + + if (_find_partition(target_params, disk->device, + disk->data_start + part_start, part_size, err) == 0) { + return; + } + + /* fallback */ + g_string_printf(target_params, "%s %" PRIu64, + disk->device, disk->data_start + part_start); +} + static GString * _dm_create_part(const LDMPartitionPrivate * const part, uint32_t cookie, GError ** const err) @@ -2747,8 +2939,7 @@ _dm_create_part(const LDMPartitionPrivate * const part, uint32_t cookie, target.size = part->size; target.type = "linear"; target.params = g_string_new(""); - g_string_printf(target.params, "%s %" PRIu64, - disk->device, disk->data_start + part->start); + _refine_partition(target.params, disk, part->start, part->size, err); GString *name = _dm_part_name(part); GString *uuid = _dm_part_uuid(part); @@ -2817,9 +3008,8 @@ _dm_create_spanned(const LDMVolumePrivate * const vol, GError ** const err) target->size = part->size; target->type = "linear"; target->params = g_string_new(""); - g_string_append_printf(target->params, "%s %" PRIu64, - disk->device, - disk->data_start + part->start); + _refine_partition(target->params, disk, part->start, part->size, err); + pos += part->size; } @@ -2878,9 +3068,7 @@ _dm_create_striped(const LDMVolumePrivate * const vol, GError ** const err) goto out; } - g_string_append_printf(target.params, " %s %" PRIu64, - disk->device, - disk->data_start + part->start); + _refine_partition(target.params, disk, part->start, part->size, err); } uint32_t cookie;