aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Chagin <dchagin@FreeBSD.org>2023-04-28 08:55:05 +0000
committerDmitry Chagin <dchagin@FreeBSD.org>2023-06-29 08:15:49 +0000
commit5e424fec72aafe1154ae23cd0674193e757a4d2a (patch)
tree71e9a04f94564d53463fb2872bf8d5f84dbd926c
parentb971d6aec11597dfdad36a53680c869062b7ba64 (diff)
downloadsrc-5e424fec72aafe1154ae23cd0674193e757a4d2a.tar.gz
src-5e424fec72aafe1154ae23cd0674193e757a4d2a.zip
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)
-rw-r--r--sys/compat/linux/linux.h68
-rw-r--r--sys/compat/linux/linux_misc.c4
-rw-r--r--sys/compat/linux/linux_stats.c49
-rw-r--r--sys/compat/linux/linux_util.c3
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
@@ -34,6 +34,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
*/
#define LINUX_BI_FUTEX_REQUEUE 0x01000000
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 *