systemd/0003-udev-restore-some-legacy-symlinks-to-maintain-backwa.patch

539 lines
20 KiB
Diff

From 39bbd48fee33d820ba3a2ed52067234b623e13c4 Mon Sep 17 00:00:00 2001
From: Franck Bui <fbui@suse.com>
Date: Fri, 13 Sep 2024 12:16:12 +0200
Subject: [PATCH] udev: restore some legacy symlinks to maintain backward
compatibility
Extracted from the openSUSE git repository, branch "compats/udev-compat-symlinks",
commit aa2d840a3b149497a0de95049482eb9f1c667a38.
- 2025-01-29: port to sd_device while trying to minimize the number of behavior changes.
---
rules.d/61-persistent-storage-compat.rules | 137 +++++++++
rules.d/meson.build | 1 +
src/udev/compat/meson.build | 8 +
src/udev/compat/path_id_compat.c | 329 +++++++++++++++++++++
src/udev/meson.build | 2 +
5 files changed, 477 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/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 3040fae8a4..ca07be282c 100644
--- a/rules.d/meson.build
+++ b/rules.d/meson.build
@@ -21,6 +21,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..d624229bd1
--- /dev/null
+++ b/src/udev/compat/meson.build
@@ -0,0 +1,8 @@
+executable('path_id_compat',
+ 'path_id_compat.c',
+ include_directories : includes,
+ dependencies : [userspace],
+ c_args : ['-DLOG_REALM=LOG_REALM_UDEV'],
+ link_with : libshared,
+ install : true,
+ install_dir : udevlibexecdir)
diff --git a/src/udev/compat/path_id_compat.c b/src/udev/compat/path_id_compat.c
new file mode 100644
index 0000000000..c5839916ea
--- /dev/null
+++ b/src/udev/compat/path_id_compat.c
@@ -0,0 +1,329 @@
+/*
+ * path_id_compat.c: compose persistent device path (compat version)
+ *
+ * Copyright (C) 2009 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+
+#include "device-util.h"
+#include "dirent-util.h"
+#include "fd-util.h"
+#include "parse-util.h"
+#include "sd-device.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(sd_device *dev, char **path) {
+ unsigned long lun;
+ const char *sysnum;
+
+ (void) sd_device_get_sysnum(dev, &sysnum);
+
+ lun = strtoul(sysnum, NULL, 10);
+ if (lun < 256) /* address method 0, peripheral device addressing with bus id of zero */
+ 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 sd_device *skip_subsystem(sd_device *dev, const char *subsys) {
+ for (sd_device *parent = dev; ; ) {
+ if (!device_in_subsystem(parent, subsys))
+ break;
+
+ dev = parent;
+ if (sd_device_get_parent(dev, &parent) < 0)
+ break;
+ }
+
+ return dev;
+}
+
+static sd_device *handle_scsi_default(sd_device *parent, char **path) {
+ sd_device *hostdev;
+ int host, bus, target, lun;
+ const char *sysname, *base, *pos;
+ _cleanup_closedir_ DIR *dir = NULL;
+ int basenum = -1;
+
+ if (sd_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host", &hostdev) < 0)
+ return NULL;
+
+ if (sd_device_get_sysname(parent, &sysname) < 0)
+ return NULL;
+ if (sscanf(sysname, "%d:%d:%d:%d", &host, &bus, &target, &lun) != 4)
+ return NULL;
+
+ /* rebase host offset to get the local relative number */
+ if (sd_device_get_syspath(hostdev, &base) < 0)
+ return NULL;
+ pos = strrchr(base, '/');
+ if (!pos)
+ return NULL;
+
+ base = strndupa_safe(base, pos - base);
+ dir = opendir(base);
+ if (!dir)
+ return NULL;
+
+ FOREACH_DIRENT_ALL(dent, dir, break) {
+ 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;
+ }
+ if (basenum == -1)
+ return NULL;
+ host -= basenum;
+
+ path_prepend(path, "scsi-%u:%u:%u:%u", host, bus, target, lun);
+ return hostdev;
+}
+
+static sd_device *handle_ata(sd_device *parent, char **path) {
+ sd_device *hostdev;
+ int host, bus, target, lun;
+ const char *name;
+
+ if (sd_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host", &hostdev) < 0)
+ return NULL;
+
+ if (sd_device_get_sysname(parent, &name) < 0)
+ return NULL;
+ 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 sd_device *handle_scsi_sas(sd_device *parent, char **path) {
+ _cleanup_closedir_ DIR *dir = NULL;
+ _cleanup_(sd_device_unrefp) sd_device *sasdev = NULL;
+ sd_device *targetdev, *target_parent, *portdev;
+ const char *sas_address, *syspath, *sysname;
+ int tmp_phy_id, phy_id = 255;
+ _cleanup_free_ char *lun = NULL;
+
+ if (sd_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target", &targetdev) < 0)
+ return NULL;
+
+ if (sd_device_get_parent(targetdev, &target_parent) < 0)
+ return NULL;
+
+ if (sd_device_get_parent(target_parent, &portdev) < 0)
+ return NULL;
+
+ if (sd_device_get_syspath(portdev, &syspath) < 0)
+ return NULL;
+ dir = opendir(syspath);
+ if (!dir)
+ return NULL;
+
+ FOREACH_DIRENT_ALL(dent, dir, break) {
+ 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;
+ }
+
+ if (phy_id == 255)
+ return NULL;
+
+ if (sd_device_get_sysname(target_parent, &sysname) < 0)
+ return NULL;
+
+ if (sd_device_new_from_subsystem_sysname(&sasdev, "sas_device", sysname) < 0)
+ return NULL;
+
+ if (sd_device_get_sysattr_value(sasdev, "sas_address", &sas_address) < 0)
+ return NULL;
+
+ if (format_lun_number(parent, &lun) < 0)
+ return NULL;
+
+ 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;
+ }
+
+ return parent;
+}
+
+static sd_device *handle_scsi(sd_device *parent, char **path) {
+ const char *devtype;
+ const char *name;
+ int r;
+
+ r = sd_device_get_devtype(parent, &devtype);
+ if (r < 0 || strcmp(devtype, "scsi_device") != 0)
+ return parent;
+
+ /* lousy scsi sysfs does not have a "subsystem" for the transport */
+ (void) sd_device_get_syspath(parent, &name);
+
+ if (strstr(name, "/end_device-") != NULL)
+ return handle_scsi_sas(parent, path);
+
+ if (strstr(name, "/ata") != NULL)
+ return handle_ata(parent, path);
+
+ return handle_scsi_default(parent, path);
+}
+
+int main(int argc, char **argv) {
+ static const struct option options[] = {
+ { "compat", required_argument, NULL, 'V' },
+ };
+ _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
+ _cleanup_free_ char *path = NULL;
+ char syspath[PATH_SIZE];
+ int r = 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");
+ return 1;
+ }
+ }
+
+ if (argv[optind] == NULL) {
+ fprintf(stderr, "No device specified\n");
+ return 2;
+ }
+
+ snprintf(syspath, PATH_SIZE, "%s%s", SYSFS_PATH, argv[optind]);
+ if (sd_device_new_from_syspath(&dev, syspath) < 0) {
+ fprintf(stderr, "unable to access '%s'\n", argv[optind]);
+ return 3;
+ }
+
+ /* walk up the chain of devices and compose path */
+ for (sd_device *parent = dev; parent; ) {
+ const char *sysname;
+
+ if (sd_device_get_sysname(parent, &sysname) < 0)
+ ;
+ else if (device_in_subsystem(parent, "scsi"))
+ parent = handle_scsi(parent, &path);
+ else if (device_in_subsystem(parent, "pci")) {
+ path_prepend(&path, "pci-%s", sysname);
+ parent = skip_subsystem(parent, "pci");
+ }
+
+ if (!parent)
+ break;
+ if (sd_device_get_parent(parent, &parent) < 0) /* return -ENOENT when no more parent */
+ break;
+ }
+
+ if (path) {
+ printf("ID_PATH_COMPAT%s=%s\n", strempty(compat_version_str), path);
+ r = 0;
+ }
+
+ return r;
+}
diff --git a/src/udev/meson.build b/src/udev/meson.build
index 3535551e74..59073f33d7 100644
--- a/src/udev/meson.build
+++ b/src/udev/meson.build
@@ -273,3 +273,5 @@ udev_pc = custom_target(
if install_sysconfdir
install_emptydir(sysconfdir / 'udev/rules.d')
endif
+
+subdir('compat')
--
2.43.0