Files
s390-tools/zdev/src/blkinfo.c
Peter Oberparleiter 1faa5d2957 zdev: Fix path resolution for multi-mount point file systems
zdev provides path resolution logic to determine which z-specific
devices contribute to the file system mounted at a specific mount point.
This logic is used by command-line option --by-path, but also to
determine the list of devices needed to enable the root file system.

Path resolution fails when a device provides multiple mount points such
as, for example, when using btrfs subvolumes, or when mounting the same
file system at multiple mount points. The failure is caused by zdev
relying on the MOUNTPOINT attribute of lsblk's output which only
contains a single mount point.

Fix this by making use of lsblk's MOUNTPOINTS attribute that contains
the full list of mount points. Note that MOUNTPOINTS was only introduced
with util-linux v2.37, therefore a fall-back to the old format is
needed.

Fixes: https://github.com/ibm-s390-linux/s390-tools/issues/129
Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Reviewed-by: Eduard Shishkin <edward6@linux.ibm.com>
Reported-by: Dan Horák <dan@danny.cz>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2022-02-02 11:54:30 +01:00

491 lines
11 KiB
C

/*
* zdev - Modify and display the persistent configuration of devices
*
* Copyright IBM Corp. 2016, 2017
*
* s390-tools is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
*/
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "blkinfo.h"
#include "devnode.h"
#include "misc.h"
#define LSBLK_CMDLINE "lsblk -P -o NAME,MAJ:MIN,FSTYPE,UUID,MOUNTPOINT,PKNAME 2>/dev/null"
#define LSBLK_CMDLINE2 "lsblk -P -o NAME,MAJ:MIN,FSTYPE,UUID,MOUNTPOINTS,PKNAME 2>/dev/null"
struct blkinfo {
struct devnode *devnode;
char *fstype;
char *uuid;
char *mountpoint;
char *parent;
};
static struct util_list *cached_blkinfos;
static struct blkinfo *blkinfo_new(const char *name, const char *majmin,
const char *fstype, const char *uuid,
const char *mountpoint, const char *parent)
{
struct blkinfo *blkinfo;
unsigned int major, minor;
blkinfo = misc_malloc(sizeof(struct blkinfo));
if (name && majmin) {
if (sscanf(majmin, "%u:%u", &major, &minor) == 2) {
blkinfo->devnode = devnode_new(BLOCKDEV, major, minor,
name);
}
}
if (fstype && *fstype)
blkinfo->fstype = misc_strdup(fstype);
if (uuid && *uuid)
blkinfo->uuid = misc_strdup(uuid);
if (mountpoint && *mountpoint)
blkinfo->mountpoint = misc_strdup(mountpoint);
if (parent && *parent)
blkinfo->parent = misc_strdup(parent);
return blkinfo;
}
static void blkinfo_free(struct blkinfo *blkinfo)
{
if (!blkinfo)
return;
free(blkinfo->devnode);
free(blkinfo->fstype);
free(blkinfo->uuid);
free(blkinfo->mountpoint);
free(blkinfo->parent);
free(blkinfo);
}
/* Used for debugging. */
void blkinfo_print(struct blkinfo *blkinfo, int level)
{
printf("%*sblkinfo at %p\n", level, "", (void *) blkinfo);
level += 2;
if (blkinfo->devnode)
devnode_print(blkinfo->devnode, level);
if (blkinfo->fstype)
printf("%*sfstype=%s\n", level, "", blkinfo->fstype);
if (blkinfo->uuid)
printf("%*suuid=%s\n", level, "", blkinfo->uuid);
if (blkinfo->mountpoint)
printf("%*smountpoint=%s\n", level, "", blkinfo->mountpoint);
if (blkinfo->parent)
printf("%*sparent=%s\n", level, "", blkinfo->parent);
}
/* Convert each occurrence of '\xnn' in @str to character with hex code <nn>. */
static void hex_unescape(char *str)
{
unsigned int c;
while ((str = strstr(str, "\\x"))) {
if (isxdigit(str[2]) && isxdigit(str[3]) &&
sscanf(str + 2, "%2x", &c) == 1) {
str[0] = (char)c;
/* Move remainder of str including nul behind <c>. */
memmove(str + /* <c> */ 1,
str + /* '\xnn' */ 4,
strlen(str + 4) + /* <nul> */ 1);
}
str++;
}
}
static char *isolate_keyword(char **line_ptr, const char *keyword)
{
char *start, *end;
start = strstr(*line_ptr, keyword);
if (!start)
return NULL;
start += strlen(keyword);
end = start;
while (*end && *end != '"')
end++;
if (*end) {
*end = 0;
*line_ptr = end + 1;
} else
*line_ptr = end;
return start;
}
static void add_blkinfos_from_line(struct util_list *blkinfos,
char *line)
{
char *name, *majmin, *fstype, *uuid, *mountpoint, *mountpoints, *parent;
struct blkinfo *blkinfo;
name = isolate_keyword(&line, "NAME=\"");
majmin = isolate_keyword(&line, "MAJ:MIN=\"");
if (!majmin)
majmin = isolate_keyword(&line, "MAJ_MIN=\"");
fstype = isolate_keyword(&line, "FSTYPE=\"");
uuid = isolate_keyword(&line, "UUID=\"");
mountpoint = isolate_keyword(&line, "MOUNTPOINT=\"");
mountpoints = isolate_keyword(&line, "MOUNTPOINTS=\"");
parent = isolate_keyword(&line, "PKNAME=\"");
if (!mountpoints) {
/* Handle old lsblk output format. */
blkinfo = blkinfo_new(name, majmin, fstype, uuid, mountpoint,
parent);
ptrlist_add(blkinfos, blkinfo);
return;
}
/* Restore newline mount point separator encoded as hex. */
hex_unescape(mountpoints);
/* Represent each mount point as a separate blkinfo to support
* resolution of multi-mount point file systems like btrfs
* subvolumes. */
while ((mountpoint = strsep(&mountpoints, "\n"))) {
blkinfo = blkinfo_new(name, majmin, fstype, uuid, mountpoint,
parent);
ptrlist_add(blkinfos, blkinfo);
}
}
static struct util_list *blkinfos_read(void)
{
char *output, *curr, *next;
struct util_list *blkinfos;
if (cached_blkinfos)
return cached_blkinfos;
output = misc_read_cmd_output(LSBLK_CMDLINE2, 0, 1);
if (output && !*output) {
/* No output might indicate no support for new lsblk command-
* line format - fall back to old format. */
free(output);
output = misc_read_cmd_output(LSBLK_CMDLINE, 0, 1);
}
if (!output)
return NULL;
blkinfos = ptrlist_new();
/* Iterate over each line. */
next = output;
while ((curr = strsep(&next, "\n"))) {
add_blkinfos_from_line(blkinfos, curr);
}
free(output);
cached_blkinfos = blkinfos;
return blkinfos;
}
static void blkinfos_free(struct util_list *blkinfos)
{
struct ptrlist_node *p, *n;
if (!blkinfos)
return;
util_list_iterate_safe(blkinfos, p, n) {
util_list_remove(blkinfos, p);
blkinfo_free(p->ptr);
free(p);
}
free(blkinfos);
}
/* Used for debugging. */
void blkinfos_print(struct util_list *blkinfos, int level)
{
struct ptrlist_node *p;
printf("%*sblkinfos at %p\n", level, "", (void *) blkinfos);
if (!blkinfos)
return;
level += 2;
util_list_iterate(blkinfos, p)
blkinfo_print(p->ptr, level);
}
/* Find a blkinfo for the specified devnode or NULL of none was found. */
static struct blkinfo *blkinfo_get_by_devnode(struct devnode *devnode)
{
struct util_list *blkinfos;
struct ptrlist_node *p;
struct blkinfo *b;
blkinfos = blkinfos_read();
if (!blkinfos)
return NULL;
/* Get UUID for the specified devnode. */
util_list_iterate(blkinfos, p) {
b = p->ptr;
if (b->devnode && devnode_cmp(b->devnode, devnode) == 0)
return b;
}
return NULL;
}
/* Find a blkinfo for the specified device name or NULL if none was found. */
static struct blkinfo *blkinfo_get_by_name(const char *name)
{
struct util_list *blkinfos;
struct ptrlist_node *p;
struct blkinfo *b;
blkinfos = blkinfos_read();
if (!blkinfos)
return NULL;
/* Get UUID for the specified devnode. */
util_list_iterate(blkinfos, p) {
b = p->ptr;
if (b->devnode && strcmp(b->devnode->name, name) == 0)
return b;
}
return NULL;
}
/* Find a blkinfo for the specified major+minor or NULL if none was found. */
static struct blkinfo *blkinfo_get_by_majmin(unsigned int major,
unsigned int minor)
{
struct util_list *blkinfos;
struct ptrlist_node *p;
struct blkinfo *b;
blkinfos = blkinfos_read();
if (!blkinfos)
return NULL;
/* Get UUID for the specified devnode. */
util_list_iterate(blkinfos, p) {
b = p->ptr;
if (b->devnode && b->devnode->major == major &&
b->devnode->minor == minor)
return b;
}
return NULL;
}
/* Return a newly allocated ptrlist of devnodes of block devices which are
* ancestors of the specified block device or NULL if no ancestors are found.
* An ancestor is a block device which is the parent of another block device
* according to lsblk data. */
struct util_list *blkinfo_get_ancestor_devnodes(struct devnode *devnode)
{
struct util_list *blkinfos;
struct util_list *todos, *result;
struct util_list *done;
struct devnode *curr, *p_devnode;
struct blkinfo *blkinfo, *parent;
struct ptrlist_node *p_curr, *p;
int num_parents;
blkinfos = blkinfos_read();
if (!blkinfos)
return NULL;
todos = ptrlist_new();
result = ptrlist_new();
done = strlist_new();
/* Repeat the resolution process until only devices with no parent
* remain. */
ptrlist_add(todos, devnode_copy(devnode));
while ((p_curr = util_list_start(todos))) {
util_list_remove(todos, p_curr);
curr = p_curr->ptr;
free(p_curr);
/* Check for parents. */
num_parents = 0;
util_list_iterate(blkinfos, p) {
blkinfo = p->ptr;
if (!blkinfo->devnode || !blkinfo->parent)
continue;
if (devnode_cmp(blkinfo->devnode, curr) != 0)
continue;
parent = blkinfo_get_by_name(blkinfo->parent);
if (parent && parent->devnode)
goto add;
/* Try to resolve name to major:minor. */
p_devnode = devnode_from_devfile(NULL, blkinfo->parent,
BLOCKDEV);
if (!p_devnode)
continue;
parent = blkinfo_get_by_majmin(p_devnode->major,
p_devnode->minor);
free(p_devnode);
if (!parent || !parent->devnode)
continue;
add:
ptrlist_add(todos, devnode_copy(parent->devnode));
num_parents++;
}
if (num_parents == 0 && devnode_cmp(curr, devnode) != 0 &&
!strlist_find(done, curr->name)) {
/* Add device with no parent to results (except for
* the initially specified device). */
ptrlist_add(result, curr);
strlist_add(done, curr->name);
} else
free(curr);
}
ptrlist_free(todos, 0);
strlist_free(done);
/* Check if any ancestors were found. */
if (util_list_is_empty(result)) {
ptrlist_free(result, 0);
return NULL;
}
return result;
}
/* Return a newly allocated ptrlist of devnodes of block devices which have
* the same file system UUID and type or NULL if no such devices are found. */
struct util_list *blkinfo_get_same_uuid_devnodes(struct devnode *devnode)
{
struct util_list *blkinfos;
struct util_list *devnodes;
struct ptrlist_node *p;
struct blkinfo *b;
char *uuid;
blkinfos = blkinfos_read();
if (!blkinfos)
return NULL;
b = blkinfo_get_by_devnode(devnode);
if (!b)
return NULL;
uuid = b->uuid;
if (!uuid)
return NULL;
/* Get all devnodes of devices with the same UUID. */
devnodes = ptrlist_new();
util_list_iterate(blkinfos, p) {
b = p->ptr;
if (!b->devnode || !b->uuid)
continue;
if (strcmp(b->uuid, uuid) != 0)
continue;
if (devnode_cmp(b->devnode, devnode) == 0)
continue;
ptrlist_add(devnodes, devnode_copy(b->devnode));
}
if (util_list_is_empty(devnodes)) {
strlist_free(devnodes);
devnodes = NULL;
}
return devnodes;
}
/* Check mountpoints in blkinfo for an entry that provides path. */
struct devnode *blkinfo_get_devnode_by_path(const char *path)
{
struct util_list *blkinfos;
struct ptrlist_node *p;
struct blkinfo *b, *match = NULL;
size_t blen, mlen = 0;
blkinfos = blkinfos_read();
if (!blkinfos)
return NULL;
util_list_iterate(blkinfos, p) {
b = p->ptr;
if (!b->devnode || !b->mountpoint)
continue;
blen = strlen(b->mountpoint);
/* path: /path/to/file
* mountpoint: /path */
if (strncmp(path, b->mountpoint, blen) != 0)
continue;
if (path[blen] && path[blen] != '/')
continue;
/* Ensure longest match. */
if (!match || blen > mlen) {
match = b;
mlen = blen;
}
}
return match ? devnode_copy(match->devnode) : NULL;
}
/* Add list of mountpoints for mounted file systems to strlist @list. */
void blkinfo_add_mountpoints(struct util_list *list)
{
struct util_list *blkinfos;
struct ptrlist_node *p;
struct blkinfo *b;
blkinfos = blkinfos_read();
if (!blkinfos)
return;
util_list_iterate(blkinfos, p) {
b = p->ptr;
if (!b->devnode || !b->mountpoint || *b->mountpoint != '/')
continue;
strlist_add_unique(list, b->mountpoint);
}
}
/* Add list of newly allocated devnodes for active swap devices to ptrlist
* @list. */
void blkinfo_add_swap_devnodes(struct util_list *list)
{
struct util_list *blkinfos;
struct ptrlist_node *p;
struct blkinfo *b;
blkinfos = blkinfos_read();
if (!blkinfos)
return;
util_list_iterate(blkinfos, p) {
b = p->ptr;
if (!b->devnode || !b->mountpoint ||
strcmp(b->mountpoint, "[SWAP]") != 0)
continue;
ptrlist_add(list, devnode_copy(b->devnode));
}
}
void blkinfo_exit(void)
{
blkinfos_free(cached_blkinfos);
}