From 67c013cbccb32debeeaed7a943b9245ab82f128d Mon Sep 17 00:00:00 2001 From: Franck Bui Date: Fri, 13 Sep 2024 12:16:12 +0200 Subject: [PATCH] udev: restore some legacy symlinks to maintain backward compatibility Extracted the openSUSE git repository, branch "compats/udev-compat-symlinks", commit aa2d840a3b149497a0de95049482eb9f1c667a38. --- meson.build | 1 + rules.d/61-persistent-storage-compat.rules | 137 ++++++++ rules.d/meson.build | 1 + src/udev/compat/meson.build | 14 + src/udev/compat/path_id_compat.c | 378 +++++++++++++++++++++ 5 files changed, 531 insertions(+) create mode 100644 rules.d/61-persistent-storage-compat.rules create mode 100644 src/udev/compat/meson.build create mode 100644 src/udev/compat/path_id_compat.c diff --git a/meson.build b/meson.build index 2eaf69bb85..5ad90aaca2 100644 --- a/meson.build +++ b/meson.build @@ -2280,6 +2280,7 @@ subdir('src/libsystemd') subdir('src/shared') subdir('src/udev') subdir('src/libudev') +subdir('src/udev/compat') # must be after 'src/libudev' for the definition of 'libudev_basic' subdir('src/cryptsetup/cryptsetup-tokens') libsystemd = shared_library( diff --git a/rules.d/61-persistent-storage-compat.rules b/rules.d/61-persistent-storage-compat.rules new file mode 100644 index 0000000000..bd229f619b --- /dev/null +++ b/rules.d/61-persistent-storage-compat.rules @@ -0,0 +1,137 @@ +# Do not edit this file, it will be overwritten on update. + +# This file contains *depecrated* rules kept only for backward +# compatibility reasons. Indeed upstream has the bad habit to change +# symlink naming schemes hence breaking systems using the old schemes. +# +# If your system uses one of the symlinks generated by these compat +# rules (usually in /etc/fstab), we encourage you to replace the +# relevant paths with the new ones (symlinks generated by +# 60-persistent-storage.rules). +# +# You might check if your system relies on one of those compat symlinks +# by disabling their creation at boot time. To do so, append +# "udev.compat_symlink_generation=0" to the kernel command line. If +# your system works flawlessly, there's a good chance that your system +# doesn't rely on them and they could be disabled permanently. +# +# Thanks ! +# +# Note: this rules file can rely on all ID_* variables (set by +# 60-persistent-storage.rule) but should not overwrite them, see +# bsc#1048679 for details. + +ACTION=="remove", GOTO="persistent_storage_end" + +SUBSYSTEM!="block", GOTO="persistent_storage_end" +KERNEL!="nvme*|sd*", GOTO="persistent_storage_end" + +# ignore partitions that span the entire disk +TEST=="whole_disk", GOTO="persistent_storage_end" + +# +# The compat symlink generation number can be specified through the kernel +# command line and in that case it will take precedence. +# +# Note: any non-supported values (including "0") will disable all generations +# whereas no values specified will be equivalent to a value "1" and therefore +# will request the creation of all compat symlinks (whatever their age). +# +IMPORT{cmdline}="udev.compat_symlink_generation" +ENV{COMPAT_SYMLINK_GENERATION}="$env{udev.compat_symlink_generation}" + +# +# Systems without the compat-symlinks-generation file are systems +# installed before compat rules were created. They might be using +# one of those compat symlinks (can be any generation). +# +ENV{COMPAT_SYMLINK_GENERATION}!="?*", IMPORT{file}="/usr/lib/udev/compat-symlink-generation" +ENV{COMPAT_SYMLINK_GENERATION}!="?*", ENV{COMPAT_SYMLINK_GENERATION}="1" + +# +# Generation #1 +# +ENV{COMPAT_SYMLINK_GENERATION}!="1", GOTO="generation_2" + +# NVMe symlinks were introduced by a SUSE specific commit (bsc#944132) which +# relied on scsi_id (unfortunately) and hence used the NVMe SCSI translation +# layer. Later upstream added (by-id) symlinks for NVMe as well but reads the +# device properties from its sysfs attributes instead. The symlinks names +# generated in both cases are not identical so we still have to generate the +# old ones for backward compatibly reasons. +# +# The SCSI translation layer for NVMe devices has been removed from the kernel +# since 4.13, therefore we had to drop the use of scsi_id and use the sysfs +# interface to mimic scsi_id and continue to generate the old compat symlinks. +# +# The rules below hopefully mimics the main cases only as it's impossible to +# re-implement the exact behavior of scsi_id via udev rules. +# +# scsi_id acted differently depending on the NVMe revision implemented by a +# device, which can lead to problems if the device firmware is +# updated. Therefore symlinks for all NVMe revisions are generated now. +# +# Extra care is needed for whitespace handling. For example we can't use +# %s{model} to retrieve the model string because udev strips any trailing +# whitespaces and some plateforms (QEMU does that) might append such chars. In +# those cases scsi_id was replacing them with a single trailing '_'. Therefore +# the currently code retrieves the model string manually making sure to +# preserve all characters so trailing whitespaces are still converted when the +# symlink is created. + +KERNEL=="nvme*", ENV{DEVTYPE}=="disk", ENV{ID_NVME_EUI_COMPAT}!="?*", ATTRS{eui}=="?*", ATTRS{eui}!="0000000000000000", \ + PROGRAM=="/bin/sh -c 'eui=\"%s{eui}\"; echo $${eui// /}", ENV{ID_NVME_EUI_COMPAT}="2%c" + +KERNEL=="nvme*", ENV{DEVTYPE}=="disk", ENV{ID_NVME_NGUID_COMPAT}!="?*", ATTRS{nguid}=="?*", \ + PROGRAM=="/bin/sh -c 'nguid=\"%s{nguid}\"; echo $${nguid//-/}", ENV{ID_NVME_NGUID_COMPAT}="%c" + +KERNEL=="nvme*", ENV{DEVTYPE}=="disk", ENV{ID_NVME_SERIAL_COMPAT}!="?*", ATTRS{model}=="?*", ATTRS{serial}=="?*", \ + PROGRAM=="/bin/sh -c ' \ + cd /sys/%p; \ + while ! [ -f model ]; do \ + cd ..; \ + [ $$(pwd) = %S ] && exit 1; \ + done; \ + cut -c 1-16 model'", ENV{ID_NVME_SERIAL_COMPAT}="SNVMe_%c%s{serial}" + +KERNEL=="nvme*", ENV{DEVTYPE}=="disk", ENV{ID_NVME_EUI_COMPAT}=="?*", SYMLINK+="disk/by-id/nvme-$env{ID_NVME_EUI_COMPAT}" +KERNEL=="nvme*", ENV{DEVTYPE}=="disk", ENV{ID_NVME_NGUID_COMPAT}=="?*", SYMLINK+="disk/by-id/nvme-$env{ID_NVME_NGUID_COMPAT}" +KERNEL=="nvme*", ENV{DEVTYPE}=="disk", ENV{ID_NVME_SERIAL_COMPAT}=="?*", SYMLINK+="disk/by-id/nvme-$env{ID_NVME_SERIAL_COMPAT}" + +KERNEL=="nvme*", ENV{DEVTYPE}=="partition", ENV{ID_NVME_EUI_COMPAT}=="?*", SYMLINK+="disk/by-id/nvme-$env{ID_NVME_EUI_COMPAT}-part%n" +KERNEL=="nvme*", ENV{DEVTYPE}=="partition", ENV{ID_NVME_NGUID_COMPAT}=="?*", SYMLINK+="disk/by-id/nvme-$env{ID_NVME_NGUID_COMPAT}-part%n" +KERNEL=="nvme*", ENV{DEVTYPE}=="partition", ENV{ID_NVME_SERIAL_COMPAT}=="?*", SYMLINK+="disk/by-id/nvme-$env{ID_NVME_SERIAL_COMPAT}-part%n" + +# Leap 42.3 ISO has a version of udev which suffers from bsc#1048679 +# (ID_SERIAL is set by the upstream rules making ID_BUS empty instead +# of "nvme"). This lead to those odd symlinks with the "nvme" prefix +# missing (bsc#1063249). +# +# They are actually only used by systems with Leap 42.3 initially +# installed and with NVMe encrypted partitions. +KERNEL=="nvme*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/-$env{ID_SERIAL}" +KERNEL=="nvme*", ENV{DEVTYPE}=="partition", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/-$env{ID_SERIAL}-part%n" + +# SCSI compat links for ATA devices, removed by f6ba1a468cea (boo#769002) +KERNEL=="sd*[!0-9]", ENV{ID_BUS}=="ata", PROGRAM=="scsi_id --whitelisted --replace-whitespace -p0x80 -d $devnode", RESULT=="?*", ENV{ID_SCSI_COMPAT}="$result", SYMLINK+="disk/by-id/scsi-$env{ID_SCSI_COMPAT}" +KERNEL=="sd*[0-9]", ENV{ID_SCSI_COMPAT}=="?*", SYMLINK+="disk/by-id/scsi-$env{ID_SCSI_COMPAT}-part%n" + +# by-path (parent device path, compat version, only for ATA/NVMe/SAS bus) (bnc#916420) +ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="ata|nvme|scsi", DEVPATH!="*/virtual/*", IMPORT{program}="path_id_compat --compat=1 %p" +ENV{DEVTYPE}=="disk", ENV{ID_PATH_COMPAT1}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH_COMPAT1}" +ENV{DEVTYPE}=="partition", ENV{ID_PATH_COMPAT1}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH_COMPAT1}-part%n" + +# This restores the symlinks for SAS disks removed by 66bba0e701b95dc42e (bsc#1040153) +ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="ata|nvme|scsi", DEVPATH!="*/virtual/*", IMPORT{program}="path_id_compat --compat=2 %p" +ENV{DEVTYPE}=="disk", ENV{ID_PATH_COMPAT2}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH_COMPAT2}" +ENV{DEVTYPE}=="partition", ENV{ID_PATH_COMPAT2}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH_COMPAT2}-part%n" + +# +# Generation #2 +# +LABEL="generation_2" +ENV{COMPAT_SYMLINK_GENERATION}!="1|2", GOTO="persistent_storage_end" + +# Currently no compat links of generation 2 + +LABEL="persistent_storage_end" diff --git a/rules.d/meson.build b/rules.d/meson.build index 20fca222da..62f5fa844b 100644 --- a/rules.d/meson.build +++ b/rules.d/meson.build @@ -20,6 +20,7 @@ rules = [ '60-persistent-v4l.rules', '60-sensor.rules', '60-serial.rules', + '61-persistent-storage-compat.rules', '70-camera.rules', '70-joystick.rules', '70-mouse.rules', diff --git a/src/udev/compat/meson.build b/src/udev/compat/meson.build new file mode 100644 index 0000000000..390d70d4fe --- /dev/null +++ b/src/udev/compat/meson.build @@ -0,0 +1,14 @@ +foreach prog : ['path_id_compat.c'] + + executable(prog.split('.')[0], + prog, + include_directories : [includes, + libudev_includes], + dependencies : [userspace, + versiondep], + c_args : ['-DLOG_REALM=LOG_REALM_UDEV'], + link_with : [udev_link_with, libudev_basic], + install_rpath : udev_rpath, + install : true, + install_dir : udevlibexecdir) +endforeach diff --git a/src/udev/compat/path_id_compat.c b/src/udev/compat/path_id_compat.c new file mode 100644 index 0000000000..f0d8e189fe --- /dev/null +++ b/src/udev/compat/path_id_compat.c @@ -0,0 +1,378 @@ +/* + * path_id_compat.c: compose persistent device path (compat version) + * + * Copyright (C) 2009 Kay Sievers + * + * Logic based on Hannes Reinecke's shell script. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libudev.h" +#include "parse-util.h" +#include "string-util.h" + +#define PATH_SIZE 16384 +#define SYSFS_PATH "/sys" + +static const char *compat_version_str = NULL; +static unsigned compat_version; + +static int path_prepend(char **path, const char *fmt, ...) +{ + va_list va; + char *old; + char *pre; + int err; + + old = *path; + + va_start(va, fmt); + err = vasprintf(&pre, fmt, va); + va_end(va); + if (err < 0) + return err; + + if (old != NULL) { + err = asprintf(path, "%s-%s", pre, old); + if (err < 0) + return err; + free(pre); + } else { + *path = pre; + } + + free(old); + return 0; +} + +/* +** Linux only supports 32 bit luns. +** See drivers/scsi/scsi_scan.c::scsilun_to_int() for more details. +*/ +static int format_lun_number(struct udev_device *dev, char **path) +{ + unsigned long lun = strtoul(udev_device_get_sysnum(dev), NULL, 10); + + /* address method 0, peripheral device addressing with bus id of zero */ + if (lun < 256) + return path_prepend(path, "lun-%d", lun); + + /* handle all other lun addressing methods by using a variant of the original lun format */ + return path_prepend(path, "lun-0x%04x%04x00000000", (lun & 0xffff), (lun >> 16) & 0xffff); +} + +static struct udev_device *skip_subsystem(struct udev_device *dev, const char *subsys) +{ + struct udev_device *parent = dev; + + while (parent != NULL) { + const char *subsystem; + + subsystem = udev_device_get_subsystem(parent); + if (subsystem == NULL || strcmp(subsystem, subsys) != 0) + break; + dev = parent; + parent = udev_device_get_parent(parent); + } + return dev; +} + +static struct udev_device *handle_scsi_default(struct udev_device *parent, char **path) +{ + struct udev_device *hostdev; + int host, bus, target, lun; + const char *name; + char *base; + char *pos; + DIR *dir; + struct dirent *dent; + int basenum; + + hostdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host"); + if (hostdev == NULL) + return NULL; + + name = udev_device_get_sysname(parent); + if (sscanf(name, "%d:%d:%d:%d", &host, &bus, &target, &lun) != 4) + return NULL; + + /* rebase host offset to get the local relative number */ + basenum = -1; + base = strdup(udev_device_get_syspath(hostdev)); + if (base == NULL) + return NULL; + pos = strrchr(base, '/'); + if (pos == NULL) { + parent = NULL; + goto out; + } + pos[0] = '\0'; + dir = opendir(base); + if (dir == NULL) { + parent = NULL; + goto out; + } + for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { + char *rest; + int i; + + if (dent->d_name[0] == '.') + continue; + if (dent->d_type != DT_DIR && dent->d_type != DT_LNK) + continue; + if (strncmp(dent->d_name, "host", 4) != 0) + continue; + i = strtoul(&dent->d_name[4], &rest, 10); + if (rest[0] != '\0') + continue; + if (basenum == -1 || i < basenum) + basenum = i; + } + closedir(dir); + if (basenum == -1) { + parent = NULL; + goto out; + } + host -= basenum; + + path_prepend(path, "scsi-%u:%u:%u:%u", host, bus, target, lun); +out: + free(base); + return hostdev; +} + +static struct udev_device *handle_ata(struct udev_device *parent, char **path) +{ + struct udev_device *hostdev; + int host, bus, target, lun; + const char *name; + + hostdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host"); + if (hostdev == NULL) + return NULL; + + name = udev_device_get_sysname(parent); + if (sscanf(name, "%d:%d:%d:%d", &host, &bus, &target, &lun) != 4) + return NULL; + + path_prepend(path, "scsi-%u:%u:%u:%u", host, bus, target, lun); + + return hostdev; +} + +static struct udev_device *handle_scsi_sas(struct udev_device *parent, char **path) +{ + struct udev *udev = udev_device_get_udev(parent); + struct udev_device *targetdev; + struct udev_device *target_parent; + struct udev_device *sasdev; + struct udev_device *portdev; + struct dirent *dent; + DIR *dir; + const char *sas_address; + int tmp_phy_id, phy_id = 255; + char *lun = NULL; + + targetdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target"); + if (!targetdev) + return NULL; + + target_parent = udev_device_get_parent(targetdev); + if (!target_parent) + return NULL; + + portdev = udev_device_get_parent(target_parent); + if (!portdev) + return NULL; + + dir = opendir(udev_device_get_syspath(portdev)); + if (!dir) + return NULL; + + for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { + const char *name = dent->d_name; + char *phy_id_str; + + if (dent->d_type != DT_LNK) + continue; + + if (strncmp(dent->d_name, "phy", 3) != 0) + continue; + + phy_id_str = strstr(name, ":"); + if (phy_id_str == NULL) + continue; + + phy_id_str++; + + tmp_phy_id = atoi(phy_id_str); + if (tmp_phy_id >= 0 && tmp_phy_id < phy_id) + phy_id = tmp_phy_id; + } + closedir(dir); + + if (phy_id == 255) + return NULL; + + sasdev = udev_device_new_from_subsystem_sysname(udev, "sas_device", + udev_device_get_sysname(target_parent)); + if (sasdev == NULL) + return NULL; + + sas_address = udev_device_get_sysattr_value(sasdev, "sas_address"); + if (sas_address == NULL) { + parent = NULL; + goto out; + } + + format_lun_number(parent, &lun); + + switch (compat_version) { + case 1: + path_prepend(path, "sas-phy%d-%s-%s", phy_id, sas_address, lun); + break; + case 2: + path_prepend(path, "sas-%s-%s", sas_address, lun); + break; + } + + if (lun) + free(lun); +out: + udev_device_unref(sasdev); + return parent; +} + +static struct udev_device *handle_scsi(struct udev_device *parent, char **path) +{ + const char *devtype; + const char *name; + + devtype = udev_device_get_devtype(parent); + if (devtype == NULL || strcmp(devtype, "scsi_device") != 0) + return parent; + + /* lousy scsi sysfs does not have a "subsystem" for the transport */ + name = udev_device_get_syspath(parent); + + if (strstr(name, "/end_device-") != NULL) { + parent = handle_scsi_sas(parent, path); + goto out; + } + + if (strstr(name, "/ata") != NULL) { + parent = handle_ata(parent, path); + goto out; + } + + parent = handle_scsi_default(parent, path); +out: + return parent; +} + +int main(int argc, char **argv) +{ + static const struct option options[] = { + { "compat", required_argument, NULL, 'V' }, + }; + struct udev *udev; + struct udev_device *dev; + struct udev_device *parent; + char syspath[PATH_SIZE]; + char *path = NULL; + int rc = 1; + + for (;;) { + int option; + + option = getopt_long(argc, argv, "v:", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'V': + compat_version_str = optarg; + break; + } + } + + if (compat_version_str) { + if (safe_atou(compat_version_str, &compat_version) < 0) { + fprintf(stderr, "--compat takes an integer.\n"); + goto exit2; + } + } + + if (argv[optind] == NULL) { + fprintf(stderr, "No device specified\n"); + rc = 2; + goto exit2; + } + + udev = udev_new(); + if (udev == NULL) + goto exit2; + + snprintf(syspath, PATH_SIZE, "%s%s", SYSFS_PATH, argv[optind]); + dev = udev_device_new_from_syspath(udev, syspath); + if (dev == NULL) { + fprintf(stderr, "unable to access '%s'\n", argv[optind]); + rc = 3; + goto exit1; + } + + /* walk up the chain of devices and compose path */ + parent = dev; + while (parent != NULL) { + const char *subsys; + + subsys = udev_device_get_subsystem(parent); + + if (subsys == NULL) { + ; + } else if (strcmp(subsys, "scsi") == 0) { + parent = handle_scsi(parent, &path); + } else if (strcmp(subsys, "pci") == 0) { + path_prepend(&path, "pci-%s", udev_device_get_sysname(parent)); + parent = skip_subsystem(parent, "pci"); + } + + parent = udev_device_get_parent(parent); + } + + if (path != NULL) { + printf("ID_PATH_COMPAT%s=%s\n", strempty(compat_version_str), path); + free(path); + rc = 0; + } + + udev_device_unref(dev); + +exit1: + udev_unref(udev); + +exit2: + return rc; +} -- 2.43.0