git: 5e424fec72aa - stable/13 - linux(4): Uniformly dev_t arguments translation

From: Dmitry Chagin <dchagin_at_FreeBSD.org>
Date: Thu, 29 Jun 2023 08:20:28 UTC
The branch stable/13 has been updated by dchagin:

URL: https://cgit.FreeBSD.org/src/commit/?id=5e424fec72aafe1154ae23cd0674193e757a4d2a

commit 5e424fec72aafe1154ae23cd0674193e757a4d2a
Author:     Dmitry Chagin <dchagin@FreeBSD.org>
AuthorDate: 2023-04-28 08:55:05 +0000
Commit:     Dmitry Chagin <dchagin@FreeBSD.org>
CommitDate: 2023-06-29 08:15:49 +0000

    linux(4): Uniformly dev_t arguments translation
    
    The two main uses of dev_t are in struct stat and as a parameter of the
    mknod system calls.
    As of version 2.6.0 of the Linux kernel, dev_t is a 32-bit quantity
    with 12 bits set asaid for the major number and 20 for the minor number.
    The in-kernel dev_t encoded as MMMmmmmm, where M is a hex digit of the
    major number and m is a hex digit of the minor number.
    The user-space dev_t encoded as mmmM MMmm, where M and m is the major
    and minor numbers accordingly. This is downward compatible with legacy
    systems where dev_t is 16 bits wide, encoded as MMmm.
    In glibc dev_t is a 64-bit quantity, with 32-bit major and minor numbers,
    encoded as MMMM Mmmm mmmM MMmm. This is downward compatible with the Linux
    kernel and with legacy systems where dev_t is 16 bits wide.
    In the FreeBSD dev_t is a 64-bit quantity. The major and minor numbers
    are encoded as MMMmmmMm, therefore conversion of the device numbers between
    Linux user-space and FreeBSD kernel required.
    
    (cherry picked from commit 166e2e5a9e87d32dbfc7838903904673873f1e71)
---
 sys/compat/linux/linux.h       | 68 ++++++++++++++++++++++++++++++++++++++++++
 sys/compat/linux/linux_misc.c  |  4 +--
 sys/compat/linux/linux_stats.c | 49 ++++++++++++------------------
 sys/compat/linux/linux_util.c  |  3 +-
 4 files changed, 91 insertions(+), 33 deletions(-)

diff --git a/sys/compat/linux/linux.h b/sys/compat/linux/linux.h
index 9061548399e4..bb70007cfc5b 100644
--- a/sys/compat/linux/linux.h
+++ b/sys/compat/linux/linux.h
@@ -33,6 +33,74 @@
  */
 typedef uint32_t	l_dev_t;
 
+/*
+ * Linux dev_t conversion routines.
+ *
+ * As of version 2.6.0 of the Linux kernel, dev_t is a 32-bit quantity
+ * with 12 bits set asaid for the major number and 20 for the minor number.
+ * The in-kernel dev_t encoded as MMMmmmmm, where M is a hex digit of the
+ * major number and m is a hex digit of the minor number.
+ * The user-space dev_t encoded as mmmM MMmm, where M and m is the major
+ * and minor numbers accordingly. This is downward compatible with legacy
+ * systems where dev_t is 16 bits wide, encoded as MMmm.
+ * In glibc dev_t is a 64-bit quantity, with 32-bit major and minor numbers,
+ * encoded as MMMM Mmmm mmmM MMmm. This is downward compatible with the Linux
+ * kernel and with legacy systems where dev_t is 16 bits wide.
+ *
+ * In the FreeBSD dev_t is a 64-bit quantity. The major and minor numbers
+ * are encoded as MMMmmmMm, therefore conversion of the device numbers between
+ * Linux user-space and FreeBSD kernel required.
+ */
+static __inline l_dev_t
+linux_encode_dev(int _major, int _minor)
+{
+
+	return ((_minor & 0xff) | ((_major & 0xfff) << 8) |
+	    (((_minor & ~0xff) << 12) & 0xfff00000));
+}
+
+static __inline l_dev_t
+linux_new_encode_dev(dev_t _dev)
+{
+
+	return (_dev == NODEV ? 0 : linux_encode_dev(major(_dev), minor(_dev)));
+}
+
+static __inline int
+linux_encode_major(dev_t _dev)
+{
+
+	return (_dev == NODEV ? 0 : major(_dev) & 0xfff);
+}
+
+static __inline int
+linux_encode_minor(dev_t _dev)
+{
+
+	return (_dev == NODEV ? 0 : minor(_dev) & 0xfffff);
+}
+
+static __inline int
+linux_decode_major(l_dev_t _dev)
+{
+
+	return ((_dev & 0xfff00) >> 8);
+}
+
+static __inline int
+linux_decode_minor(l_dev_t _dev)
+{
+
+	return ((_dev & 0xff) | ((_dev & 0xfff00000) >> 12));
+}
+
+static __inline dev_t
+linux_decode_dev(l_dev_t _dev)
+{
+
+	return (makedev(linux_decode_major(_dev), linux_decode_minor(_dev)));
+}
+
 /*
  * Private Brandinfo flags
  */
diff --git a/sys/compat/linux/linux_misc.c b/sys/compat/linux/linux_misc.c
index ba0ac190a946..bc6ff9559493 100644
--- a/sys/compat/linux/linux_misc.c
+++ b/sys/compat/linux/linux_misc.c
@@ -901,7 +901,7 @@ linux_mknod(struct thread *td, struct linux_mknod_args *args)
 	case S_IFCHR:
 	case S_IFBLK:
 		error = kern_mknodat(td, AT_FDCWD, path, seg,
-		    args->mode, args->dev);
+		    args->mode, linux_decode_dev(args->dev));
 		break;
 
 	case S_IFDIR:
@@ -956,7 +956,7 @@ linux_mknodat(struct thread *td, struct linux_mknodat_args *args)
 	case S_IFCHR:
 	case S_IFBLK:
 		error = kern_mknodat(td, dfd, path, seg, args->mode,
-		    args->dev);
+		    linux_decode_dev(args->dev));
 		break;
 
 	case S_IFDIR:
diff --git a/sys/compat/linux/linux_stats.c b/sys/compat/linux/linux_stats.c
index 07ef72706d75..a5c1949a4d28 100644
--- a/sys/compat/linux/linux_stats.c
+++ b/sys/compat/linux/linux_stats.c
@@ -57,6 +57,7 @@ __FBSDID("$FreeBSD$");
 #include <machine/../linux/linux_proto.h>
 #endif
 
+#include <compat/linux/linux.h>
 #include <compat/linux/linux_file.h>
 #include <compat/linux/linux_util.h>
 
@@ -139,38 +140,19 @@ linux_kern_lstat(struct thread *td, const char *path, enum uio_seg pathseg,
 }
 #endif
 
-/*
- * l_dev_t has the same encoding as dev_t in the latter's low 16 bits, so
- * truncation of a dev_t to 16 bits gives the same result as unpacking
- * using major() and minor() and repacking in the l_dev_t format.  This
- * detail is hidden in dev_to_ldev().  Overflow in conversions of dev_t's
- * are not checked for, as for other fields.
- *
- * dev_to_ldev() is only used for translating st_dev.  When we convert
- * st_rdev for copying it out, it isn't really a dev_t, but has already
- * been translated to an l_dev_t in a nontrivial way.  Translating it
- * again would be illogical but would have no effect since the low 16
- * bits have the same encoding.
- *
- * The nontrivial translation for st_rdev renumbers some devices, but not
- * ones that can be mounted on, so it is consistent with the translation
- * for st_dev except when the renumbering or truncation causes conflicts.
- */
-#define	dev_to_ldev(d)	((uint16_t)(d))
-
 static int
 newstat_copyout(struct stat *buf, void *ubuf)
 {
 	struct l_newstat tbuf;
 
 	bzero(&tbuf, sizeof(tbuf));
-	tbuf.st_dev = dev_to_ldev(buf->st_dev);
+	tbuf.st_dev = linux_new_encode_dev(buf->st_dev);
 	tbuf.st_ino = buf->st_ino;
 	tbuf.st_mode = buf->st_mode;
 	tbuf.st_nlink = buf->st_nlink;
 	tbuf.st_uid = buf->st_uid;
 	tbuf.st_gid = buf->st_gid;
-	tbuf.st_rdev = buf->st_rdev;
+	tbuf.st_rdev = linux_new_encode_dev(buf->st_rdev);
 	tbuf.st_size = buf->st_size;
 	tbuf.st_atim.tv_sec = buf->st_atim.tv_sec;
 	tbuf.st_atim.tv_nsec = buf->st_atim.tv_nsec;
@@ -239,19 +221,27 @@ linux_newfstat(struct thread *td, struct linux_newfstat_args *args)
 }
 
 #if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32))
+
+static __inline uint16_t
+linux_old_encode_dev(dev_t _dev)
+{
+
+	return (_dev == NODEV ? 0 : linux_encode_dev(major(_dev), minor(_dev)));
+}
+
 static int
 old_stat_copyout(struct stat *buf, void *ubuf)
 {
 	struct l_old_stat lbuf;
 
 	bzero(&lbuf, sizeof(lbuf));
-	lbuf.st_dev = dev_to_ldev(buf->st_dev);
+	lbuf.st_dev = linux_old_encode_dev(buf->st_dev);
 	lbuf.st_ino = buf->st_ino;
 	lbuf.st_mode = buf->st_mode;
 	lbuf.st_nlink = buf->st_nlink;
 	lbuf.st_uid = buf->st_uid;
 	lbuf.st_gid = buf->st_gid;
-	lbuf.st_rdev = buf->st_rdev;
+	lbuf.st_rdev = linux_old_encode_dev(buf->st_rdev);
 	lbuf.st_size = MIN(buf->st_size, INT32_MAX);
 	lbuf.st_atim.tv_sec = buf->st_atim.tv_sec;
 	lbuf.st_atim.tv_nsec = buf->st_atim.tv_nsec;
@@ -550,13 +540,13 @@ stat64_copyout(struct stat *buf, void *ubuf)
 	struct l_stat64 lbuf;
 
 	bzero(&lbuf, sizeof(lbuf));
-	lbuf.st_dev = dev_to_ldev(buf->st_dev);
+	lbuf.st_dev = linux_new_encode_dev(buf->st_dev);
 	lbuf.st_ino = buf->st_ino;
 	lbuf.st_mode = buf->st_mode;
 	lbuf.st_nlink = buf->st_nlink;
 	lbuf.st_uid = buf->st_uid;
 	lbuf.st_gid = buf->st_gid;
-	lbuf.st_rdev = buf->st_rdev;
+	lbuf.st_rdev = linux_new_encode_dev(buf->st_rdev);
 	lbuf.st_size = buf->st_size;
 	lbuf.st_atim.tv_sec = buf->st_atim.tv_sec;
 	lbuf.st_atim.tv_nsec = buf->st_atim.tv_nsec;
@@ -762,11 +752,10 @@ statx_copyout(struct stat *buf, void *ubuf)
 	tbuf.stx_ctime.tv_nsec = buf->st_ctim.tv_nsec;
 	tbuf.stx_mtime.tv_sec = buf->st_mtim.tv_sec;
 	tbuf.stx_mtime.tv_nsec = buf->st_mtim.tv_nsec;
-
-	tbuf.stx_rdev_major = buf->st_rdev >> 8;
-	tbuf.stx_rdev_minor = buf->st_rdev & 0xff;
-	tbuf.stx_dev_major = buf->st_dev >> 8;
-	tbuf.stx_dev_minor = buf->st_dev & 0xff;
+	tbuf.stx_rdev_major = linux_encode_major(buf->st_rdev);
+	tbuf.stx_rdev_minor = linux_encode_minor(buf->st_rdev);
+	tbuf.stx_dev_major = linux_encode_major(buf->st_dev);
+	tbuf.stx_dev_minor = linux_encode_minor(buf->st_dev);
 
 	return (copyout(&tbuf, ubuf, sizeof(tbuf)));
 }
diff --git a/sys/compat/linux/linux_util.c b/sys/compat/linux/linux_util.c
index 5995ac5e18af..498320937fd3 100644
--- a/sys/compat/linux/linux_util.c
+++ b/sys/compat/linux/linux_util.c
@@ -35,6 +35,7 @@
 __FBSDID("$FreeBSD$");
 
 #include <sys/param.h>
+#include <sys/types.h>
 #include <sys/bus.h>
 #include <sys/conf.h>
 #include <sys/fcntl.h>
@@ -248,7 +249,7 @@ translate_vnhook_major_minor(struct vnode *vp, struct stat *sb)
 		sb->st_dev = rootdevmp->mnt_stat.f_fsid.val[0];
 
 	if (linux_vn_get_major_minor(vp, &major, &minor) == 0)
-		sb->st_rdev = (major << 8 | minor);
+		sb->st_rdev = makedev(major, minor);
 }
 
 char *