diff options
Diffstat (limited to 'SOURCES/0001-ntsync.patch')
-rw-r--r-- | SOURCES/0001-ntsync.patch | 492 |
1 files changed, 154 insertions, 338 deletions
diff --git a/SOURCES/0001-ntsync.patch b/SOURCES/0001-ntsync.patch index 00a3538..4a16758 100644 --- a/SOURCES/0001-ntsync.patch +++ b/SOURCES/0001-ntsync.patch @@ -1,37 +1,33 @@ -From 0f079f29cb143deb96685a5d7e5c40ee3e709cf8 Mon Sep 17 00:00:00 2001 +From 36ef0070410e229e52c9de58d6021df36a4b1707 Mon Sep 17 00:00:00 2001 From: Peter Jung <admin@ptr1337.dev> -Date: Fri, 5 Jul 2024 10:32:47 +0200 -Subject: [PATCH 08/10] ntsync +Date: Sat, 3 Aug 2024 09:34:15 +0200 +Subject: [PATCH 09/12] ntsync Signed-off-by: Peter Jung <admin@ptr1337.dev> --- Documentation/userspace-api/index.rst | 1 + - .../userspace-api/ioctl/ioctl-number.rst | 2 + Documentation/userspace-api/ntsync.rst | 398 +++++ MAINTAINERS | 9 + - drivers/misc/Kconfig | 11 + - drivers/misc/Makefile | 1 + - drivers/misc/ntsync.c | 1232 +++++++++++++++ - include/uapi/linux/ntsync.h | 62 + + drivers/misc/Kconfig | 1 - + drivers/misc/ntsync.c | 989 +++++++++++- + include/uapi/linux/ntsync.h | 39 + tools/testing/selftests/Makefile | 1 + .../selftests/drivers/ntsync/.gitignore | 1 + .../testing/selftests/drivers/ntsync/Makefile | 7 + tools/testing/selftests/drivers/ntsync/config | 1 + .../testing/selftests/drivers/ntsync/ntsync.c | 1407 +++++++++++++++++ - 13 files changed, 3133 insertions(+) + 11 files changed, 2850 insertions(+), 4 deletions(-) create mode 100644 Documentation/userspace-api/ntsync.rst - create mode 100644 drivers/misc/ntsync.c - create mode 100644 include/uapi/linux/ntsync.h create mode 100644 tools/testing/selftests/drivers/ntsync/.gitignore create mode 100644 tools/testing/selftests/drivers/ntsync/Makefile create mode 100644 tools/testing/selftests/drivers/ntsync/config create mode 100644 tools/testing/selftests/drivers/ntsync/ntsync.c diff --git a/Documentation/userspace-api/index.rst b/Documentation/userspace-api/index.rst -index afecfe3cc4a8..d5745a500fa7 100644 +index 8a251d71fa6e..02bea81fb4bf 100644 --- a/Documentation/userspace-api/index.rst +++ b/Documentation/userspace-api/index.rst -@@ -62,6 +62,7 @@ Everything else +@@ -64,6 +64,7 @@ Everything else vduse futex2 perf_ring_buffer @@ -39,19 +35,6 @@ index afecfe3cc4a8..d5745a500fa7 100644 .. only:: subproject and html -diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst -index c472423412bf..a141e8e65c5d 100644 ---- a/Documentation/userspace-api/ioctl/ioctl-number.rst -+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst -@@ -174,6 +174,8 @@ Code Seq# Include File Comments - 'M' 00-0F drivers/video/fsl-diu-fb.h conflict! - 'N' 00-1F drivers/usb/scanner.h - 'N' 40-7F drivers/block/nvme.c -+'N' 80-8F uapi/linux/ntsync.h NT synchronization primitives -+ <mailto:wine-devel@winehq.org> - 'O' 00-06 mtd/ubi-user.h UBI - 'P' all linux/soundcard.h conflict! - 'P' 60-6F sound/sscape_ioctl.h conflict! diff --git a/Documentation/userspace-api/ntsync.rst b/Documentation/userspace-api/ntsync.rst new file mode 100644 index 000000000000..767844637a7d @@ -457,10 +440,10 @@ index 000000000000..767844637a7d + ``objs`` and in ``alert``. If this is attempted, the function fails + with ``EINVAL``. diff --git a/MAINTAINERS b/MAINTAINERS -index 3121709d99e3..baa28e4151aa 100644 +index b27470be2e6a..4112729fc23a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS -@@ -15720,6 +15720,15 @@ T: git https://github.com/Paragon-Software-Group/linux-ntfs3.git +@@ -15983,6 +15983,15 @@ T: git https://github.com/Paragon-Software-Group/linux-ntfs3.git F: Documentation/filesystems/ntfs3.rst F: fs/ntfs3/ @@ -477,104 +460,66 @@ index 3121709d99e3..baa28e4151aa 100644 M: Finn Thain <fthain@linux-m68k.org> L: linux-m68k@lists.linux-m68k.org diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig -index 4fb291f0bf7c..801ed229ed7d 100644 +index faf983680040..2907b5c23368 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig -@@ -506,6 +506,17 @@ config OPEN_DICE +@@ -507,7 +507,6 @@ config OPEN_DICE - If unsure, say N. - -+config NTSYNC -+ tristate "NT synchronization primitive emulation" -+ help -+ This module provides kernel support for emulation of Windows NT -+ synchronization primitives. It is not a hardware driver. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called ntsync. -+ -+ If unsure, say N. -+ - config VCPU_STALL_DETECTOR - tristate "Guest vCPU stall detector" - depends on OF && HAS_IOMEM -diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile -index ea6ea5bbbc9c..153a3f4837e8 100644 ---- a/drivers/misc/Makefile -+++ b/drivers/misc/Makefile -@@ -59,6 +59,7 @@ obj-$(CONFIG_PVPANIC) += pvpanic/ - obj-$(CONFIG_UACCE) += uacce/ - obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o - obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o -+obj-$(CONFIG_NTSYNC) += ntsync.o - obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o - obj-$(CONFIG_OPEN_DICE) += open-dice.o - obj-$(CONFIG_GP_PCI1XXXX) += mchp_pci1xxxx/ + config NTSYNC + tristate "NT synchronization primitive emulation" +- depends on BROKEN + help + This module provides kernel support for emulation of Windows NT + synchronization primitives. It is not a hardware driver. diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c -new file mode 100644 -index 000000000000..87a24798a5c7 ---- /dev/null +index 3c2f743c58b0..87a24798a5c7 100644 +--- a/drivers/misc/ntsync.c +++ b/drivers/misc/ntsync.c -@@ -0,0 +1,1232 @@ -+// SPDX-License-Identifier: GPL-2.0-only -+/* -+ * ntsync.c - Kernel driver for NT synchronization primitives -+ * -+ * Copyright (C) 2024 Elizabeth Figura <zfigura@codeweavers.com> -+ */ -+ -+#include <linux/anon_inodes.h> +@@ -6,11 +6,17 @@ + */ + + #include <linux/anon_inodes.h> +#include <linux/atomic.h> -+#include <linux/file.h> -+#include <linux/fs.h> + #include <linux/file.h> + #include <linux/fs.h> +#include <linux/hrtimer.h> +#include <linux/ktime.h> -+#include <linux/miscdevice.h> -+#include <linux/module.h> + #include <linux/miscdevice.h> + #include <linux/module.h> +#include <linux/mutex.h> -+#include <linux/overflow.h> + #include <linux/overflow.h> +#include <linux/sched.h> +#include <linux/sched/signal.h> -+#include <linux/slab.h> -+#include <linux/spinlock.h> -+#include <uapi/linux/ntsync.h> -+ -+#define NTSYNC_NAME "ntsync" -+ -+enum ntsync_type { -+ NTSYNC_TYPE_SEM, + #include <linux/slab.h> + #include <linux/spinlock.h> + #include <uapi/linux/ntsync.h> +@@ -19,6 +25,8 @@ + + enum ntsync_type { + NTSYNC_TYPE_SEM, + NTSYNC_TYPE_MUTEX, + NTSYNC_TYPE_EVENT, -+}; -+ -+/* -+ * Individual synchronization primitives are represented by -+ * struct ntsync_obj, and each primitive is backed by a file. -+ * -+ * The whole namespace is represented by a struct ntsync_device also -+ * backed by a file. -+ * -+ * Both rely on struct file for reference counting. Individual -+ * ntsync_obj objects take a reference to the device when created. + }; + + /* +@@ -30,10 +38,13 @@ enum ntsync_type { + * + * Both rely on struct file for reference counting. Individual + * ntsync_obj objects take a reference to the device when created. + * Wait operations take a reference to each object being waited on for + * the duration of the wait. -+ */ -+ -+struct ntsync_obj { -+ spinlock_t lock; + */ + + struct ntsync_obj { + spinlock_t lock; + int dev_locked; -+ -+ enum ntsync_type type; -+ -+ struct file *file; -+ struct ntsync_device *dev; -+ -+ /* The following fields are protected by the object lock. */ -+ union { -+ struct { -+ __u32 count; -+ __u32 max; -+ } sem; + + enum ntsync_type type; + +@@ -46,13 +57,335 @@ struct ntsync_obj { + __u32 count; + __u32 max; + } sem; + struct { + __u32 count; + pid_t owner; @@ -584,7 +529,7 @@ index 000000000000..87a24798a5c7 + bool manual; + bool signaled; + } event; -+ } u; + } u; + + /* + * any_waiters is protected by the object lock, but all_waiters is @@ -634,9 +579,9 @@ index 000000000000..87a24798a5c7 + bool ownerdead; + __u32 count; + struct ntsync_q_entry entries[]; -+}; -+ -+struct ntsync_device { + }; + + struct ntsync_device { + /* + * Wait-all operations must atomically grab all objects, and be totally + * ordered with respect to each other and wait-any operations. @@ -649,9 +594,9 @@ index 000000000000..87a24798a5c7 + */ + struct mutex wait_all_lock; + -+ struct file *file; -+}; -+ + struct file *file; + }; + +/* + * Single objects are locked using obj->lock. + * @@ -904,57 +849,54 @@ index 000000000000..87a24798a5c7 + } +} + -+/* -+ * Actually change the semaphore state, returning -EOVERFLOW if it is made -+ * invalid. -+ */ -+static int post_sem_state(struct ntsync_obj *sem, __u32 count) -+{ -+ __u32 sum; -+ + /* + * Actually change the semaphore state, returning -EOVERFLOW if it is made + * invalid. +@@ -61,7 +394,7 @@ static int post_sem_state(struct ntsync_obj *sem, __u32 count) + { + __u32 sum; + +- lockdep_assert_held(&sem->lock); + ntsync_assert_held(sem); -+ -+ if (check_add_overflow(sem->u.sem.count, count, &sum) || -+ sum > sem->u.sem.max) -+ return -EOVERFLOW; -+ -+ sem->u.sem.count = sum; -+ return 0; -+} -+ -+static int ntsync_sem_post(struct ntsync_obj *sem, void __user *argp) -+{ + + if (check_add_overflow(sem->u.sem.count, count, &sum) || + sum > sem->u.sem.max) +@@ -73,9 +406,11 @@ static int post_sem_state(struct ntsync_obj *sem, __u32 count) + + static int ntsync_sem_post(struct ntsync_obj *sem, void __user *argp) + { + struct ntsync_device *dev = sem->dev; -+ __u32 __user *user_args = argp; -+ __u32 prev_count; -+ __u32 args; + __u32 __user *user_args = argp; + __u32 prev_count; + __u32 args; + bool all; -+ int ret; -+ -+ if (copy_from_user(&args, argp, sizeof(args))) -+ return -EFAULT; -+ -+ if (sem->type != NTSYNC_TYPE_SEM) -+ return -EINVAL; -+ + int ret; + + if (copy_from_user(&args, argp, sizeof(args))) +@@ -84,12 +419,17 @@ static int ntsync_sem_post(struct ntsync_obj *sem, void __user *argp) + if (sem->type != NTSYNC_TYPE_SEM) + return -EINVAL; + +- spin_lock(&sem->lock); + all = ntsync_lock_obj(dev, sem); -+ -+ prev_count = sem->u.sem.count; -+ ret = post_sem_state(sem, args); + + prev_count = sem->u.sem.count; + ret = post_sem_state(sem, args); + if (!ret) { + if (all) + try_wake_all_obj(dev, sem); + try_wake_any_sem(sem); + } -+ + +- spin_unlock(&sem->lock); + ntsync_unlock_obj(dev, sem, all); -+ -+ if (!ret && put_user(prev_count, user_args)) -+ ret = -EFAULT; -+ -+ return ret; -+} -+ + + if (!ret && put_user(prev_count, user_args)) + ret = -EFAULT; +@@ -97,6 +437,226 @@ static int ntsync_sem_post(struct ntsync_obj *sem, void __user *argp) + return ret; + } + +/* + * Actually change the mutex state, returning -EPERM if not the owner. + */ @@ -1175,25 +1117,13 @@ index 000000000000..87a24798a5c7 + return 0; +} + -+static int ntsync_obj_release(struct inode *inode, struct file *file) -+{ -+ struct ntsync_obj *obj = file->private_data; -+ -+ fput(obj->dev->file); -+ kfree(obj); -+ -+ return 0; -+} -+ -+static long ntsync_obj_ioctl(struct file *file, unsigned int cmd, -+ unsigned long parm) -+{ -+ struct ntsync_obj *obj = file->private_data; -+ void __user *argp = (void __user *)parm; -+ -+ switch (cmd) { -+ case NTSYNC_IOC_SEM_POST: -+ return ntsync_sem_post(obj, argp); + static int ntsync_obj_release(struct inode *inode, struct file *file) + { + struct ntsync_obj *obj = file->private_data; +@@ -116,6 +676,22 @@ static long ntsync_obj_ioctl(struct file *file, unsigned int cmd, + switch (cmd) { + case NTSYNC_IOC_SEM_POST: + return ntsync_sem_post(obj, argp); + case NTSYNC_IOC_SEM_READ: + return ntsync_sem_read(obj, argp); + case NTSYNC_IOC_MUTEX_UNLOCK: @@ -1210,84 +1140,23 @@ index 000000000000..87a24798a5c7 + return ntsync_event_set(obj, argp, true); + case NTSYNC_IOC_EVENT_READ: + return ntsync_event_read(obj, argp); -+ default: -+ return -ENOIOCTLCMD; -+ } -+} -+ -+static const struct file_operations ntsync_obj_fops = { -+ .owner = THIS_MODULE, -+ .release = ntsync_obj_release, -+ .unlocked_ioctl = ntsync_obj_ioctl, -+ .compat_ioctl = compat_ptr_ioctl, -+ .llseek = no_llseek, -+}; -+ -+static struct ntsync_obj *ntsync_alloc_obj(struct ntsync_device *dev, -+ enum ntsync_type type) -+{ -+ struct ntsync_obj *obj; -+ -+ obj = kzalloc(sizeof(*obj), GFP_KERNEL); -+ if (!obj) -+ return NULL; -+ obj->type = type; -+ obj->dev = dev; -+ get_file(dev->file); -+ spin_lock_init(&obj->lock); + default: + return -ENOIOCTLCMD; + } +@@ -141,6 +717,9 @@ static struct ntsync_obj *ntsync_alloc_obj(struct ntsync_device *dev, + obj->dev = dev; + get_file(dev->file); + spin_lock_init(&obj->lock); + INIT_LIST_HEAD(&obj->any_waiters); + INIT_LIST_HEAD(&obj->all_waiters); + atomic_set(&obj->all_hint, 0); -+ -+ return obj; -+} -+ -+static int ntsync_obj_get_fd(struct ntsync_obj *obj) -+{ -+ struct file *file; -+ int fd; -+ -+ fd = get_unused_fd_flags(O_CLOEXEC); -+ if (fd < 0) -+ return fd; -+ file = anon_inode_getfile("ntsync", &ntsync_obj_fops, obj, O_RDWR); -+ if (IS_ERR(file)) { -+ put_unused_fd(fd); -+ return PTR_ERR(file); -+ } -+ obj->file = file; -+ fd_install(fd, file); -+ -+ return fd; -+} -+ -+static int ntsync_create_sem(struct ntsync_device *dev, void __user *argp) -+{ -+ struct ntsync_sem_args __user *user_args = argp; -+ struct ntsync_sem_args args; -+ struct ntsync_obj *sem; -+ int fd; -+ -+ if (copy_from_user(&args, argp, sizeof(args))) -+ return -EFAULT; -+ -+ if (args.count > args.max) -+ return -EINVAL; -+ -+ sem = ntsync_alloc_obj(dev, NTSYNC_TYPE_SEM); -+ if (!sem) -+ return -ENOMEM; -+ sem->u.sem.count = args.count; -+ sem->u.sem.max = args.max; -+ fd = ntsync_obj_get_fd(sem); -+ if (fd < 0) { -+ kfree(sem); -+ return fd; -+ } -+ -+ return put_user(fd, &user_args->sem); -+} -+ + + return obj; + } +@@ -191,6 +770,400 @@ static int ntsync_create_sem(struct ntsync_device *dev, void __user *argp) + return put_user(fd, &user_args->sem); + } + +static int ntsync_create_mutex(struct ntsync_device *dev, void __user *argp) +{ + struct ntsync_mutex_args __user *user_args = argp; @@ -1682,96 +1551,43 @@ index 000000000000..87a24798a5c7 + return ret; +} + -+static int ntsync_char_open(struct inode *inode, struct file *file) -+{ -+ struct ntsync_device *dev; -+ -+ dev = kzalloc(sizeof(*dev), GFP_KERNEL); -+ if (!dev) -+ return -ENOMEM; -+ + static int ntsync_char_open(struct inode *inode, struct file *file) + { + struct ntsync_device *dev; +@@ -199,6 +1172,8 @@ static int ntsync_char_open(struct inode *inode, struct file *file) + if (!dev) + return -ENOMEM; + + mutex_init(&dev->wait_all_lock); + -+ file->private_data = dev; -+ dev->file = file; -+ return nonseekable_open(inode, file); -+} -+ -+static int ntsync_char_release(struct inode *inode, struct file *file) -+{ -+ struct ntsync_device *dev = file->private_data; -+ -+ kfree(dev); -+ -+ return 0; -+} -+ -+static long ntsync_char_ioctl(struct file *file, unsigned int cmd, -+ unsigned long parm) -+{ -+ struct ntsync_device *dev = file->private_data; -+ void __user *argp = (void __user *)parm; -+ -+ switch (cmd) { + file->private_data = dev; + dev->file = file; + return nonseekable_open(inode, file); +@@ -220,8 +1195,16 @@ static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + void __user *argp = (void __user *)parm; + + switch (cmd) { + case NTSYNC_IOC_CREATE_EVENT: + return ntsync_create_event(dev, argp); + case NTSYNC_IOC_CREATE_MUTEX: + return ntsync_create_mutex(dev, argp); -+ case NTSYNC_IOC_CREATE_SEM: -+ return ntsync_create_sem(dev, argp); + case NTSYNC_IOC_CREATE_SEM: + return ntsync_create_sem(dev, argp); + case NTSYNC_IOC_WAIT_ALL: + return ntsync_wait_all(dev, argp); + case NTSYNC_IOC_WAIT_ANY: + return ntsync_wait_any(dev, argp); -+ default: -+ return -ENOIOCTLCMD; -+ } -+} -+ -+static const struct file_operations ntsync_fops = { -+ .owner = THIS_MODULE, -+ .open = ntsync_char_open, -+ .release = ntsync_char_release, -+ .unlocked_ioctl = ntsync_char_ioctl, -+ .compat_ioctl = compat_ptr_ioctl, -+ .llseek = no_llseek, -+}; -+ -+static struct miscdevice ntsync_misc = { -+ .minor = MISC_DYNAMIC_MINOR, -+ .name = NTSYNC_NAME, -+ .fops = &ntsync_fops, -+}; -+ -+module_misc_device(ntsync_misc); -+ -+MODULE_AUTHOR("Elizabeth Figura <zfigura@codeweavers.com>"); -+MODULE_DESCRIPTION("Kernel driver for NT synchronization primitives"); -+MODULE_LICENSE("GPL"); + default: + return -ENOIOCTLCMD; + } diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h -new file mode 100644 -index 000000000000..4a8095a3fc34 ---- /dev/null +index dcfa38fdc93c..4a8095a3fc34 100644 +--- a/include/uapi/linux/ntsync.h +++ b/include/uapi/linux/ntsync.h -@@ -0,0 +1,62 @@ -+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -+/* -+ * Kernel support for NT synchronization primitive emulation -+ * -+ * Copyright (C) 2021-2022 Elizabeth Figura <zfigura@codeweavers.com> -+ */ -+ -+#ifndef __LINUX_NTSYNC_H -+#define __LINUX_NTSYNC_H -+ -+#include <linux/types.h> -+ -+struct ntsync_sem_args { -+ __u32 sem; -+ __u32 count; -+ __u32 max; -+}; -+ +@@ -16,8 +16,47 @@ struct ntsync_sem_args { + __u32 max; + }; + +struct ntsync_mutex_args { + __u32 mutex; + __u32 owner; @@ -1799,13 +1615,13 @@ index 000000000000..4a8095a3fc34 + +#define NTSYNC_MAX_WAIT_COUNT 64 + -+#define NTSYNC_IOC_CREATE_SEM _IOWR('N', 0x80, struct ntsync_sem_args) + #define NTSYNC_IOC_CREATE_SEM _IOWR('N', 0x80, struct ntsync_sem_args) +#define NTSYNC_IOC_WAIT_ANY _IOWR('N', 0x82, struct ntsync_wait_args) +#define NTSYNC_IOC_WAIT_ALL _IOWR('N', 0x83, struct ntsync_wait_args) +#define NTSYNC_IOC_CREATE_MUTEX _IOWR('N', 0x84, struct ntsync_sem_args) +#define NTSYNC_IOC_CREATE_EVENT _IOWR('N', 0x87, struct ntsync_event_args) -+ -+#define NTSYNC_IOC_SEM_POST _IOWR('N', 0x81, __u32) + + #define NTSYNC_IOC_SEM_POST _IOWR('N', 0x81, __u32) +#define NTSYNC_IOC_MUTEX_UNLOCK _IOWR('N', 0x85, struct ntsync_mutex_args) +#define NTSYNC_IOC_MUTEX_KILL _IOW ('N', 0x86, __u32) +#define NTSYNC_IOC_EVENT_SET _IOR ('N', 0x88, __u32) @@ -1814,10 +1630,10 @@ index 000000000000..4a8095a3fc34 +#define NTSYNC_IOC_SEM_READ _IOR ('N', 0x8b, struct ntsync_sem_args) +#define NTSYNC_IOC_MUTEX_READ _IOR ('N', 0x8c, struct ntsync_mutex_args) +#define NTSYNC_IOC_EVENT_READ _IOR ('N', 0x8d, struct ntsync_event_args) -+ -+#endif + + #endif diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile -index e1504833654d..6f95206325e1 100644 +index 9039f3709aff..d5aeaa8fe3ca 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -16,6 +16,7 @@ TARGETS += damon @@ -1826,8 +1642,8 @@ index e1504833654d..6f95206325e1 100644 TARGETS += drivers/dma-buf +TARGETS += drivers/ntsync TARGETS += drivers/s390x/uvdevice + TARGETS += drivers/net TARGETS += drivers/net/bonding - TARGETS += drivers/net/team diff --git a/tools/testing/selftests/drivers/ntsync/.gitignore b/tools/testing/selftests/drivers/ntsync/.gitignore new file mode 100644 index 000000000000..848573a3d3ea @@ -3269,5 +3085,5 @@ index 000000000000..5fa2c9a0768c + +TEST_HARNESS_MAIN -- -2.45.2 +2.46.0.rc1 |