From b627d5aae4a8221ccd80dafb592edabfa6cc47cd Mon Sep 17 00:00:00 2001
From: Franck Bui <fbui@suse.com>
Date: Fri, 13 Sep 2024 12:16:12 +0200
Subject: [PATCH 1/1] udev: restore some legacy symlinks to maintain backward
 compatibility

Extracted the openSUSE git repository, branch "compats/udev-compat-symlinks",
commit aa2d840a3b149497a0de95049482eb9f1c667a38.
---
 rules.d/61-persistent-storage-compat.rules | 137 ++++++++
 rules.d/meson.build                        |   1 +
 src/udev/compat/meson.build                |  13 +
 src/udev/compat/path_id_compat.c           | 378 +++++++++++++++++++++
 src/udev/meson.build                       |   2 +
 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/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..2f07b6a9c7
--- /dev/null
+++ b/src/udev/compat/meson.build
@@ -0,0 +1,13 @@
+foreach prog : ['path_id_compat.c']
+
+        executable(prog.split('.')[0],
+                   prog,
+                   include_directories : [includes,
+                                          libudev_includes],
+                   dependencies : [userspace],
+                   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 <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 "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;
+}
diff --git a/src/udev/meson.build b/src/udev/meson.build
index 3535551e74..d728f512d6 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') # must be after 'src/libudev' for the definition of 'libudev_basic'
-- 
2.43.0

