From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from kanga.kvack.org (kanga.kvack.org [205.233.56.17]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 40638606D4FC for ; Wed, 4 Nov 2026 19:34:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b=GAUi2SUu Received: by kanga.kvack.org (Postfix) id 47DF96B05C1; Wed, 4 Nov 2026 14:34:08 -0500 (EST) Received: by kanga.kvack.org (Postfix, from userid 40) id 4590D6B05C3; Wed, 4 Nov 2026 14:34:08 -0500 (EST) X-Delivered-To: int-list-linux-mm@kvack.org Received: by kanga.kvack.org (Postfix, from userid 63042) id 36E4A6B05CB; Wed, 4 Nov 2026 14:34:08 -0500 (EST) X-Delivered-To: linux-mm@kvack.org Received: from relay.hostedemail.com (smtprelay0011.hostedemail.com [216.40.44.11]) by kanga.kvack.org (Postfix) with ESMTP id 0C23C6B05C1 for ; Wed, 4 Nov 2026 14:34:08 -0500 (EST) Received: from smtpin10.hostedemail.com (lb01a-stub [10.200.18.249]) by unirelay05.hostedemail.com (Postfix) with ESMTP id 94D384084C for ; Wed, 4 Nov 2026 19:34:07 +0000 (UTC) X-FDA: 85398437334.10.0E0EC0F Received: from sea.source.kernel.org (sea.source.kernel.org [172.234.252.31]) by imf30.hostedemail.com (Postfix) with ESMTP id BF69E8000B for ; Wed, 4 Nov 2026 19:34:05 +0000 (UTC) Authentication-Results: imf30.hostedemail.com; dkim=pass header.d=kernel.org header.s=k20260515 header.b=GAUi2SUu; spf=pass (imf30.hostedemail.com: domain of brauner@kernel.org designates 172.234.252.31 as permitted sender) smtp.mailfrom=brauner@kernel.org; dmarc=pass (policy=quarantine) header.from=kernel.org ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=hostedemail.com; s=arc-20220608; t=1793820845; h=from:from:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:dkim-signature; bh=wOiGbtXh+26P6cFgB3YXbnrWEhf8CPNBoutfZuqe0cI=; b=Qd8+35I/V5o/bZZpYRuVqFuLQtlPhoOwlNhrvcW+CViZimd8c19WqNMKvhs4IEHtDQ3F5p NWbOM9fIyLMynBih1ClIO/nFSgrb4tNM/AAaMrrNp4bKxbOxOTMU87YDuR7X8YetWxLXyE UwFDloFTDTZZIBvtdoQlVQiOFH9lZeE= ARC-Authentication-Results: i=1; imf30.hostedemail.com; dkim=pass header.d=kernel.org header.s=k20260515 header.b=GAUi2SUu; spf=pass (imf30.hostedemail.com: domain of brauner@kernel.org designates 172.234.252.31 as permitted sender) smtp.mailfrom=brauner@kernel.org; dmarc=pass (policy=quarantine) header.from=kernel.org ARC-Seal: i=1; a=rsa-sha256; d=hostedemail.com; s=arc-20220608; cv=none; t=1793820845; b=mEiUPeXcqwPfIVNNme4j4VFNXmLOqUEPzNOzRSGKA+fvWPkdaN8ml/Lp+MuTWCGQ66ybLg cMNIBMZDIkZQnIg7Msi2xakeHlAGgW+2zrPBIuU3PH5J6XwE4bUFphUUW0zerTraG70udl gMkc+lgvFAoTDzZEzAb3wTWIIuNR7gg= Received: from smtp.kernel.org (quasi.space.kernel.org [100.103.45.18]) by sea.source.kernel.org (Postfix) with ESMTP id 03C8A43A4C; Wed, 4 Nov 2026 19:34:05 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 156601F00A02; Wed, 4 Nov 2026 19:33:52 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1793820844; bh=wOiGbtXh+26P6cFgB3YXbnrWEhf8CPNBoutfZuqe0cI=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=GAUi2SUug0UxPlfFUUONWwiBaqhW+CPwHi9ROt2tgHXEeUA5HnhKW49vg0h//eRek 19VdBVnFuVj0FumboOxOLM8ZGbD/sXZJ18BizGNDqR7bU1rcpq9wqzj03DLTrZxycb xCZzycJdxDPLJft+HIdgBnsg1C2coUbbb6sFjgx+Z90PctyxTbRw87FiQL/EAfCnsD wiHtGkQ2vyZTUstIsghXbyaDluDmFswvpSimcZ+xR5fteGCZrQT/i14tZH1sOvmBSc l6QV3ryMIU8G+gcr/NG5UtajUyb9hEoWX8DYhdTOTtqkDHMl0qtdUHBBmVhxDrFDpD alh7bcK12D7bg== From: Christian Brauner To: linux-fsdevel@vger.kernel.org Cc: Linus Torvalds , Jann Horn , Josef Bacik , Chris Mason , Jeff Layton , NeilBrown , Eric Dumazet , Thomas Gleixner , Al Viro , Jonathan Corbet , Christoph Hellwig , Greg Kroah-Hartman , Joel Granados , Kees Cook , Jan Kara , Jan Kara , linux-kernel@vger.kernel.org, linux-nfs@vger.kernel.org, linux-acpi@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-s390@vger.kernel.org, linux-scsi@vger.kernel.org, linux-um@lists.infradead.org, linux-wireless@vger.kernel.org, linuxppc-dev@lists.ozlabs.org, netdev@vger.kernel.org, ntfs3@lists.linux.dev, platform-driver-x86@vger.kernel.org, coreteam@netfilter.org, kexec@lists.infradead.org, linux-pci@vger.kernel.org, linux-sound@vger.kernel.org, netfilter-devel@vger.kernel.org, dri-devel@lists.freedesktop.org, ibm-acpi-devel@lists.sourceforge.net, jfs-discussion@lists.sourceforge.net, linux-alpha@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-cifs@vger.kernel.org, linux-fbdev@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-mm@kvack.org, linux-sh@vger.kernel.org, linux-usb@vger.kernel.org, samba-technical@lists.samba.org, sparclinux@vger.kernel.org, linux-m68k@lists.linux-m68k.org, linux-doc@vger.kernel.org, linux-serial@vger.kernel.org, linux-atm-general@lists.sourceforge.net, Takashi Iwai , linux-input@vger.kernel.org, linux-raid@vger.kernel.org, linux-ext4@vger.kernel.org, linux-modules@vger.kernel.org, linux-debuggers@vger.kernel.org, "Christian Brauner (Amutable)" , Chuck Lever Subject: [PATCH RESEND v2 19/23] proc: pin the entry around the vm_operations of its mappings Date: Wed, 4 Nov 2026 20:33:50 +0100 Message-ID: <20261104-work-procfs-core-v2-19-76ace653a773@kernel.org> In-Reply-To: <20261104-work-procfs-core-v2-0-76ace653a773@kernel.org> References: <20261104-work-procfs-core-v2-0-76ace653a773@kernel.org> X-Archived-By: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" X-Mailer: b4 0.17-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=11693; i=brauner@kernel.org; h=from:subject:message-id; bh=1Gn60dKTBgMHLZNNt783bvPpMphe5Oyiwe1oBT0VS+Y=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMWS9bu+QKEs7mSWU5xnOGc8epSacpjzTovXorKiOP1wfa q7krTrcUcrCIMbFICumyOLQbhIut5ynYrNRpgbMHFYmkCEMXJwCMJFbTYwMF/JeHn5hJ2XP5f3c W3fLzrrjN1YX6loYlbr2VRpwJ074xfCH43/Y586lty6++81yYZFC6JEJB84FLxQ6q3urlnveMrt 2bgA= X-Developer-Key: i=brauner@kernel.org; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Content-Transfer-Encoding: 8bit X-Stat-Signature: ntchf1ehxnycmor3kwyezju349xfyqaq X-Rspamd-Server: rspam01 X-Rspam-User: X-Spam-Status: No, score=-18.8 required=3.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER,INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_PASS, SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=4.0.1 X-Rspamd-Queue-Id: BF69E8000B X-HE-Tag: 1793820845-367718 X-HE-Meta: U2FsdGVkX1+vrynECMhJNPSnHHT++554WtrbSm7ANl9Zp0vJHozd4I0tDIkFDsltPiu6TE2DWutL0FZ1sdUstLT5dpry+0PZoDcJRSY4TrtRxInCuX1nJkbkjRnVPD9pEnDk0M1MrZ3FfQvMlHpXz8CPDnl4v7hUme0R238hbovGoZF5HJ5mO+qcmjnMGaBxOpS/ChkHJoS0O2l3WVtA3Q5RJ8tInCDP0sg2wdlZAm9iJvGaaonXusTX+t/ZVxVbw9zmvz8s2wGSsm2YSGR+X3qa2wIm11LNLi/dTOhq/i35ZcLJRPfr7EiNt9VtpHpQPvHHsYIJ6qsRGC8Wxkccua9HZ88pTBiRKYgHgUVOgb1WiIXqnF4Zf+xl9VDZiJuBi99iLBqE0Txpvi9khwYTBYogvJU3jYhKpBH8IN0AP2/T+EBO8uXMrTqwHICCdLV3PSZyMQtaHaq/DN1HBvwiM8biEH91UOepGJCCU2u4GDcwAezuOvRo+46xblREIosbfCuGsS87JQnKt+/bI8tG6YEypwSaVbNBzUpkL5dLAMtbrvoy5JIED4dKaiJEVJX+Xu5p+LA36vwLFfd+q/DqDEjdpv3T/PeWnF6F5nxbSaoQLjELpJGlvum1AzXMkKaprSC4rTUr1ok7fHxC2FmIVPHktMXEVWVrWvxor48gCqJYnoHMJ+Iog6i3QDqW80+fRS2ydiDx2lHoBXli3IDV3I5vKdBxCXhmirdPF/K5gnZcG2Ytn6+gpodePsw2jdwI4r5TB/ZcTQvGjBPosf8nZ0ZH5CUmtfF3ZZQW+3aW1jHD0rsIIohGRpYSxHvUbPTwnZNWJaJZLUS40afmDOU/fymCb1o5lFO+KdcvSdTAFpIwZ2nkTSWWJv4pshRDe4EHhtn1kQpxEwjhFw4+viADMg9cHXVPNB5VtjqXUhS4xYUtQ0Gr1QNBIfPjxC6VwBigCqGF4bRF96vY/bGguGC hMFu54VX O2qc5pQrMVmkkGRsbVuAyKVguFcKJqihg4D/fhS1w0ABMd8DuJYt0fbNuO1mdoT3/uHNqRXE6rB0NzD2blqN3JQSNd9U6B9Mq46AJDEteDeDPU0HNaKv9rpMmNNC2Ug2Mc7FbqmYHX2uchqZIwofhJRl0zR3NmBI7BqdDmB2MFGha2myRgRmg7TyRKeuX0OOsqbUL7byFPUcRLFKSvNagx4XQdCQRqIdJ4M4dMGdwGoAghPjsgIAiIGKVbM+hRMK1jN4HWLpB3P+9TveNhEtt3c9prJq4jiU9s3/5Fj+Qw6sOp2aupoG0bUbsePReadVrIbGolVrB7Cpbmuo= Sender: owner-linux-mm@kvack.org Precedence: bulk X-Loop: owner-majordomo@kvack.org List-ID: List-Subscribe: X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-26) on aws-us-west-2-korg-listserv-alma10-1 proc_reg_mmap() pins the entry around ->proc_mmap() and that's it. What the entry hands out lives on without the pin: the vm_operations it put on the mapping run on every fault, on every write to a read-only page and on every ptrace access with the entry unpinned, and the mapping itself survives remove_proc_entry(). For /proc/vmcore that means the fault handler runs after vmcore_cleanup() freed what it looks at, and a mapping of the old memory keeps pointing at it after fadump gave that memory back to the page allocator. /proc/bus/pci has the same problem when a device goes away. So do what kernfs does for the mappings of sysfs files. procfs puts vm_operations of its own on the mapping. They pin the entry around its ->open(), ->fault(), ->page_mkwrite() and ->access() and fail with SIGBUS, or do nothing, once the entry is gone. The entry's vm_operations are kept in its open node, so every mapping of an entry has to use the same ones, like kernfs asks that of every mapping of an open file. ->close() can't be wrapped, it runs when the mapping goes away and that can be after the entry is gone, so a mapping that has one is refused. When the entry is removed procfs unmaps the mappings of every open file of an entry that has been mapped before it calls ->proc_release(), so a fault into one of them finds the entry gone. That unmaps the entry's own address space, the way kernfs_drain_open_files() does it. /proc/bus/pci maps through the iomem address space that /dev/mem and the sysfs resource files share, see iomem_get_mapping(), and unmapping that would take every /dev/mem and BAR mapping on the system with it. So those mappings are left alone, like the sysfs resource files leave theirs, and keep pointing at the BAR of a device that is gone. To know the files an entry that can be mapped gets an open node like an entry with a ->proc_release() has one. The mapping is linked into the file's address space only after ->mmap() returned, so a removal that runs in between doesn't find it. kernfs has the same window. Entries that are never removed aren't wrapped, procfs never pins them. Signed-off-by: Christian Brauner (Amutable) --- fs/proc/generic.c | 6 +-- fs/proc/inode.c | 126 ++++++++++++++++++++++++++++++++++++++++++++---- fs/proc/internal.h | 9 ++-- include/linux/proc_fs.h | 15 ++++++ 4 files changed, 140 insertions(+), 16 deletions(-) diff --git a/fs/proc/generic.c b/fs/proc/generic.c index 1bad3b068d53..81a6cb560c75 100644 --- a/fs/proc/generic.c +++ b/fs/proc/generic.c @@ -505,14 +505,14 @@ static int pde_set_flags(struct proc_dir_entry *pde) /* * procfs has to know the open files of an entry whose ->proc_release() it - * calls at removal. A permanent entry is never removed and skips all that, - * see proc_reg_open(). + * calls at removal and whose mappings it undoes then. A permanent entry is + * never removed and skips all that, see proc_reg_open(). */ static bool pde_needs_open_node(const struct proc_dir_entry *pde) { if (!S_ISREG(pde->mode) || pde_is_permanent(pde)) return false; - return pde->proc_ops->proc_release; + return pde->proc_ops->proc_release || pde->proc_ops->proc_mmap; } /* diff --git a/fs/proc/inode.c b/fs/proc/inode.c index 717cbaad65df..5e423bcad278 100644 --- a/fs/proc/inode.c +++ b/fs/proc/inode.c @@ -312,7 +312,8 @@ static void pde_seq_release(struct proc_dir_entry *pde, struct inode *inode, * * PDE is locked on entry, unlocked on exit. */ -static void close_pdeo(struct proc_dir_entry *pde, struct pde_opener *pdeo) +static void close_pdeo(struct proc_dir_entry *pde, struct pde_opener *pdeo, + bool rundown) __releases(&pde->pde_unload_lock) { /* @@ -336,7 +337,17 @@ static void close_pdeo(struct proc_dir_entry *pde, struct pde_opener *pdeo) spin_unlock(&pde->pde_unload_lock); file = pdeo->file; - pde->proc_ops->proc_release(file_inode(file), file); + /* + * A mapping of a removed entry faults, see proc_vm_ops. Like + * kernfs this unmaps the entry's own address space. A file + * that maps through another one, /proc/bus/pci through the + * iomem address space of /dev/mem, keeps its mappings like a + * sysfs resource file does. + */ + if (rundown && pde->open->vm_ops) + unmap_mapping_range(file_inode(file)->i_mapping, 0, 0, 1); + if (pde->proc_ops->proc_release) + pde->proc_ops->proc_release(file_inode(file), file); spin_lock(&pde->pde_unload_lock); /* Strictly after ->proc_release, see above. */ @@ -362,7 +373,7 @@ void proc_entry_rundown(struct proc_dir_entry *de) while (!list_empty(&de->open->files)) { struct pde_opener *pdeo; pdeo = list_first_entry(&de->open->files, struct pde_opener, lh); - close_pdeo(de, pdeo); + close_pdeo(de, pdeo, true); spin_lock(&de->pde_unload_lock); } spin_unlock(&de->pde_unload_lock); @@ -642,12 +653,106 @@ static long proc_reg_compat_ioctl(struct file *file, unsigned int cmd, unsigned #define proc_reg_compat_ioctl NULL #endif -static int pde_mmap(struct proc_dir_entry *pde, struct file *file, struct vm_area_struct *vma) +/* + * The vm_operations of an entry's mappings run with the entry pinned, like + * the entry's file methods do, and a mapping of a removed entry faults. + * proc_entry_rundown() unmaps the mappings of every open file and a fault + * after that finds the entry gone. Like kernfs_vm_ops, and like there only + * these four methods of the entry's vm_operations are called. + */ +static void proc_vma_open(struct vm_area_struct *vma) +{ + struct proc_dir_entry *pde = PDE(file_inode(vma->vm_file)); + const struct vm_operations_struct *vm_ops = pde->open->vm_ops; + + if (!pde_pin(pde)) + return; + if (vm_ops->open) + vm_ops->open(vma); + pde_unpin(pde); +} + +static vm_fault_t proc_vma_fault(struct vm_fault *vmf) +{ + struct proc_dir_entry *pde = PDE(file_inode(vmf->vma->vm_file)); + const struct vm_operations_struct *vm_ops = pde->open->vm_ops; + vm_fault_t ret = VM_FAULT_SIGBUS; + + if (!pde_pin(pde)) + return VM_FAULT_SIGBUS; + if (vm_ops->fault) + ret = vm_ops->fault(vmf); + pde_unpin(pde); + return ret; +} + +static vm_fault_t proc_vma_page_mkwrite(struct vm_fault *vmf) +{ + struct proc_dir_entry *pde = PDE(file_inode(vmf->vma->vm_file)); + const struct vm_operations_struct *vm_ops = pde->open->vm_ops; + vm_fault_t ret = 0; + + if (!pde_pin(pde)) + return VM_FAULT_SIGBUS; + if (vm_ops->page_mkwrite) + ret = vm_ops->page_mkwrite(vmf); + else + file_update_time(vmf->vma->vm_file); + pde_unpin(pde); + return ret; +} + +static int proc_vma_access(struct vm_area_struct *vma, unsigned long addr, + void *buf, int len, int write) +{ + struct proc_dir_entry *pde = PDE(file_inode(vma->vm_file)); + const struct vm_operations_struct *vm_ops = pde->open->vm_ops; + int ret = -EINVAL; + + if (!pde_pin(pde)) + return -EINVAL; + if (vm_ops->access) + ret = vm_ops->access(vma, addr, buf, len, write); + pde_unpin(pde); + return ret; +} + +static const struct vm_operations_struct proc_vm_ops = { + .open = proc_vma_open, + .fault = proc_vma_fault, + .page_mkwrite = proc_vma_page_mkwrite, + .access = proc_vma_access, +}; + +static int pde_mmap(struct proc_dir_entry *pde, struct file *file, + struct vm_area_struct *vma) { const auto mmap = pde->proc_ops->proc_mmap; - if (mmap) - return mmap(file, vma); - return -EIO; + struct pde_open_node *on = pde->open; + int rv; + + if (!mmap) + return -EIO; + rv = mmap(file, vma); + if (rv || pde_is_permanent(pde)) + return rv; + /* the entry mapped something else, that isn't ours to wrap */ + if (vma->vm_file != file) + return 0; + /* + * ->close() can't be wrapped, it runs when the mapping goes away and + * that can be after the entry is gone, see close_pdeo(). + */ + if (!vma->vm_ops || vma->vm_ops->close) + return -EINVAL; + + /* every mapping of an entry uses the same vm_operations, see above */ + guard(spinlock)(&pde->pde_unload_lock); + if (on->vm_ops && on->vm_ops != vma->vm_ops) + return -EINVAL; + on->vm_ops = vma->vm_ops; + vma->vm_ops = &proc_vm_ops; + return 0; } static int proc_reg_mmap(struct file *file, struct vm_area_struct *vma) @@ -743,8 +848,9 @@ static int proc_reg_open(struct inode *inode, struct file *file) * 2) rmmod isn't blocked by opening file in /proc and sitting on * the descriptor (including "rmmod foo release hook. Those entries - * have an open node, see proc_register(). + * Save every "struct file" with custom ->release hook, and every + * one that can be mapped, proc_entry_rundown() unmaps those. Those + * entries have an open node, see proc_register(). */ if (!use_pde(pde)) return -ENOENT; @@ -794,7 +900,7 @@ static int proc_reg_release(struct inode *inode, struct file *file) spin_lock(&pde->pde_unload_lock); list_for_each_entry(pdeo, &pde->open->files, lh) { if (pdeo->file == file) { - close_pdeo(pde, pdeo); + close_pdeo(pde, pdeo, false); goto out; } } diff --git a/fs/proc/internal.h b/fs/proc/internal.h index ae0f9779cf1b..0b1c0585d6a6 100644 --- a/fs/proc/internal.h +++ b/fs/proc/internal.h @@ -373,12 +373,15 @@ struct pde_opener { } __randomize_layout; /* - * The open files of an entry whose ->proc_release() procfs has to call when - * the entry is removed while they are still open. Allocated with the entry, - * see proc_register(). + * The open files of an entry whose ->proc_release() procfs has to call and + * whose mappings it has to undo when the entry is removed while they are + * still open, that is of an entry with a ->proc_release() or a + * ->proc_mmap(). Allocated with the entry, see proc_register(). */ struct pde_open_node { struct list_head files; /* who did ->open, but not ->release */ + /* of its mappings, the same for all of them, see pde_mmap() */ + const struct vm_operations_struct *vm_ops; }; extern const struct inode_operations proc_link_inode_operations; extern const struct inode_operations proc_pid_link_inode_operations; diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h index 2ba4e4a39e13..5bf039d20990 100644 --- a/include/linux/proc_fs.h +++ b/include/linux/proc_fs.h @@ -86,6 +86,21 @@ struct proc_ops { #ifdef CONFIG_COMPAT long (*proc_compat_ioctl)(struct file *, unsigned int, unsigned long); #endif + /* + * procfs pins the entry around ->proc_mmap() and wraps the + * vm_operations of the mapping so that ->open(), ->fault(), + * ->page_mkwrite() and ->access() run with the entry pinned as well. + * Nothing else of them is called, like for a kernfs file. Every + * mapping of an entry has to use the same vm_operations. A ->close() + * can't be wrapped, it runs when the mapping goes away and that can + * be after the entry is gone, so a mapping that has one is refused. + * Removing the entry unmaps the mappings in its own address space, + * like kernfs does, and a fault into one of them is SIGBUS from then + * on. A file that maps through another address space, /proc/bus/pci + * through the one of /dev/mem, keeps its mappings like a sysfs + * resource file does. The mappings of a permanent entry are left + * alone. + */ int (*proc_mmap)(struct file *, struct vm_area_struct *); unsigned long (*proc_get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); /* -- 2.53.0