From nobody Thu Sep 24 13:37:32 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C5716484882 for ; Wed, 23 Sep 2026 22:44:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790203464; cv=none; b=ILW0YH7CMhY7mREl0IT9BP78yk3YgG5+t8LHJ1z1nCF0ZzqAA+/sDQxXYSau0DYJy05akO6JwkIvO3LfvFF+VL2JBfOBo5W7Le6evWzyKF9Bm+ReEkm+N/iP88awBFsS1Sgo23jFtoiliUbQddBwU4AtZeZURhBddpc4PX7WvyU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790203464; c=relaxed/simple; bh=5GEZ3ja58DI0UQj4lRPUu6xrAU32yO4Wnmquf2NfRBQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=d7WF1dvKoeZLagtR14GVXWPmUzQ/sgiMFyjWFvl1HL4hjIZbHnZMosJjDOv07/MljWYkjU1CJEjU8CbG9cgCjsIBfi10Bj/cK3y1Md4U9MfKTPWmZnenRSyrBikkt2FjPPZyJijqwGUC5g8kP+4Ffh5UoaFlKbHrhnR9YahVlp0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=TLDWEyiH; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="TLDWEyiH" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 117221F00893; Wed, 23 Sep 2026 22:44:19 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1790203462; bh=XCYcL1R5NicLL/NhwgzYeBgYTteRbyS24vJmmzlRDaw=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=TLDWEyiH4tsSHQ5jJ4W2INbg6NtCw24Nd55zZ1YffQlaEZPAogYwjP2a/jc2DrkPj fXfbbY0AbOWflZJduor+NXDIF6D+FNeJF7fl8dSS/E5uP0Qr0d96bjebpX+f4ESMn/ iPGrG7bYGtfPO3T4UR+BK/3sz9bYHuMUdh/5T7cjpw4U7scGJJsTDgxf2jodSDjEoI sKzZnyHAKe6jGpPk8E3MGJqmqx+C5rJILC4+9pbw+A1ID0OAaQjT1kgvaS4YzT/Y1G pojgEXXMxey8NvFAyaDGkdrYwSbl0qyYLPQj7yrknb+ildxh1CLxjM3OlCEiyPkYsh qIjtHZqFGtM2Q== From: Pratyush Yadav To: Pasha Tatashin , Mike Rapoport , Pratyush Yadav , Andrew Morton , David Hildenbrand , Lorenzo Stoakes , Alexander Graf , Hugh Dickins , Baolin Wang , David Matlack , Samiullah Khawaja Cc: kexec@lists.infradead.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org Subject: [RFC PATCH 1/6] liveupdate: luo_file: look up outgoing tokens by id Date: Thu, 24 Sep 2026 00:44:00 +0200 Message-ID: <20260923224408.3745689-2-pratyush@kernel.org> X-Mailer: git-send-email 2.56.0.rc1.310.g51773c2048-goog In-Reply-To: <20260923224408.3745689-1-pratyush@kernel.org> References: <20260923224408.3745689-1-pratyush@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: "Pratyush Yadav (Google)" liveupdate_get_token_outgoing() is used to look up tokens of dependencies. It does the search based on struct file. LUO preserves resources; the files are only handles to those resources. LUO provides file handlers a ->get_id() callback that can return the unique identifier of a resource. LUO uses it to ensure the same resource can't be preserved twice if it can be referred to by multiple files. The default ID is the struct file, but file handlers can implement the callback and return something else. For example, memfd returns the inode of the file, not the struct file itself. Use ID to look up dependencies in liveupdate_get_token_outgoing() instead of struct file. This allows the lookup to work with handlers that use things other than the struct file. Signed-off-by: Pratyush Yadav (Google) --- include/linux/liveupdate.h | 6 +++--- kernel/liveupdate/luo_file.c | 21 ++++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h index 6051abc0612c..1cddbab50b98 100644 --- a/include/linux/liveupdate.h +++ b/include/linux/liveupdate.h @@ -253,9 +253,9 @@ void liveupdate_flb_put_outgoing(struct liveupdate_flb = *flb); int liveupdate_get_file_incoming(struct liveupdate_session *s, u64 token, struct file **filep); =20 -/* Get a token for an outgoing file, or -ENOENT if file is not preserved */ +/* Get a token for a preserved object, or -ENOENT if it is not preserved */ int liveupdate_get_token_outgoing(struct liveupdate_session *s, - struct file *file, u64 *tokenp); + unsigned long id, u64 *tokenp); =20 #else /* CONFIG_LIVEUPDATE */ =20 @@ -316,7 +316,7 @@ static inline int liveupdate_get_file_incoming(struct l= iveupdate_session *s, } =20 static inline int liveupdate_get_token_outgoing(struct liveupdate_session = *s, - struct file *file, u64 *tokenp) + unsigned long id, u64 *tokenp) { return -EOPNOTSUPP; } diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c index a39992da504f..51817d202ce2 100644 --- a/kernel/liveupdate/luo_file.c +++ b/kernel/liveupdate/luo_file.c @@ -950,29 +950,32 @@ void liveupdate_unregister_file_handler(struct liveup= date_file_handler *fh) } =20 /** - * liveupdate_get_token_outgoing - Get the token for a preserved file. + * liveupdate_get_token_outgoing - Get the token for a preserved object. * @s: The outgoing liveupdate session. - * @file: The file object to search for. + * @id: The identifier of the preserved object to search for, as retur= ned + * by the owning handler's ->get_id(). * @tokenp: Output parameter for the found token. * - * Searches the list of preserved files in an outgoing session for a match= ing - * file object. If found, the corresponding user-provided token is returne= d. + * Searches the list of preserved files in an outgoing session for an obje= ct + * with a matching identifier. If found, the corresponding user-provided t= oken + * is returned. * - * This function is intended for in-kernel callers that need to correlate a - * file with its liveupdate token. + * For handlers that do not implement ->get_id() use the 'struct file' poi= nter + * as their identifier. * * Context: It must be called with session mutex acquired. - * Return: 0 on success, -ENOENT if the file is not preserved in this sess= ion. + * Return: 0 on success, -ENOENT if no such object is preserved in this + * session. */ int liveupdate_get_token_outgoing(struct liveupdate_session *s, - struct file *file, u64 *tokenp) + unsigned long id, u64 *tokenp) { struct luo_file_set *file_set =3D luo_file_set_from_session_locked(s); struct luo_file *luo_file; int err =3D -ENOENT; =20 list_for_each_entry(luo_file, &file_set->files_list, list) { - if (luo_file->file =3D=3D file) { + if (luo_get_id(luo_file->fh, luo_file->file) =3D=3D id) { if (tokenp) *tokenp =3D luo_file->token; err =3D 0; --=20 2.56.0.rc1.310.g51773c2048-goog From nobody Thu Sep 24 13:37:32 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 44FDD579822 for ; Wed, 23 Sep 2026 22:44:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790203466; cv=none; b=I/J6kQiggr0UjqT4m7k5RDpKEgREkItuwMXApgVK3lYqubYWzH5632ONUNnwWkVw7vhfgNEn3hwew3OFdGc+v4+4OopghEa4f9QkL+iVouW8ND/piqi0FAgM/QWJ6vlhazMDENccVj74jg8rl1tu4xHBgWla11laBaz/hcAqy5Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790203466; c=relaxed/simple; bh=b2FD4HnaecV/favYI7++QkP0O3dptt9tpaa+kslwvlQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Ya/ruUOquhH8RsSSFa5927mO8CzyIDdF4L+JMROGXazYPHpnQWuaXWlTU++dxX/BVJwYAiXFnbYjAykMSeE9MGNtXVRMkL2nXnrpYgN+7O2yazbX2kW67wXjoXh0ouRqpDbUYJAQ7yN6L7YultJ8/2uxMoSgSjqJSWT98ou8VCg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=i6SWVv8J; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="i6SWVv8J" Received: by smtp.kernel.org (Postfix) with ESMTPSA id B9D4C1F000FF; Wed, 23 Sep 2026 22:44:22 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1790203465; bh=5nQ+hBf6qVec8z+IXo2hdLde0/quAUf4P/g17WYv1iw=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=i6SWVv8JLcwPyjTj/ZDmJk2VviUSkDwPsJXxoiR+68IM0XkG2b/fK11NvvSzU1pUr 1QY4Nv5LrjQFn+aUKbtb0oehm0nI8M4hQNRrQSnb8f8Vk+4Nwg61O4TVn/5bfO7kDG WRJgBF0aN5ghNfbptXbCvvvM/IHl2/k3bBVfQ699CFcZgTxhuMoaxWde76uHDTD1AJ esysnoUGA8ha6czLuP7V5/2FL0G7zB6+NtUSaKQ3nVu9yEO/Hgi0kQpXMymT0AF2xx a4L+ue7xBokmi5+T5BLMctK8w6q7SJ7KjPf+fDarlrpngQ2DmuhEyTyjGKQyUoeMR1 gbdRrYSdIIc0Q== From: Pratyush Yadav To: Pasha Tatashin , Mike Rapoport , Pratyush Yadav , Andrew Morton , David Hildenbrand , Lorenzo Stoakes , Alexander Graf , Hugh Dickins , Baolin Wang , David Matlack , Samiullah Khawaja Cc: kexec@lists.infradead.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org Subject: [RFC PATCH 2/6] shmem: add tmpfs_create_mount() to create tmpfs mounts internally Date: Thu, 24 Sep 2026 00:44:01 +0200 Message-ID: <20260923224408.3745689-3-pratyush@kernel.org> X-Mailer: git-send-email 2.56.0.rc1.310.g51773c2048-goog In-Reply-To: <20260923224408.3745689-1-pratyush@kernel.org> References: <20260923224408.3745689-1-pratyush@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: "Pratyush Yadav (Google)" Upcoming commits will add support for preserving a tmpfs mount via LUO. While the retrieve logic can create the mount by constructing an options string from its preserved data, it is simpler to let it directly pass the parameters. tmpfs_create_mount() allows creating a mount with the specified max_blocks and mode. The function only takes max_blocks and mode for now because that is all the tmpfs preservation will use. All other values are left to their defaults. Signed-off-by: Pratyush Yadav (Google) --- mm/internal.h | 1 + mm/shmem.c | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/mm/internal.h b/mm/internal.h index 38b1165212c9..d82c92c8fddd 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -1436,6 +1436,7 @@ int shmem_add_to_page_cache(struct folio *folio, pgoff_t index, void *expected, gfp_t gfp); int shmem_inode_acct_blocks(struct inode *inode, long pages); bool shmem_recalc_inode(struct inode *inode, long alloced, long swapped); +struct vfsmount *tmpfs_create_mount(unsigned long max_blocks, umode_t mode= ); =20 #ifdef CONFIG_SHRINKER_DEBUG static inline __printf(2, 0) int shrinker_debugfs_name_alloc( diff --git a/mm/shmem.c b/mm/shmem.c index 897fa2b61346..8660405b6ab8 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -5313,6 +5313,44 @@ static struct file_system_type shmem_fs_type =3D { .fs_flags =3D FS_USERNS_MOUNT | FS_ALLOW_IDMAP | FS_MGTIME, }; =20 +/** + * tmpfs_create_mount - Create a new tmpfs instance from explicit paramete= rs. + * @max_blocks: Block limit of the filesystem, in PAGE_SIZE units, as held= by + * shmem_sb_info::max_blocks. + * @mode: Mode of the root directory. + * + * Creates a new tmpfs superblock and returns a mount for it. Everything n= ot + * named above is left at its default. + * + * The returned mount belongs to no mount namespace and is owned by the ca= ller, + * who must either mntput() it or hand it to something that takes it over. + * + * Return: the new mount, or an ERR_PTR. + */ +struct vfsmount *tmpfs_create_mount(unsigned long max_blocks, umode_t mode) +{ + struct shmem_options *ctx; + struct fs_context *fc; + struct vfsmount *mnt; + + if (!IS_ENABLED(CONFIG_TMPFS)) + return ERR_PTR(-EOPNOTSUPP); + + fc =3D fs_context_for_mount(&shmem_fs_type, 0); + if (IS_ERR(fc)) + return ERR_CAST(fc); + + ctx =3D fc->fs_private; + ctx->blocks =3D max_blocks; + ctx->seen |=3D SHMEM_SEEN_BLOCKS; + ctx->mode =3D mode; + + mnt =3D fc_mount(fc); + put_fs_context(fc); + + return mnt; +} + #if defined(CONFIG_SYSFS) && defined(CONFIG_TMPFS) =20 #define __INIT_KOBJ_ATTR(_name, _mode, _show, _store) \ --=20 2.56.0.rc1.310.g51773c2048-goog From nobody Thu Sep 24 13:37:32 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0C8D051AECB for ; Wed, 23 Sep 2026 22:44:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790203469; cv=none; b=SDOk+afDPtB77sxATivyNiG1HmiMddKgi+7401lGZd5Ce4OeVjEJq9IVg1YV0CcmpFAJWvJ0F3F3EhjRr2tHW8JRc5IC9c7wavIi4xrsOqXrxeZFGQpccivfFw+8GdiHp1uHB49dOmyUkq+MFTvp6q7NUY0JXh5ucJwoBiFnALY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790203469; c=relaxed/simple; bh=NJOevRPPbUqn+rLnkCjRvXNFVqlQ8S7uytUxRuvqzeo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=umQ7LH6ikejhPzTFgRFER94fmpUqDSve2qshQuu75EUA4FXCD90jl1v2yguBoxyckmPXYiUHZzfhkFCl+MkqkDl+robYZyhb8JS+pBYOdINNxZUyPyMIwKfiq5mi5XhwL1wngwISHai+rsIZnEdYgj+Q8lqPs2nJBONA7WV+xr0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Pa/cjCtO; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Pa/cjCtO" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 6E5BB1F00893; Wed, 23 Sep 2026 22:44:25 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1790203467; bh=7qvuMFxeBj+B+G3C9EN8sSVcZBEZuVQAHrFO4Xty0ZA=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=Pa/cjCtODH8tQvSaIgT1T1D3gbx8C2FVjR5J83eGcAOvlSe2xloUgJBbPpAsjxZ7U Ad5qy5KJDhE5OQLNeMDHo41xi0JyCwQ0AeUqJ+KkpKTkme/G4wt58b4+AOzFI0TTZ6 EIf1UQkvhx+MgN9o5eo+YOxdtusXX0Sc4xrCDYkHvKjnE+AJ1hd4UX8QoptrbXJcYF QgAdlDAHeP6/PD8OmoJdxomH7kvsTd24Dmmxcz0Eku56bSPwHwK6VvraMiBeMOnavh wge2I68bX0Nis0ZFQJl9JC1FpuPxVxJDheQkHVuwwVH/6N6WdEX8CKdF0ce+RCcK60 E1F0cm+jUc67A== From: Pratyush Yadav To: Pasha Tatashin , Mike Rapoport , Pratyush Yadav , Andrew Morton , David Hildenbrand , Lorenzo Stoakes , Alexander Graf , Hugh Dickins , Baolin Wang , David Matlack , Samiullah Khawaja Cc: kexec@lists.infradead.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org Subject: [RFC PATCH 3/6] fs/namespace: Add vfs_open_detached_mount() Date: Thu, 24 Sep 2026 00:44:02 +0200 Message-ID: <20260923224408.3745689-4-pratyush@kernel.org> X-Mailer: git-send-email 2.56.0.rc1.310.g51773c2048-goog In-Reply-To: <20260923224408.3745689-1-pratyush@kernel.org> References: <20260923224408.3745689-1-pratyush@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: "Pratyush Yadav (Google)" A mount created in the kernel by fc_mount() and friends has mnt_ns =3D=3D NULL, and userspace cannot attach such a mount anywhere. The missing step is the tail of fsmount(2): allocate an anonymous mount namespace, insert the mount as its root, open an O_PATH file on it and mark the file FMODE_NEED_UNMOUNT so the mount is torn down on the final fput() if it is never attached. The anonymous namespace is what gives the detached mount an owning user namespace for permission checks, a place for child mounts, and a lifetime tied to the file. None of the pieces are reachable from outside fs/namespace.c: alloc_mnt_ns() and mnt_add_to_ns() are static, and the sequence is already open-coded twice, in fsmount(2) and in open_detached_copy(). Factor it out so an in-kernel creator of a mount can hand it to userspace. The Live Update Orchestrator needs this to return a tmpfs it re-created from state preserved across a kexec; userspace attaches the resulting fd with move_mount(), exactly as it would an fsmount(2) fd. Only add the helper. Converting fsmount() and open_detached_copy() to it is an obvious follow-up but is left out to keep this small. It is not exported, as the only caller is built in. Signed-off-by: Pratyush Yadav (Google) --- This patch is entirely LLM generated. It works, but I am not competent enough with VFS APIs to even guess if it is sensible. So please don't take it too seriously. It makes the RFC testable, but for a proper series I will do a lot more homework to make sure this doesn't completely abuse VFS APIs. --- fs/namespace.c | 59 +++++++++++++++++++++++++++++++++++++++++++ include/linux/mount.h | 1 + 2 files changed, 60 insertions(+) diff --git a/fs/namespace.c b/fs/namespace.c index 1ecd96c918b3..5e7ba2b61dd9 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -4435,6 +4435,65 @@ static unsigned int attr_flags_to_mnt_flags(u64 attr= _flags) return mnt_flags; } =20 +/** + * vfs_open_detached_mount - Publish a new mount as a detached mount file. + * @mnt: The mount to publish. Must not be attached to a mount namespace. = The + * caller's reference is consumed on success. + * + * Places @mnt into a new anonymous mount namespace and opens an O_PATH fi= le on + * its root, marked FMODE_NEED_UNMOUNT. This is what fsmount(2) hands back= , and + * the resulting file behaves the same way: it can be attached to the + * filesystem hierarchy with + * + * move_mount(fd, "", dfd, path, MOVE_MOUNT_F_EMPTY_PATH) + * + * and the mount is torn down on the final fput() if it never was. + * + * A mount that is not the root of an anonymous mount namespace cannot be + * attached by userspace at all, so a mount freshly made by fc_mount() and + * friends has to go through here before it can be given away. + * + * Return: the new file, or an ERR_PTR. On failure the caller's reference = to + * @mnt is dropped, as the mount cannot be published. + */ +struct file *vfs_open_detached_mount(struct vfsmount *mnt) +{ + struct path path __free(path_put) =3D {}; + struct mnt_namespace *ns; + struct file *file; + + if (WARN_ON_ONCE(real_mount(mnt)->mnt_ns)) + return ERR_PTR(-EINVAL); + + ns =3D alloc_mnt_ns(current->nsproxy->mnt_ns->user_ns, true); + if (IS_ERR(ns)) { + mntput(mnt); + return ERR_CAST(ns); + } + + /* The caller's reference becomes the namespace's reference. */ + ns->root =3D real_mount(mnt); + ns->nr_mounts =3D 1; + mnt_add_to_ns(ns, real_mount(mnt)); + + path.mnt =3D mntget(mnt); + path.dentry =3D dget(mnt->mnt_root); + + file =3D dentry_open(&path, O_PATH, current_cred()); + if (IS_ERR(file)) { + dissolve_on_fput(mnt); + return file; + } + + /* + * An apparent O_PATH fd, with a note that the mount needs to be + * unmounted on the final fput() rather than simply put. + */ + file->f_mode |=3D FMODE_NEED_UNMOUNT; + + return file; +} + /* * Create a kernel mount representation for a new, prepared superblock * (specified by fs_fd) and attach to an open_tree-like file descriptor. diff --git a/include/linux/mount.h b/include/linux/mount.h index acfe7ef86a1b..44c1141bc574 100644 --- a/include/linux/mount.h +++ b/include/linux/mount.h @@ -89,6 +89,7 @@ extern struct vfsmount *vfs_create_mount(struct fs_contex= t *fc); extern struct vfsmount *vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data); +struct file *vfs_open_detached_mount(struct vfsmount *mnt); =20 extern void mnt_set_expiry(struct vfsmount *mnt, struct list_head *expiry_= list); extern void mark_mounts_for_expiry(struct list_head *mounts); --=20 2.56.0.rc1.310.g51773c2048-goog From nobody Thu Sep 24 13:37:32 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8733C481FAE for ; Wed, 23 Sep 2026 22:44:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790203472; cv=none; b=fye2EAXnuB2Xq0QP4A9IcobrfpzecU5yGEHrkDn78jV4q6R/EzMMD+qTYcmSxtfecRKL65rqzYZDYQ0DlMIOTm7H5RbNyBfaG1JStXSDXvi7anMJstMn+q+0iCndrxHWYBTX0dSmyfBN/ODK6CmzMJoc/g4Cq2V5ZoFfeLHmGv8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790203472; c=relaxed/simple; bh=V7E1zXdRY8nI3cT2DhL00bEOLhvv9T2Airbs/uap6Eg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=M+btiiqKRqvNJa6PzzdVg/Bp8/0/GTY9eODMmEcFn39oUH7tHc2UYoC31ZGy429DAqZKcD5om/lTSLlB+jWEz5dKDCZUjL88yEu/NDdMFjBQqLBCSvnZHbgutVN8ab6WK3dRP03Ux06ShgJE75ZEYOMhe/AvWJmVHwzNIyI3Kyg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=NEtO2rLN; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="NEtO2rLN" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 22BA51F000FF; Wed, 23 Sep 2026 22:44:27 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1790203470; bh=TG50/Oj6obc8zTFrv/jURXZ3Tcj121N/yUWt0s9C2mM=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=NEtO2rLNYVXZMv8r5AhB1e8KxUAjgw2pw4WHDdKLtR5m7AGMbRnG4Oalfr1s5P5Vw BFuuwCXaEItYhL18UnClzyKxlIUyX8u2qMyYKcTrB1LPc8oQtd2dpze3rSIRHsD5ji k83hcubX09ocbH5Xq58qFgcCf0BwVYFVuaN/hfZ5m2zyuUHu/FQZA9kY7HV8orxI9S m/kIamQfGD7uQzrD3txboo8boabrvHfu/LQqz0hjq+8MttBOVahtKNVOCSQrtyd1eQ JPjVVlJ5blLHAxkIHF2nVDfAEAuq+V3v6qRgHr/d2aFRQmRrRD0fF10lIvIAQFUzlj oTSD9afL6GQaw== From: Pratyush Yadav To: Pasha Tatashin , Mike Rapoport , Pratyush Yadav , Andrew Morton , David Hildenbrand , Lorenzo Stoakes , Alexander Graf , Hugh Dickins , Baolin Wang , David Matlack , Samiullah Khawaja Cc: kexec@lists.infradead.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org Subject: [RFC PATCH 4/6] mm/memfd_luo: allow preserving a tmpfs mount Date: Thu, 24 Sep 2026 00:44:03 +0200 Message-ID: <20260923224408.3745689-5-pratyush@kernel.org> X-Mailer: git-send-email 2.56.0.rc1.310.g51773c2048-goog In-Reply-To: <20260923224408.3745689-1-pratyush@kernel.org> References: <20260923224408.3745689-1-pratyush@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: "Pratyush Yadav (Google)" A memfd can be preserved across a live update, but it has no name, so anything that needs a filesystem path cannot use one. The main use case is to fetch VMM binaries that need to be kept on the host memory during a live update to avoid the latency of fetching them from network. The host does not have any attached local storage to store the binaries. Allow preserving a tmpfs mount. The mount is identified with a file pointing to it. Only preserve the number of blocks and root directory mode. All other options are reset to their default values and can be set up again by a remount. The retrieved file points to a detached mount that can be attached to a path with move_mount(2). Mounts with memory policies, quotas, ID mappings, and casefolding are not supported. Move SHMEM_SB() into shmem_fs.h to it can be used in memfd_luo. Signed-off-by: Pratyush Yadav (Google) --- Documentation/core-api/liveupdate.rst | 1 + Documentation/mm/index.rst | 1 + Documentation/mm/tmpfs_preservation.rst | 24 +++ MAINTAINERS | 1 + include/linux/kho/abi/tmpfs.h | 48 +++++ include/linux/shmem_fs.h | 5 + mm/memfd_luo.c | 238 +++++++++++++++++++++++- mm/shmem.c | 5 - 8 files changed, 315 insertions(+), 8 deletions(-) create mode 100644 Documentation/mm/tmpfs_preservation.rst create mode 100644 include/linux/kho/abi/tmpfs.h diff --git a/Documentation/core-api/liveupdate.rst b/Documentation/core-api= /liveupdate.rst index 5a292d0f3706..c92a0a8f6b90 100644 --- a/Documentation/core-api/liveupdate.rst +++ b/Documentation/core-api/liveupdate.rst @@ -34,6 +34,7 @@ The following types of file descriptors can be preserved :maxdepth: 1 =20 ../mm/memfd_preservation + ../mm/tmpfs_preservation =20 Public API =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D diff --git a/Documentation/mm/index.rst b/Documentation/mm/index.rst index 13a79f5d092c..46c83fa8db8a 100644 --- a/Documentation/mm/index.rst +++ b/Documentation/mm/index.rst @@ -72,6 +72,7 @@ documentation, or deleted if it has served its purpose. page_table_check remap_file_pages split_page_table_lock + tmpfs_preservation transhuge unevictable-lru vmalloced-kernel-stacks diff --git a/Documentation/mm/tmpfs_preservation.rst b/Documentation/mm/tmp= fs_preservation.rst new file mode 100644 index 000000000000..0c7f5bdaf7ee --- /dev/null +++ b/Documentation/mm/tmpfs_preservation.rst @@ -0,0 +1,24 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D +tmpfs Preservation via LUO +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D + +.. kernel-doc:: mm/memfd_luo.c + :doc: tmpfs Preservation via LUO + +tmpfs Preservation ABI +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +.. kernel-doc:: include/linux/kho/abi/tmpfs.h + :doc: tmpfs Live Update ABI + +.. kernel-doc:: include/linux/kho/abi/tmpfs.h + :internal: + +See Also +=3D=3D=3D=3D=3D=3D=3D=3D + +- :doc:`/mm/memfd_preservation` +- :doc:`/core-api/liveupdate` +- :doc:`/core-api/kho/index` diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..0527e92fd7ad 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15185,6 +15185,7 @@ S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git F: Documentation/core-api/liveupdate.rst F: Documentation/mm/memfd_preservation.rst +F: Documentation/mm/tmpfs_preservation.rst F: Documentation/userspace-api/liveupdate.rst F: include/linux/kho/abi/ F: include/linux/liveupdate.h diff --git a/include/linux/kho/abi/tmpfs.h b/include/linux/kho/abi/tmpfs.h new file mode 100644 index 000000000000..bed41048a19b --- /dev/null +++ b/include/linux/kho/abi/tmpfs.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Copyright (C) 2026, Google LLC. + * Pratyush Yadav + */ + +#ifndef _LINUX_KHO_ABI_TMPFS_H +#define _LINUX_KHO_ABI_TMPFS_H + +#include +#include +#include +#include + +/** + * DOC: tmpfs Live Update ABI + * + * tmpfs uses the ABI defined below for preserving a mount and the regular + * files in it across a kexec reboot using the LUO. + * + * The mount metadata is preserved via `tmpfs_luo_mnt_ser`. + * + * This interface is a contract. Any modification to the structure layout + * constitutes a breaking change. Such changes require incrementing the ve= rsion + * number in the corresponding compatible string. + */ + +/** + * struct tmpfs_luo_mnt_ser - Serialized state of a preserved tmpfs mount. + * @max_blocks: The block limit of the filesystem in PAGE_SIZE units. 0 me= ans + * unlimited. + * @mode: The mode of the root directory. + * @flags: Flags for the mount. Unused flag bits must be set to 0. + * + * Ownership is not preserved; the restored root directory belongs to whoe= ver + * retrieves the mount. + */ +struct tmpfs_luo_mnt_ser { + u64 max_blocks; + u32 mode; + u32 flags; +} __packed; + +/* The compatibility string for the tmpfs mount file handler */ +#define TMPFS_LUO_MNT_FH_COMPATIBLE "tmpfs-mnt-v1" + +#endif /* _LINUX_KHO_ABI_TMPFS_H */ diff --git a/include/linux/shmem_fs.h b/include/linux/shmem_fs.h index 5663dff53186..d41d38541c73 100644 --- a/include/linux/shmem_fs.h +++ b/include/linux/shmem_fs.h @@ -95,6 +95,11 @@ static inline struct shmem_inode_info *SHMEM_I(struct in= ode *inode) return container_of(inode, struct shmem_inode_info, vfs_inode); } =20 +static inline struct shmem_sb_info *SHMEM_SB(struct super_block *sb) +{ + return sb->s_fs_info; +} + /* * Functions in mm/shmem.c called directly from elsewhere: */ diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c index 59de210bee5f..f29571666364 100644 --- a/mm/memfd_luo.c +++ b/mm/memfd_luo.c @@ -71,12 +71,19 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt =20 #include +#include #include #include +#include #include #include #include +#include +#include #include +#include +#include +#include #include #include #include @@ -606,17 +613,242 @@ static struct liveupdate_file_handler memfd_luo_hand= ler =3D { .compatible =3D MEMFD_LUO_FH_COMPATIBLE, }; =20 +/** + * DOC: tmpfs Preservation via LUO + * + * Overview + * =3D=3D=3D=3D=3D=3D=3D=3D + * + * tmpfs mounts can be preserved via LUO. This allows userspace to preserv= e an + * in-memory filesystem across kexec. + * + * Preservation is not transparent. Only the properties listed below survi= ve; + * everything else comes back at its default. + * + * Preserving + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + * + * A mount is preserved through a file descriptor for its root. An ``O_PAT= H`` + * descriptor is rejected, so one obtained from fsmount(2) has to be reope= ned + * first with ``openat(fsmount_fd, ".", O_RDONLY | O_DIRECTORY)``. + * + * Mounts with memory policies, id mappings, quotas, and casefolding are n= ot + * supported. + * + * Attaching a memory policy after preserve will fail the freeze. + * + * Restoring + * =3D=3D=3D=3D=3D=3D=3D=3D=3D + * + * The mount comes back as a detached mount file descriptor with the same + * semantics as fsmount(2). Userspace attaches it with:: + * + * move_mount(fd, "", AT_FDCWD, "/some/path", MOVE_MOUNT_F_EMPTY_PATH); + * + * Closing the mount descriptor without ever attaching it tears the mount = down, + * and everything in it goes too. + * + * Like fsmount(2)'s, the restored mount descriptor is an ``O_PATH`` one, = so + * preserving the same mount again for a second live update means reopenin= g its + * root. + * + * Preserved Properties + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + * + * ``nr_blocks=3D``: + * The block limit. Restoring file contents is charged against it. + * + * Root directory mode + * Including the sticky and setgid bits. + * + * Not Preserved + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + * + * All properties which are not preserved must be assumed to be reset to + * default. This section describes some of those properties which may be m= ore of + * note. + * + * Ownership + * Everything comes back owned by whoever retrieves it. If different own= ership + * is needed, userspace must do that after retrieving the mount. + * + * Timestamps, inode numbers, extended attributes and ACLs + * A restored mount root is a new inode with fresh timestamps and a new + * number. utimensat(2) can set the timestamps again once the mount is + * attached. + */ + +static unsigned long tmpfs_luo_mnt_id(struct super_block *sb) +{ + return (unsigned long)sb; +} + +static bool tmpfs_luo_mnt_can_preserve(struct liveupdate_file_handler *fh, + struct file *file) +{ + struct vfsmount *mnt =3D file->f_path.mnt; + struct super_block *sb =3D mnt->mnt_sb; + + if (sb->s_magic !=3D TMPFS_MAGIC) + return false; + + /* The root of a whole filesystem, not a bind mount of a subdirectory. */ + if (file->f_path.dentry !=3D mnt->mnt_root || mnt->mnt_root !=3D sb->s_ro= ot) + return false; + + /* These features are not supported. */ + if (SHMEM_SB(sb)->mpol || sb_has_quota_active(sb, USRQUOTA) || + sb_has_encoding(sb) || is_idmapped_mnt(mnt)) + return false; + + return true; +} + +static unsigned long tmpfs_luo_mnt_get_id(struct file *file) +{ + return tmpfs_luo_mnt_id(file->f_path.mnt->mnt_sb); +} + +static int tmpfs_luo_mnt_preserve(struct liveupdate_file_op_args *args) +{ + struct tmpfs_luo_mnt_ser *ser; + + ser =3D kho_alloc_preserve(sizeof(*ser)); + if (IS_ERR(ser)) + return PTR_ERR(ser); + + /* + * ser only saves mode and max_blocks. Since they can change by a + * remount, save them on freeze(). So nothing to save for now. + */ + args->serialized_data =3D virt_to_phys(ser); + + return 0; +} + +static int tmpfs_luo_mnt_freeze(struct liveupdate_file_op_args *args) +{ + struct super_block *sb =3D args->file->f_path.mnt->mnt_sb; + struct shmem_sb_info *sbinfo =3D SHMEM_SB(sb); + struct tmpfs_luo_mnt_ser *ser; + + /* + * A remount can install a memory policy after can_preserve() accepted + * the mount. + */ + if (sbinfo->mpol) + return -EOPNOTSUPP; + + ser =3D phys_to_virt(args->serialized_data); + ser->max_blocks =3D sbinfo->max_blocks; + ser->mode =3D sbinfo->mode & 07777; + + return 0; +} + +static void tmpfs_luo_mnt_unpreserve(struct liveupdate_file_op_args *args) +{ + kho_unpreserve_free(phys_to_virt(args->serialized_data)); +} + +static int tmpfs_luo_mnt_retrieve(struct liveupdate_file_op_args *args) +{ + struct tmpfs_luo_mnt_ser *ser; + struct vfsmount *mnt; + struct file *file; + int err; + + if (!args->serialized_data) + return -EINVAL; + + ser =3D phys_to_virt(args->serialized_data); + + if ((ser->mode & ~07777) || ser->flags) { + err =3D -EINVAL; + goto free_ser; + } + + mnt =3D tmpfs_create_mount(ser->max_blocks, ser->mode); + if (IS_ERR(mnt)) { + pr_err("failed to create tmpfs mount: %pe\n", mnt); + err =3D PTR_ERR(mnt); + goto free_ser; + } + + file =3D vfs_open_detached_mount(mnt); + if (IS_ERR(file)) { + pr_err("failed to open detached tmpfs mount: %pe\n", file); + err =3D PTR_ERR(file); + goto free_ser; + } + + args->file =3D file; + kho_restore_free(ser); + + return 0; + +free_ser: + kho_restore_free(ser); + return err; +} + +static void tmpfs_luo_mnt_finish(struct liveupdate_file_op_args *args) +{ + /* + * A successful retrieve() already freed the serialized state, and a + * failed one cleaned up everything it could. Only an un-retrieved + * mount is left to clean up here. + */ + if (args->retrieve_status || !args->serialized_data) + return; + + kho_restore_free(phys_to_virt(args->serialized_data)); +} + +static const struct liveupdate_file_ops tmpfs_luo_mnt_ops =3D { + .freeze =3D tmpfs_luo_mnt_freeze, + .finish =3D tmpfs_luo_mnt_finish, + .retrieve =3D tmpfs_luo_mnt_retrieve, + .preserve =3D tmpfs_luo_mnt_preserve, + .unpreserve =3D tmpfs_luo_mnt_unpreserve, + .can_preserve =3D tmpfs_luo_mnt_can_preserve, + .get_id =3D tmpfs_luo_mnt_get_id, + .owner =3D THIS_MODULE, +}; + +static struct liveupdate_file_handler tmpfs_luo_mnt_handler =3D { + .ops =3D &tmpfs_luo_mnt_ops, + .compatible =3D TMPFS_LUO_MNT_FH_COMPATIBLE, +}; + static int __init memfd_luo_init(void) { - int err =3D liveupdate_register_file_handler(&memfd_luo_handler); + int err; =20 - if (err && err !=3D -EOPNOTSUPP) { - pr_err("Could not register luo filesystem handler: %pe\n", + err =3D liveupdate_register_file_handler(&memfd_luo_handler); + if (err) { + if (err =3D=3D -EOPNOTSUPP) + return 0; + + pr_err("Could not register luo memfd handler: %pe\n", ERR_PTR(err)); =20 return err; } =20 + err =3D liveupdate_register_file_handler(&tmpfs_luo_mnt_handler); + if (err) { + pr_err("Could not register luo tmpfs mount handler: %pe\n", + ERR_PTR(err)); + + goto err_unregister_memfd; + } + return 0; + +err_unregister_memfd: + liveupdate_unregister_file_handler(&memfd_luo_handler); + + return err; } late_initcall(memfd_luo_init); diff --git a/mm/shmem.c b/mm/shmem.c index 8660405b6ab8..c76dd3646f6f 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -162,11 +162,6 @@ static int shmem_swapin_folio(struct inode *inode, pgo= ff_t index, struct folio **foliop, enum sgp_type sgp, gfp_t gfp, struct vm_fault *vmf, vm_fault_t *fault_type); =20 -static inline struct shmem_sb_info *SHMEM_SB(struct super_block *sb) -{ - return sb->s_fs_info; -} - /* * shmem_file_setup pre-accounts the whole fixed size of a VM object, * for shared memory and for shared anonymous (/dev/zero) mappings --=20 2.56.0.rc1.310.g51773c2048-goog From nobody Thu Sep 24 13:37:32 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 956B04825CE for ; Wed, 23 Sep 2026 22:44:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790203475; cv=none; b=cAcOL6muARVEpJBmgwoEyLe4eWb4XNcEXhFDEhLK7xsad5DhFMiDXXTQXeik1CJ9Vusp6pW6MIVG5BSqPV12U0cditem/fWrJzPpiQZu4DfnjwQFm+QGob7fyHLbSLFXQ90KSuI/UZREslqINqcoi0RFwbtGiPcWvndsva3fiig= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790203475; c=relaxed/simple; bh=GGV8nPGSqrYWum0gXCpQQIp9nv8IHezy/F7Vs4aBRYA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=GFwbsMSN746fIhm9tlqJrB3PkRxT7Hhw7z18RjW5N6/fK3aVr590jgqutUWKXyShnTvLT0yLgvzUcbFimduZrKE028x7b6GlpPLwCZAh8WqS2zY9q8rfQzscYAn/DOWR8FGcaz4Ue82ZMXcFFM+cR51wRpQXNN/FaH7Da0wgNiI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=fP2ibDoP; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="fP2ibDoP" Received: by smtp.kernel.org (Postfix) with ESMTPSA id CC0F21F00898; Wed, 23 Sep 2026 22:44:30 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1790203473; bh=ppyzkpiHfzcRPB9/8pM+Rg0xhLEz5xnxGptHULBj05Q=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=fP2ibDoPZ4EXQS+PSvLtiwA3SOjO2JC8tuNRaL2UUfZxIX+HeouMuBP/+Y71TSpa5 CmvlFyGFpn/eyAgKl0Yw71IT65dJKaRFXeJDVwFi7tGEzV3GFq08QO9IQR4x2uGWAg aQaxngQ5F1vtH6RZWMMhRBARZHnVDGZGlBQJSJ7oJjUvFJs8cdfMAgEu9/eE9OL967 E/32rcLdGy9tqPe8rrP9zizmXV2V1884jVR4S8DW/ILwEMkDvB2Aog4KhFn4hRv+tc i63l844SnbMNZ6sWJ6SRfQXkJsBdn66kSiEaYBsmyaUrRwj3GQS6O7gBryB0A90PTM dlyZdVkf089BQ== From: Pratyush Yadav To: Pasha Tatashin , Mike Rapoport , Pratyush Yadav , Andrew Morton , David Hildenbrand , Lorenzo Stoakes , Alexander Graf , Hugh Dickins , Baolin Wang , David Matlack , Samiullah Khawaja Cc: kexec@lists.infradead.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org Subject: [RFC PATCH 5/6] mm/memfd_luo: allow preserving a tmpfs file Date: Thu, 24 Sep 2026 00:44:04 +0200 Message-ID: <20260923224408.3745689-6-pratyush@kernel.org> X-Mailer: git-send-email 2.56.0.rc1.310.g51773c2048-goog In-Reply-To: <20260923224408.3745689-1-pratyush@kernel.org> References: <20260923224408.3745689-1-pratyush@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: "Pratyush Yadav (Google)" A memfd can be preserved across a live update, but it has no name, so anything that needs a filesystem path cannot use one. The main use case is to fetch VMM binaries that need to be kept on the host memory during a live update to avoid the latency of fetching them from network. The host does not have any attached local storage to store the binaries. Allow preserving files in a tmpfs mount. Only regular files in the mount root are supported to keep things simple. Files in subdirectories will be rejected. The mount must be preserved before the files because the file takes a dependency on the mount. Restoration can be done in any order. Files are allowed to be renamed after preservation, but they can not be unlinked. Unlinked files will cause the freeze() callback to fail and result in a failed live update. Re-use the memfd preservation logic and ABI since it already knows how to preserve a shmem file. The same restrictions around file resizing apply. Signed-off-by: Pratyush Yadav (Google) --- The usage of liveupdate_get_file_incoming() makes lockdep scream. The locking itself is fine, but two locks of the same class are taken. Likely need some annotation in liveupdate_get_file_incoming(). That is separate work and not done by this series. --- include/linux/kho/abi/tmpfs.h | 35 ++++ mm/memfd_luo.c | 342 ++++++++++++++++++++++++++++++++-- 2 files changed, 366 insertions(+), 11 deletions(-) diff --git a/include/linux/kho/abi/tmpfs.h b/include/linux/kho/abi/tmpfs.h index bed41048a19b..cd47e2eb5041 100644 --- a/include/linux/kho/abi/tmpfs.h +++ b/include/linux/kho/abi/tmpfs.h @@ -19,6 +19,11 @@ * tmpfs uses the ABI defined below for preserving a mount and the regular * files in it across a kexec reboot using the LUO. * + * Regular files in the root of such a mount are preserved individually in= to + * `struct tmpfs_luo_file_ser` and reference their mount by its LUO token.= Only + * the folios holding the file contents are true handover payload; they us= e the + * memfd ABI (`struct memfd_luo_folio_ser`) unchanged. + * * The mount metadata is preserved via `tmpfs_luo_mnt_ser`. * * This interface is a contract. Any modification to the structure layout @@ -42,7 +47,37 @@ struct tmpfs_luo_mnt_ser { u32 flags; } __packed; =20 +/** + * struct tmpfs_luo_file_ser - Serialized state of a preserved tmpfs file. + * @mnt_token: The LUO token of the tmpfs mount this file lives in. + * @pos: The file's current position (f_pos). + * @size: The total size of the file in bytes (i_size). + * @mode: The permission bits of the file (i_mode). The file type is + * always S_IFREG. + * @flags: Flags for the file. Unused flag bits must be set to 0. + * @nr_folios: Number of folios in the folios array. + * @folios: KHO vmalloc descriptor pointing to the array of + * struct memfd_luo_folio_ser. + * @name: The NUL-terminated name of the file in the root of the moun= t, + * This is a single path component: it never contains '/', is = never + * "." or "..", and all bytes after the terminator must be 0. + */ +struct tmpfs_luo_file_ser { + u64 mnt_token; + u64 pos; + u64 size; + u32 mode; + u32 flags; + u64 nr_folios; + struct kho_vmalloc folios; + /* NAME_MAX is uAPI. */ + char name[NAME_MAX + 1]; +} __packed; + /* The compatibility string for the tmpfs mount file handler */ #define TMPFS_LUO_MNT_FH_COMPATIBLE "tmpfs-mnt-v1" =20 +/* The compatibility string for the tmpfs file handler */ +#define TMPFS_LUO_FILE_FH_COMPATIBLE "tmpfs-file-v1" + #endif /* _LINUX_KHO_ABI_TMPFS_H */ diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c index f29571666364..aacae90218cc 100644 --- a/mm/memfd_luo.c +++ b/mm/memfd_luo.c @@ -619,8 +619,8 @@ static struct liveupdate_file_handler memfd_luo_handler= =3D { * Overview * =3D=3D=3D=3D=3D=3D=3D=3D * - * tmpfs mounts can be preserved via LUO. This allows userspace to preserv= e an - * in-memory filesystem across kexec. + * tmpfs mounts and the files contained can be preserved via LUO. This all= ows + * userspace to preserve in-memory files with a filesystem path across kex= ec. * * Preservation is not transparent. Only the properties listed below survi= ve; * everything else comes back at its default. @@ -632,10 +632,16 @@ static struct liveupdate_file_handler memfd_luo_handl= er =3D { * descriptor is rejected, so one obtained from fsmount(2) has to be reope= ned * first with ``openat(fsmount_fd, ".", O_RDONLY | O_DIRECTORY)``. * - * Mounts with memory policies, id mappings, quotas, and casefolding are n= ot - * supported. + * Each file is then preserved through its own FD. A file depends on its m= ount, + * so the mount must already be preserved in the same session; otherwise + * preserving the file fails with ``-ENOENT``. * - * Attaching a memory policy after preserve will fail the freeze. + * Only regular files linked at the root are supported. Files in subdirect= ories + * are not supported. Mounts with memory policies, id mappings, quotas, and + * casefolding are also not supported. + * + * Unlinking a file or attaching a memory policy after preserve will fail = the + * freeze. * * Restoring * =3D=3D=3D=3D=3D=3D=3D=3D=3D @@ -645,8 +651,12 @@ static struct liveupdate_file_handler memfd_luo_handle= r =3D { * * move_mount(fd, "", AT_FDCWD, "/some/path", MOVE_MOUNT_F_EMPTY_PATH); * - * Closing the mount descriptor without ever attaching it tears the mount = down, - * and everything in it goes too. + * Until then the restored files are reachable only through the descriptor= s LUO + * hands back. Closing the mount descriptor without ever attaching it tear= s the + * mount down, and everything in it goes too. + * + * Unlike preservation, files may be retrieved before their mount. If the = mount + * cannot be retrieved, every file in it fails with the same error. * * Like fsmount(2)'s, the restored mount descriptor is an ``O_PATH`` one, = so * preserving the same mount again for a second live update means reopenin= g its @@ -655,12 +665,30 @@ static struct liveupdate_file_handler memfd_luo_handl= er =3D { * Preserved Properties * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D * + * Of the mount: + * * ``nr_blocks=3D``: * The block limit. Restoring file contents is charged against it. * * Root directory mode * Including the sticky and setgid bits. * + * Of each file: + * + * Contents and size + * Holes are filled by allocating pages for them during preservation, as= with + * memfd. + * + * Name + * A single component in the root of the mount. + * + * Permission bits + * Not including the setuid and setgid bits; see below. + * + * File position + * So that the returned descriptor can be read from or written to where = the + * old one left off. + * * Not Preserved * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D * @@ -670,12 +698,11 @@ static struct liveupdate_file_handler memfd_luo_handl= er =3D { * * Ownership * Everything comes back owned by whoever retrieves it. If different own= ership - * is needed, userspace must do that after retrieving the mount. + * is needed, userspace must do that after retrieving the files. * * Timestamps, inode numbers, extended attributes and ACLs - * A restored mount root is a new inode with fresh timestamps and a new - * number. utimensat(2) can set the timestamps again once the mount is - * attached. + * A restored file is a new inode with fresh timestamps and a new number. + * utimensat(2) can set the timestamps again once the mount is attached. */ =20 static unsigned long tmpfs_luo_mnt_id(struct super_block *sb) @@ -821,6 +848,289 @@ static struct liveupdate_file_handler tmpfs_luo_mnt_h= andler =3D { .compatible =3D TMPFS_LUO_MNT_FH_COMPATIBLE, }; =20 +static bool tmpfs_luo_file_can_preserve(struct liveupdate_file_handler *fh, + struct file *file) +{ + struct inode *inode =3D file_inode(file); + struct dentry *dentry =3D file->f_path.dentry; + struct super_block *sb =3D inode->i_sb; + + if (!shmem_file(file) || (sb->s_flags & SB_NOUSER)) + return false; + + /* Only linked regular files allowed. */ + if (!S_ISREG(inode->i_mode) || d_unlinked(dentry)) + return false; + + /* Only files in the root of the mount are supported for now. */ + if (dentry->d_parent !=3D sb->s_root) + return false; + + return true; +} + +static unsigned long tmpfs_luo_file_get_id(struct file *file) +{ + return (unsigned long)file_inode(file); +} + +static int tmpfs_luo_file_preserve(struct liveupdate_file_op_args *args) +{ + struct inode *inode =3D file_inode(args->file); + struct memfd_luo_folio_ser *folios_ser; + u64 nr_folios, inode_size, mnt_token; + struct tmpfs_luo_file_ser *ser; + int err; + + /* Find the token of the mount. It is identified by its superblock. */ + err =3D liveupdate_get_token_outgoing(args->session, tmpfs_luo_mnt_id(ino= de->i_sb), + &mnt_token); + if (err) + return err; + + ser =3D kho_alloc_preserve(sizeof(*ser)); + if (IS_ERR(ser)) + return PTR_ERR(ser); + + inode_lock(inode); + shmem_freeze(inode, true); + + inode_size =3D i_size_read(inode); + + /* + * memfd_pin_folios() caps at UINT_MAX folios; refuse larger files to + * avoid silently preserving only a prefix. + */ + if (DIV_ROUND_UP_ULL(inode_size, PAGE_SIZE) > UINT_MAX) { + err =3D -EFBIG; + goto err_free_ser; + } + + ser->mnt_token =3D mnt_token; + + /* + * Preserve only folios now. Name, mode, etc. can change later and are + * cheap to preserve. They will be preserved on freeze(). + */ + err =3D memfd_luo_preserve_folios(args->file, &ser->folios, &folios_ser, + &nr_folios); + if (err) + goto err_free_ser; + + ser->nr_folios =3D nr_folios; + inode_unlock(inode); + + args->private_data =3D folios_ser; + args->serialized_data =3D virt_to_phys(ser); + + return 0; + +err_free_ser: + kho_unpreserve_free(ser); + shmem_freeze(inode, false); + inode_unlock(inode); + return err; +} + +static int tmpfs_luo_file_freeze(struct liveupdate_file_op_args *args) +{ + struct dentry *dentry =3D args->file->f_path.dentry; + struct inode *inode =3D file_inode(args->file); + struct dentry *root =3D inode->i_sb->s_root; + struct tmpfs_luo_file_ser *ser; + int err; + + /* + * Lock the mount root to block renames and unlinks, then the inode + * itself to stabilize its mode and size. + */ + inode_lock_shared_nested(d_inode(root), I_MUTEX_PARENT); + inode_lock_shared(inode); + + /* Renamed into a subdirectory. */ + if (dentry->d_parent !=3D root) { + err =3D -EOPNOTSUPP; + goto unlock; + } + + /* Unlinked after preserve. */ + if (d_unlinked(dentry)) { + err =3D -ENOENT; + goto unlock; + } + + ser =3D phys_to_virt(args->serialized_data); + + /* + * Zero-pads the tail, which the ABI requires. Cannot fail, the dentry + * name is bounded by NAME_MAX, but check anyway. + */ + if (strscpy_pad(ser->name, dentry->d_name.name) < 0) { + err =3D -ENAMETOOLONG; + goto unlock; + } + + ser->mode =3D inode->i_mode & 0777; + ser->size =3D i_size_read(inode); + ser->pos =3D args->file->f_pos; + + err =3D 0; + +unlock: + inode_unlock_shared(inode); + inode_unlock_shared(d_inode(root)); + return err; +} + +static void tmpfs_luo_file_unpreserve(struct liveupdate_file_op_args *args) +{ + struct inode *inode =3D file_inode(args->file); + struct tmpfs_luo_file_ser *ser; + + inode_lock(inode); + shmem_freeze(inode, false); + + ser =3D phys_to_virt(args->serialized_data); + memfd_luo_unpreserve_folios(&ser->folios, args->private_data, + ser->nr_folios); + + kho_unpreserve_free(ser); + inode_unlock(inode); +} + +/* + * A name from the previous kernel is untrusted input. There is no separate + * length, so the NUL terminator is the only bound. + */ +static bool tmpfs_luo_name_valid(const char *name, size_t size) +{ + size_t len =3D strnlen(name, size); + + /* Non-empty, NUL terminated, and a single component. */ + if (!len || len =3D=3D size || memchr(name, '/', len)) + return false; + + return strcmp(name, ".") && strcmp(name, ".."); +} + +static int tmpfs_luo_file_retrieve(struct liveupdate_file_op_args *args) +{ + struct memfd_luo_folio_ser *folios_ser; + struct file *mnt_file, *file; + struct tmpfs_luo_file_ser *ser; + struct inode *inode; + int err; + + if (!args->serialized_data) + return -EINVAL; + + ser =3D phys_to_virt(args->serialized_data); + + if ((ser->mode & ~0777) || ser->flags || + !tmpfs_luo_name_valid(ser->name, sizeof(ser->name))) { + err =3D -EINVAL; + goto free_ser; + } + + err =3D liveupdate_get_file_incoming(args->session, ser->mnt_token, + &mnt_file); + if (err) { + pr_err("failed to retrieve tmpfs mount: %pe\n", ERR_PTR(err)); + goto free_ser; + } + + /* + * TODO: This is racy. This will link the file in the mount so after + * this call userspace can already open the file and write to it. + * Ideally we should first create the inode and set it up, and only + * then link it to the root. + */ + file =3D file_open_root(&mnt_file->f_path, ser->name, + O_RDWR | O_CREAT | O_EXCL | O_LARGEFILE, + ser->mode); + fput(mnt_file); + if (IS_ERR(file)) { + pr_err("failed to create '%s': %pe\n", ser->name, file); + err =3D PTR_ERR(file); + goto free_ser; + } + + inode =3D file_inode(file); + + inode_lock(inode); + i_size_write(inode, ser->size); + inode_unlock(inode); + + vfs_setpos(file, ser->pos, MAX_LFS_FILESIZE); + + if (ser->nr_folios) { + folios_ser =3D kho_restore_vmalloc(&ser->folios); + if (!folios_ser) { + err =3D -EINVAL; + goto put_file; + } + + err =3D memfd_luo_retrieve_folios(file, folios_ser, + ser->nr_folios); + vfree(folios_ser); + if (err) + goto put_file; + } + + args->file =3D file; + kho_restore_free(ser); + + return 0; + +put_file: + fput(file); +free_ser: + kho_restore_free(ser); + return err; +} + +static void tmpfs_luo_file_finish(struct liveupdate_file_op_args *args) +{ + struct memfd_luo_folio_ser *folios_ser; + struct tmpfs_luo_file_ser *ser; + + /* + * A successful retrieve() already consumed the preserved memory, and a + * failed one cleaned up what it could. Only a file that was never + * retrieved is left to discard here. + */ + if (args->retrieve_status || !args->serialized_data) + return; + + ser =3D phys_to_virt(args->serialized_data); + + if (ser->nr_folios) { + folios_ser =3D kho_restore_vmalloc(&ser->folios); + if (folios_ser) { + memfd_luo_discard_folios(folios_ser, ser->nr_folios); + vfree(folios_ser); + } + } + + kho_restore_free(ser); +} + +static const struct liveupdate_file_ops tmpfs_luo_file_ops =3D { + .freeze =3D tmpfs_luo_file_freeze, + .finish =3D tmpfs_luo_file_finish, + .retrieve =3D tmpfs_luo_file_retrieve, + .preserve =3D tmpfs_luo_file_preserve, + .unpreserve =3D tmpfs_luo_file_unpreserve, + .can_preserve =3D tmpfs_luo_file_can_preserve, + .get_id =3D tmpfs_luo_file_get_id, + .owner =3D THIS_MODULE, +}; + +static struct liveupdate_file_handler tmpfs_luo_file_handler =3D { + .ops =3D &tmpfs_luo_file_ops, + .compatible =3D TMPFS_LUO_FILE_FH_COMPATIBLE, +}; + static int __init memfd_luo_init(void) { int err; @@ -844,8 +1154,18 @@ static int __init memfd_luo_init(void) goto err_unregister_memfd; } =20 + err =3D liveupdate_register_file_handler(&tmpfs_luo_file_handler); + if (err) { + pr_err("Could not register luo tmpfs file handler: %pe\n", + ERR_PTR(err)); + + goto err_unregister_tmpfs_mnt; + } + return 0; =20 +err_unregister_tmpfs_mnt: + liveupdate_unregister_file_handler(&tmpfs_luo_mnt_handler); err_unregister_memfd: liveupdate_unregister_file_handler(&memfd_luo_handler); =20 --=20 2.56.0.rc1.310.g51773c2048-goog From nobody Thu Sep 24 13:37:32 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3E26058125A for ; Wed, 23 Sep 2026 22:44:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790203477; cv=none; b=ty9VghAtC2TUqZ7zzHHUNI0VnDZItsixtqPLXAuz4KIHVL2crm4UAoWSihC76ktdpd8ixmvzhHyGO2csFy8d1PgkLldo/23zzBV+fVOnjw4rrKy+NxtgHwGuZ2F4sWVMErnMkcTE2crmclGzdvD6BtmCFfzgIlKVlphGG1CnimY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790203477; c=relaxed/simple; bh=WmYxQeadB88LZiuc8wfcLY5gKIwbh5i2JDt14HveVm0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=LcZ9OzYULHqig4cYl5Wn9vZ7U01D5UrOYrLzsO+zVQlCIW4sMtku96Dw3J/5Zn9Z98RXqPBwA3D+lEePWuWjU36eUymuvEhFu2nPl5r7/jvfdMYldvd4qpQY5l2BiGEUGAcfuWkGQjBq/ztXI8ksjcMm8z4zUZpsgejE0teuDF8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=lrNPl32y; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="lrNPl32y" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 80C181F000FF; Wed, 23 Sep 2026 22:44:33 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1790203475; bh=eG1RVyLzxpChpBzlqXiCEe5ZgN0Ss8wEK7e+2vb2e54=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=lrNPl32yv6F7bSR3BBzsqEEgJOJEV8vFbf2FKbw9lshqzfR65ccPCOJrqAkD2p5dL dceuUNDxeMsYJbywPccHmuE230lS6vk/AYFN7uRp5Y6af4bXBnog4RDEgjovaNk0vH 1tZtoFXrlSDPdSi+zHKFGvM3jhrruDUqwLP+kB+BFI9KazHg/iH9HA8BeB4POaGwcE oTusEO9VVQk7Ea3J+NAnoHhjtImwGRjE4HBy+5RREt4Uc+/M0VsG6hXtY8iPiMATNZ mLX0J6Za6n2riopDl4RwuE2OFpMbcT4q2cyZEkPOqeC+RrAoUGR3QXVuCkGVKYeCbI 90kT3SnYBBQgQ== From: Pratyush Yadav To: Pasha Tatashin , Mike Rapoport , Pratyush Yadav , Andrew Morton , David Hildenbrand , Lorenzo Stoakes , Alexander Graf , Hugh Dickins , Baolin Wang , David Matlack , Samiullah Khawaja Cc: kexec@lists.infradead.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org Subject: [RFC PATCH 6/6] selftests/liveupdate: add tmpfs kexec test Date: Thu, 24 Sep 2026 00:44:05 +0200 Message-ID: <20260923224408.3745689-7-pratyush@kernel.org> X-Mailer: git-send-email 2.56.0.rc1.310.g51773c2048-goog In-Reply-To: <20260923224408.3745689-1-pratyush@kernel.org> References: <20260923224408.3745689-1-pratyush@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: "Pratyush Yadav (Google)" Add a two-stage kexec test for tmpfs preservation, in the same shape as luo_kexec_simple. The first stage create a tmpfs mount, creates a file in it, write some data to it, and preserves both. In the second stage, it retrieves both and verifies the contents of the file. It also verifies it can be reached by a filesystem path. Add the test to run_vmtests.sh to allow automated runs. Signed-off-by: Pratyush Yadav (Google) --- tools/testing/selftests/liveupdate/Makefile | 1 + .../selftests/liveupdate/luo_kexec_tmpfs.c | 191 ++++++++++++++++++ .../selftests/liveupdate/run-vmtests.sh | 1 + 3 files changed, 193 insertions(+) create mode 100644 tools/testing/selftests/liveupdate/luo_kexec_tmpfs.c diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/se= lftests/liveupdate/Makefile index 634211c66652..c35aee04b4a3 100644 --- a/tools/testing/selftests/liveupdate/Makefile +++ b/tools/testing/selftests/liveupdate/Makefile @@ -3,6 +3,7 @@ TEST_GEN_PROGS +=3D liveupdate =20 TEST_GEN_PROGS_EXTENDED +=3D luo_kexec_simple +TEST_GEN_PROGS_EXTENDED +=3D luo_kexec_tmpfs TEST_GEN_PROGS_EXTENDED +=3D luo_multi_session TEST_GEN_PROGS_EXTENDED +=3D luo_stress_sessions TEST_GEN_PROGS_EXTENDED +=3D luo_stress_files diff --git a/tools/testing/selftests/liveupdate/luo_kexec_tmpfs.c b/tools/t= esting/selftests/liveupdate/luo_kexec_tmpfs.c new file mode 100644 index 000000000000..608a6eddbd7b --- /dev/null +++ b/tools/testing/selftests/liveupdate/luo_kexec_tmpfs.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * Copyright (C) 2026, Google LLC. + * Pratyush Yadav + * + * Validate preservation of a tmpfs mount and a file in it across a kexec + * reboot. Stage 1 creates a tmpfs, puts a file in it and preserves both. + * Stage 2 retrieves the mount, attaches it with move_mount(2), and checks= that + * the file's contents are intact and that it is reachable by path. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TEST_SESSION_NAME "tmpfs-session" +#define TMPFS_MNT_TOKEN 0x2A +#define TMPFS_FILE_TOKEN 0x2B + +/* Constants for the state-tracking mechanism, specific to this test file.= */ +#define STATE_SESSION_NAME "kexec_tmpfs_state" +#define STATE_MEMFD_TOKEN 998 + +#define TMPFS_DIR "/tmpfs" +#define TMPFS_FILE_NAME "state.bin" +#define TMPFS_FILE_PATH TMPFS_DIR "/" TMPFS_FILE_NAME +#define TMPFS_DATA "hello tmpfs kexec world" + +#ifndef MOVE_MOUNT_F_EMPTY_PATH +#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 +#endif + +/* nolibc has no move_mount() wrapper. */ +static int move_mount_empty_from(int from_fd, const char *to_path) +{ + return syscall(__NR_move_mount, from_fd, "", AT_FDCWD, to_path, + MOVE_MOUNT_F_EMPTY_PATH); +} + +static void write_file(int fd, const char *data, size_t len) +{ + ssize_t written =3D write(fd, data, len); + + if (written < 0 || (size_t)written !=3D len) + fail_exit("write of %zu bytes returned %zd", len, written); +} + +static void verify_contents(int fd, const char *expected, size_t len) +{ + char buf[128]; + ssize_t got; + + if (len > sizeof(buf)) + fail_exit("test data too large for buffer"); + + /* nolibc has no pread(). */ + if (lseek(fd, 0, SEEK_SET) < 0) + fail_exit("lseek to the start of the file"); + + got =3D read(fd, buf, len); + if (got < 0 || (size_t)got !=3D len) + fail_exit("read of %zu bytes returned %zd", len, got); + + if (memcmp(buf, expected, len)) + fail_exit("file contents do not match"); +} + +/* Stage 1: Executed before the kexec reboot. */ +static void run_stage_1(int luo_fd) +{ + int session_fd, mnt_fd, file_fd; + + ksft_print_msg("[STAGE 1] Starting pre-kexec setup...\n"); + + ksft_print_msg("[STAGE 1] Creating state file for next stage (2)...\n"); + create_state_file(luo_fd, STATE_SESSION_NAME, STATE_MEMFD_TOKEN, 2); + + session_fd =3D luo_create_session(luo_fd, TEST_SESSION_NAME); + if (session_fd < 0) + fail_exit("luo_create_session for '%s'", TEST_SESSION_NAME); + + ksft_print_msg("[STAGE 1] Mounting tmpfs at %s...\n", TMPFS_DIR); + if (mkdir(TMPFS_DIR, 0755) < 0) + fail_exit("mkdir %s", TMPFS_DIR); + if (mount("tmpfs", TMPFS_DIR, "tmpfs", 0, NULL) < 0) + fail_exit("mount tmpfs at %s", TMPFS_DIR); + + /* + * A plain open of the mount root. LUO's preserve uses fget(), which + * refuses O_PATH descriptors, so an fsmount(2) fd cannot be handed to + * it directly either. + */ + mnt_fd =3D open(TMPFS_DIR, O_RDONLY | O_DIRECTORY); + if (mnt_fd < 0) + fail_exit("open %s", TMPFS_DIR); + + file_fd =3D open(TMPFS_FILE_PATH, O_RDWR | O_CREAT, 0644); + if (file_fd < 0) + fail_exit("open %s", TMPFS_FILE_PATH); + + write_file(file_fd, TMPFS_DATA, sizeof(TMPFS_DATA)); + + /* The mount must be preserved before any file that lives in it. */ + ksft_print_msg("[STAGE 1] Preserving mount (token %#x)...\n", + TMPFS_MNT_TOKEN); + if (luo_session_preserve_fd(session_fd, mnt_fd, TMPFS_MNT_TOKEN) < 0) + fail_exit("luo_session_preserve_fd for the mount"); + + ksft_print_msg("[STAGE 1] Preserving file (token %#x)...\n", + TMPFS_FILE_TOKEN); + if (luo_session_preserve_fd(session_fd, file_fd, TMPFS_FILE_TOKEN) < 0) + fail_exit("luo_session_preserve_fd for the file"); + + close(file_fd); + close(mnt_fd); + close(luo_fd); + daemonize_and_wait(); +} + +/* Stage 2: Executed after the kexec reboot. */ +static void run_stage_2(int luo_fd, int state_session_fd) +{ + int session_fd, mnt_fd, file_fd, path_fd, stage; + + ksft_print_msg("[STAGE 2] Starting post-kexec verification...\n"); + + restore_and_read_stage(state_session_fd, STATE_MEMFD_TOKEN, &stage); + if (stage !=3D 2) + fail_exit("Expected stage 2, but state file contains %d", stage); + + session_fd =3D luo_retrieve_session(luo_fd, TEST_SESSION_NAME); + if (session_fd < 0) + fail_exit("luo_retrieve_session for '%s'", TEST_SESSION_NAME); + + ksft_print_msg("[STAGE 2] Retrieving mount (token %#x)...\n", + TMPFS_MNT_TOKEN); + mnt_fd =3D luo_session_retrieve_fd(session_fd, TMPFS_MNT_TOKEN); + if (mnt_fd < 0) + fail_exit("luo_session_retrieve_fd for the mount"); + + ksft_print_msg("[STAGE 2] Attaching the restored mount at %s...\n", + TMPFS_DIR); + if (mkdir(TMPFS_DIR, 0755) < 0) + fail_exit("mkdir %s", TMPFS_DIR); + if (move_mount_empty_from(mnt_fd, TMPFS_DIR) < 0) + fail_exit("move_mount of the restored mount to %s", TMPFS_DIR); + close(mnt_fd); + + ksft_print_msg("[STAGE 2] Retrieving file (token %#x)...\n", + TMPFS_FILE_TOKEN); + file_fd =3D luo_session_retrieve_fd(session_fd, TMPFS_FILE_TOKEN); + if (file_fd < 0) + fail_exit("luo_session_retrieve_fd for the file"); + + /* The contents are intact through the retrieved fd. */ + verify_contents(file_fd, TMPFS_DATA, sizeof(TMPFS_DATA)); + + /* And the file is reachable by path, which is the point. */ + path_fd =3D open(TMPFS_FILE_PATH, O_RDONLY); + if (path_fd < 0) + fail_exit("open restored %s by path", TMPFS_FILE_PATH); + + verify_contents(path_fd, TMPFS_DATA, sizeof(TMPFS_DATA)); + + close(path_fd); + close(file_fd); + + ksft_print_msg("[STAGE 2] Test data verified successfully.\n"); + if (luo_session_finish(session_fd) < 0) + fail_exit("luo_session_finish for test session"); + close(session_fd); + + if (luo_session_finish(state_session_fd) < 0) + fail_exit("luo_session_finish for state session"); + close(state_session_fd); + + ksft_print_msg("\n--- TMPFS KEXEC TEST PASSED ---\n"); +} + +int main(int argc, char *argv[]) +{ + return luo_test(argc, argv, STATE_SESSION_NAME, + run_stage_1, run_stage_2); +} diff --git a/tools/testing/selftests/liveupdate/run-vmtests.sh b/tools/test= ing/selftests/liveupdate/run-vmtests.sh index d656ce58c5a7..624d1916eeda 100755 --- a/tools/testing/selftests/liveupdate/run-vmtests.sh +++ b/tools/testing/selftests/liveupdate/run-vmtests.sh @@ -17,6 +17,7 @@ skipped=3D0 =20 TEST_NAMES=3D( "luo_kexec_simple" + "luo_kexec_tmpfs" "luo_multi_session" "luo_stress_files" "luo_stress_sessions" --=20 2.56.0.rc1.310.g51773c2048-goog