* Re: [PATCH v2 03/11] fsopen.2: document 'new' mount api
From: Aleksa Sarai @ 2025-08-07 12:50 UTC (permalink / raw)
To: Alejandro Colomar
Cc: Michael T. Kerrisk, Alexander Viro, Jan Kara, Askar Safin,
G. Branden Robinson, linux-man, linux-api, linux-fsdevel,
linux-kernel, David Howells, Christian Brauner
In-Reply-To: <afty6mfpowwj3kzzbn3p7s4j4ovmput34dtqfzzwa57ocaita4@2jj4qandbnw3>
[-- Attachment #1: Type: text/plain, Size: 14892 bytes --]
On 2025-08-07, Alejandro Colomar <alx@kernel.org> wrote:
> Hi Aleksa,
>
> On Thu, Aug 07, 2025 at 03:44:37AM +1000, Aleksa Sarai wrote:
> > This is loosely based on the original documentation written by David
> > Howells and later maintained by Christian Brauner, but has been
> > rewritten to be more from a user perspective (as well as fixing a few
> > critical mistakes).
> >
> > Co-developed-by: David Howells <dhowells@redhat.com>
> > Co-developed-by: Christian Brauner <brauner@kernel.org>
>
> Please use Co-authored-by. It's documented under CONTRIBUTING.d/:
>
> $ cat CONTRIBUTING.d/patches/description | grep -A99 Trailer;
> Trailer
> Sign your patch with "Signed-off-by:". Read about the
> "Developer's Certificate of Origin" at
> <https://www.kernel.org/doc/Documentation/process/submitting-patches.rst>.
> When appropriate, other tags documented in that file, such as
> "Reported-by:", "Reviewed-by:", "Acked-by:", and "Suggested-by:"
> can be added to the patch. We use "Co-authored-by:" instead of
> "Co-developed-by:". Example:
>
> Signed-off-by: Alejandro Colomar <alx@kernel.org>
>
> I think 'author' is more appropriate than 'developer' for documentation.
> It is also more consistent with the Copyright notice, which assigns
> copyright to the authors (documented in AUTHORS). And ironically, even
> the kernel documentation about Co-authored-by talks about authorship
> instead of development:
>
> Co-developed-by: states that the patch was co-created by
> multiple developers; it is used to give attribution to
> co-authors (in addition to the author attributed by the From:
> tag) when several people work on a single patch.
>
> > Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
> > ---
> > man/man2/fsopen.2 | 319 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
> > 1 file changed, 319 insertions(+)
> >
> > diff --git a/man/man2/fsopen.2 b/man/man2/fsopen.2
> > new file mode 100644
> > index 000000000000..ad38ef0782be
> > --- /dev/null
> > +++ b/man/man2/fsopen.2
> > @@ -0,0 +1,319 @@
> > +.\" Copyright, the authors of the Linux man-pages project
> > +.\"
> > +.\" SPDX-License-Identifier: Linux-man-pages-copyleft
> > +.\"
> > +.TH fsopen 2 (date) "Linux man-pages (unreleased)"
> > +.SH NAME
> > +fsopen \- create a new filesystem context
> > +.SH LIBRARY
> > +Standard C library
> > +.RI ( libc ,\~ \-lc )
> > +.SH SYNOPSIS
> > +.nf
> > +.BR "#include <sys/mount.h>"
> > +.P
> > +.BI "int fsopen(const char *" fsname ", unsigned int " flags ");"
> > +.fi
> > +.SH DESCRIPTION
> > +The
> > +.BR fsopen ()
> > +system call is part of the suite of file descriptor based mount facilities in
> > +Linux.
> > +.P
> > +.BR fsopen ()
> > +creates a blank filesystem configuration context within the kernel
> > +for the filesystem named by
> > +.IR fsname ,
> > +puts the context into creation mode and attaches it to a file descriptor,
> > +which is then returned.
> > +The calling process must have the
> > +.B \%CAP_SYS_ADMIN
> > +capability in order to create a new filesystem configuration context.
> > +.P
> > +A filesystem configuration context is an in-kernel representation of a pending
> > +transaction,
>
> This page still needs semantic newlines. (Please review all pages
> regarding that.) (In this specific sentence, I'd break after 'is'.)
I did try adding them to this page (and all of the other pages -- I
suspect the pages later in the patchset have more aggressive newlining).
If you compare the newline placement between v1 and v2 you'll see that I
have added a lot of newlines in all of the man-pages, but it's possible
I missed a couple of sentences like this one.
To be honest I feel quite lost where the "semantic newlines" school
would deem appropriate to place newlines, and man-pages(7) is very terse
on the topic. Outside of very obvious examples,
it just feels wrong
to have such choppy
line break usage.
I understand
the argument that
this helps
with reviewing diffs,
but I really find it
incredibly unnatural.
(And this tongue-in-cheek example
is probably wrong too.)
> > +containing a set of configuration parameters that are to be applied
> > +when creating a new instance of a filesystem
> > +(or modifying the configuration of an existing filesystem instance,
> > +such as when using
> > +.BR fspick (2)).
> > +.P
> > +After obtaining a filesystem configuration context with
> > +.BR fsopen (),
> > +the general workflow for operating on the context looks like the following:
> > +.IP (1) 5
> > +Pass the filesystem context file descriptor to
> > +.BR fsconfig (2)
> > +to specify any desired filesystem parameters.
> > +This may be done as many times as necessary.
> > +.IP (2)
> > +Pass the same filesystem context file descriptor to
>
> Do we need to say "same"? I guess it's obvious. Or do you expect
> any confusion if we don't?
The first time I saw this interface I was confused when you pass
which file descriptor (especially around the FSCONFIG_CMD_CREATE stage),
so I felt it better to make it clear which file descriptor we are
talking about.
> > +.BR fsconfig (2)
> > +with
> > +.B \%FSCONFIG_CMD_CREATE
> > +to create an instance of the configured filesystem.
> > +.IP (3)
> > +Pass the same filesystem context file descriptor to
> > +.BR fsmount (2)
> > +to create a new mount object for the root of the filesystem,
> > +which is then attached to a new file descriptor.
> > +This also places the filesystem context file descriptor into reconfiguration
> > +mode,
> > +similar to the mode produced by
> > +.BR fspick (2).
> > +.IP (4)
> > +Use the mount object file descriptor as a
> > +.I dirfd
> > +argument to "*at()" system calls;
> > +or attach the mount object to a mount point
> > +by passing the mount object file descriptor to
> > +.BR move_mount (2).
> > +.P
> > +A filesystem context will move between different modes throughout its
> > +lifecycle
> > +(such as the creation phase when created with
> > +.BR fsopen (),
> > +the reconfiguration phase when an existing filesystem instance is selected by
> > +.BR fspick (2),
> > +and the intermediate "needs-mount" phase between
> > +.\" FS_CONTEXT_NEEDS_MOUNT is the term the kernel uses for this.
> > +.BR \%FSCONFIG_CMD_CREATE
> > +and
> > +.BR fsmount (2)),
> > +which has an impact on what operations are permitted on the filesystem context.
> > +.P
> > +The file descriptor returned by
> > +.BR fsopen ()
> > +also acts as a channel for filesystem drivers to provide more comprehensive
> > +error, warning, and information messages
>
> Should we just say "diagnostic messages" to avoid explicitly mentioning
> all the levels?
Sure.
> > +than are normally provided through the standard
> > +.BR errno (3)
> > +interface for system calls.
> > +If an error occurs at any time during the workflow mentioned above,
> > +calling
> > +.BR read (2)
> > +on the filesystem context file descriptor will retrieve any ancillary
> > +information about the encountered errors.
> > +(See the "Message retrieval interface" section for more details on the message
> > +format.)
> > +.P
> > +.I flags
> > +can be used to control aspects of the creation of the filesystem configuration
> > +context file descriptor.
> > +A value for
> > +.I flags
> > +is constructed by bitwise ORing
> > +zero or more of the following constants:
> > +.RS
> > +.TP
> > +.B FSOPEN_CLOEXEC
> > +Set the close-on-exec
> > +.RB ( FD_CLOEXEC )
> > +flag on the new file descriptor.
> > +See the description of the
> > +.B O_CLOEXEC
> > +flag in
> > +.BR open (2)
> > +for reasons why this may be useful.
> > +.RE
> > +.P
> > +A list of filesystems supported by the running kernel
> > +(and thus a list of valid values for
> > +.IR fsname )
> > +can be obtained from
> > +.IR /proc/filesystems .
> > +(See also
> > +.BR proc_filesystems (5).)
> > +.SS Message retrieval interface
> > +When doing operations on a filesystem configuration context,
> > +the filesystem driver may choose to provide ancillary information to userspace
> > +in the form of message strings.
> > +.P
> > +The filesystem context file descriptors returned by
> > +.BR fsopen ()
> > +and
> > +.BR fspick (2)
> > +may be queried for message strings at any time by calling
> > +.BR read (2)
> > +on the file descriptor.
> > +Each call to
> > +.BR read (2)
> > +will return a single message,
> > +prefixed to indicate its class:
> > +.RS
> > +.TP
> > +.B "e <message>"
> > +An error message was logged.
> > +This is usually associated with an error being returned from the corresponding
> > +system call which triggered this message.
> > +.TP
> > +.B "w <message>"
> > +A warning message was logged.
> > +.TP
> > +.B "i <message>"
> > +An informational message was logged.
> > +.RE
> > +.P
> > +Messages are removed from the queue as they are read.
> > +Note that the message queue has limited depth,
> > +so it is possible for messages to get lost.
> > +If there are no messages in the message queue,
> > +.B read(2)
> > +will return no data and
> > +.I errno
> > +will be set to
> > +.BR \%ENODATA .
> > +If the
> > +.I buf
> > +argument to
> > +.BR read (2)
> > +is not large enough to contain the message,
> > +.BR read (2)
> > +will return no data and
> > +.I errno
> > +will be set to
> > +.BR \%EMSGSIZE .
> > +.P
> > +If there are multiple filesystem context file descriptors referencing the same
> > +filesystem instance
> > +(such as if you call
> > +.BR fspick (2)
> > +multiple times for the same mount),
> > +each one gets its own independent message queue.
> > +This does not apply to file descriptors that were duplicated with
> > +.BR dup (2).
> > +.P
> > +Messages strings will usually be prefixed by the filesystem driver that logged
>
> s/Messages/Message/
>
> BTW, here, I'd break after 'prefixed', and then after the ','.
>
> > +the message, though this may not always be the case.
> > +See the Linux kernel source code for details.
> > +.SH RETURN VALUE
> > +On success, a new file descriptor is returned.
> > +On error, \-1 is returned, and
> > +.I errno
> > +is set to indicate the error.
> > +.SH ERRORS
> > +.TP
> > +.B EFAULT
> > +.I fsname
> > +is NULL
> > +or a pointer to a location
> > +outside the calling process's accessible address space.
> > +.TP
> > +.B EINVAL
> > +.I flags
> > +had an invalid flag set.
> > +.TP
> > +.B EMFILE
> > +The calling process has too many open files to create more.
> > +.TP
> > +.B ENFILE
> > +The system has too many open files to create more.
> > +.TP
> > +.B ENODEV
> > +The filesystem named by
> > +.I fsname
> > +is not supported by the kernel.
> > +.TP
> > +.B ENOMEM
> > +The kernel could not allocate sufficient memory to complete the operation.
> > +.TP
> > +.B EPERM
> > +The calling process does not have the required
> > +.B \%CAP_SYS_ADMIN
> > +capability.
> > +.SH STANDARDS
> > +Linux.
> > +.SH HISTORY
> > +Linux 5.2.
> > +.\" commit 24dcb3d90a1f67fe08c68a004af37df059d74005
> > +glibc 2.36.
> > +.SH EXAMPLES
> > +To illustrate the workflow for creating a new mount,
> > +the following is an example of how to mount an
> > +.BR ext4 (5)
> > +filesystem stored on
> > +.I /dev/sdb1
> > +onto
> > +.IR /mnt .
> > +.P
> > +.in +4n
> > +.EX
> > +int fsfd, mntfd;
> > +\&
> > +fsfd = fsopen("ext4", FSOPEN_CLOEXEC);
> > +fsconfig(fsfd, FSCONFIG_SET_FLAG, "ro", NULL, 0);
> > +fsconfig(fsfd, FSCONFIG_SET_PATH, "source", "/dev/sdb1", AT_FDCWD);
> > +fsconfig(fsfd, FSCONFIG_SET_FLAG, "noatime", NULL, 0);
> > +fsconfig(fsfd, FSCONFIG_SET_FLAG, "acl", NULL, 0);
> > +fsconfig(fsfd, FSCONFIG_SET_FLAG, "user_xattr", NULL, 0);
> > +fsconfig(fsfd, FSCONFIG_SET_FLAG, "iversion", NULL, 0)
> > +fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
> > +mntfd = fsmount(fsfd, FSMOUNT_CLOEXEC, MOUNT_ATTR_RELATIME);
> > +move_mount(mntfd, "", AT_FDCWD, "/mnt", MOVE_MOUNT_F_EMPTY_PATH);
> > +.EE
> > +.in
> > +.P
> > +First, an ext4 configuration context is created and attached to the file
>
> Here, I'd break after the ',', and if you need to break again, after
> 'created'.
Okay, I wanted to avoid having lines with single words due to semantic
newlines, but if that's what you prefer I can update that everywhere...
> > +descriptor
> > +.IR fsfd .
> > +Then, a series of parameters
> > +(such as the source of the filesystem)
> > +are provided using
> > +.BR fsconfig (2),
> > +followed by the filesystem instance being created with
> > +.BR \%FSCONFIG_CMD_CREATE .
> > +.BR fsmount (2)
> > +is then used to create a new mount object attached to the file descriptor
> > +.IR mntfd ,
> > +which is then attached to the intended mount point using
> > +.BR move_mount (2).
> > +.P
> > +The above procedure is functionally equivalent to the following mount operation
> > +using
> > +.BR mount (2):
> > +.P
> > +.in +4n
> > +.EX
> > +mount("/dev/sdb1", "/mnt", "ext4", MS_RELATIME,
> > + "ro,noatime,acl,user_xattr,iversion");
> > +.EE
> > +.in
> > +.P
> > +And here's an example of creating a mount object
> > +of an NFS server share
> > +and setting a Smack security module label.
> > +However, instead of attaching it to a mount point,
> > +the program uses the mount object directly
> > +to open a file from the NFS share.
> > +.P
> > +.in +4n
> > +.EX
> > +int fsfd, mntfd, fd;
> > +\&
> > +fsfd = fsopen("nfs", 0);
> > +fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "example.com/pub/linux", 0);
> > +fsconfig(fsfd, FSCONFIG_SET_STRING, "nfsvers", "3", 0);
> > +fsconfig(fsfd, FSCONFIG_SET_STRING, "rsize", "65536", 0);
> > +fsconfig(fsfd, FSCONFIG_SET_STRING, "wsize", "65536", 0);
> > +fsconfig(fsfd, FSCONFIG_SET_STRING, "smackfsdef", "foolabel", 0);
> > +fsconfig(fsfd, FSCONFIG_SET_FLAG, "rdma", NULL, 0);
> > +fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
> > +mntfd = fsmount(fsfd, 0, MOUNT_ATTR_NODEV);
> > +fd = openat(mntfd, "src/linux-5.2.tar.xz", O_RDONLY);
> > +.EE
> > +.in
> > +.P
> > +Unlike the previous example,
> > +this operation has no trivial equivalent with
> > +.BR mount (2),
> > +as it was not previously possible to create a mount object
> > +that is not attached to any mount point.
> > +.SH SEE ALSO
> > +.BR fsconfig (2),
> > +.BR fsmount (2),
> > +.BR fspick (2),
> > +.BR mount (2),
> > +.BR mount_setattr (2),
> > +.BR move_mount (2),
> > +.BR open_tree (2),
> > +.BR mount_namespaces (7)
>
> Other than those minor comments, the text LGTM.
>
>
> Cheers,
> Alex
>
> --
> <https://www.alejandro-colomar.es/>
--
Aleksa Sarai
Senior Software Engineer (Containers)
SUSE Linux GmbH
https://www.cyphar.com/
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v2 02/11] mount_setattr.2: move mount_attr struct to mount_attr.2type
From: Aleksa Sarai @ 2025-08-07 12:38 UTC (permalink / raw)
To: Alejandro Colomar
Cc: Michael T. Kerrisk, Alexander Viro, Jan Kara, Askar Safin,
G. Branden Robinson, linux-man, linux-api, linux-fsdevel,
linux-kernel, David Howells, Christian Brauner
In-Reply-To: <cselxzuadkkf4cfx45fm3cm77mkq7gxrbzg7idr5726p52w5qa@sarhby7scgp6>
[-- Attachment #1: Type: text/plain, Size: 4422 bytes --]
On 2025-08-07, Alejandro Colomar <alx@kernel.org> wrote:
> Hi Aleksa,
>
> On Thu, Aug 07, 2025 at 03:44:36AM +1000, Aleksa Sarai wrote:
> > As with open_how(2type), it makes sense to move this to a separate man
> > page. In addition, future man pages added in this patchset will want to
> > reference mount_attr(2type).
> >
> > Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
> > ---
> > man/man2/mount_setattr.2 | 17 ++++---------
> > man/man2type/mount_attr.2type | 58 +++++++++++++++++++++++++++++++++++++++++++
> > 2 files changed, 63 insertions(+), 12 deletions(-)
> >
> > diff --git a/man/man2/mount_setattr.2 b/man/man2/mount_setattr.2
> > index c96f0657f046..d44fafc93a20 100644
> > --- a/man/man2/mount_setattr.2
> > +++ b/man/man2/mount_setattr.2
> > @@ -114,18 +114,11 @@ .SH DESCRIPTION
> > .I attr
> > argument of
> > .BR mount_setattr ()
> > -is a structure of the following form:
> > -.P
> > -.in +4n
> > -.EX
> > -struct mount_attr {
> > - __u64 attr_set; /* Mount properties to set */
> > - __u64 attr_clr; /* Mount properties to clear */
> > - __u64 propagation; /* Mount propagation type */
> > - __u64 userns_fd; /* User namespace file descriptor */
> > -};
> > -.EE
> > -.in
> > +is a pointer to a
> > +.I mount_attr
> > +structure,
> > +described in
> > +.BR mount_attr (2type).
> > .P
> > The
> > .I attr_set
> > diff --git a/man/man2type/mount_attr.2type b/man/man2type/mount_attr.2type
> > new file mode 100644
> > index 000000000000..b7a3ace6b3b9
> > --- /dev/null
> > +++ b/man/man2type/mount_attr.2type
> > @@ -0,0 +1,58 @@
> > +
>
> Please remove this blank. It is not diagnosed by groff(1), but I think
> it should be diagnosed (blank lines are diagnosed elsewhere). I've
> reported a bug to groff(1) (Branden will be reading this, anyway).
>
> > +.\" Copyright, the authors of the Linux man-pages project
> > +.\"
> > +.\" SPDX-License-Identifier: Linux-man-pages-copyleft
> > +.\"
> > +.TH mount_attr 2type (date) "Linux man-pages (unreleased)"
> > +.SH NAME
> > +mount_attr \- what mount properties to set and clear
> > +.SH LIBRARY
> > +Linux kernel headers
> > +.SH SYNOPSIS
> > +.EX
> > +.B #include <sys/mount.h>
> > +.P
> > +.B struct mount_attr {
> > +.BR " __u64 attr_set;" " /* Mount properties to set */"
> > +.BR " __u64 attr_clr;" " /* Mount properties to clear */"
> > +.BR " __u64 propagation;" " /* Mount propagation type */"
> > +.BR " __u64 userns_fd;" " /* User namespace file descriptor */"
> > + /* ... */
> > +.B };
> > +.EE
> > +.SH DESCRIPTION
> > +Specifies which mount properties should be changed with
> > +.BR mount_setattr (2).
> > +.P
> > +The fields are as follows:
> > +.TP
> > +.I .attr_set
> > +This field specifies which
> > +.BI MOUNT_ATTR_ *
> > +attribute flags to set.
> > +.TP
> > +.I .attr_clr
> > +This fields specifies which
> > +.BI MOUNT_ATTR_ *
> > +attribute flags to clear.
> > +.TP
> > +.I .propagation
> > +This field specifies what mount propagation will be applied.
> > +The valid values of this field are the same propagation types described in
> > +.BR mount_namespaces (7).
> > +.TP
> > +.I .userns_fd
> > +This fields specifies a file descriptor that indicates which user namespace to
>
> s/fields/field/
>
> > +use as a reference for ID-mapped mounts with
> > +.BR MOUNT_ATTR_IDMAP .
> > +.SH VERSIONS
> > +Extra fields may be appended to the structure,
> > +with a zero value in a new field resulting in
> > +the kernel behaving as though that extension field was not present.
> > +Therefore, a user
> > +.I must
> > +zero-fill this structure on initialization.
>
> I think this would be more appropriate for HISTORY. In VERSIONS, we
> usually document differences with the BSDs or other systems.
>
> While moving this to HISTORY, it would also be useful to mention the
> glibc version that added the structure. In the future, we'd document
> the versions of glibc and Linux that have added members.
Sure, though I just copied this section from open_how(2type).
> > +.SH STANDARDS
> > +Linux.
> > +.SH SEE ALSO
> > +.BR mount_setattr (2)
>
> Have a lovely day!
> Alex
>
> --
> <https://www.alejandro-colomar.es/>
--
Aleksa Sarai
Senior Software Engineer (Containers)
SUSE Linux GmbH
https://www.cyphar.com/
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v2 03/11] fsopen.2: document 'new' mount api
From: Alejandro Colomar @ 2025-08-07 11:38 UTC (permalink / raw)
To: Aleksa Sarai
Cc: Michael T. Kerrisk, Alexander Viro, Jan Kara, Askar Safin,
G. Branden Robinson, linux-man, linux-api, linux-fsdevel,
linux-kernel, David Howells, Christian Brauner
In-Reply-To: <20250807-new-mount-api-v2-3-558a27b8068c@cyphar.com>
[-- Attachment #1: Type: text/plain, Size: 12796 bytes --]
Hi Aleksa,
On Thu, Aug 07, 2025 at 03:44:37AM +1000, Aleksa Sarai wrote:
> This is loosely based on the original documentation written by David
> Howells and later maintained by Christian Brauner, but has been
> rewritten to be more from a user perspective (as well as fixing a few
> critical mistakes).
>
> Co-developed-by: David Howells <dhowells@redhat.com>
> Co-developed-by: Christian Brauner <brauner@kernel.org>
Please use Co-authored-by. It's documented under CONTRIBUTING.d/:
$ cat CONTRIBUTING.d/patches/description | grep -A99 Trailer;
Trailer
Sign your patch with "Signed-off-by:". Read about the
"Developer's Certificate of Origin" at
<https://www.kernel.org/doc/Documentation/process/submitting-patches.rst>.
When appropriate, other tags documented in that file, such as
"Reported-by:", "Reviewed-by:", "Acked-by:", and "Suggested-by:"
can be added to the patch. We use "Co-authored-by:" instead of
"Co-developed-by:". Example:
Signed-off-by: Alejandro Colomar <alx@kernel.org>
I think 'author' is more appropriate than 'developer' for documentation.
It is also more consistent with the Copyright notice, which assigns
copyright to the authors (documented in AUTHORS). And ironically, even
the kernel documentation about Co-authored-by talks about authorship
instead of development:
Co-developed-by: states that the patch was co-created by
multiple developers; it is used to give attribution to
co-authors (in addition to the author attributed by the From:
tag) when several people work on a single patch.
> Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
> ---
> man/man2/fsopen.2 | 319 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 319 insertions(+)
>
> diff --git a/man/man2/fsopen.2 b/man/man2/fsopen.2
> new file mode 100644
> index 000000000000..ad38ef0782be
> --- /dev/null
> +++ b/man/man2/fsopen.2
> @@ -0,0 +1,319 @@
> +.\" Copyright, the authors of the Linux man-pages project
> +.\"
> +.\" SPDX-License-Identifier: Linux-man-pages-copyleft
> +.\"
> +.TH fsopen 2 (date) "Linux man-pages (unreleased)"
> +.SH NAME
> +fsopen \- create a new filesystem context
> +.SH LIBRARY
> +Standard C library
> +.RI ( libc ,\~ \-lc )
> +.SH SYNOPSIS
> +.nf
> +.BR "#include <sys/mount.h>"
> +.P
> +.BI "int fsopen(const char *" fsname ", unsigned int " flags ");"
> +.fi
> +.SH DESCRIPTION
> +The
> +.BR fsopen ()
> +system call is part of the suite of file descriptor based mount facilities in
> +Linux.
> +.P
> +.BR fsopen ()
> +creates a blank filesystem configuration context within the kernel
> +for the filesystem named by
> +.IR fsname ,
> +puts the context into creation mode and attaches it to a file descriptor,
> +which is then returned.
> +The calling process must have the
> +.B \%CAP_SYS_ADMIN
> +capability in order to create a new filesystem configuration context.
> +.P
> +A filesystem configuration context is an in-kernel representation of a pending
> +transaction,
This page still needs semantic newlines. (Please review all pages
regarding that.) (In this specific sentence, I'd break after 'is'.)
> +containing a set of configuration parameters that are to be applied
> +when creating a new instance of a filesystem
> +(or modifying the configuration of an existing filesystem instance,
> +such as when using
> +.BR fspick (2)).
> +.P
> +After obtaining a filesystem configuration context with
> +.BR fsopen (),
> +the general workflow for operating on the context looks like the following:
> +.IP (1) 5
> +Pass the filesystem context file descriptor to
> +.BR fsconfig (2)
> +to specify any desired filesystem parameters.
> +This may be done as many times as necessary.
> +.IP (2)
> +Pass the same filesystem context file descriptor to
Do we need to say "same"? I guess it's obvious. Or do you expect
any confusion if we don't?
> +.BR fsconfig (2)
> +with
> +.B \%FSCONFIG_CMD_CREATE
> +to create an instance of the configured filesystem.
> +.IP (3)
> +Pass the same filesystem context file descriptor to
> +.BR fsmount (2)
> +to create a new mount object for the root of the filesystem,
> +which is then attached to a new file descriptor.
> +This also places the filesystem context file descriptor into reconfiguration
> +mode,
> +similar to the mode produced by
> +.BR fspick (2).
> +.IP (4)
> +Use the mount object file descriptor as a
> +.I dirfd
> +argument to "*at()" system calls;
> +or attach the mount object to a mount point
> +by passing the mount object file descriptor to
> +.BR move_mount (2).
> +.P
> +A filesystem context will move between different modes throughout its
> +lifecycle
> +(such as the creation phase when created with
> +.BR fsopen (),
> +the reconfiguration phase when an existing filesystem instance is selected by
> +.BR fspick (2),
> +and the intermediate "needs-mount" phase between
> +.\" FS_CONTEXT_NEEDS_MOUNT is the term the kernel uses for this.
> +.BR \%FSCONFIG_CMD_CREATE
> +and
> +.BR fsmount (2)),
> +which has an impact on what operations are permitted on the filesystem context.
> +.P
> +The file descriptor returned by
> +.BR fsopen ()
> +also acts as a channel for filesystem drivers to provide more comprehensive
> +error, warning, and information messages
Should we just say "diagnostic messages" to avoid explicitly mentioning
all the levels?
> +than are normally provided through the standard
> +.BR errno (3)
> +interface for system calls.
> +If an error occurs at any time during the workflow mentioned above,
> +calling
> +.BR read (2)
> +on the filesystem context file descriptor will retrieve any ancillary
> +information about the encountered errors.
> +(See the "Message retrieval interface" section for more details on the message
> +format.)
> +.P
> +.I flags
> +can be used to control aspects of the creation of the filesystem configuration
> +context file descriptor.
> +A value for
> +.I flags
> +is constructed by bitwise ORing
> +zero or more of the following constants:
> +.RS
> +.TP
> +.B FSOPEN_CLOEXEC
> +Set the close-on-exec
> +.RB ( FD_CLOEXEC )
> +flag on the new file descriptor.
> +See the description of the
> +.B O_CLOEXEC
> +flag in
> +.BR open (2)
> +for reasons why this may be useful.
> +.RE
> +.P
> +A list of filesystems supported by the running kernel
> +(and thus a list of valid values for
> +.IR fsname )
> +can be obtained from
> +.IR /proc/filesystems .
> +(See also
> +.BR proc_filesystems (5).)
> +.SS Message retrieval interface
> +When doing operations on a filesystem configuration context,
> +the filesystem driver may choose to provide ancillary information to userspace
> +in the form of message strings.
> +.P
> +The filesystem context file descriptors returned by
> +.BR fsopen ()
> +and
> +.BR fspick (2)
> +may be queried for message strings at any time by calling
> +.BR read (2)
> +on the file descriptor.
> +Each call to
> +.BR read (2)
> +will return a single message,
> +prefixed to indicate its class:
> +.RS
> +.TP
> +.B "e <message>"
> +An error message was logged.
> +This is usually associated with an error being returned from the corresponding
> +system call which triggered this message.
> +.TP
> +.B "w <message>"
> +A warning message was logged.
> +.TP
> +.B "i <message>"
> +An informational message was logged.
> +.RE
> +.P
> +Messages are removed from the queue as they are read.
> +Note that the message queue has limited depth,
> +so it is possible for messages to get lost.
> +If there are no messages in the message queue,
> +.B read(2)
> +will return no data and
> +.I errno
> +will be set to
> +.BR \%ENODATA .
> +If the
> +.I buf
> +argument to
> +.BR read (2)
> +is not large enough to contain the message,
> +.BR read (2)
> +will return no data and
> +.I errno
> +will be set to
> +.BR \%EMSGSIZE .
> +.P
> +If there are multiple filesystem context file descriptors referencing the same
> +filesystem instance
> +(such as if you call
> +.BR fspick (2)
> +multiple times for the same mount),
> +each one gets its own independent message queue.
> +This does not apply to file descriptors that were duplicated with
> +.BR dup (2).
> +.P
> +Messages strings will usually be prefixed by the filesystem driver that logged
s/Messages/Message/
BTW, here, I'd break after 'prefixed', and then after the ','.
> +the message, though this may not always be the case.
> +See the Linux kernel source code for details.
> +.SH RETURN VALUE
> +On success, a new file descriptor is returned.
> +On error, \-1 is returned, and
> +.I errno
> +is set to indicate the error.
> +.SH ERRORS
> +.TP
> +.B EFAULT
> +.I fsname
> +is NULL
> +or a pointer to a location
> +outside the calling process's accessible address space.
> +.TP
> +.B EINVAL
> +.I flags
> +had an invalid flag set.
> +.TP
> +.B EMFILE
> +The calling process has too many open files to create more.
> +.TP
> +.B ENFILE
> +The system has too many open files to create more.
> +.TP
> +.B ENODEV
> +The filesystem named by
> +.I fsname
> +is not supported by the kernel.
> +.TP
> +.B ENOMEM
> +The kernel could not allocate sufficient memory to complete the operation.
> +.TP
> +.B EPERM
> +The calling process does not have the required
> +.B \%CAP_SYS_ADMIN
> +capability.
> +.SH STANDARDS
> +Linux.
> +.SH HISTORY
> +Linux 5.2.
> +.\" commit 24dcb3d90a1f67fe08c68a004af37df059d74005
> +glibc 2.36.
> +.SH EXAMPLES
> +To illustrate the workflow for creating a new mount,
> +the following is an example of how to mount an
> +.BR ext4 (5)
> +filesystem stored on
> +.I /dev/sdb1
> +onto
> +.IR /mnt .
> +.P
> +.in +4n
> +.EX
> +int fsfd, mntfd;
> +\&
> +fsfd = fsopen("ext4", FSOPEN_CLOEXEC);
> +fsconfig(fsfd, FSCONFIG_SET_FLAG, "ro", NULL, 0);
> +fsconfig(fsfd, FSCONFIG_SET_PATH, "source", "/dev/sdb1", AT_FDCWD);
> +fsconfig(fsfd, FSCONFIG_SET_FLAG, "noatime", NULL, 0);
> +fsconfig(fsfd, FSCONFIG_SET_FLAG, "acl", NULL, 0);
> +fsconfig(fsfd, FSCONFIG_SET_FLAG, "user_xattr", NULL, 0);
> +fsconfig(fsfd, FSCONFIG_SET_FLAG, "iversion", NULL, 0)
> +fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
> +mntfd = fsmount(fsfd, FSMOUNT_CLOEXEC, MOUNT_ATTR_RELATIME);
> +move_mount(mntfd, "", AT_FDCWD, "/mnt", MOVE_MOUNT_F_EMPTY_PATH);
> +.EE
> +.in
> +.P
> +First, an ext4 configuration context is created and attached to the file
Here, I'd break after the ',', and if you need to break again, after
'created'.
> +descriptor
> +.IR fsfd .
> +Then, a series of parameters
> +(such as the source of the filesystem)
> +are provided using
> +.BR fsconfig (2),
> +followed by the filesystem instance being created with
> +.BR \%FSCONFIG_CMD_CREATE .
> +.BR fsmount (2)
> +is then used to create a new mount object attached to the file descriptor
> +.IR mntfd ,
> +which is then attached to the intended mount point using
> +.BR move_mount (2).
> +.P
> +The above procedure is functionally equivalent to the following mount operation
> +using
> +.BR mount (2):
> +.P
> +.in +4n
> +.EX
> +mount("/dev/sdb1", "/mnt", "ext4", MS_RELATIME,
> + "ro,noatime,acl,user_xattr,iversion");
> +.EE
> +.in
> +.P
> +And here's an example of creating a mount object
> +of an NFS server share
> +and setting a Smack security module label.
> +However, instead of attaching it to a mount point,
> +the program uses the mount object directly
> +to open a file from the NFS share.
> +.P
> +.in +4n
> +.EX
> +int fsfd, mntfd, fd;
> +\&
> +fsfd = fsopen("nfs", 0);
> +fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "example.com/pub/linux", 0);
> +fsconfig(fsfd, FSCONFIG_SET_STRING, "nfsvers", "3", 0);
> +fsconfig(fsfd, FSCONFIG_SET_STRING, "rsize", "65536", 0);
> +fsconfig(fsfd, FSCONFIG_SET_STRING, "wsize", "65536", 0);
> +fsconfig(fsfd, FSCONFIG_SET_STRING, "smackfsdef", "foolabel", 0);
> +fsconfig(fsfd, FSCONFIG_SET_FLAG, "rdma", NULL, 0);
> +fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
> +mntfd = fsmount(fsfd, 0, MOUNT_ATTR_NODEV);
> +fd = openat(mntfd, "src/linux-5.2.tar.xz", O_RDONLY);
> +.EE
> +.in
> +.P
> +Unlike the previous example,
> +this operation has no trivial equivalent with
> +.BR mount (2),
> +as it was not previously possible to create a mount object
> +that is not attached to any mount point.
> +.SH SEE ALSO
> +.BR fsconfig (2),
> +.BR fsmount (2),
> +.BR fspick (2),
> +.BR mount (2),
> +.BR mount_setattr (2),
> +.BR move_mount (2),
> +.BR open_tree (2),
> +.BR mount_namespaces (7)
Other than those minor comments, the text LGTM.
Cheers,
Alex
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* Re: [PATCH v2 02/11] mount_setattr.2: move mount_attr struct to mount_attr.2type
From: Alejandro Colomar @ 2025-08-07 11:11 UTC (permalink / raw)
To: Aleksa Sarai
Cc: Michael T. Kerrisk, Alexander Viro, Jan Kara, Askar Safin,
G. Branden Robinson, linux-man, linux-api, linux-fsdevel,
linux-kernel, David Howells, Christian Brauner
In-Reply-To: <20250807-new-mount-api-v2-2-558a27b8068c@cyphar.com>
[-- Attachment #1: Type: text/plain, Size: 3941 bytes --]
Hi Aleksa,
On Thu, Aug 07, 2025 at 03:44:36AM +1000, Aleksa Sarai wrote:
> As with open_how(2type), it makes sense to move this to a separate man
> page. In addition, future man pages added in this patchset will want to
> reference mount_attr(2type).
>
> Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
> ---
> man/man2/mount_setattr.2 | 17 ++++---------
> man/man2type/mount_attr.2type | 58 +++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 63 insertions(+), 12 deletions(-)
>
> diff --git a/man/man2/mount_setattr.2 b/man/man2/mount_setattr.2
> index c96f0657f046..d44fafc93a20 100644
> --- a/man/man2/mount_setattr.2
> +++ b/man/man2/mount_setattr.2
> @@ -114,18 +114,11 @@ .SH DESCRIPTION
> .I attr
> argument of
> .BR mount_setattr ()
> -is a structure of the following form:
> -.P
> -.in +4n
> -.EX
> -struct mount_attr {
> - __u64 attr_set; /* Mount properties to set */
> - __u64 attr_clr; /* Mount properties to clear */
> - __u64 propagation; /* Mount propagation type */
> - __u64 userns_fd; /* User namespace file descriptor */
> -};
> -.EE
> -.in
> +is a pointer to a
> +.I mount_attr
> +structure,
> +described in
> +.BR mount_attr (2type).
> .P
> The
> .I attr_set
> diff --git a/man/man2type/mount_attr.2type b/man/man2type/mount_attr.2type
> new file mode 100644
> index 000000000000..b7a3ace6b3b9
> --- /dev/null
> +++ b/man/man2type/mount_attr.2type
> @@ -0,0 +1,58 @@
> +
Please remove this blank. It is not diagnosed by groff(1), but I think
it should be diagnosed (blank lines are diagnosed elsewhere). I've
reported a bug to groff(1) (Branden will be reading this, anyway).
> +.\" Copyright, the authors of the Linux man-pages project
> +.\"
> +.\" SPDX-License-Identifier: Linux-man-pages-copyleft
> +.\"
> +.TH mount_attr 2type (date) "Linux man-pages (unreleased)"
> +.SH NAME
> +mount_attr \- what mount properties to set and clear
> +.SH LIBRARY
> +Linux kernel headers
> +.SH SYNOPSIS
> +.EX
> +.B #include <sys/mount.h>
> +.P
> +.B struct mount_attr {
> +.BR " __u64 attr_set;" " /* Mount properties to set */"
> +.BR " __u64 attr_clr;" " /* Mount properties to clear */"
> +.BR " __u64 propagation;" " /* Mount propagation type */"
> +.BR " __u64 userns_fd;" " /* User namespace file descriptor */"
> + /* ... */
> +.B };
> +.EE
> +.SH DESCRIPTION
> +Specifies which mount properties should be changed with
> +.BR mount_setattr (2).
> +.P
> +The fields are as follows:
> +.TP
> +.I .attr_set
> +This field specifies which
> +.BI MOUNT_ATTR_ *
> +attribute flags to set.
> +.TP
> +.I .attr_clr
> +This fields specifies which
> +.BI MOUNT_ATTR_ *
> +attribute flags to clear.
> +.TP
> +.I .propagation
> +This field specifies what mount propagation will be applied.
> +The valid values of this field are the same propagation types described in
> +.BR mount_namespaces (7).
> +.TP
> +.I .userns_fd
> +This fields specifies a file descriptor that indicates which user namespace to
s/fields/field/
> +use as a reference for ID-mapped mounts with
> +.BR MOUNT_ATTR_IDMAP .
> +.SH VERSIONS
> +Extra fields may be appended to the structure,
> +with a zero value in a new field resulting in
> +the kernel behaving as though that extension field was not present.
> +Therefore, a user
> +.I must
> +zero-fill this structure on initialization.
I think this would be more appropriate for HISTORY. In VERSIONS, we
usually document differences with the BSDs or other systems.
While moving this to HISTORY, it would also be useful to mention the
glibc version that added the structure. In the future, we'd document
the versions of glibc and Linux that have added members.
> +.SH STANDARDS
> +Linux.
> +.SH SEE ALSO
> +.BR mount_setattr (2)
Have a lovely day!
Alex
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* Re: [PATCH v2 01/11] mount_setattr.2: document glibc >= 2.36 syscall wrappers
From: Alejandro Colomar @ 2025-08-07 10:39 UTC (permalink / raw)
To: Aleksa Sarai
Cc: Michael T. Kerrisk, Alexander Viro, Jan Kara, Askar Safin,
G. Branden Robinson, linux-man, linux-api, linux-fsdevel,
linux-kernel, David Howells, Christian Brauner
In-Reply-To: <20250807-new-mount-api-v2-1-558a27b8068c@cyphar.com>
[-- Attachment #1: Type: text/plain, Size: 3386 bytes --]
Hi Aleksa,
On Thu, Aug 07, 2025 at 03:44:35AM +1000, Aleksa Sarai wrote:
> Glibc 2.36 added syscall wrappers for the entire family of fd-based
> mount syscalls, including mount_setattr(2). Thus it's no longer
> necessary to instruct users to do raw syscall(2) operations.
>
> Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
Thanks! I've applied and pushed the patch.
Have a lovely day!
Alex
> ---
> man/man2/mount_setattr.2 | 45 +++++++--------------------------------------
> 1 file changed, 7 insertions(+), 38 deletions(-)
>
> diff --git a/man/man2/mount_setattr.2 b/man/man2/mount_setattr.2
> index 60d9cf9de8aa..c96f0657f046 100644
> --- a/man/man2/mount_setattr.2
> +++ b/man/man2/mount_setattr.2
> @@ -10,21 +10,12 @@ .SH LIBRARY
> .RI ( libc ,\~ \-lc )
> .SH SYNOPSIS
> .nf
> -.BR "#include <linux/fcntl.h>" " /* Definition of " AT_* " constants */"
> -.BR "#include <linux/mount.h>" " /* Definition of " MOUNT_ATTR_* " constants */"
> -.BR "#include <sys/syscall.h>" " /* Definition of " SYS_* " constants */"
> -.B #include <unistd.h>
> +.BR "#include <fcntl.h>" " /* Definition of " AT_* " constants */"
> +.B #include <sys/mount.h>
> .P
> -.BI "int syscall(SYS_mount_setattr, int " dirfd ", const char *" path ,
> -.BI " unsigned int " flags ", struct mount_attr *" attr \
> -", size_t " size );
> +.BI "int mount_setattr(int " dirfd ", const char *" path ", unsigned int " flags ","
> +.BI " struct mount_attr *" attr ", size_t " size );"
> .fi
> -.P
> -.IR Note :
> -glibc provides no wrapper for
> -.BR mount_setattr (),
> -necessitating the use of
> -.BR syscall (2).
> .SH DESCRIPTION
> The
> .BR mount_setattr ()
> @@ -586,6 +577,7 @@ .SH HISTORY
> .\" commit 7d6beb71da3cc033649d641e1e608713b8220290
> .\" commit 2a1867219c7b27f928e2545782b86daaf9ad50bd
> .\" commit 9caccd41541a6f7d6279928d9f971f6642c361af
> +glibc 2.36.
> .SH NOTES
> .SS ID-mapped mounts
> Creating an ID-mapped mount makes it possible to
> @@ -914,37 +906,14 @@ .SH EXAMPLES
> #include <err.h>
> #include <fcntl.h>
> #include <getopt.h>
> -#include <linux/mount.h>
> -#include <linux/types.h>
> +#include <sys/mount.h>
> +#include <sys/types.h>
> #include <stdbool.h>
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> -#include <sys/syscall.h>
> #include <unistd.h>
> \&
> -static inline int
> -mount_setattr(int dirfd, const char *path, unsigned int flags,
> - struct mount_attr *attr, size_t size)
> -{
> - return syscall(SYS_mount_setattr, dirfd, path, flags,
> - attr, size);
> -}
> -\&
> -static inline int
> -open_tree(int dirfd, const char *filename, unsigned int flags)
> -{
> - return syscall(SYS_open_tree, dirfd, filename, flags);
> -}
> -\&
> -static inline int
> -move_mount(int from_dirfd, const char *from_path,
> - int to_dirfd, const char *to_path, unsigned int flags)
> -{
> - return syscall(SYS_move_mount, from_dirfd, from_path,
> - to_dirfd, to_path, flags);
> -}
> -\&
> static const struct option longopts[] = {
> {"map\-mount", required_argument, NULL, \[aq]a\[aq]},
> {"recursive", no_argument, NULL, \[aq]b\[aq]},
>
> --
> 2.50.1
>
>
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* Re: [PATCH v4 2/4] procfs: add "pidns" mount option
From: Aleksa Sarai @ 2025-08-07 7:17 UTC (permalink / raw)
To: Askar Safin
Cc: amir73il, brauner, corbet, jack, linux-api, linux-doc,
linux-fsdevel, linux-kernel, linux-kselftest, luto, shuah, viro
In-Reply-To: <2025-08-06.1754489257-elated-baubles-defiant-growls-beloved-jewelry-9Ofm2b@cyphar.com>
[-- Attachment #1: Type: text/plain, Size: 2293 bytes --]
On 2025-08-07, Aleksa Sarai <cyphar@cyphar.com> wrote:
> On 2025-08-06, Askar Safin <safinaskar@zohomail.com> wrote:
> > > I just realised that we probably also want to support FSCONFIG_SET_PATH
> >
> > I just checked kernel code. Indeed nobody uses FSCONFIG_SET_PATH.
> > Moreover, fsparam_path macro is present since 5.1. And for all this
> > time nobody used it. So, let's just remove FSCONFIG_SET_PATH. Nobody
> > used it, so this will not break anything.
> >
> > If you okay with that, I can submit patch, removing it.
>
> I would prefer you didn't -- "*at()" semantics are very useful to a lot
> of programs (*especially* AT_EMPTY_PATH). I would like the pidns= stuff
> to support it, and probably also overlayfs...
>
> I suspect the primary issue is that when migrating to the new mount API,
> filesystem devs just went with the easiest thing to use
> (FSCONFIG_SET_STRING) even though FSCONFIG_SET_PATH would be better. I
> suspect the lack of documentation around fsconfig(2) played a part too.
>
> My impression is that interest in the minutia about fsconfig(2) is quite
> low on the list of priorities for most filesystem devs, and so the neat
> aspects of fsconfig(2) haven't been fully utilised. (In LPC last year,
> we struggled to come to an agreement on how filesystems should use the
> read(2)-based error interface.)
>
> We can very easily move fsparam_string() or fsparam_file_or_string()
> parameters to fsparam_path() and a future fsparam_file_or_path(). I
> would much prefer that as a user.
Actually, fsparam_bdev() accepts FSCONFIG_SET_PATH in a very roundabout
way (and the checker doesn't verify anything...?). So there is at least
one user (ext4's "journal_path"), it's just not well-documented (which
I'm trying to fix ;]).
My plan is to update fs_lookup_param() to be more useful for the (fairly
common) use-case of wanting to support paths and file descriptors, and
going through to clean up some of these unused fsparam_* helpers (or
fsparam_* helpers being abused to implement stuff that the fs_parser
core already supports).
At the very least, overlayfs, ext4, and this procfs patchset can make
use of it.
--
Aleksa Sarai
Senior Software Engineer (Containers)
SUSE Linux GmbH
https://www.cyphar.com/
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v2 2/2] vfs: output mount_too_revealing() errors to fscontext
From: kernel test robot @ 2025-08-07 4:56 UTC (permalink / raw)
To: Aleksa Sarai, Alexander Viro, Christian Brauner, Jan Kara
Cc: oe-kbuild-all, David Howells, linux-api, linux-kernel,
linux-fsdevel, Aleksa Sarai
In-Reply-To: <20250806-errorfc-mount-too-revealing-v2-2-534b9b4d45bb@cyphar.com>
Hi Aleksa,
kernel test robot noticed the following build errors:
[auto build test ERROR on 66639db858112bf6b0f76677f7517643d586e575]
url: https://github.com/intel-lab-lkp/linux/commits/Aleksa-Sarai/fscontext-add-custom-prefix-log-helpers/20250806-141024
base: 66639db858112bf6b0f76677f7517643d586e575
patch link: https://lore.kernel.org/r/20250806-errorfc-mount-too-revealing-v2-2-534b9b4d45bb%40cyphar.com
patch subject: [PATCH v2 2/2] vfs: output mount_too_revealing() errors to fscontext
config: riscv-randconfig-002-20250807 (https://download.01.org/0day-ci/archive/20250807/202508071236.2BTGpdZx-lkp@intel.com/config)
compiler: riscv32-linux-gcc (GCC) 8.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250807/202508071236.2BTGpdZx-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202508071236.2BTGpdZx-lkp@intel.com/
All errors (new ones prefixed by >>, old ones prefixed by <<):
WARNING: modpost: vmlinux: section mismatch in reference: prp_dup_discard_out_of_sequence+0x266 (section: .text.prp_dup_discard_out_of_sequence) -> ili9486_spi_driver_exit (section: .exit.text)
WARNING: modpost: vmlinux: section mismatch in reference: prp_dup_discard_out_of_sequence+0x2ae (section: .text.prp_dup_discard_out_of_sequence) -> ili9486_spi_driver_exit (section: .exit.text)
WARNING: modpost: vmlinux: section mismatch in reference: prp_dup_discard_out_of_sequence+0x2f2 (section: .text.prp_dup_discard_out_of_sequence) -> mi0283qt_spi_driver_exit (section: .exit.text)
WARNING: modpost: vmlinux: section mismatch in reference: prp_dup_discard_out_of_sequence+0x33e (section: .text.prp_dup_discard_out_of_sequence) -> mi0283qt_spi_driver_exit (section: .exit.text)
WARNING: modpost: vmlinux: section mismatch in reference: ida_free+0xa0 (section: .text.ida_free) -> .L0 (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: ida_free+0xba (section: .text.ida_free) -> .L0 (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: ida_free+0xdc (section: .text.ida_free) -> devices_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: ida_alloc_range+0x4c (section: .text.ida_alloc_range) -> devices_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: ida_alloc_range+0x9c (section: .text.ida_alloc_range) -> .L0 (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: ida_alloc_range+0x31a (section: .text.ida_alloc_range) -> devices_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: kobj_kset_leave+0x2 (section: .text.kobj_kset_leave) -> save_async_options (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: __kobject_del+0x18 (section: .text.__kobject_del) -> .LVL39 (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area_rev+0x2aa (section: .text.mas_empty_area_rev) -> classes_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area_rev+0x2ba (section: .text.mas_empty_area_rev) -> classes_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area_rev+0x2c0 (section: .text.mas_empty_area_rev) -> classes_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area_rev+0x2d0 (section: .text.mas_empty_area_rev) -> classes_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area_rev+0x2da (section: .text.mas_empty_area_rev) -> classes_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area_rev+0x2ec (section: .text.mas_empty_area_rev) -> .L0 (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area_rev+0x2fe (section: .text.mas_empty_area_rev) -> __platform_driver_probe (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area_rev+0x314 (section: .text.mas_empty_area_rev) -> __platform_driver_probe (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area_rev+0x328 (section: .text.mas_empty_area_rev) -> __platform_driver_probe (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area_rev+0x34c (section: .text.mas_empty_area_rev) -> __platform_driver_probe (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area_rev+0x398 (section: .text.mas_empty_area_rev) -> __platform_driver_probe (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area_rev+0x39e (section: .text.mas_empty_area_rev) -> __platform_driver_probe (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area_rev+0x3d4 (section: .text.mas_empty_area_rev) -> classes_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area_rev+0x400 (section: .text.mas_empty_area_rev) -> classes_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area_rev+0x42a (section: .text.mas_empty_area_rev) -> classes_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mt_dump_node+0x230 (section: .text.mt_dump_node) -> classes_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mt_dump_node+0x24a (section: .text.mt_dump_node) -> __platform_driver_probe (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mt_dump+0x20 (section: .text.mt_dump) -> classes_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mt_dump+0x32 (section: .text.mt_dump) -> classes_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mt_dump+0x42 (section: .text.mt_dump) -> classes_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mt_dump+0x4c (section: .text.mt_dump) -> classes_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mt_dump+0x56 (section: .text.mt_dump) -> classes_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mt_dump+0x7c (section: .text.mt_dump) -> classes_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mt_dump+0xd4 (section: .text.mt_dump) -> __platform_driver_probe (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area+0x43e (section: .text.mas_empty_area) -> .L0 (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area+0x454 (section: .text.mas_empty_area) -> .L0 (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area+0x466 (section: .text.mas_empty_area) -> .L0 (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area+0x4b2 (section: .text.mas_empty_area) -> platform_bus_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area+0x4ba (section: .text.mas_empty_area) -> .L0 (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area+0x4d2 (section: .text.mas_empty_area) -> .L0 (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area+0x532 (section: .text.mas_empty_area) -> .L0 (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area+0x548 (section: .text.mas_empty_area) -> __platform_create_bundle (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area+0x572 (section: .text.mas_empty_area) -> .L461 (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area+0x574 (section: .text.mas_empty_area) -> __platform_create_bundle (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area+0x57a (section: .text.mas_empty_area) -> __platform_create_bundle (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area+0x592 (section: .text.mas_empty_area) -> .L459 (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area+0x5de (section: .text.mas_empty_area) -> .L457 (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area+0x5e4 (section: .text.mas_empty_area) -> .L458 (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_empty_area+0x5f0 (section: .text.mas_empty_area) -> .L0 (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_root_expand+0x84 (section: .text.mas_root_expand) -> .L495 (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_root_expand+0x98 (section: .text.mas_root_expand) -> cpu_dev_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_prev_range+0x18 (section: .text.mas_prev_range) -> classes_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: mas_prev+0x18 (section: .text.mas_prev) -> classes_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: __rb_insert_augmented+0xc8 (section: .text.__rb_insert_augmented) -> auxiliary_bus_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: __rb_insert_augmented+0xe8 (section: .text.__rb_insert_augmented) -> auxiliary_bus_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: __rb_insert_augmented+0xf8 (section: .text.__rb_insert_augmented) -> auxiliary_bus_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: __rb_insert_augmented+0x102 (section: .text.__rb_insert_augmented) -> auxiliary_bus_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: __rb_insert_augmented+0x114 (section: .text.__rb_insert_augmented) -> mount_param (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: rb_first+0x8 (section: .text.rb_first) -> mount_param (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: rb_first+0xa (section: .text.rb_first) -> mount_param (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: rb_first+0x10 (section: .text.rb_first) -> mount_param (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: rb_last+0x8 (section: .text.rb_last) -> mount_param (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: rb_last+0xa (section: .text.rb_last) -> mount_param (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: rb_last+0x10 (section: .text.rb_last) -> mount_param (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: __rb_erase_color+0xda (section: .text.__rb_erase_color) -> auxiliary_bus_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: __rb_erase_color+0xf8 (section: .text.__rb_erase_color) -> mount_param (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: __rb_erase_color+0x188 (section: .text.__rb_erase_color) -> auxiliary_bus_init (section: .init.text)
WARNING: modpost: vmlinux: section mismatch in reference: 0x15a8 (section: __ex_table) -> .LASF2568 (section: .debug_str)
ERROR: modpost: __ex_table+0x15a8 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x15ac (section: __ex_table) -> .LASF2570 (section: .debug_str)
ERROR: modpost: __ex_table+0x15ac references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x15b4 (section: __ex_table) -> .LASF2572 (section: .debug_str)
ERROR: modpost: __ex_table+0x15b4 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x15b8 (section: __ex_table) -> .LASF2574 (section: .debug_str)
ERROR: modpost: __ex_table+0x15b8 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x15c0 (section: __ex_table) -> .LASF2576 (section: .debug_str)
ERROR: modpost: __ex_table+0x15c0 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x15c4 (section: __ex_table) -> .LASF2578 (section: .debug_str)
ERROR: modpost: __ex_table+0x15c4 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x15cc (section: __ex_table) -> .LASF2580 (section: .debug_str)
ERROR: modpost: __ex_table+0x15cc references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x15d0 (section: __ex_table) -> .LASF2574 (section: .debug_str)
ERROR: modpost: __ex_table+0x15d0 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x15d8 (section: __ex_table) -> .LASF2583 (section: .debug_str)
ERROR: modpost: __ex_table+0x15d8 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x15dc (section: __ex_table) -> .LASF2574 (section: .debug_str)
ERROR: modpost: __ex_table+0x15dc references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x15e4 (section: __ex_table) -> .LASF2586 (section: .debug_str)
ERROR: modpost: __ex_table+0x15e4 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x15e8 (section: __ex_table) -> .LASF2588 (section: .debug_str)
ERROR: modpost: __ex_table+0x15e8 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x15f0 (section: __ex_table) -> .L0 (section: __ex_table)
ERROR: modpost: __ex_table+0x15f0 references non-executable section '__ex_table'
WARNING: modpost: vmlinux: section mismatch in reference: 0x15f4 (section: __ex_table) -> .L0 (section: __ex_table)
ERROR: modpost: __ex_table+0x15f4 references non-executable section '__ex_table'
WARNING: modpost: vmlinux: section mismatch in reference: 0x15fc (section: __ex_table) -> .L0 (section: __ex_table)
ERROR: modpost: __ex_table+0x15fc references non-executable section '__ex_table'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1600 (section: __ex_table) -> firsttime (section: .data.firsttime.60983)
>> ERROR: modpost: __ex_table+0x1600 references non-executable section '.data.firsttime.60983'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1614 (section: __ex_table) -> .LASF230 (section: .debug_str)
ERROR: modpost: __ex_table+0x1614 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1618 (section: __ex_table) -> .LASF232 (section: .debug_str)
ERROR: modpost: __ex_table+0x1618 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1620 (section: __ex_table) -> .LASF234 (section: .debug_str)
ERROR: modpost: __ex_table+0x1620 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1624 (section: __ex_table) -> .LASF232 (section: .debug_str)
ERROR: modpost: __ex_table+0x1624 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x162c (section: __ex_table) -> .LASF237 (section: .debug_str)
ERROR: modpost: __ex_table+0x162c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1630 (section: __ex_table) -> .LASF232 (section: .debug_str)
ERROR: modpost: __ex_table+0x1630 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1638 (section: __ex_table) -> .LASF240 (section: .debug_str)
ERROR: modpost: __ex_table+0x1638 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x163c (section: __ex_table) -> .LASF232 (section: .debug_str)
ERROR: modpost: __ex_table+0x163c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1644 (section: __ex_table) -> .LASF243 (section: .debug_str)
ERROR: modpost: __ex_table+0x1644 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1648 (section: __ex_table) -> .LASF232 (section: .debug_str)
ERROR: modpost: __ex_table+0x1648 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1650 (section: __ex_table) -> .LASF246 (section: .debug_str)
ERROR: modpost: __ex_table+0x1650 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1654 (section: __ex_table) -> .LASF232 (section: .debug_str)
ERROR: modpost: __ex_table+0x1654 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x165c (section: __ex_table) -> .LASF249 (section: .debug_str)
ERROR: modpost: __ex_table+0x165c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1660 (section: __ex_table) -> .LASF251 (section: .debug_str)
ERROR: modpost: __ex_table+0x1660 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1668 (section: __ex_table) -> .LASF253 (section: .debug_str)
ERROR: modpost: __ex_table+0x1668 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x166c (section: __ex_table) -> .LASF255 (section: .debug_str)
ERROR: modpost: __ex_table+0x166c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1674 (section: __ex_table) -> .LASF257 (section: .debug_str)
ERROR: modpost: __ex_table+0x1674 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1678 (section: __ex_table) -> .LASF259 (section: .debug_str)
ERROR: modpost: __ex_table+0x1678 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1680 (section: __ex_table) -> .LASF261 (section: .debug_str)
ERROR: modpost: __ex_table+0x1680 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1684 (section: __ex_table) -> .LASF263 (section: .debug_str)
ERROR: modpost: __ex_table+0x1684 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x168c (section: __ex_table) -> .LASF265 (section: .debug_str)
ERROR: modpost: __ex_table+0x168c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1690 (section: __ex_table) -> .LASF267 (section: .debug_str)
ERROR: modpost: __ex_table+0x1690 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1698 (section: __ex_table) -> .LASF269 (section: .debug_str)
ERROR: modpost: __ex_table+0x1698 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x169c (section: __ex_table) -> .LASF271 (section: .debug_str)
ERROR: modpost: __ex_table+0x169c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x16a4 (section: __ex_table) -> .LASF273 (section: .debug_str)
ERROR: modpost: __ex_table+0x16a4 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x16a8 (section: __ex_table) -> .LASF275 (section: .debug_str)
ERROR: modpost: __ex_table+0x16a8 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x16b0 (section: __ex_table) -> .LASF277 (section: .debug_str)
ERROR: modpost: __ex_table+0x16b0 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x16b4 (section: __ex_table) -> .LASF279 (section: .debug_str)
ERROR: modpost: __ex_table+0x16b4 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x16bc (section: __ex_table) -> .LASF281 (section: .debug_str)
ERROR: modpost: __ex_table+0x16bc references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x16c0 (section: __ex_table) -> .LASF283 (section: .debug_str)
ERROR: modpost: __ex_table+0x16c0 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x16c8 (section: __ex_table) -> .LASF285 (section: .debug_str)
ERROR: modpost: __ex_table+0x16c8 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x16cc (section: __ex_table) -> .LASF287 (section: .debug_str)
ERROR: modpost: __ex_table+0x16cc references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x16d4 (section: __ex_table) -> .LASF289 (section: .debug_str)
ERROR: modpost: __ex_table+0x16d4 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x16d8 (section: __ex_table) -> .LASF291 (section: .debug_str)
ERROR: modpost: __ex_table+0x16d8 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x16e4 (section: __ex_table) -> .LASF4984 (section: .debug_str)
ERROR: modpost: __ex_table+0x16e4 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x16ec (section: __ex_table) -> .LASF4986 (section: .debug_str)
ERROR: modpost: __ex_table+0x16ec references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x16f0 (section: __ex_table) -> .LASF4984 (section: .debug_str)
ERROR: modpost: __ex_table+0x16f0 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x16fc (section: __ex_table) -> .LASF4984 (section: .debug_str)
ERROR: modpost: __ex_table+0x16fc references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1704 (section: __ex_table) -> .LLST20 (section: .debug_loc)
ERROR: modpost: __ex_table+0x1704 references non-executable section '.debug_loc'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1708 (section: __ex_table) -> .LLST22 (section: .debug_loc)
ERROR: modpost: __ex_table+0x1708 references non-executable section '.debug_loc'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1710 (section: __ex_table) -> .LLST23 (section: .debug_loc)
ERROR: modpost: __ex_table+0x1710 references non-executable section '.debug_loc'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1714 (section: __ex_table) -> .LASF4984 (section: .debug_str)
ERROR: modpost: __ex_table+0x1714 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x171c (section: __ex_table) -> .LASF270 (section: .debug_str)
ERROR: modpost: __ex_table+0x171c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1720 (section: __ex_table) -> .LASF272 (section: .debug_str)
ERROR: modpost: __ex_table+0x1720 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x174c (section: __ex_table) -> .LASF1801 (section: .debug_str)
ERROR: modpost: __ex_table+0x174c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1750 (section: __ex_table) -> .LASF1803 (section: .debug_str)
ERROR: modpost: __ex_table+0x1750 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1758 (section: __ex_table) -> .LASF1805 (section: .debug_str)
ERROR: modpost: __ex_table+0x1758 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x175c (section: __ex_table) -> .LASF1807 (section: .debug_str)
ERROR: modpost: __ex_table+0x175c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1764 (section: __ex_table) -> .LASF1809 (section: .debug_str)
ERROR: modpost: __ex_table+0x1764 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1768 (section: __ex_table) -> .LASF1807 (section: .debug_str)
ERROR: modpost: __ex_table+0x1768 references non-executable section '.debug_str'
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [PATCH v3 30/30] docs: add documentation for memfd preservation via LUO
From: Pasha Tatashin @ 2025-08-07 1:44 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, changyuanl, pasha.tatashin, rppt,
dmatlack, rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda,
aliceryhl, masahiroy, akpm, tj, yoann.congal, mmaurer,
roman.gushchin, chenridong, axboe, mark.rutland, jannh,
vincent.guittot, hannes, dan.j.williams, david, joel.granados,
rostedt, anna.schumaker, song, zhangguopeng, linux, linux-kernel,
linux-doc, linux-mm, gregkh, tglx, mingo, bp, dave.hansen, x86,
hpa, rafael, dakr, bartosz.golaszewski, cw00.choi, myungjoo.ham,
yesanishhere, Jonathan.Cameron, quic_zijuhu, aleksander.lobakin,
ira.weiny, andriy.shevchenko, leon, lukas, bhelgaas, wagi,
djeffery, stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu
In-Reply-To: <20250807014442.3829950-1-pasha.tatashin@soleen.com>
From: Pratyush Yadav <ptyadav@amazon.de>
Add the documentation under the "Preserving file descriptors" section of
LUO's documentation. The doc describes the properties preserved,
behaviour of the file under different LUO states, serialization format,
and current limitations.
Signed-off-by: Pratyush Yadav <ptyadav@amazon.de>
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
Documentation/core-api/liveupdate.rst | 7 ++
Documentation/mm/index.rst | 1 +
Documentation/mm/memfd_preservation.rst | 138 ++++++++++++++++++++++++
MAINTAINERS | 1 +
4 files changed, 147 insertions(+)
create mode 100644 Documentation/mm/memfd_preservation.rst
diff --git a/Documentation/core-api/liveupdate.rst b/Documentation/core-api/liveupdate.rst
index 41c4b76cd3ec..232d5f623992 100644
--- a/Documentation/core-api/liveupdate.rst
+++ b/Documentation/core-api/liveupdate.rst
@@ -18,6 +18,13 @@ LUO Preserving File Descriptors
.. kernel-doc:: kernel/liveupdate/luo_files.c
:doc: LUO file descriptors
+The following types of file descriptors can be preserved
+
+.. toctree::
+ :maxdepth: 1
+
+ ../mm/memfd_preservation
+
Public API
==========
.. kernel-doc:: include/linux/liveupdate.h
diff --git a/Documentation/mm/index.rst b/Documentation/mm/index.rst
index fb45acba16ac..c504156149a0 100644
--- a/Documentation/mm/index.rst
+++ b/Documentation/mm/index.rst
@@ -47,6 +47,7 @@ documentation, or deleted if it has served its purpose.
hugetlbfs_reserv
ksm
memory-model
+ memfd_preservation
mmu_notifier
multigen_lru
numa
diff --git a/Documentation/mm/memfd_preservation.rst b/Documentation/mm/memfd_preservation.rst
new file mode 100644
index 000000000000..416cd1dafc97
--- /dev/null
+++ b/Documentation/mm/memfd_preservation.rst
@@ -0,0 +1,138 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+==========================
+Memfd Preservation via LUO
+==========================
+
+Overview
+========
+
+Memory file descriptors (memfd) can be preserved over a kexec using the Live
+Update Orchestrator (LUO) file preservation. This allows userspace to transfer
+its memory contents to the next kernel after a kexec.
+
+The preservation is not intended to be transparent. Only select properties of
+the file are preserved. All others are reset to default. The preserved
+properties are described below.
+
+.. note::
+ The LUO API is not stabilized yet, so the preserved properties of a memfd are
+ also not stable and are subject to backwards incompatible changes.
+
+.. note::
+ Currently a memfd backed by Hugetlb is not supported. Memfds created
+ with ``MFD_HUGETLB`` will be rejected.
+
+Preserved Properties
+====================
+
+The following properties of the memfd are preserved across kexec:
+
+File Contents
+ All data stored in the file is preserved.
+
+File Size
+ The size of the file is preserved. Holes in the file are filled by allocating
+ pages for them during preservation.
+
+File Position
+ The current file position is preserved, allowing applications to continue
+ reading/writing from their last position.
+
+File Status Flags
+ memfds are always opened with ``O_RDWR`` and ``O_LARGEFILE``. This property is
+ maintained.
+
+Non-Preserved Properties
+========================
+
+All properties which are not preserved must be assumed to be reset to default.
+This section describes some of those properties which may be more of note.
+
+``FD_CLOEXEC`` flag
+ A memfd can be created with the ``MFD_CLOEXEC`` flag that sets the
+ ``FD_CLOEXEC`` on the file. This flag is not preserved and must be set again
+ after restore via ``fcntl()``.
+
+Seals
+ File seals are not preserved. The file is unsealed on restore and if needed,
+ must be sealed again via ``fcntl()``.
+
+Behavior with LUO states
+========================
+
+This section described the behavior of the memfd in the different LUO states.
+
+Normal Phase
+ During the normal phase, the memfd can be marked for preservation using the
+ ``LIVEUPDATE_IOCTL_FD_PRESERVE`` ioctl. The memfd acts as a regular memfd
+ during this phase with no additional restrictions.
+
+Prepared Phase
+ After LUO enters ``LIVEUPDATE_STATE_PREPARED``, the memfd is serialized and
+ prepared for the next kernel. During this phase, the below things happen:
+
+ - All the folios are pinned. If some folios reside in ``ZONE_MIGRATE``, they
+ are migrated out. This ensures none of the preserved folios land in KHO
+ scratch area.
+ - Pages in swap are swapped in. Currently, there is no way to pass pages in
+ swap over KHO, so all swapped out pages are swapped back in and pinned.
+ - The memfd goes into "frozen mapping" mode. The file can no longer grow or
+ shrink, or punch holes. This ensures the serialized mappings stay in sync.
+ The file can still be read from or written to or mmap-ed.
+
+Freeze Phase
+ Updates the current file position in the serialized data to capture any
+ changes that occurred between prepare and freeze phases. After this, the FD is
+ not allowed to be accessed.
+
+Restoration Phase
+ After being restored, the memfd is functional as normal with the properties
+ listed above restored.
+
+Cancellation
+ If the liveupdate is canceled after going into prepared phase, the memfd
+ functions like in normal phase.
+
+Serialization format
+====================
+
+The state is serialized in an FDT with the following structure::
+
+ /dts-v1/;
+
+ / {
+ compatible = "memfd-v1";
+ pos = <current_file_position>;
+ size = <file_size_in_bytes>;
+ folios = <array_of_preserved_folio_descriptors>;
+ };
+
+Each folio descriptor contains:
+
+- PFN + flags (8 bytes)
+
+ - Physical frame number (PFN) of the preserved folio (bits 63:12).
+ - Folio flags (bits 11:0):
+
+ - ``PRESERVED_FLAG_DIRTY`` (bit 0)
+ - ``PRESERVED_FLAG_UPTODATE`` (bit 1)
+
+- Folio index within the file (8 bytes).
+
+Limitations
+===========
+
+The current implementation has the following limitations:
+
+Size
+ Currently the size of the file is limited by the size of the FDT. The FDT can
+ be at of most ``MAX_PAGE_ORDER`` order. By default this is 4 MiB with 4K
+ pages. Each page in the file is tracked using 16 bytes. This limits the
+ maximum size of the file to 1 GiB.
+
+See Also
+========
+
+- :doc:`Live Update Orchestrator </admin-guide/liveupdate>`
+- :doc:`/core-api/kho/concepts`
diff --git a/MAINTAINERS b/MAINTAINERS
index 7421d21672f3..50482363c9d4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14215,6 +14215,7 @@ S: Maintained
F: Documentation/ABI/testing/sysfs-kernel-liveupdate
F: Documentation/admin-guide/liveupdate.rst
F: Documentation/core-api/liveupdate.rst
+F: Documentation/mm/memfd_preservation.rst
F: Documentation/userspace-api/liveupdate.rst
F: include/linux/liveupdate.h
F: include/uapi/linux/liveupdate.h
--
2.50.1.565.gc32cd1483b-goog
^ permalink raw reply related
* [PATCH v3 29/30] luo: allow preserving memfd
From: Pasha Tatashin @ 2025-08-07 1:44 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, changyuanl, pasha.tatashin, rppt,
dmatlack, rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda,
aliceryhl, masahiroy, akpm, tj, yoann.congal, mmaurer,
roman.gushchin, chenridong, axboe, mark.rutland, jannh,
vincent.guittot, hannes, dan.j.williams, david, joel.granados,
rostedt, anna.schumaker, song, zhangguopeng, linux, linux-kernel,
linux-doc, linux-mm, gregkh, tglx, mingo, bp, dave.hansen, x86,
hpa, rafael, dakr, bartosz.golaszewski, cw00.choi, myungjoo.ham,
yesanishhere, Jonathan.Cameron, quic_zijuhu, aleksander.lobakin,
ira.weiny, andriy.shevchenko, leon, lukas, bhelgaas, wagi,
djeffery, stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu
In-Reply-To: <20250807014442.3829950-1-pasha.tatashin@soleen.com>
From: Pratyush Yadav <ptyadav@amazon.de>
The ability to preserve a memfd allows userspace to use KHO and LUO to
transfer its memory contents to the next kernel. This is useful in many
ways. For one, it can be used with IOMMUFD as the backing store for
IOMMU page tables. Preserving IOMMUFD is essential for performing a
hypervisor live update with passthrough devices. memfd support provides
the first building block for making that possible.
For another, applications with a large amount of memory that takes time
to reconstruct, reboots to consume kernel upgrades can be very
expensive. memfd with LUO gives those applications reboot-persistent
memory that they can use to quickly save and reconstruct that state.
While memfd is backed by either hugetlbfs or shmem, currently only
support on shmem is added. To be more precise, support for anonymous
shmem files is added.
The handover to the next kernel is not transparent. All the properties
of the file are not preserved; only its memory contents, position, and
size. The recreated file gets the UID and GID of the task doing the
restore, and the task's cgroup gets charged with the memory.
After LUO is in prepared state, the file cannot grow or shrink, and all
its pages are pinned to avoid migrations and swapping. The file can
still be read from or written to.
Co-developed-by: Changyuan Lyu <changyuanl@google.com>
Signed-off-by: Changyuan Lyu <changyuanl@google.com>
Co-developed-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Signed-off-by: Pratyush Yadav <ptyadav@amazon.de>
---
MAINTAINERS | 2 +
mm/Makefile | 1 +
mm/memfd_luo.c | 507 +++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 510 insertions(+)
create mode 100644 mm/memfd_luo.c
diff --git a/MAINTAINERS b/MAINTAINERS
index b88b77977649..7421d21672f3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14209,6 +14209,7 @@ F: tools/testing/selftests/livepatch/
LIVE UPDATE
M: Pasha Tatashin <pasha.tatashin@soleen.com>
+R: Pratyush Yadav <pratyush@kernel.org>
L: linux-kernel@vger.kernel.org
S: Maintained
F: Documentation/ABI/testing/sysfs-kernel-liveupdate
@@ -14218,6 +14219,7 @@ F: Documentation/userspace-api/liveupdate.rst
F: include/linux/liveupdate.h
F: include/uapi/linux/liveupdate.h
F: kernel/liveupdate/
+F: mm/memfd_luo.c
F: tools/testing/selftests/liveupdate/
LLC (802.2)
diff --git a/mm/Makefile b/mm/Makefile
index ef54aa615d9d..0a9936ffc172 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -100,6 +100,7 @@ obj-$(CONFIG_NUMA) += memory-tiers.o
obj-$(CONFIG_DEVICE_MIGRATION) += migrate_device.o
obj-$(CONFIG_TRANSPARENT_HUGEPAGE) += huge_memory.o khugepaged.o
obj-$(CONFIG_PAGE_COUNTER) += page_counter.o
+obj-$(CONFIG_LIVEUPDATE) += memfd_luo.o
obj-$(CONFIG_MEMCG_V1) += memcontrol-v1.o
obj-$(CONFIG_MEMCG) += memcontrol.o vmpressure.o
ifdef CONFIG_SWAP
diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
new file mode 100644
index 000000000000..0c91b40a2080
--- /dev/null
+++ b/mm/memfd_luo.c
@@ -0,0 +1,507 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (c) 2025, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@soleen.com>
+ * Changyuan Lyu <changyuanl@google.com>
+ *
+ * Copyright (C) 2025 Amazon.com Inc. or its affiliates.
+ * Pratyush Yadav <ptyadav@amazon.de>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/file.h>
+#include <linux/io.h>
+#include <linux/libfdt.h>
+#include <linux/liveupdate.h>
+#include <linux/kexec_handover.h>
+#include <linux/shmem_fs.h>
+#include <linux/bits.h>
+#include "internal.h"
+
+static const char memfd_luo_compatible[] = "memfd-v1";
+
+#define PRESERVED_PFN_MASK GENMASK(63, 12)
+#define PRESERVED_PFN_SHIFT 12
+#define PRESERVED_FLAG_DIRTY BIT(0)
+#define PRESERVED_FLAG_UPTODATE BIT(1)
+
+#define PRESERVED_FOLIO_PFN(desc) (((desc) & PRESERVED_PFN_MASK) >> PRESERVED_PFN_SHIFT)
+#define PRESERVED_FOLIO_FLAGS(desc) ((desc) & ~PRESERVED_PFN_MASK)
+#define PRESERVED_FOLIO_MKDESC(pfn, flags) (((pfn) << PRESERVED_PFN_SHIFT) | (flags))
+
+struct memfd_luo_preserved_folio {
+ /*
+ * The folio descriptor is made of 2 parts. The bottom 12 bits are used
+ * for storing flags, the others for storing the PFN.
+ */
+ u64 foliodesc;
+ u64 index;
+};
+
+static int memfd_luo_preserve_folios(struct memfd_luo_preserved_folio *pfolios,
+ struct folio **folios,
+ unsigned int nr_folios)
+{
+ unsigned int i;
+ int err;
+
+ for (i = 0; i < nr_folios; i++) {
+ struct memfd_luo_preserved_folio *pfolio = &pfolios[i];
+ struct folio *folio = folios[i];
+ unsigned int flags = 0;
+ unsigned long pfn;
+
+ err = kho_preserve_folio(folio);
+ if (err)
+ goto err_unpreserve;
+
+ pfn = folio_pfn(folio);
+ if (folio_test_dirty(folio))
+ flags |= PRESERVED_FLAG_DIRTY;
+ if (folio_test_uptodate(folio))
+ flags |= PRESERVED_FLAG_UPTODATE;
+
+ pfolio->foliodesc = PRESERVED_FOLIO_MKDESC(pfn, flags);
+ pfolio->index = folio->index;
+ }
+
+ return 0;
+
+err_unpreserve:
+ i--;
+ for (; i >= 0; i--)
+ WARN_ON_ONCE(kho_unpreserve_folio(folios[i]));
+ return err;
+}
+
+static void memfd_luo_unpreserve_folios(const struct memfd_luo_preserved_folio *pfolios,
+ unsigned int nr_folios)
+{
+ unsigned int i;
+
+ for (i = 0; i < nr_folios; i++) {
+ const struct memfd_luo_preserved_folio *pfolio = &pfolios[i];
+ struct folio *folio;
+
+ if (!pfolio->foliodesc)
+ continue;
+
+ folio = pfn_folio(PRESERVED_FOLIO_PFN(pfolio->foliodesc));
+
+ kho_unpreserve_folio(folio);
+ unpin_folio(folio);
+ }
+}
+
+static void *memfd_luo_create_fdt(unsigned long size)
+{
+ unsigned int order = get_order(size);
+ struct folio *fdt_folio;
+ int err = 0;
+ void *fdt;
+
+ if (order > MAX_PAGE_ORDER)
+ return NULL;
+
+ fdt_folio = folio_alloc(GFP_KERNEL, order);
+ if (!fdt_folio)
+ return NULL;
+
+ fdt = folio_address(fdt_folio);
+
+ err |= fdt_create(fdt, (1 << (order + PAGE_SHIFT)));
+ err |= fdt_finish_reservemap(fdt);
+ err |= fdt_begin_node(fdt, "");
+ if (err)
+ goto free;
+
+ return fdt;
+
+free:
+ folio_put(fdt_folio);
+ return NULL;
+}
+
+static int memfd_luo_finish_fdt(void *fdt)
+{
+ int err;
+
+ err = fdt_end_node(fdt);
+ if (err)
+ return err;
+
+ return fdt_finish(fdt);
+}
+
+static int memfd_luo_prepare(struct liveupdate_file_handler *handler,
+ struct file *file, u64 *data)
+{
+ struct memfd_luo_preserved_folio *preserved_folios;
+ struct inode *inode = file_inode(file);
+ unsigned int max_folios, nr_folios = 0;
+ int err = 0, preserved_size;
+ struct folio **folios;
+ long size, nr_pinned;
+ pgoff_t offset;
+ void *fdt;
+ u64 pos;
+
+ if (WARN_ON_ONCE(!shmem_file(file)))
+ return -EINVAL;
+
+ inode_lock(inode);
+ shmem_i_mapping_freeze(inode, true);
+
+ size = i_size_read(inode);
+ if ((PAGE_ALIGN(size) / PAGE_SIZE) > UINT_MAX) {
+ err = -E2BIG;
+ goto err_unlock;
+ }
+
+ /*
+ * Guess the number of folios based on inode size. Real number might end
+ * up being smaller if there are higher order folios.
+ */
+ max_folios = PAGE_ALIGN(size) / PAGE_SIZE;
+ folios = kvmalloc_array(max_folios, sizeof(*folios), GFP_KERNEL);
+ if (!folios) {
+ err = -ENOMEM;
+ goto err_unfreeze;
+ }
+
+ /*
+ * Pin the folios so they don't move around behind our back. This also
+ * ensures none of the folios are in CMA -- which ensures they don't
+ * fall in KHO scratch memory. It also moves swapped out folios back to
+ * memory.
+ *
+ * A side effect of doing this is that it allocates a folio for all
+ * indices in the file. This might waste memory on sparse memfds. If
+ * that is really a problem in the future, we can have a
+ * memfd_pin_folios() variant that does not allocate a page on empty
+ * slots.
+ */
+ nr_pinned = memfd_pin_folios(file, 0, size - 1, folios, max_folios,
+ &offset);
+ if (nr_pinned < 0) {
+ err = nr_pinned;
+ pr_err("failed to pin folios: %d\n", err);
+ goto err_free_folios;
+ }
+ /* nr_pinned won't be more than max_folios which is also unsigned int. */
+ nr_folios = (unsigned int)nr_pinned;
+
+ preserved_size = sizeof(struct memfd_luo_preserved_folio) * nr_folios;
+ if (check_mul_overflow(sizeof(struct memfd_luo_preserved_folio),
+ nr_folios, &preserved_size)) {
+ err = -E2BIG;
+ goto err_unpin;
+ }
+
+ /*
+ * Most of the space should be taken by preserved folios. So take its
+ * size, plus a page for other properties.
+ */
+ fdt = memfd_luo_create_fdt(PAGE_ALIGN(preserved_size) + PAGE_SIZE);
+ if (!fdt) {
+ err = -ENOMEM;
+ goto err_unpin;
+ }
+
+ pos = file->f_pos;
+ err = fdt_property(fdt, "pos", &pos, sizeof(pos));
+ if (err)
+ goto err_free_fdt;
+
+ err = fdt_property(fdt, "size", &size, sizeof(size));
+ if (err)
+ goto err_free_fdt;
+
+ err = fdt_property_placeholder(fdt, "folios", preserved_size,
+ (void **)&preserved_folios);
+ if (err) {
+ pr_err("Failed to reserve folios property in FDT: %s\n",
+ fdt_strerror(err));
+ err = -ENOMEM;
+ goto err_free_fdt;
+ }
+
+ err = memfd_luo_preserve_folios(preserved_folios, folios, nr_folios);
+ if (err)
+ goto err_free_fdt;
+
+ err = memfd_luo_finish_fdt(fdt);
+ if (err)
+ goto err_unpreserve;
+
+ err = kho_preserve_folio(virt_to_folio(fdt));
+ if (err)
+ goto err_unpreserve;
+
+ kvfree(folios);
+ inode_unlock(inode);
+
+ *data = virt_to_phys(fdt);
+ return 0;
+
+err_unpreserve:
+ memfd_luo_unpreserve_folios(preserved_folios, nr_folios);
+err_free_fdt:
+ folio_put(virt_to_folio(fdt));
+err_unpin:
+ unpin_folios(folios, nr_pinned);
+err_free_folios:
+ kvfree(folios);
+err_unfreeze:
+ shmem_i_mapping_freeze(inode, false);
+err_unlock:
+ inode_unlock(inode);
+ return err;
+}
+
+static int memfd_luo_freeze(struct liveupdate_file_handler *handler,
+ struct file *file, u64 *data)
+{
+ u64 pos = file->f_pos;
+ void *fdt;
+ int err;
+
+ if (WARN_ON_ONCE(!*data))
+ return -EINVAL;
+
+ fdt = phys_to_virt(*data);
+
+ /*
+ * The pos or size might have changed since prepare. Everything else
+ * stays the same.
+ */
+ err = fdt_setprop(fdt, 0, "pos", &pos, sizeof(pos));
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static void memfd_luo_cancel(struct liveupdate_file_handler *handler,
+ struct file *file, u64 data)
+{
+ const struct memfd_luo_preserved_folio *pfolios;
+ struct inode *inode = file_inode(file);
+ struct folio *fdt_folio;
+ void *fdt;
+ int len;
+
+ if (WARN_ON_ONCE(!data))
+ return;
+
+ inode_lock(inode);
+ shmem_i_mapping_freeze(inode, false);
+
+ fdt = phys_to_virt(data);
+ fdt_folio = virt_to_folio(fdt);
+ pfolios = fdt_getprop(fdt, 0, "folios", &len);
+ if (pfolios)
+ memfd_luo_unpreserve_folios(pfolios, len / sizeof(*pfolios));
+
+ kho_unpreserve_folio(fdt_folio);
+ folio_put(fdt_folio);
+ inode_unlock(inode);
+}
+
+static struct folio *memfd_luo_get_fdt(u64 data)
+{
+ return kho_restore_folio((phys_addr_t)data);
+}
+
+static void memfd_luo_finish(struct liveupdate_file_handler *handler,
+ struct file *file, u64 data, bool reclaimed)
+{
+ const struct memfd_luo_preserved_folio *pfolios;
+ struct folio *fdt_folio;
+ int len;
+
+ if (reclaimed)
+ return;
+
+ fdt_folio = memfd_luo_get_fdt(data);
+
+ pfolios = fdt_getprop(folio_address(fdt_folio), 0, "folios", &len);
+ if (pfolios)
+ memfd_luo_unpreserve_folios(pfolios, len / sizeof(*pfolios));
+
+ folio_put(fdt_folio);
+}
+
+static int memfd_luo_retrieve(struct liveupdate_file_handler *handler, u64 data,
+ struct file **file_p)
+{
+ const struct memfd_luo_preserved_folio *pfolios;
+ int nr_pfolios, len, ret = 0, i = 0;
+ struct address_space *mapping;
+ struct folio *folio, *fdt_folio;
+ const u64 *pos, *size;
+ struct inode *inode;
+ struct file *file;
+ const void *fdt;
+
+ fdt_folio = memfd_luo_get_fdt(data);
+ if (!fdt_folio)
+ return -ENOENT;
+
+ fdt = page_to_virt(folio_page(fdt_folio, 0));
+
+ pfolios = fdt_getprop(fdt, 0, "folios", &len);
+ if (!pfolios || len % sizeof(*pfolios)) {
+ pr_err("invalid 'folios' property\n");
+ ret = -EINVAL;
+ goto put_fdt;
+ }
+ nr_pfolios = len / sizeof(*pfolios);
+
+ size = fdt_getprop(fdt, 0, "size", &len);
+ if (!size || len != sizeof(u64)) {
+ pr_err("invalid 'size' property\n");
+ ret = -EINVAL;
+ goto put_folios;
+ }
+
+ pos = fdt_getprop(fdt, 0, "pos", &len);
+ if (!pos || len != sizeof(u64)) {
+ pr_err("invalid 'pos' property\n");
+ ret = -EINVAL;
+ goto put_folios;
+ }
+
+ file = shmem_file_setup("", 0, VM_NORESERVE);
+
+ if (IS_ERR(file)) {
+ ret = PTR_ERR(file);
+ pr_err("failed to setup file: %d\n", ret);
+ goto put_folios;
+ }
+
+ inode = file->f_inode;
+ mapping = inode->i_mapping;
+ vfs_setpos(file, *pos, MAX_LFS_FILESIZE);
+
+ for (; i < nr_pfolios; i++) {
+ const struct memfd_luo_preserved_folio *pfolio = &pfolios[i];
+ phys_addr_t phys;
+ u64 index;
+ int flags;
+
+ if (!pfolio->foliodesc)
+ continue;
+
+ phys = PFN_PHYS(PRESERVED_FOLIO_PFN(pfolio->foliodesc));
+ folio = kho_restore_folio(phys);
+ if (!folio) {
+ pr_err("Unable to restore folio at physical address: %llx\n",
+ phys);
+ goto put_file;
+ }
+ index = pfolio->index;
+ flags = PRESERVED_FOLIO_FLAGS(pfolio->foliodesc);
+
+ /* Set up the folio for insertion. */
+ /*
+ * TODO: Should find a way to unify this and
+ * shmem_alloc_and_add_folio().
+ */
+ __folio_set_locked(folio);
+ __folio_set_swapbacked(folio);
+
+ ret = mem_cgroup_charge(folio, NULL, mapping_gfp_mask(mapping));
+ if (ret) {
+ pr_err("shmem: failed to charge folio index %d: %d\n",
+ i, ret);
+ goto unlock_folio;
+ }
+
+ ret = shmem_add_to_page_cache(folio, mapping, index, NULL,
+ mapping_gfp_mask(mapping));
+ if (ret) {
+ pr_err("shmem: failed to add to page cache folio index %d: %d\n",
+ i, ret);
+ goto unlock_folio;
+ }
+
+ if (flags & PRESERVED_FLAG_UPTODATE)
+ folio_mark_uptodate(folio);
+ if (flags & PRESERVED_FLAG_DIRTY)
+ folio_mark_dirty(folio);
+
+ ret = shmem_inode_acct_blocks(inode, 1);
+ if (ret) {
+ pr_err("shmem: failed to account folio index %d: %d\n",
+ i, ret);
+ goto unlock_folio;
+ }
+
+ shmem_recalc_inode(inode, 1, 0);
+ folio_add_lru(folio);
+ folio_unlock(folio);
+ folio_put(folio);
+ }
+
+ inode->i_size = *size;
+ *file_p = file;
+ folio_put(fdt_folio);
+ return 0;
+
+unlock_folio:
+ folio_unlock(folio);
+ folio_put(folio);
+put_file:
+ fput(file);
+ i++;
+put_folios:
+ for (; i < nr_pfolios; i++) {
+ const struct memfd_luo_preserved_folio *pfolio = &pfolios[i];
+
+ folio = kho_restore_folio(PRESERVED_FOLIO_PFN(pfolio->foliodesc));
+ if (folio)
+ folio_put(folio);
+ }
+
+put_fdt:
+ folio_put(fdt_folio);
+ return ret;
+}
+
+static bool memfd_luo_can_preserve(struct liveupdate_file_handler *handler,
+ struct file *file)
+{
+ struct inode *inode = file_inode(file);
+
+ return shmem_file(file) && !inode->i_nlink;
+}
+
+static const struct liveupdate_file_ops memfd_luo_file_ops = {
+ .prepare = memfd_luo_prepare,
+ .freeze = memfd_luo_freeze,
+ .cancel = memfd_luo_cancel,
+ .finish = memfd_luo_finish,
+ .retrieve = memfd_luo_retrieve,
+ .can_preserve = memfd_luo_can_preserve,
+ .owner = THIS_MODULE,
+};
+
+static struct liveupdate_file_handler memfd_luo_handler = {
+ .ops = &memfd_luo_file_ops,
+ .compatible = memfd_luo_compatible,
+};
+
+static int __init memfd_luo_init(void)
+{
+ int err;
+
+ err = liveupdate_register_file_handler(&memfd_luo_handler);
+ if (err)
+ pr_err("Could not register luo filesystem handler: %d\n", err);
+
+ return err;
+}
+late_initcall(memfd_luo_init);
--
2.50.1.565.gc32cd1483b-goog
^ permalink raw reply related
* [PATCH v3 28/30] mm: shmem: export some functions to internal.h
From: Pasha Tatashin @ 2025-08-07 1:44 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, changyuanl, pasha.tatashin, rppt,
dmatlack, rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda,
aliceryhl, masahiroy, akpm, tj, yoann.congal, mmaurer,
roman.gushchin, chenridong, axboe, mark.rutland, jannh,
vincent.guittot, hannes, dan.j.williams, david, joel.granados,
rostedt, anna.schumaker, song, zhangguopeng, linux, linux-kernel,
linux-doc, linux-mm, gregkh, tglx, mingo, bp, dave.hansen, x86,
hpa, rafael, dakr, bartosz.golaszewski, cw00.choi, myungjoo.ham,
yesanishhere, Jonathan.Cameron, quic_zijuhu, aleksander.lobakin,
ira.weiny, andriy.shevchenko, leon, lukas, bhelgaas, wagi,
djeffery, stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu
In-Reply-To: <20250807014442.3829950-1-pasha.tatashin@soleen.com>
From: Pratyush Yadav <ptyadav@amazon.de>
shmem_inode_acct_blocks(), shmem_recalc_inode(), and
shmem_add_to_page_cache() are used by shmem_alloc_and_add_folio(). This
functionality will also be used in the future by Live Update
Orchestrator (LUO) to recreate memfd files after a live update.
Signed-off-by: Pratyush Yadav <ptyadav@amazon.de>
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
mm/internal.h | 6 ++++++
mm/shmem.c | 10 +++++-----
2 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/mm/internal.h b/mm/internal.h
index 45b725c3dc03..5cf487ee6f83 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -1566,6 +1566,12 @@ void __meminit __init_page_from_nid(unsigned long pfn, int nid);
unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg,
int priority);
+int shmem_add_to_page_cache(struct folio *folio,
+ struct address_space *mapping,
+ 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);
+
#ifdef CONFIG_SHRINKER_DEBUG
static inline __printf(2, 0) int shrinker_debugfs_name_alloc(
struct shrinker *shrinker, const char *fmt, va_list ap)
diff --git a/mm/shmem.c b/mm/shmem.c
index ef57e2649a41..eea2e8ca205f 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -219,7 +219,7 @@ static inline void shmem_unacct_blocks(unsigned long flags, long pages)
vm_unacct_memory(pages * VM_ACCT(PAGE_SIZE));
}
-static int shmem_inode_acct_blocks(struct inode *inode, long pages)
+int shmem_inode_acct_blocks(struct inode *inode, long pages)
{
struct shmem_inode_info *info = SHMEM_I(inode);
struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb);
@@ -435,7 +435,7 @@ static void shmem_free_inode(struct super_block *sb, size_t freed_ispace)
*
* Return: true if swapped was incremented from 0, for shmem_writeout().
*/
-static bool shmem_recalc_inode(struct inode *inode, long alloced, long swapped)
+bool shmem_recalc_inode(struct inode *inode, long alloced, long swapped)
{
struct shmem_inode_info *info = SHMEM_I(inode);
bool first_swapped = false;
@@ -898,9 +898,9 @@ static void shmem_update_stats(struct folio *folio, int nr_pages)
/*
* Somewhat like filemap_add_folio, but error if expected item has gone.
*/
-static int shmem_add_to_page_cache(struct folio *folio,
- struct address_space *mapping,
- pgoff_t index, void *expected, gfp_t gfp)
+int shmem_add_to_page_cache(struct folio *folio,
+ struct address_space *mapping,
+ pgoff_t index, void *expected, gfp_t gfp)
{
XA_STATE_ORDER(xas, &mapping->i_pages, index, folio_order(folio));
unsigned long nr = folio_nr_pages(folio);
--
2.50.1.565.gc32cd1483b-goog
^ permalink raw reply related
* [PATCH v3 27/30] mm: shmem: allow freezing inode mapping
From: Pasha Tatashin @ 2025-08-07 1:44 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, changyuanl, pasha.tatashin, rppt,
dmatlack, rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda,
aliceryhl, masahiroy, akpm, tj, yoann.congal, mmaurer,
roman.gushchin, chenridong, axboe, mark.rutland, jannh,
vincent.guittot, hannes, dan.j.williams, david, joel.granados,
rostedt, anna.schumaker, song, zhangguopeng, linux, linux-kernel,
linux-doc, linux-mm, gregkh, tglx, mingo, bp, dave.hansen, x86,
hpa, rafael, dakr, bartosz.golaszewski, cw00.choi, myungjoo.ham,
yesanishhere, Jonathan.Cameron, quic_zijuhu, aleksander.lobakin,
ira.weiny, andriy.shevchenko, leon, lukas, bhelgaas, wagi,
djeffery, stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu
In-Reply-To: <20250807014442.3829950-1-pasha.tatashin@soleen.com>
From: Pratyush Yadav <ptyadav@amazon.de>
To prepare a shmem inode for live update via the Live Update
Orchestrator (LUO), its index -> folio mappings must be serialized. Once
the mappings are serialized, they cannot change since it would cause the
serialized data to become inconsistent. This can be done by pinning the
folios to avoid migration, and by making sure no folios can be added to
or removed from the inode.
While mechanisms to pin folios already exist, the only way to stop
folios being added or removed are the grow and shrink file seals. But
file seals come with their own semantics, one of which is that they
can't be removed. This doesn't work with liveupdate since it can be
cancelled or error out, which would need the seals to be removed and the
file's normal functionality to be restored.
Introduce SHMEM_F_MAPPING_FROZEN to indicate this instead. It is
internal to shmem and is not directly exposed to userspace. It functions
similar to F_SEAL_GROW | F_SEAL_SHRINK, but additionally disallows hole
punching, and can be removed.
Signed-off-by: Pratyush Yadav <ptyadav@amazon.de>
Signed-off-by: Pasha Tatashin <pahsa.tatashin@soleen.com>
---
include/linux/shmem_fs.h | 17 +++++++++++++++++
mm/shmem.c | 12 +++++++++++-
2 files changed, 28 insertions(+), 1 deletion(-)
diff --git a/include/linux/shmem_fs.h b/include/linux/shmem_fs.h
index 923f0da5f6c4..f68fc14f7664 100644
--- a/include/linux/shmem_fs.h
+++ b/include/linux/shmem_fs.h
@@ -24,6 +24,14 @@ struct swap_iocb;
#define SHMEM_F_NORESERVE BIT(0)
/* Disallow swapping. */
#define SHMEM_F_LOCKED BIT(1)
+/*
+ * Disallow growing, shrinking, or hole punching in the inode. Combined with
+ * folio pinning, makes sure the inode's mapping stays fixed.
+ *
+ * In some ways similar to F_SEAL_GROW | F_SEAL_SHRINK, but can be removed and
+ * isn't directly visible to userspace.
+ */
+#define SHMEM_F_MAPPING_FROZEN BIT(2)
struct shmem_inode_info {
spinlock_t lock;
@@ -186,6 +194,15 @@ static inline bool shmem_file(struct file *file)
return shmem_mapping(file->f_mapping);
}
+/* Must be called with inode lock taken exclusive. */
+static inline void shmem_i_mapping_freeze(struct inode *inode, bool freeze)
+{
+ if (freeze)
+ SHMEM_I(inode)->flags |= SHMEM_F_MAPPING_FROZEN;
+ else
+ SHMEM_I(inode)->flags &= ~SHMEM_F_MAPPING_FROZEN;
+}
+
/*
* If fallocate(FALLOC_FL_KEEP_SIZE) has been used, there may be pages
* beyond i_size's notion of EOF, which fallocate has committed to reserving:
diff --git a/mm/shmem.c b/mm/shmem.c
index 8e6b3f003da5..ef57e2649a41 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -1329,7 +1329,8 @@ static int shmem_setattr(struct mnt_idmap *idmap,
loff_t newsize = attr->ia_size;
/* protected by i_rwsem */
- if ((newsize < oldsize && (info->seals & F_SEAL_SHRINK)) ||
+ if ((info->flags & SHMEM_F_MAPPING_FROZEN) ||
+ (newsize < oldsize && (info->seals & F_SEAL_SHRINK)) ||
(newsize > oldsize && (info->seals & F_SEAL_GROW)))
return -EPERM;
@@ -3352,6 +3353,10 @@ shmem_write_begin(const struct kiocb *iocb, struct address_space *mapping,
return -EPERM;
}
+ if (unlikely((info->flags & SHMEM_F_MAPPING_FROZEN) &&
+ pos + len > inode->i_size))
+ return -EPERM;
+
ret = shmem_get_folio(inode, index, pos + len, &folio, SGP_WRITE);
if (ret)
return ret;
@@ -3725,6 +3730,11 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset,
inode_lock(inode);
+ if (info->flags & SHMEM_F_MAPPING_FROZEN) {
+ error = -EPERM;
+ goto out;
+ }
+
if (mode & FALLOC_FL_PUNCH_HOLE) {
struct address_space *mapping = file->f_mapping;
loff_t unmap_start = round_up(offset, PAGE_SIZE);
--
2.50.1.565.gc32cd1483b-goog
^ permalink raw reply related
* [PATCH v3 26/30] mm: shmem: use SHMEM_F_* flags instead of VM_* flags
From: Pasha Tatashin @ 2025-08-07 1:44 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, changyuanl, pasha.tatashin, rppt,
dmatlack, rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda,
aliceryhl, masahiroy, akpm, tj, yoann.congal, mmaurer,
roman.gushchin, chenridong, axboe, mark.rutland, jannh,
vincent.guittot, hannes, dan.j.williams, david, joel.granados,
rostedt, anna.schumaker, song, zhangguopeng, linux, linux-kernel,
linux-doc, linux-mm, gregkh, tglx, mingo, bp, dave.hansen, x86,
hpa, rafael, dakr, bartosz.golaszewski, cw00.choi, myungjoo.ham,
yesanishhere, Jonathan.Cameron, quic_zijuhu, aleksander.lobakin,
ira.weiny, andriy.shevchenko, leon, lukas, bhelgaas, wagi,
djeffery, stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu
In-Reply-To: <20250807014442.3829950-1-pasha.tatashin@soleen.com>
From: Pratyush Yadav <ptyadav@amazon.de>
shmem_inode_info::flags can have the VM flags VM_NORESERVE and
VM_LOCKED. These are used to suppress pre-accounting or to lock the
pages in the inode respectively. Using the VM flags directly makes it
difficult to add shmem-specific flags that are unrelated to VM behavior
since one would need to find a VM flag not used by shmem and re-purpose
it.
Introduce SHMEM_F_NORESERVE and SHMEM_F_LOCKED which represent the same
information, but their bits are independent of the VM flags. Callers can
still pass VM_NORESERVE to shmem_get_inode(), but it gets transformed to
the shmem-specific flag internally.
No functional changes intended.
Signed-off-by: Pratyush Yadav <ptyadav@amazon.de>
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
include/linux/shmem_fs.h | 6 ++++++
mm/shmem.c | 30 +++++++++++++++++-------------
2 files changed, 23 insertions(+), 13 deletions(-)
diff --git a/include/linux/shmem_fs.h b/include/linux/shmem_fs.h
index 6d0f9c599ff7..923f0da5f6c4 100644
--- a/include/linux/shmem_fs.h
+++ b/include/linux/shmem_fs.h
@@ -10,6 +10,7 @@
#include <linux/xattr.h>
#include <linux/fs_parser.h>
#include <linux/userfaultfd_k.h>
+#include <linux/bits.h>
struct swap_iocb;
@@ -19,6 +20,11 @@ struct swap_iocb;
#define SHMEM_MAXQUOTAS 2
#endif
+/* Suppress pre-accounting of the entire object size. */
+#define SHMEM_F_NORESERVE BIT(0)
+/* Disallow swapping. */
+#define SHMEM_F_LOCKED BIT(1)
+
struct shmem_inode_info {
spinlock_t lock;
unsigned int seals; /* shmem seals */
diff --git a/mm/shmem.c b/mm/shmem.c
index e2c76a30802b..8e6b3f003da5 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -175,20 +175,20 @@ static inline struct shmem_sb_info *SHMEM_SB(struct super_block *sb)
*/
static inline int shmem_acct_size(unsigned long flags, loff_t size)
{
- return (flags & VM_NORESERVE) ?
+ return (flags & SHMEM_F_NORESERVE) ?
0 : security_vm_enough_memory_mm(current->mm, VM_ACCT(size));
}
static inline void shmem_unacct_size(unsigned long flags, loff_t size)
{
- if (!(flags & VM_NORESERVE))
+ if (!(flags & SHMEM_F_NORESERVE))
vm_unacct_memory(VM_ACCT(size));
}
static inline int shmem_reacct_size(unsigned long flags,
loff_t oldsize, loff_t newsize)
{
- if (!(flags & VM_NORESERVE)) {
+ if (!(flags & SHMEM_F_NORESERVE)) {
if (VM_ACCT(newsize) > VM_ACCT(oldsize))
return security_vm_enough_memory_mm(current->mm,
VM_ACCT(newsize) - VM_ACCT(oldsize));
@@ -206,7 +206,7 @@ static inline int shmem_reacct_size(unsigned long flags,
*/
static inline int shmem_acct_blocks(unsigned long flags, long pages)
{
- if (!(flags & VM_NORESERVE))
+ if (!(flags & SHMEM_F_NORESERVE))
return 0;
return security_vm_enough_memory_mm(current->mm,
@@ -215,7 +215,7 @@ static inline int shmem_acct_blocks(unsigned long flags, long pages)
static inline void shmem_unacct_blocks(unsigned long flags, long pages)
{
- if (flags & VM_NORESERVE)
+ if (flags & SHMEM_F_NORESERVE)
vm_unacct_memory(pages * VM_ACCT(PAGE_SIZE));
}
@@ -1588,7 +1588,7 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug,
int nr_pages;
bool split = false;
- if ((info->flags & VM_LOCKED) || sbinfo->noswap)
+ if ((info->flags & SHMEM_F_LOCKED) || sbinfo->noswap)
goto redirty;
if (!total_swap_pages)
@@ -2971,15 +2971,15 @@ int shmem_lock(struct file *file, int lock, struct ucounts *ucounts)
* ipc_lock_object() when called from shmctl_do_lock(),
* no serialization needed when called from shm_destroy().
*/
- if (lock && !(info->flags & VM_LOCKED)) {
+ if (lock && !(info->flags & SHMEM_F_LOCKED)) {
if (!user_shm_lock(inode->i_size, ucounts))
goto out_nomem;
- info->flags |= VM_LOCKED;
+ info->flags |= SHMEM_F_LOCKED;
mapping_set_unevictable(file->f_mapping);
}
- if (!lock && (info->flags & VM_LOCKED) && ucounts) {
+ if (!lock && (info->flags & SHMEM_F_LOCKED) && ucounts) {
user_shm_unlock(inode->i_size, ucounts);
- info->flags &= ~VM_LOCKED;
+ info->flags &= ~SHMEM_F_LOCKED;
mapping_clear_unevictable(file->f_mapping);
}
retval = 0;
@@ -3123,7 +3123,9 @@ static struct inode *__shmem_get_inode(struct mnt_idmap *idmap,
spin_lock_init(&info->lock);
atomic_set(&info->stop_eviction, 0);
info->seals = F_SEAL_SEAL;
- info->flags = flags & VM_NORESERVE;
+ info->flags = 0;
+ if (flags & VM_NORESERVE)
+ info->flags |= SHMEM_F_NORESERVE;
info->i_crtime = inode_get_mtime(inode);
info->fsflags = (dir == NULL) ? 0 :
SHMEM_I(dir)->fsflags & SHMEM_FL_INHERITED;
@@ -5862,8 +5864,10 @@ static inline struct inode *shmem_get_inode(struct mnt_idmap *idmap,
/* common code */
static struct file *__shmem_file_setup(struct vfsmount *mnt, const char *name,
- loff_t size, unsigned long flags, unsigned int i_flags)
+ loff_t size, unsigned long vm_flags,
+ unsigned int i_flags)
{
+ unsigned long flags = (vm_flags & VM_NORESERVE) ? SHMEM_F_NORESERVE : 0;
struct inode *inode;
struct file *res;
@@ -5880,7 +5884,7 @@ static struct file *__shmem_file_setup(struct vfsmount *mnt, const char *name,
return ERR_PTR(-ENOMEM);
inode = shmem_get_inode(&nop_mnt_idmap, mnt->mnt_sb, NULL,
- S_IFREG | S_IRWXUGO, 0, flags);
+ S_IFREG | S_IRWXUGO, 0, vm_flags);
if (IS_ERR(inode)) {
shmem_unacct_size(flags, size);
return ERR_CAST(inode);
--
2.50.1.565.gc32cd1483b-goog
^ permalink raw reply related
* [PATCH v3 25/30] MAINTAINERS: add liveupdate entry
From: Pasha Tatashin @ 2025-08-07 1:44 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, changyuanl, pasha.tatashin, rppt,
dmatlack, rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda,
aliceryhl, masahiroy, akpm, tj, yoann.congal, mmaurer,
roman.gushchin, chenridong, axboe, mark.rutland, jannh,
vincent.guittot, hannes, dan.j.williams, david, joel.granados,
rostedt, anna.schumaker, song, zhangguopeng, linux, linux-kernel,
linux-doc, linux-mm, gregkh, tglx, mingo, bp, dave.hansen, x86,
hpa, rafael, dakr, bartosz.golaszewski, cw00.choi, myungjoo.ham,
yesanishhere, Jonathan.Cameron, quic_zijuhu, aleksander.lobakin,
ira.weiny, andriy.shevchenko, leon, lukas, bhelgaas, wagi,
djeffery, stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu
In-Reply-To: <20250807014442.3829950-1-pasha.tatashin@soleen.com>
Add a MAINTAINERS file entry for the new Live Update Orchestrator
introduced in previous patches.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
MAINTAINERS | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 35cf4f95ed46..b88b77977649 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14207,6 +14207,19 @@ F: kernel/module/livepatch.c
F: samples/livepatch/
F: tools/testing/selftests/livepatch/
+LIVE UPDATE
+M: Pasha Tatashin <pasha.tatashin@soleen.com>
+L: linux-kernel@vger.kernel.org
+S: Maintained
+F: Documentation/ABI/testing/sysfs-kernel-liveupdate
+F: Documentation/admin-guide/liveupdate.rst
+F: Documentation/core-api/liveupdate.rst
+F: Documentation/userspace-api/liveupdate.rst
+F: include/linux/liveupdate.h
+F: include/uapi/linux/liveupdate.h
+F: kernel/liveupdate/
+F: tools/testing/selftests/liveupdate/
+
LLC (802.2)
L: netdev@vger.kernel.org
S: Odd fixes
--
2.50.1.565.gc32cd1483b-goog
^ permalink raw reply related
* [PATCH v3 22/30] liveupdate: add selftests for subsystems un/registration
From: Pasha Tatashin @ 2025-08-07 1:44 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, changyuanl, pasha.tatashin, rppt,
dmatlack, rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda,
aliceryhl, masahiroy, akpm, tj, yoann.congal, mmaurer,
roman.gushchin, chenridong, axboe, mark.rutland, jannh,
vincent.guittot, hannes, dan.j.williams, david, joel.granados,
rostedt, anna.schumaker, song, zhangguopeng, linux, linux-kernel,
linux-doc, linux-mm, gregkh, tglx, mingo, bp, dave.hansen, x86,
hpa, rafael, dakr, bartosz.golaszewski, cw00.choi, myungjoo.ham,
yesanishhere, Jonathan.Cameron, quic_zijuhu, aleksander.lobakin,
ira.weiny, andriy.shevchenko, leon, lukas, bhelgaas, wagi,
djeffery, stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu
In-Reply-To: <20250807014442.3829950-1-pasha.tatashin@soleen.com>
Introduce a self-test mechanism for the LUO to allow verification of
core subsystem management functionality. This is primarily intended
for developers and system integrators validating the live update
feature.
The tests are enabled via the new Kconfig option
CONFIG_LIVEUPDATE_SELFTESTS (default 'n') and are triggered through
a new ioctl command, LIVEUPDATE_IOCTL_SELFTESTS, added to the
/dev/liveupdate device node.
This ioctl accepts commands defined in luo_selftests.h to:
- LUO_CMD_SUBSYSTEM_REGISTER: Creates and registers a dummy LUO
subsystem using the liveupdate_register_subsystem() function. It
allocates a data page and copies initial data from userspace.
- LUO_CMD_SUBSYSTEM_UNREGISTER: Unregisters the specified dummy
subsystem using the liveupdate_unregister_subsystem() function and
cleans up associated test resources.
- LUO_CMD_SUBSYSTEM_GETDATA: Copies the data page associated with a
registered test subsystem back to userspace, allowing verification of
data potentially modified or preserved by test callbacks.
This provides a way to test the fundamental registration and
unregistration flows within the LUO framework from userspace without
requiring a full live update sequence.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/liveupdate/Kconfig | 15 ++
kernel/liveupdate/Makefile | 1 +
kernel/liveupdate/luo_selftests.c | 345 ++++++++++++++++++++++++++++++
kernel/liveupdate/luo_selftests.h | 84 ++++++++
4 files changed, 445 insertions(+)
create mode 100644 kernel/liveupdate/luo_selftests.c
create mode 100644 kernel/liveupdate/luo_selftests.h
diff --git a/kernel/liveupdate/Kconfig b/kernel/liveupdate/Kconfig
index 75a17ca8a592..5be04ede357d 100644
--- a/kernel/liveupdate/Kconfig
+++ b/kernel/liveupdate/Kconfig
@@ -47,6 +47,21 @@ config LIVEUPDATE_SYSFS_API
If unsure, say N.
+config LIVEUPDATE_SELFTESTS
+ bool "Live Update Orchestrator - self-tests"
+ depends on LIVEUPDATE
+ help
+ Say Y here to build self-tests for the LUO framework. When enabled,
+ these tests can be initiated via the ioctl interface to help verify
+ the core live update functionality.
+
+ This option is primarily intended for developers working on the
+ live update feature or for validation purposes during system
+ integration.
+
+ If you are unsure or are building a production kernel where size
+ or attack surface is a concern, say N.
+
config KEXEC_HANDOVER
bool "kexec handover"
depends on ARCH_SUPPORTS_KEXEC_HANDOVER && ARCH_SUPPORTS_KEXEC_FILE
diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile
index 47f5d0378a75..9b8b69517463 100644
--- a/kernel/liveupdate/Makefile
+++ b/kernel/liveupdate/Makefile
@@ -13,4 +13,5 @@ obj-$(CONFIG_KEXEC_HANDOVER) += kexec_handover.o
obj-$(CONFIG_KEXEC_HANDOVER_DEBUG) += kexec_handover_debug.o
obj-$(CONFIG_LIVEUPDATE) += luo.o
+obj-$(CONFIG_LIVEUPDATE_SELFTESTS) += luo_selftests.o
obj-$(CONFIG_LIVEUPDATE_SYSFS_API) += luo_sysfs.o
diff --git a/kernel/liveupdate/luo_selftests.c b/kernel/liveupdate/luo_selftests.c
new file mode 100644
index 000000000000..824d6a99f8fc
--- /dev/null
+++ b/kernel/liveupdate/luo_selftests.c
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (c) 2025, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@soleen.com>
+ */
+
+/**
+ * DOC: LUO Selftests
+ *
+ * We provide ioctl-based selftest interface for the LUO. It provides a
+ * mechanism to test core LUO functionality, particularly the registration,
+ * unregistration, and data handling aspects of LUO subsystems, without
+ * requiring a full live update event sequence.
+ *
+ * The tests are intended primarily for developers working on the LUO framework
+ * or for validation purposes during system integration. This functionality is
+ * conditionally compiled based on the `CONFIG_LIVEUPDATE_SELFTESTS` Kconfig
+ * option and should typically be disabled in production kernels.
+ *
+ * Interface:
+ * The selftests are accessed via the `/dev/liveupdate` character device using
+ * the `LIVEUPDATE_IOCTL_SELFTESTS` ioctl command. The argument to the ioctl
+ * is a pointer to a `struct liveupdate_selftest` structure (defined in
+ * `uapi/linux/liveupdate.h`), which contains:
+ * - `cmd`: The specific selftest command to execute (e.g.,
+ * `LUO_CMD_SUBSYSTEM_REGISTER`).
+ * - `arg`: A pointer to a command-specific argument structure. For subsystem
+ * tests, this points to a `struct luo_arg_subsystem` (defined in
+ * `luo_selftests.h`).
+ *
+ * Commands:
+ * - `LUO_CMD_SUBSYSTEM_REGISTER`:
+ * Registers a new dummy LUO subsystem. It allocates kernel memory for test
+ * data, copies initial data from the user-provided `data_page`, sets up
+ * simple logging callbacks, and calls the core
+ * `liveupdate_register_subsystem()`
+ * function. Requires `arg` pointing to `struct luo_arg_subsystem`.
+ * - `LUO_CMD_SUBSYSTEM_UNREGISTER`:
+ * Unregisters a previously registered dummy subsystem identified by `name`.
+ * It calls the core `liveupdate_unregister_subsystem()` function and then
+ * frees the associated kernel memory and internal tracking structures.
+ * Requires `arg` pointing to `struct luo_arg_subsystem` (only `name` used).
+ * - `LUO_CMD_SUBSYSTEM_GETDATA`:
+ * Copies the content of the kernel data page associated with the specified
+ * dummy subsystem (`name`) back to the user-provided `data_page`. This allows
+ * userspace to verify the state of the data after potential test operations.
+ * Requires `arg` pointing to `struct luo_arg_subsystem`.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/debugfs.h>
+#include <linux/errno.h>
+#include <linux/gfp.h>
+#include <linux/kexec_handover.h>
+#include <linux/liveupdate.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+#include <uapi/linux/liveupdate.h>
+#include "luo_internal.h"
+#include "luo_selftests.h"
+
+static struct luo_subsystems {
+ struct liveupdate_subsystem handle;
+ char name[LUO_NAME_LENGTH];
+ void *data;
+ bool in_use;
+ bool preserved;
+} luo_subsystems[LUO_MAX_SUBSYSTEMS];
+
+/* Only allow one selftest ioctl operation at a time */
+static DEFINE_MUTEX(luo_ioctl_mutex);
+
+static int luo_subsystem_prepare(struct liveupdate_subsystem *h, u64 *data)
+{
+ struct luo_subsystems *s = container_of(h, struct luo_subsystems,
+ handle);
+ unsigned long phys_addr = __pa(s->data);
+ int ret;
+
+ ret = kho_preserve_phys(phys_addr, PAGE_SIZE);
+ if (ret)
+ return ret;
+
+ s->preserved = true;
+ *data = phys_addr;
+ pr_info("Subsystem '%s' prepare data[%lx]\n",
+ s->name, phys_addr);
+
+ if (strstr(s->name, NAME_PREPARE_FAIL))
+ return -EAGAIN;
+
+ return 0;
+}
+
+static int luo_subsystem_freeze(struct liveupdate_subsystem *h, u64 *data)
+{
+ struct luo_subsystems *s = container_of(h, struct luo_subsystems,
+ handle);
+
+ pr_info("Subsystem '%s' freeze data[%llx]\n", s->name, *data);
+
+ return 0;
+}
+
+static void luo_subsystem_cancel(struct liveupdate_subsystem *h, u64 data)
+{
+ struct luo_subsystems *s = container_of(h, struct luo_subsystems,
+ handle);
+
+ pr_info("Subsystem '%s' canel data[%llx]\n", s->name, data);
+ s->preserved = false;
+ WARN_ON(kho_unpreserve_phys(data, PAGE_SIZE));
+}
+
+static void luo_subsystem_finish(struct liveupdate_subsystem *h, u64 data)
+{
+ struct luo_subsystems *s = container_of(h, struct luo_subsystems,
+ handle);
+
+ pr_info("Subsystem '%s' finish data[%llx]\n", s->name, data);
+}
+
+static const struct liveupdate_subsystem_ops luo_selftest_subsys_ops = {
+ .prepare = luo_subsystem_prepare,
+ .freeze = luo_subsystem_freeze,
+ .cancel = luo_subsystem_cancel,
+ .finish = luo_subsystem_finish,
+ .owner = THIS_MODULE,
+};
+
+static int luo_subsystem_idx(char *name)
+{
+ int i;
+
+ for (i = 0; i < LUO_MAX_SUBSYSTEMS; i++) {
+ if (luo_subsystems[i].in_use &&
+ !strcmp(luo_subsystems[i].name, name))
+ break;
+ }
+
+ if (i == LUO_MAX_SUBSYSTEMS) {
+ pr_warn("Subsystem with name '%s' is not registred\n", name);
+
+ return -EINVAL;
+ }
+
+ return i;
+}
+
+static void luo_put_and_free_subsystem(char *name)
+{
+ int i = luo_subsystem_idx(name);
+
+ if (i < 0)
+ return;
+
+ if (luo_subsystems[i].preserved)
+ kho_unpreserve_phys(__pa(luo_subsystems[i].data), PAGE_SIZE);
+ free_page((unsigned long)luo_subsystems[i].data);
+ luo_subsystems[i].in_use = false;
+ luo_subsystems[i].preserved = false;
+}
+
+static int luo_get_and_alloc_subsystem(char *name, void __user *data,
+ struct liveupdate_subsystem **hp)
+{
+ unsigned long page_addr, i;
+
+ page_addr = get_zeroed_page(GFP_KERNEL);
+ if (!page_addr) {
+ pr_warn("Failed to allocate memory for subsystem data\n");
+ return -ENOMEM;
+ }
+
+ if (copy_from_user((void *)page_addr, data, PAGE_SIZE)) {
+ free_page(page_addr);
+ return -EFAULT;
+ }
+
+ for (i = 0; i < LUO_MAX_SUBSYSTEMS; i++) {
+ if (!luo_subsystems[i].in_use)
+ break;
+ }
+
+ if (i == LUO_MAX_SUBSYSTEMS) {
+ pr_warn("Maximum number of subsystems registered\n");
+ free_page(page_addr);
+ return -ENOMEM;
+ }
+
+ luo_subsystems[i].in_use = true;
+ luo_subsystems[i].handle.ops = &luo_selftest_subsys_ops;
+ luo_subsystems[i].handle.name = luo_subsystems[i].name;
+ strscpy(luo_subsystems[i].name, name, LUO_NAME_LENGTH);
+ luo_subsystems[i].data = (void *)page_addr;
+
+ *hp = &luo_subsystems[i].handle;
+
+ return 0;
+}
+
+static int luo_cmd_subsystem_unregister(void __user *argp)
+{
+ struct luo_arg_subsystem arg;
+ int ret, i;
+
+ if (copy_from_user(&arg, argp, sizeof(arg)))
+ return -EFAULT;
+
+ i = luo_subsystem_idx(arg.name);
+ if (i < 0)
+ return i;
+
+ ret = liveupdate_unregister_subsystem(&luo_subsystems[i].handle);
+ if (ret)
+ return ret;
+
+ luo_put_and_free_subsystem(arg.name);
+
+ return 0;
+}
+
+static int luo_cmd_subsystem_register(void __user *argp)
+{
+ struct liveupdate_subsystem *h;
+ struct luo_arg_subsystem arg;
+ int ret;
+
+ if (copy_from_user(&arg, argp, sizeof(arg)))
+ return -EFAULT;
+
+ ret = luo_get_and_alloc_subsystem(arg.name,
+ (void __user *)arg.data_page, &h);
+ if (ret)
+ return ret;
+
+ ret = liveupdate_register_subsystem(h);
+ if (ret)
+ luo_put_and_free_subsystem(arg.name);
+
+ return ret;
+}
+
+static int luo_cmd_subsystem_getdata(void __user *argp)
+{
+ struct luo_arg_subsystem arg;
+ int i;
+
+ if (copy_from_user(&arg, argp, sizeof(arg)))
+ return -EFAULT;
+
+ i = luo_subsystem_idx(arg.name);
+ if (i < 0)
+ return i;
+
+ if (copy_to_user(arg.data_page, luo_subsystems[i].data,
+ PAGE_SIZE)) {
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+static int luo_ioctl_selftests(void __user *argp)
+{
+ struct liveupdate_selftest luo_st;
+ void __user *cmd_argp;
+ int ret = 0;
+
+ if (copy_from_user(&luo_st, argp, sizeof(luo_st)))
+ return -EFAULT;
+
+ cmd_argp = (void __user *)luo_st.arg;
+
+ mutex_lock(&luo_ioctl_mutex);
+ switch (luo_st.cmd) {
+ case LUO_CMD_SUBSYSTEM_REGISTER:
+ ret = luo_cmd_subsystem_register(cmd_argp);
+ break;
+
+ case LUO_CMD_SUBSYSTEM_UNREGISTER:
+ ret = luo_cmd_subsystem_unregister(cmd_argp);
+ break;
+
+ case LUO_CMD_SUBSYSTEM_GETDATA:
+ ret = luo_cmd_subsystem_getdata(cmd_argp);
+ break;
+
+ default:
+ pr_warn("ioctl: unknown self-test command nr: 0x%llx\n",
+ luo_st.cmd);
+ ret = -ENOTTY;
+ break;
+ }
+ mutex_unlock(&luo_ioctl_mutex);
+
+ return ret;
+}
+
+static long luo_selftest_ioctl(struct file *filep, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret = 0;
+
+ if (_IOC_TYPE(cmd) != LIVEUPDATE_IOCTL_TYPE)
+ return -ENOTTY;
+
+ switch (cmd) {
+ case LIVEUPDATE_IOCTL_FREEZE:
+ ret = luo_freeze();
+ break;
+
+ case LIVEUPDATE_IOCTL_SELFTESTS:
+ ret = luo_ioctl_selftests((void __user *)arg);
+ break;
+
+ default:
+ pr_warn("ioctl: unknown command nr: 0x%x\n", _IOC_NR(cmd));
+ ret = -ENOTTY;
+ break;
+ }
+
+ return ret;
+}
+
+static const struct file_operations luo_selftest_fops = {
+ .open = nonseekable_open,
+ .unlocked_ioctl = luo_selftest_ioctl,
+};
+
+static int __init luo_seltesttest_init(void)
+{
+ if (!liveupdate_debugfs_root) {
+ pr_err("liveupdate root is not set\n");
+ return 0;
+ }
+ debugfs_create_file_unsafe("luo_selftest", 0600,
+ liveupdate_debugfs_root, NULL,
+ &luo_selftest_fops);
+ return 0;
+}
+
+late_initcall(luo_seltesttest_init);
diff --git a/kernel/liveupdate/luo_selftests.h b/kernel/liveupdate/luo_selftests.h
new file mode 100644
index 000000000000..098f2e9e6a78
--- /dev/null
+++ b/kernel/liveupdate/luo_selftests.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (c) 2025, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@soleen.com>
+ */
+
+#ifndef _LINUX_LUO_SELFTESTS_H
+#define _LINUX_LUO_SELFTESTS_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+/* Maximum number of subsystem self-test can register */
+#define LUO_MAX_SUBSYSTEMS 16
+#define LUO_NAME_LENGTH 32
+
+#define LUO_CMD_SUBSYSTEM_REGISTER 0
+#define LUO_CMD_SUBSYSTEM_UNREGISTER 1
+#define LUO_CMD_SUBSYSTEM_GETDATA 2
+struct luo_arg_subsystem {
+ char name[LUO_NAME_LENGTH];
+ void *data_page;
+};
+
+/*
+ * Test name prefixes:
+ * normal: prepare and freeze callbacks do not fail
+ * prepare_fail: prepare callback fails for this test.
+ * freeze_fail: freeze callback fails for this test
+ */
+#define NAME_NORMAL "ksft_luo"
+#define NAME_PREPARE_FAIL "ksft_prepare_fail"
+#define NAME_FREEZE_FAIL "ksft_freeze_fail"
+
+/**
+ * struct liveupdate_selftest - Holds directions for the self-test operations.
+ * @cmd: Selftest comman defined in luo_selftests.h.
+ * @arg: Argument for the self test command.
+ *
+ * This structure is used only for the selftest purposes.
+ */
+struct liveupdate_selftest {
+ __u64 cmd;
+ __u64 arg;
+};
+
+/**
+ * LIVEUPDATE_IOCTL_FREEZE - Notify subsystems of imminent reboot
+ * transition.
+ *
+ * Argument: None.
+ *
+ * Notifies the live update subsystem and associated components that the kernel
+ * is about to execute the final reboot transition into the new kernel (e.g.,
+ * via kexec). This action triggers the internal %LIVEUPDATE_FREEZE kernel
+ * event. This event provides subsystems a final, brief opportunity (within the
+ * "blackout window") to save critical state or perform last-moment quiescing.
+ * Any remaining or deferred state saving for items marked via the PRESERVE
+ * ioctls typically occurs in response to the %LIVEUPDATE_FREEZE event.
+ *
+ * This ioctl should only be called when the system is in the
+ * %LIVEUPDATE_STATE_PREPARED state. This command does not transfer data.
+ *
+ * Return: 0 if the notification is successfully processed by the kernel (but
+ * reboot follows). Returns a negative error code if the notification fails
+ * or if the system is not in the %LIVEUPDATE_STATE_PREPARED state.
+ */
+#define LIVEUPDATE_IOCTL_FREEZE \
+ _IO(LIVEUPDATE_IOCTL_TYPE, 0x05)
+
+/**
+ * LIVEUPDATE_IOCTL_SELFTESTS - Interface for the LUO selftests
+ *
+ * Argument: Pointer to &struct liveupdate_selftest.
+ *
+ * Use by LUO selftests, commands are declared in luo_selftests.h
+ *
+ * Return: 0 on success, negative error code on failure (e.g., invalid token).
+ */
+#define LIVEUPDATE_IOCTL_SELFTESTS \
+ _IOWR(LIVEUPDATE_IOCTL_TYPE, 0x08, struct liveupdate_selftest)
+
+#endif /* _LINUX_LUO_SELFTESTS_H */
--
2.50.1.565.gc32cd1483b-goog
^ permalink raw reply related
* [PATCH v3 24/30] docs: add luo documentation
From: Pasha Tatashin @ 2025-08-07 1:44 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, changyuanl, pasha.tatashin, rppt,
dmatlack, rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda,
aliceryhl, masahiroy, akpm, tj, yoann.congal, mmaurer,
roman.gushchin, chenridong, axboe, mark.rutland, jannh,
vincent.guittot, hannes, dan.j.williams, david, joel.granados,
rostedt, anna.schumaker, song, zhangguopeng, linux, linux-kernel,
linux-doc, linux-mm, gregkh, tglx, mingo, bp, dave.hansen, x86,
hpa, rafael, dakr, bartosz.golaszewski, cw00.choi, myungjoo.ham,
yesanishhere, Jonathan.Cameron, quic_zijuhu, aleksander.lobakin,
ira.weiny, andriy.shevchenko, leon, lukas, bhelgaas, wagi,
djeffery, stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu
In-Reply-To: <20250807014442.3829950-1-pasha.tatashin@soleen.com>
Add the documentation files for the Live Update Orchestrator
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
Documentation/admin-guide/index.rst | 1 +
Documentation/admin-guide/liveupdate.rst | 16 +++++++
Documentation/core-api/index.rst | 1 +
Documentation/core-api/liveupdate.rst | 50 ++++++++++++++++++++++
Documentation/userspace-api/index.rst | 1 +
Documentation/userspace-api/liveupdate.rst | 25 +++++++++++
6 files changed, 94 insertions(+)
create mode 100644 Documentation/admin-guide/liveupdate.rst
create mode 100644 Documentation/core-api/liveupdate.rst
create mode 100644 Documentation/userspace-api/liveupdate.rst
diff --git a/Documentation/admin-guide/index.rst b/Documentation/admin-guide/index.rst
index 259d79fbeb94..3f59ccf32760 100644
--- a/Documentation/admin-guide/index.rst
+++ b/Documentation/admin-guide/index.rst
@@ -95,6 +95,7 @@ likely to be of interest on almost any system.
cgroup-v2
cgroup-v1/index
cpu-load
+ liveupdate
mm/index
module-signing
namespaces/index
diff --git a/Documentation/admin-guide/liveupdate.rst b/Documentation/admin-guide/liveupdate.rst
new file mode 100644
index 000000000000..ff05cc1dd784
--- /dev/null
+++ b/Documentation/admin-guide/liveupdate.rst
@@ -0,0 +1,16 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=================
+Live Update sysfs
+=================
+:Author: Pasha Tatashin <pasha.tatashin@soleen.com>
+
+LUO sysfs interface
+===================
+.. kernel-doc:: kernel/liveupdate/luo_sysfs.c
+ :doc: LUO sysfs interface
+
+See Also
+========
+
+- :doc:`Live Update Orchestrator </core-api/liveupdate>`
diff --git a/Documentation/core-api/index.rst b/Documentation/core-api/index.rst
index a03a99c2cac5..a8b7d1417f0a 100644
--- a/Documentation/core-api/index.rst
+++ b/Documentation/core-api/index.rst
@@ -137,6 +137,7 @@ Documents that don't fit elsewhere or which have yet to be categorized.
:maxdepth: 1
librs
+ liveupdate
netlink
.. only:: subproject and html
diff --git a/Documentation/core-api/liveupdate.rst b/Documentation/core-api/liveupdate.rst
new file mode 100644
index 000000000000..41c4b76cd3ec
--- /dev/null
+++ b/Documentation/core-api/liveupdate.rst
@@ -0,0 +1,50 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+========================
+Live Update Orchestrator
+========================
+:Author: Pasha Tatashin <pasha.tatashin@soleen.com>
+
+.. kernel-doc:: kernel/liveupdate/luo_core.c
+ :doc: Live Update Orchestrator (LUO)
+
+LUO Subsystems Participation
+============================
+.. kernel-doc:: kernel/liveupdate/luo_subsystems.c
+ :doc: LUO Subsystems support
+
+LUO Preserving File Descriptors
+===============================
+.. kernel-doc:: kernel/liveupdate/luo_files.c
+ :doc: LUO file descriptors
+
+Public API
+==========
+.. kernel-doc:: include/linux/liveupdate.h
+
+.. kernel-doc:: kernel/liveupdate/luo_core.c
+ :export:
+
+.. kernel-doc:: kernel/liveupdate/luo_subsystems.c
+ :export:
+
+.. kernel-doc:: kernel/liveupdate/luo_files.c
+ :export:
+
+Internal API
+============
+.. kernel-doc:: kernel/liveupdate/luo_core.c
+ :internal:
+
+.. kernel-doc:: kernel/liveupdate/luo_subsystems.c
+ :internal:
+
+.. kernel-doc:: kernel/liveupdate/luo_files.c
+ :internal:
+
+See Also
+========
+
+- :doc:`Live Update uAPI </userspace-api/liveupdate>`
+- :doc:`Live Update SysFS </admin-guide/liveupdate>`
+- :doc:`/core-api/kho/concepts`
diff --git a/Documentation/userspace-api/index.rst b/Documentation/userspace-api/index.rst
index b8c73be4fb11..ee8326932cb0 100644
--- a/Documentation/userspace-api/index.rst
+++ b/Documentation/userspace-api/index.rst
@@ -62,6 +62,7 @@ Everything else
ELF
netlink/index
+ liveupdate
sysfs-platform_profile
vduse
futex2
diff --git a/Documentation/userspace-api/liveupdate.rst b/Documentation/userspace-api/liveupdate.rst
new file mode 100644
index 000000000000..70b5017c0e3c
--- /dev/null
+++ b/Documentation/userspace-api/liveupdate.rst
@@ -0,0 +1,25 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+================
+Live Update uAPI
+================
+:Author: Pasha Tatashin <pasha.tatashin@soleen.com>
+
+ioctl interface
+===============
+.. kernel-doc:: kernel/liveupdate/luo_ioctl.c
+ :doc: LUO ioctl Interface
+
+ioctl uAPI
+===========
+.. kernel-doc:: include/uapi/linux/liveupdate.h
+
+LUO selftests ioctl
+===================
+.. kernel-doc:: kernel/liveupdate/luo_selftests.c
+ :doc: LUO Selftests
+
+See Also
+========
+
+- :doc:`Live Update Orchestrator </core-api/liveupdate>`
--
2.50.1.565.gc32cd1483b-goog
^ permalink raw reply related
* [PATCH v3 23/30] selftests/liveupdate: add subsystem/state tests
From: Pasha Tatashin @ 2025-08-07 1:44 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, changyuanl, pasha.tatashin, rppt,
dmatlack, rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda,
aliceryhl, masahiroy, akpm, tj, yoann.congal, mmaurer,
roman.gushchin, chenridong, axboe, mark.rutland, jannh,
vincent.guittot, hannes, dan.j.williams, david, joel.granados,
rostedt, anna.schumaker, song, zhangguopeng, linux, linux-kernel,
linux-doc, linux-mm, gregkh, tglx, mingo, bp, dave.hansen, x86,
hpa, rafael, dakr, bartosz.golaszewski, cw00.choi, myungjoo.ham,
yesanishhere, Jonathan.Cameron, quic_zijuhu, aleksander.lobakin,
ira.weiny, andriy.shevchenko, leon, lukas, bhelgaas, wagi,
djeffery, stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu
In-Reply-To: <20250807014442.3829950-1-pasha.tatashin@soleen.com>
Introduces a new set of userspace selftests for the LUO. These tests
verify the functionality LUO by using the kernel-side selftest ioctls
provided by the LUO module, primarily focusing on subsystem management
and basic LUO state transitions.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/liveupdate/.gitignore | 1 +
tools/testing/selftests/liveupdate/Makefile | 7 +
tools/testing/selftests/liveupdate/config | 6 +
.../testing/selftests/liveupdate/liveupdate.c | 406 ++++++++++++++++++
5 files changed, 421 insertions(+)
create mode 100644 tools/testing/selftests/liveupdate/.gitignore
create mode 100644 tools/testing/selftests/liveupdate/Makefile
create mode 100644 tools/testing/selftests/liveupdate/config
create mode 100644 tools/testing/selftests/liveupdate/liveupdate.c
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 030da61dbff3..3f76ee8ddda6 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -53,6 +53,7 @@ TARGETS += kvm
TARGETS += landlock
TARGETS += lib
TARGETS += livepatch
+TARGETS += liveupdate
TARGETS += lkdtm
TARGETS += lsm
TARGETS += membarrier
diff --git a/tools/testing/selftests/liveupdate/.gitignore b/tools/testing/selftests/liveupdate/.gitignore
new file mode 100644
index 000000000000..af6e773cf98f
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/.gitignore
@@ -0,0 +1 @@
+/liveupdate
diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/selftests/liveupdate/Makefile
new file mode 100644
index 000000000000..2a573c36016e
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+CFLAGS += -Wall -O2 -Wno-unused-function
+CFLAGS += $(KHDR_INCLUDES)
+
+TEST_GEN_PROGS += liveupdate
+
+include ../lib.mk
diff --git a/tools/testing/selftests/liveupdate/config b/tools/testing/selftests/liveupdate/config
new file mode 100644
index 000000000000..382c85b89570
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/config
@@ -0,0 +1,6 @@
+CONFIG_KEXEC_FILE=y
+CONFIG_KEXEC_HANDOVER=y
+CONFIG_KEXEC_HANDOVER_DEBUG=y
+CONFIG_LIVEUPDATE=y
+CONFIG_LIVEUPDATE_SYSFS_API=y
+CONFIG_LIVEUPDATE_SELFTESTS=y
diff --git a/tools/testing/selftests/liveupdate/liveupdate.c b/tools/testing/selftests/liveupdate/liveupdate.c
new file mode 100644
index 000000000000..b59767a7aaba
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/liveupdate.c
@@ -0,0 +1,406 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (c) 2025, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@soleen.com>
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+#include <linux/liveupdate.h>
+
+#include "../kselftest.h"
+#include "../kselftest_harness.h"
+#include "../../../../kernel/liveupdate/luo_selftests.h"
+
+struct subsystem_info {
+ void *data_page;
+ void *verify_page;
+ char test_name[LUO_NAME_LENGTH];
+ bool registered;
+};
+
+FIXTURE(subsystem) {
+ int fd;
+ int fd_dbg;
+ struct subsystem_info si[LUO_MAX_SUBSYSTEMS];
+};
+
+FIXTURE(state) {
+ int fd;
+ int fd_dbg;
+};
+
+#define LUO_DEVICE "/dev/liveupdate"
+#define LUO_DBG_DEVICE "/sys/kernel/debug/liveupdate/luo_selftest"
+#define LUO_SYSFS_STATE "/sys/kernel/liveupdate/state"
+static size_t page_size;
+
+const char *const luo_state_str[] = {
+ [LIVEUPDATE_STATE_UNDEFINED] = "undefined",
+ [LIVEUPDATE_STATE_NORMAL] = "normal",
+ [LIVEUPDATE_STATE_PREPARED] = "prepared",
+ [LIVEUPDATE_STATE_FROZEN] = "frozen",
+ [LIVEUPDATE_STATE_UPDATED] = "updated",
+};
+
+static int run_luo_selftest_cmd(int fd_dbg, __u64 cmd_code,
+ struct luo_arg_subsystem *subsys_arg)
+{
+ struct liveupdate_selftest k_arg;
+
+ k_arg.cmd = cmd_code;
+ k_arg.arg = (__u64)(unsigned long)subsys_arg;
+
+ return ioctl(fd_dbg, LIVEUPDATE_IOCTL_SELFTESTS, &k_arg);
+}
+
+static int register_subsystem(int fd_dbg, struct subsystem_info *si)
+{
+ struct luo_arg_subsystem subsys_arg;
+ int ret;
+
+ memset(&subsys_arg, 0, sizeof(subsys_arg));
+ snprintf(subsys_arg.name, LUO_NAME_LENGTH, "%s", si->test_name);
+ subsys_arg.data_page = si->data_page;
+
+ ret = run_luo_selftest_cmd(fd_dbg, LUO_CMD_SUBSYSTEM_REGISTER,
+ &subsys_arg);
+ if (!ret)
+ si->registered = true;
+
+ return ret;
+}
+
+static int unregister_subsystem(int fd_dbg, struct subsystem_info *si)
+{
+ struct luo_arg_subsystem subsys_arg;
+ int ret;
+
+ memset(&subsys_arg, 0, sizeof(subsys_arg));
+ snprintf(subsys_arg.name, LUO_NAME_LENGTH, "%s", si->test_name);
+
+ ret = run_luo_selftest_cmd(fd_dbg, LUO_CMD_SUBSYSTEM_UNREGISTER,
+ &subsys_arg);
+ if (!ret)
+ si->registered = false;
+
+ return ret;
+}
+
+static int get_sysfs_state(void)
+{
+ char buf[64];
+ ssize_t len;
+ int fd, i;
+
+ fd = open(LUO_SYSFS_STATE, O_RDONLY);
+ if (fd < 0) {
+ ksft_print_msg("Failed to open sysfs state file '%s': %s\n",
+ LUO_SYSFS_STATE, strerror(errno));
+ return -errno;
+ }
+
+ len = read(fd, buf, sizeof(buf) - 1);
+ close(fd);
+
+ if (len <= 0) {
+ ksft_print_msg("Failed to read sysfs state file '%s': %s\n",
+ LUO_SYSFS_STATE, strerror(errno));
+ return -errno;
+ }
+ if (buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+ else
+ buf[len] = '\0';
+
+ for (i = 0; i < ARRAY_SIZE(luo_state_str); i++) {
+ if (!strcmp(buf, luo_state_str[i]))
+ return i;
+ }
+
+ return -EIO;
+}
+
+FIXTURE_SETUP(state)
+{
+ int state;
+
+ page_size = sysconf(_SC_PAGE_SIZE);
+ self->fd = open(LUO_DEVICE, O_RDWR);
+ if (self->fd < 0)
+ SKIP(return, "open(%s) failed [%d]", LUO_DEVICE, errno);
+
+ self->fd_dbg = open(LUO_DBG_DEVICE, O_RDWR);
+ ASSERT_GE(self->fd_dbg, 0);
+
+ state = get_sysfs_state();
+ if (state < 0) {
+ if (state == -ENOENT || state == -EACCES)
+ SKIP(return, "sysfs state not accessible (%d)", state);
+ }
+}
+
+FIXTURE_TEARDOWN(state)
+{
+ struct liveupdate_ioctl_set_event cancel = {
+ .size = sizeof(cancel),
+ .event = LIVEUPDATE_CANCEL,
+ };
+ struct liveupdate_ioctl_get_state ligs = {.size = sizeof(ligs)};
+
+ ioctl(self->fd, LIVEUPDATE_IOCTL_GET_STATE, &ligs);
+ if (ligs.state != LIVEUPDATE_STATE_NORMAL)
+ ioctl(self->fd, LIVEUPDATE_IOCTL_SET_EVENT, &cancel);
+ close(self->fd);
+}
+
+FIXTURE_SETUP(subsystem)
+{
+ int i;
+
+ page_size = sysconf(_SC_PAGE_SIZE);
+ memset(&self->si, 0, sizeof(self->si));
+ self->fd = open(LUO_DEVICE, O_RDWR);
+ if (self->fd < 0)
+ SKIP(return, "open(%s) failed [%d]", LUO_DEVICE, errno);
+
+ self->fd_dbg = open(LUO_DBG_DEVICE, O_RDWR);
+ ASSERT_GE(self->fd_dbg, 0);
+
+ for (i = 0; i < LUO_MAX_SUBSYSTEMS; i++) {
+ snprintf(self->si[i].test_name, LUO_NAME_LENGTH,
+ NAME_NORMAL ".%d", i);
+
+ self->si[i].data_page = mmap(NULL, page_size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS,
+ -1, 0);
+ ASSERT_NE(MAP_FAILED, self->si[i].data_page);
+ memset(self->si[i].data_page, 'A' + i, page_size);
+
+ self->si[i].verify_page = mmap(NULL, page_size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS,
+ -1, 0);
+ ASSERT_NE(MAP_FAILED, self->si[i].verify_page);
+ memset(self->si[i].verify_page, 0, page_size);
+ }
+}
+
+FIXTURE_TEARDOWN(subsystem)
+{
+ struct liveupdate_ioctl_set_event cancel = {
+ .size = sizeof(cancel),
+ .event = LIVEUPDATE_CANCEL,
+ };
+ enum liveupdate_state state = LIVEUPDATE_STATE_NORMAL;
+ int i;
+
+ ioctl(self->fd, LIVEUPDATE_IOCTL_GET_STATE, &state);
+ if (state != LIVEUPDATE_STATE_NORMAL)
+ ioctl(self->fd, LIVEUPDATE_IOCTL_SET_EVENT, &cancel);
+
+ for (i = 0; i < LUO_MAX_SUBSYSTEMS; i++) {
+ if (self->si[i].registered)
+ unregister_subsystem(self->fd_dbg, &self->si[i]);
+ munmap(self->si[i].data_page, page_size);
+ munmap(self->si[i].verify_page, page_size);
+ }
+
+ close(self->fd);
+}
+
+TEST_F(state, normal)
+{
+ struct liveupdate_ioctl_get_state ligs = {.size = sizeof(ligs)};
+
+ ASSERT_EQ(0, ioctl(self->fd, LIVEUPDATE_IOCTL_GET_STATE, &ligs));
+ ASSERT_EQ(ligs.state, LIVEUPDATE_STATE_NORMAL);
+}
+
+TEST_F(state, prepared)
+{
+ struct liveupdate_ioctl_get_state ligs = {.size = sizeof(ligs)};
+ struct liveupdate_ioctl_set_event prepare = {
+ .size = sizeof(prepare),
+ .event = LIVEUPDATE_PREPARE,
+ };
+ struct liveupdate_ioctl_set_event cancel = {
+ .size = sizeof(cancel),
+ .event = LIVEUPDATE_CANCEL,
+ };
+
+ ASSERT_EQ(0, ioctl(self->fd, LIVEUPDATE_IOCTL_SET_EVENT, &prepare));
+
+ ASSERT_EQ(0, ioctl(self->fd, LIVEUPDATE_IOCTL_GET_STATE, &ligs));
+ ASSERT_EQ(ligs.state, LIVEUPDATE_STATE_PREPARED);
+
+ ASSERT_EQ(0, ioctl(self->fd, LIVEUPDATE_IOCTL_SET_EVENT, &cancel));
+
+ ASSERT_EQ(0, ioctl(self->fd, LIVEUPDATE_IOCTL_GET_STATE, &ligs));
+ ASSERT_EQ(ligs.state, LIVEUPDATE_STATE_NORMAL);
+}
+
+TEST_F(state, sysfs_normal)
+{
+ ASSERT_EQ(LIVEUPDATE_STATE_NORMAL, get_sysfs_state());
+}
+
+TEST_F(state, sysfs_prepared)
+{
+ struct liveupdate_ioctl_set_event prepare = {
+ .size = sizeof(prepare),
+ .event = LIVEUPDATE_PREPARE,
+ };
+ struct liveupdate_ioctl_set_event cancel = {
+ .size = sizeof(cancel),
+ .event = LIVEUPDATE_CANCEL,
+ };
+
+ ASSERT_EQ(0, ioctl(self->fd, LIVEUPDATE_IOCTL_SET_EVENT, &prepare));
+ ASSERT_EQ(LIVEUPDATE_STATE_PREPARED, get_sysfs_state());
+
+ ASSERT_EQ(0, ioctl(self->fd, LIVEUPDATE_IOCTL_SET_EVENT, &cancel));
+ ASSERT_EQ(LIVEUPDATE_STATE_NORMAL, get_sysfs_state());
+}
+
+TEST_F(state, sysfs_frozen)
+{
+ struct liveupdate_ioctl_set_event prepare = {
+ .size = sizeof(prepare),
+ .event = LIVEUPDATE_PREPARE,
+ };
+ struct liveupdate_ioctl_set_event cancel = {
+ .size = sizeof(cancel),
+ .event = LIVEUPDATE_CANCEL,
+ };
+
+ ASSERT_EQ(0, ioctl(self->fd, LIVEUPDATE_IOCTL_SET_EVENT, &prepare));
+
+ ASSERT_EQ(LIVEUPDATE_STATE_PREPARED, get_sysfs_state());
+
+ ASSERT_EQ(0, ioctl(self->fd_dbg, LIVEUPDATE_IOCTL_FREEZE, NULL));
+ ASSERT_EQ(LIVEUPDATE_STATE_FROZEN, get_sysfs_state());
+
+ ASSERT_EQ(0, ioctl(self->fd, LIVEUPDATE_IOCTL_SET_EVENT, &cancel));
+ ASSERT_EQ(LIVEUPDATE_STATE_NORMAL, get_sysfs_state());
+}
+
+TEST_F(subsystem, register_unregister)
+{
+ ASSERT_EQ(0, register_subsystem(self->fd_dbg, &self->si[0]));
+ ASSERT_EQ(0, unregister_subsystem(self->fd_dbg, &self->si[0]));
+}
+
+TEST_F(subsystem, double_unregister)
+{
+ ASSERT_EQ(0, register_subsystem(self->fd_dbg, &self->si[0]));
+ ASSERT_EQ(0, unregister_subsystem(self->fd_dbg, &self->si[0]));
+ EXPECT_NE(0, unregister_subsystem(self->fd_dbg, &self->si[0]));
+ EXPECT_TRUE(errno == EINVAL || errno == ENOENT);
+}
+
+TEST_F(subsystem, register_unregister_many)
+{
+ int i;
+
+ for (i = 0; i < LUO_MAX_SUBSYSTEMS; i++)
+ ASSERT_EQ(0, register_subsystem(self->fd_dbg, &self->si[i]));
+
+ for (i = 0; i < LUO_MAX_SUBSYSTEMS; i++)
+ ASSERT_EQ(0, unregister_subsystem(self->fd_dbg, &self->si[i]));
+}
+
+TEST_F(subsystem, getdata_verify)
+{
+ struct liveupdate_ioctl_get_state ligs = {.size = sizeof(ligs), .state = 0};
+ struct liveupdate_ioctl_set_event prepare = {
+ .size = sizeof(prepare),
+ .event = LIVEUPDATE_PREPARE,
+ };
+ struct liveupdate_ioctl_set_event cancel = {
+ .size = sizeof(cancel),
+ .event = LIVEUPDATE_CANCEL,
+ };
+ int i;
+
+ for (i = 0; i < LUO_MAX_SUBSYSTEMS; i++)
+ ASSERT_EQ(0, register_subsystem(self->fd_dbg, &self->si[i]));
+
+ ASSERT_EQ(0, ioctl(self->fd, LIVEUPDATE_IOCTL_SET_EVENT, &prepare));
+ ASSERT_EQ(0, ioctl(self->fd, LIVEUPDATE_IOCTL_GET_STATE, &ligs));
+ ASSERT_EQ(ligs.state, LIVEUPDATE_STATE_PREPARED);
+
+ for (i = 0; i < LUO_MAX_SUBSYSTEMS; i++) {
+ struct luo_arg_subsystem subsys_arg;
+
+ memset(&subsys_arg, 0, sizeof(subsys_arg));
+ snprintf(subsys_arg.name, LUO_NAME_LENGTH, "%s",
+ self->si[i].test_name);
+ subsys_arg.data_page = self->si[i].verify_page;
+
+ ASSERT_EQ(0, run_luo_selftest_cmd(self->fd_dbg,
+ LUO_CMD_SUBSYSTEM_GETDATA,
+ &subsys_arg));
+ ASSERT_EQ(0, memcmp(self->si[i].data_page,
+ self->si[i].verify_page,
+ page_size));
+ }
+
+ ASSERT_EQ(0, ioctl(self->fd, LIVEUPDATE_IOCTL_SET_EVENT, &cancel));
+ ASSERT_EQ(0, ioctl(self->fd, LIVEUPDATE_IOCTL_GET_STATE, &ligs));
+ ASSERT_EQ(ligs.state, LIVEUPDATE_STATE_NORMAL);
+
+ for (i = 0; i < LUO_MAX_SUBSYSTEMS; i++)
+ ASSERT_EQ(0, unregister_subsystem(self->fd_dbg, &self->si[i]));
+}
+
+TEST_F(subsystem, prepare_fail)
+{
+ struct liveupdate_ioctl_set_event prepare = {
+ .size = sizeof(prepare),
+ .event = LIVEUPDATE_PREPARE,
+ };
+ struct liveupdate_ioctl_set_event cancel = {
+ .size = sizeof(cancel),
+ .event = LIVEUPDATE_CANCEL,
+ };
+ int i;
+
+ snprintf(self->si[LUO_MAX_SUBSYSTEMS - 1].test_name, LUO_NAME_LENGTH,
+ NAME_PREPARE_FAIL ".%d", LUO_MAX_SUBSYSTEMS - 1);
+
+ for (i = 0; i < LUO_MAX_SUBSYSTEMS; i++)
+ ASSERT_EQ(0, register_subsystem(self->fd_dbg, &self->si[i]));
+
+ ASSERT_EQ(-1, ioctl(self->fd, LIVEUPDATE_IOCTL_SET_EVENT, &prepare));
+
+ for (i = 0; i < LUO_MAX_SUBSYSTEMS; i++)
+ ASSERT_EQ(0, unregister_subsystem(self->fd_dbg, &self->si[i]));
+
+ snprintf(self->si[LUO_MAX_SUBSYSTEMS - 1].test_name, LUO_NAME_LENGTH,
+ NAME_NORMAL ".%d", LUO_MAX_SUBSYSTEMS - 1);
+
+ for (i = 0; i < LUO_MAX_SUBSYSTEMS; i++)
+ ASSERT_EQ(0, register_subsystem(self->fd_dbg, &self->si[i]));
+
+ ASSERT_EQ(0, ioctl(self->fd, LIVEUPDATE_IOCTL_SET_EVENT, &prepare));
+ ASSERT_EQ(0, ioctl(self->fd_dbg, LIVEUPDATE_IOCTL_FREEZE, NULL));
+ ASSERT_EQ(0, ioctl(self->fd, LIVEUPDATE_IOCTL_SET_EVENT, &cancel));
+ ASSERT_EQ(LIVEUPDATE_STATE_NORMAL, get_sysfs_state());
+
+ for (i = 0; i < LUO_MAX_SUBSYSTEMS; i++)
+ ASSERT_EQ(0, unregister_subsystem(self->fd_dbg, &self->si[i]));
+}
+
+TEST_HARNESS_MAIN
--
2.50.1.565.gc32cd1483b-goog
^ permalink raw reply related
* [PATCH v3 21/30] kho: move kho debugfs directory to liveupdate
From: Pasha Tatashin @ 2025-08-07 1:44 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, changyuanl, pasha.tatashin, rppt,
dmatlack, rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda,
aliceryhl, masahiroy, akpm, tj, yoann.congal, mmaurer,
roman.gushchin, chenridong, axboe, mark.rutland, jannh,
vincent.guittot, hannes, dan.j.williams, david, joel.granados,
rostedt, anna.schumaker, song, zhangguopeng, linux, linux-kernel,
linux-doc, linux-mm, gregkh, tglx, mingo, bp, dave.hansen, x86,
hpa, rafael, dakr, bartosz.golaszewski, cw00.choi, myungjoo.ham,
yesanishhere, Jonathan.Cameron, quic_zijuhu, aleksander.lobakin,
ira.weiny, andriy.shevchenko, leon, lukas, bhelgaas, wagi,
djeffery, stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu
In-Reply-To: <20250807014442.3829950-1-pasha.tatashin@soleen.com>
Now, that LUO and KHO both live under kernel/liveupdate, it makes
sense to also move the kho debugfs files to liveupdate/
The old names:
/sys/kernel/debug/kho/out/
/sys/kernel/debug/kho/in/
The new names:
/sys/kernel/debug/liveupdate/kho_out/
/sys/kernel/debug/liveupdate/kho_in/
Also, export the liveupdate_debufs_root, so LUO selftests could use
it as well.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/liveupdate/kexec_handover_debug.c | 11 ++++++-----
kernel/liveupdate/luo_internal.h | 4 ++++
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/kernel/liveupdate/kexec_handover_debug.c b/kernel/liveupdate/kexec_handover_debug.c
index af4bad225630..f06d6cdfeab3 100644
--- a/kernel/liveupdate/kexec_handover_debug.c
+++ b/kernel/liveupdate/kexec_handover_debug.c
@@ -14,8 +14,9 @@
#include <linux/libfdt.h>
#include <linux/mm.h>
#include "kexec_handover_internal.h"
+#include "luo_internal.h"
-static struct dentry *debugfs_root;
+struct dentry *liveupdate_debugfs_root;
struct fdt_debugfs {
struct list_head list;
@@ -120,7 +121,7 @@ __init void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt)
INIT_LIST_HEAD(&dbg->fdt_list);
- dir = debugfs_create_dir("in", debugfs_root);
+ dir = debugfs_create_dir("in", liveupdate_debugfs_root);
if (IS_ERR(dir)) {
err = PTR_ERR(dir);
goto err_out;
@@ -180,7 +181,7 @@ __init int kho_out_debugfs_init(struct kho_debugfs *dbg)
INIT_LIST_HEAD(&dbg->fdt_list);
- dir = debugfs_create_dir("out", debugfs_root);
+ dir = debugfs_create_dir("out", liveupdate_debugfs_root);
if (IS_ERR(dir))
return -ENOMEM;
@@ -214,8 +215,8 @@ __init int kho_out_debugfs_init(struct kho_debugfs *dbg)
__init int kho_debugfs_init(void)
{
- debugfs_root = debugfs_create_dir("kho", NULL);
- if (IS_ERR(debugfs_root))
+ liveupdate_debugfs_root = debugfs_create_dir("liveupdate", NULL);
+ if (IS_ERR(liveupdate_debugfs_root))
return -ENOENT;
return 0;
}
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 9091ed04c606..78bea012c383 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -53,4 +53,8 @@ void luo_sysfs_notify(void);
static inline void luo_sysfs_notify(void) {}
#endif
+#ifdef CONFIG_KEXEC_HANDOVER_DEBUG
+extern struct dentry *liveupdate_debugfs_root;
+#endif
+
#endif /* _LINUX_LUO_INTERNAL_H */
--
2.50.1.565.gc32cd1483b-goog
^ permalink raw reply related
* [PATCH v3 20/30] reboot: call liveupdate_reboot() before kexec
From: Pasha Tatashin @ 2025-08-07 1:44 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, changyuanl, pasha.tatashin, rppt,
dmatlack, rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda,
aliceryhl, masahiroy, akpm, tj, yoann.congal, mmaurer,
roman.gushchin, chenridong, axboe, mark.rutland, jannh,
vincent.guittot, hannes, dan.j.williams, david, joel.granados,
rostedt, anna.schumaker, song, zhangguopeng, linux, linux-kernel,
linux-doc, linux-mm, gregkh, tglx, mingo, bp, dave.hansen, x86,
hpa, rafael, dakr, bartosz.golaszewski, cw00.choi, myungjoo.ham,
yesanishhere, Jonathan.Cameron, quic_zijuhu, aleksander.lobakin,
ira.weiny, andriy.shevchenko, leon, lukas, bhelgaas, wagi,
djeffery, stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu
In-Reply-To: <20250807014442.3829950-1-pasha.tatashin@soleen.com>
Modify the reboot() syscall handler in kernel/reboot.c to call
liveupdate_reboot() when processing the LINUX_REBOOT_CMD_KEXEC
command.
This ensures that the Live Update Orchestrator is notified just
before the kernel executes the kexec jump. The liveupdate_reboot()
function triggers the final LIVEUPDATE_FREEZE event, allowing
participating subsystems to perform last-minute state saving within
the blackout window, and transitions the LUO state machine to FROZEN.
The call is placed immediately before kernel_kexec() to ensure LUO
finalization happens at the latest possible moment before the kernel
transition.
If liveupdate_reboot() returns an error (indicating a failure during
LUO finalization), the kexec operation is aborted to prevent proceeding
with an inconsistent state.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/reboot.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/kernel/reboot.c b/kernel/reboot.c
index ec087827c85c..bdeb04a773db 100644
--- a/kernel/reboot.c
+++ b/kernel/reboot.c
@@ -13,6 +13,7 @@
#include <linux/kexec.h>
#include <linux/kmod.h>
#include <linux/kmsg_dump.h>
+#include <linux/liveupdate.h>
#include <linux/reboot.h>
#include <linux/suspend.h>
#include <linux/syscalls.h>
@@ -797,6 +798,9 @@ SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
#ifdef CONFIG_KEXEC_CORE
case LINUX_REBOOT_CMD_KEXEC:
+ ret = liveupdate_reboot();
+ if (ret)
+ break;
ret = kernel_kexec();
break;
#endif
--
2.50.1.565.gc32cd1483b-goog
^ permalink raw reply related
* [PATCH v3 19/30] liveupdate: luo_sysfs: add sysfs state monitoring
From: Pasha Tatashin @ 2025-08-07 1:44 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, changyuanl, pasha.tatashin, rppt,
dmatlack, rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda,
aliceryhl, masahiroy, akpm, tj, yoann.congal, mmaurer,
roman.gushchin, chenridong, axboe, mark.rutland, jannh,
vincent.guittot, hannes, dan.j.williams, david, joel.granados,
rostedt, anna.schumaker, song, zhangguopeng, linux, linux-kernel,
linux-doc, linux-mm, gregkh, tglx, mingo, bp, dave.hansen, x86,
hpa, rafael, dakr, bartosz.golaszewski, cw00.choi, myungjoo.ham,
yesanishhere, Jonathan.Cameron, quic_zijuhu, aleksander.lobakin,
ira.weiny, andriy.shevchenko, leon, lukas, bhelgaas, wagi,
djeffery, stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu
In-Reply-To: <20250807014442.3829950-1-pasha.tatashin@soleen.com>
Introduce a sysfs interface for the Live Update Orchestrator
under /sys/kernel/liveupdate/. This interface provides a way for
userspace tools and scripts to monitor the current state of the LUO
state machine.
The main feature is a read-only file, state, which displays the
current LUO state as a string ("normal", "prepared", "frozen",
"updated"). The interface uses sysfs_notify to allow userspace
listeners (e.g., via poll) to be efficiently notified of state changes.
ABI documentation for this new sysfs interface is added in
Documentation/ABI/testing/sysfs-kernel-liveupdate.
This read-only sysfs interface complements the main ioctl interface
provided by /dev/liveupdate, which handles LUO control operations and
resource management.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
.../ABI/testing/sysfs-kernel-liveupdate | 51 ++++++++++
kernel/liveupdate/Kconfig | 18 ++++
kernel/liveupdate/Makefile | 1 +
kernel/liveupdate/luo_core.c | 1 +
kernel/liveupdate/luo_internal.h | 6 ++
kernel/liveupdate/luo_sysfs.c | 92 +++++++++++++++++++
6 files changed, 169 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-kernel-liveupdate
create mode 100644 kernel/liveupdate/luo_sysfs.c
diff --git a/Documentation/ABI/testing/sysfs-kernel-liveupdate b/Documentation/ABI/testing/sysfs-kernel-liveupdate
new file mode 100644
index 000000000000..bb85cbae4943
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-kernel-liveupdate
@@ -0,0 +1,51 @@
+What: /sys/kernel/liveupdate/
+Date: May 2025
+KernelVersion: 6.16.0
+Contact: pasha.tatashin@soleen.com
+Description: Directory containing interfaces to query the live
+ update orchestrator. Live update is the ability to reboot the
+ host kernel (e.g., via kexec, without a full power cycle) while
+ keeping specifically designated devices operational ("alive")
+ across the transition. After the new kernel boots, these devices
+ can be re-attached to their original workloads (e.g., virtual
+ machines) with their state preserved. This is particularly
+ useful, for example, for quick hypervisor updates without
+ terminating running virtual machines.
+
+
+What: /sys/kernel/liveupdate/state
+Date: May 2025
+KernelVersion: 6.16.0
+Contact: pasha.tatashin@soleen.com
+Description: Read-only file that displays the current state of the live
+ update orchestrator as a string. Possible values are:
+
+ "normal" No live update operation is in progress. This is
+ the default operational state.
+
+ "prepared" The live update preparation phase has completed
+ successfully (e.g., triggered via the
+ /dev/liveupdate event). Kernel subsystems have
+ been notified via the %LIVEUPDATE_PREPARE
+ event/callback and should have initiated state
+ saving. User workloads (e.g., VMs) are generally
+ still running, but some operations (like device
+ unbinding or new DMA mappings) might be
+ restricted. The system is ready for the reboot
+ trigger.
+
+ "frozen" The final reboot notification has been sent
+ (e.g., triggered via the 'reboot()' syscall),
+ corresponding to the %LIVEUPDATE_REBOOT kernel
+ event. Subsystems have had their final chance to
+ save state. User workloads must be suspended.
+ The system is about to execute the reboot into
+ the new kernel (imminent kexec). This state
+ corresponds to the "blackout window".
+
+ "updated" The system has successfully rebooted into the
+ new kernel via live update. Restoration of
+ preserved resources can now occur (typically via
+ ioctl commands). The system is awaiting the
+ final 'finish' signal after user space completes
+ restoration tasks.
diff --git a/kernel/liveupdate/Kconfig b/kernel/liveupdate/Kconfig
index f6b0bde188d9..75a17ca8a592 100644
--- a/kernel/liveupdate/Kconfig
+++ b/kernel/liveupdate/Kconfig
@@ -29,6 +29,24 @@ config LIVEUPDATE
If unsure, say N.
+config LIVEUPDATE_SYSFS_API
+ bool "Live Update sysfs monitoring interface"
+ depends on SYSFS
+ depends on LIVEUPDATE
+ help
+ Enable a sysfs interface for the Live Update Orchestrator
+ at /sys/kernel/liveupdate/.
+
+ This allows monitoring the LUO state ('normal', 'prepared',
+ 'frozen', 'updated') via the read-only 'state' file.
+
+ This interface complements the primary /dev/liveupdate ioctl
+ interface, which handles the full update process.
+ This sysfs API may be useful for scripting, or userspace monitoring
+ needed to coordinate application restarts and minimize downtime.
+
+ If unsure, say N.
+
config KEXEC_HANDOVER
bool "kexec handover"
depends on ARCH_SUPPORTS_KEXEC_HANDOVER && ARCH_SUPPORTS_KEXEC_FILE
diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile
index c67fa2797796..47f5d0378a75 100644
--- a/kernel/liveupdate/Makefile
+++ b/kernel/liveupdate/Makefile
@@ -13,3 +13,4 @@ obj-$(CONFIG_KEXEC_HANDOVER) += kexec_handover.o
obj-$(CONFIG_KEXEC_HANDOVER_DEBUG) += kexec_handover_debug.o
obj-$(CONFIG_LIVEUPDATE) += luo.o
+obj-$(CONFIG_LIVEUPDATE_SYSFS_API) += luo_sysfs.o
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 64d53b31d6d8..bd07ee859112 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -100,6 +100,7 @@ static inline bool is_current_luo_state(enum liveupdate_state expected_state)
static void __luo_set_state(enum liveupdate_state state)
{
WRITE_ONCE(luo_state, state);
+ luo_sysfs_notify();
}
static inline void luo_set_state(enum liveupdate_state state)
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 01bd0d3b023b..9091ed04c606 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -47,4 +47,10 @@ int luo_file_freeze(u64 token);
int luo_file_cancel(u64 token);
int luo_file_finish(u64 token);
+#ifdef CONFIG_LIVEUPDATE_SYSFS_API
+void luo_sysfs_notify(void);
+#else
+static inline void luo_sysfs_notify(void) {}
+#endif
+
#endif /* _LINUX_LUO_INTERNAL_H */
diff --git a/kernel/liveupdate/luo_sysfs.c b/kernel/liveupdate/luo_sysfs.c
new file mode 100644
index 000000000000..935946bb741b
--- /dev/null
+++ b/kernel/liveupdate/luo_sysfs.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (c) 2025, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@soleen.com>
+ */
+
+/**
+ * DOC: LUO sysfs interface
+ *
+ * Provides a sysfs interface at ``/sys/kernel/liveupdate/`` for monitoring LUO
+ * state. Live update allows rebooting the kernel (via kexec) while preserving
+ * designated device state for attached workloads (e.g., VMs), useful for
+ * minimizing downtime during hypervisor updates.
+ *
+ * /sys/kernel/liveupdate/state
+ * ----------------------------
+ * - Permissions: Read-only
+ * - Description: Displays the current LUO state string.
+ * - Valid States:
+ * @normal
+ * Idle state.
+ * @prepared
+ * Preparation phase complete (triggered via '/dev/liveupdate'). Resources
+ * checked, state saving initiated via %LIVEUPDATE_PREPARE event.
+ * Workloads mostly running but may be restricted. Ready forreboot
+ * trigger.
+ * @frozen
+ * Final reboot notification sent (triggered via 'reboot'). Corresponds to
+ * %LIVEUPDATE_REBOOT event. Final state saving. Workloads must be
+ * suspended. System about to kexec ("blackout window").
+ * @updated
+ * New kernel booted via live update. Awaiting 'finish' signal.
+ *
+ * Userspace Interaction & Blackout Window Reduction
+ * -------------------------------------------------
+ * Userspace monitors the ``state`` file to coordinate actions:
+ * - Suspend workloads before @frozen state is entered.
+ * - Initiate resource restoration upon entering @updated state.
+ * - Resume workloads after restoration, minimizing downtime.
+ */
+
+#include <linux/kobject.h>
+#include <linux/liveupdate.h>
+#include <linux/sysfs.h>
+#include "luo_internal.h"
+
+static bool luo_sysfs_initialized;
+
+#define LUO_DIR_NAME "liveupdate"
+
+void luo_sysfs_notify(void)
+{
+ if (luo_sysfs_initialized)
+ sysfs_notify(kernel_kobj, LUO_DIR_NAME, "state");
+}
+
+/* Show the current live update state */
+static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%s\n", luo_current_state_str());
+}
+
+static struct kobj_attribute state_attribute = __ATTR_RO(state);
+
+static struct attribute *luo_attrs[] = {
+ &state_attribute.attr,
+ NULL
+};
+
+static struct attribute_group luo_attr_group = {
+ .attrs = luo_attrs,
+ .name = LUO_DIR_NAME,
+};
+
+static int __init luo_init(void)
+{
+ int ret;
+
+ ret = sysfs_create_group(kernel_kobj, &luo_attr_group);
+ if (ret) {
+ pr_err("Failed to create group\n");
+ return ret;
+ }
+
+ luo_sysfs_initialized = true;
+ pr_info("Initialized\n");
+
+ return 0;
+}
+subsys_initcall(luo_init);
--
2.50.1.565.gc32cd1483b-goog
^ permalink raw reply related
* [PATCH v3 18/30] liveupdate: luo_files: luo_ioctl: Add ioctls for per-file state management
From: Pasha Tatashin @ 2025-08-07 1:44 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, changyuanl, pasha.tatashin, rppt,
dmatlack, rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda,
aliceryhl, masahiroy, akpm, tj, yoann.congal, mmaurer,
roman.gushchin, chenridong, axboe, mark.rutland, jannh,
vincent.guittot, hannes, dan.j.williams, david, joel.granados,
rostedt, anna.schumaker, song, zhangguopeng, linux, linux-kernel,
linux-doc, linux-mm, gregkh, tglx, mingo, bp, dave.hansen, x86,
hpa, rafael, dakr, bartosz.golaszewski, cw00.choi, myungjoo.ham,
yesanishhere, Jonathan.Cameron, quic_zijuhu, aleksander.lobakin,
ira.weiny, andriy.shevchenko, leon, lukas, bhelgaas, wagi,
djeffery, stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu
In-Reply-To: <20250807014442.3829950-1-pasha.tatashin@soleen.com>
Introduce a set of new ioctls to allow a userspace agent to query and
control the live update state of individual file descriptors that have
been registered for preservation.
Previously, state transitions (prepare, freeze, finish) were handled
globally for all registered resources by the main LUO state machine.
This patch provides a more granular interface, enabling a controlling
agent to manage the lifecycle of specific FDs independently, which is
useful for performance reasons.
- Adds LIVEUPDATE_IOCTL_GET_FD_STATE to query the current state
(e.g., NORMAL, PREPARED, FROZEN) of a file identified by its token.
- Adds LIVEUPDATE_IOCTL_SET_FD_EVENT to trigger state transitions
(PREPARE, FREEZE, CANCEL, FINISH) for a single file.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
include/uapi/linux/liveupdate.h | 62 +++++++++++++
kernel/liveupdate/luo_files.c | 152 +++++++++++++++++++++++++++++++
kernel/liveupdate/luo_internal.h | 8 ++
kernel/liveupdate/luo_ioctl.c | 48 ++++++++++
4 files changed, 270 insertions(+)
diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
index 37ec5656443b..833da5a8c064 100644
--- a/include/uapi/linux/liveupdate.h
+++ b/include/uapi/linux/liveupdate.h
@@ -128,6 +128,8 @@ enum {
LIVEUPDATE_CMD_FD_RESTORE = 0x02,
LIVEUPDATE_CMD_GET_STATE = 0x03,
LIVEUPDATE_CMD_SET_EVENT = 0x04,
+ LIVEUPDATE_CMD_GET_FD_STATE = 0x05,
+ LIVEUPDATE_CMD_SET_FD_EVENT = 0x06,
};
/**
@@ -334,4 +336,64 @@ struct liveupdate_ioctl_set_event {
#define LIVEUPDATE_IOCTL_SET_EVENT \
_IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SET_EVENT)
+/**
+ * struct liveupdate_ioctl_get_fd_state - ioctl(LIVEUPDATE_IOCTL_GET_FD_STATE)
+ * @size: Input; sizeof(struct liveupdate_ioctl_get_fd_state)
+ * @incoming: Input; If 1, query the state of a restored file from the incoming
+ * (previous kernel's) set. If 0, query a file being prepared for
+ * preservation in the current set.
+ * @token: Input; Token of FD for which to get state.
+ * @state: Output; The live update state of this FD.
+ *
+ * Query the current live update state of a specific preserved file descriptor.
+ *
+ * - %LIVEUPDATE_STATE_NORMAL: Default state
+ * - %LIVEUPDATE_STATE_PREPARED: Prepare callback has been performed on this FD.
+ * - %LIVEUPDATE_STATE_FROZEN: Freeze callback ahs been performed on this FD.
+ * - %LIVEUPDATE_STATE_UPDATED: The system has successfully rebooted into the
+ * new kernel.
+ *
+ * See the definition of &enum liveupdate_state for more details on each state.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+struct liveupdate_ioctl_get_fd_state {
+ __u32 size;
+ __u8 incoming;
+ __aligned_u64 token;
+ __u32 state;
+};
+
+#define LIVEUPDATE_IOCTL_GET_FD_STATE \
+ _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_GET_FD_STATE)
+
+/**
+ * struct liveupdate_ioctl_set_fd_event - ioctl(LIVEUPDATE_IOCTL_SET_FD_EVENT)
+ * @size: Input; sizeof(struct liveupdate_ioctl_set_fd_event)
+ * @event: Input; The live update event.
+ * @token: Input; Token of FD for which to set the provided event.
+ *
+ * Notify a specific preserved file descriptor of an event, that causes a state
+ * transition for that file descriptor.
+ *
+ * Event, can be one of the following:
+ *
+ * - %LIVEUPDATE_PREPARE: Initiates the FD live update preparation phase.
+ * - %LIVEUPDATE_FREEZE: Initiates the FD live update freeze phase.
+ * - %LIVEUPDATE_CANCEL: Cancel the FD preparation or freeze phase.
+ * - %LIVEUPDATE_FINISH: FD Restoration completion and trigger cleanup.
+ *
+ * See the definition of &enum liveupdate_event for more details on each state.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+struct liveupdate_ioctl_set_fd_event {
+ __u32 size;
+ __u32 event;
+ __aligned_u64 token;
+};
+
+#define LIVEUPDATE_IOCTL_SET_FD_EVENT \
+ _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SET_FD_EVENT)
+
#endif /* _UAPI_LIVEUPDATE_H */
diff --git a/kernel/liveupdate/luo_files.c b/kernel/liveupdate/luo_files.c
index 63f8b086b785..0d68d0c8c45e 100644
--- a/kernel/liveupdate/luo_files.c
+++ b/kernel/liveupdate/luo_files.c
@@ -740,6 +740,158 @@ void luo_unregister_all_files(void)
WARN_ON_ONCE(atomic64_read(&luo_files_count) != 0);
}
+/**
+ * luo_file_get_state - Get the preservation state of a specific file.
+ * @token: The token of the file to query.
+ * @statep: Output pointer to store the file's current live update state.
+ * @incoming: If true, query the state of a restored file from the incoming
+ * (previous kernel's) set. If false, query a file being prepared
+ * for preservation in the current set.
+ *
+ * Finds the file associated with the given @token in either the incoming
+ * or outgoing tracking arrays and returns its current LUO state
+ * (NORMAL, PREPARED, FROZEN, UPDATED).
+ *
+ * Return: 0 on success, -ENOENT if the token is not found.
+ */
+int luo_file_get_state(u64 token, enum liveupdate_state *statep, bool incoming)
+{
+ struct luo_file *luo_file;
+ struct xarray *target_xa;
+ int ret = 0;
+
+ luo_state_read_enter();
+
+ target_xa = incoming ? &luo_files_xa_in : &luo_files_xa_out;
+ luo_file = xa_load(target_xa, token);
+
+ if (!luo_file) {
+ ret = -ENOENT;
+ goto out_unlock;
+ }
+
+ scoped_guard(mutex, &luo_file->mutex)
+ *statep = luo_file->state;
+
+out_unlock:
+ luo_state_read_exit();
+ return ret;
+}
+
+/**
+ * luo_file_prepare - Prepare a single registered file for live update.
+ * @token: The token of the file to prepare.
+ *
+ * Finds the file associated with @token and transitions it to the PREPARED
+ * state by invoking its handler's ->prepare() callback. This allows for
+ * granular, per-file preparation before the global LUO PREPARE event.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int luo_file_prepare(u64 token)
+{
+ struct luo_file *luo_file;
+ int ret;
+
+ luo_state_read_enter();
+ luo_file = xa_load(&luo_files_xa_out, token);
+ if (!luo_file) {
+ ret = -ENOENT;
+ goto out_unlock;
+ }
+
+ ret = luo_files_prepare_one(luo_file);
+out_unlock:
+ luo_state_read_exit();
+ return ret;
+}
+
+/**
+ * luo_file_freeze - Freeze a single prepared file for live update.
+ * @token: The token of the file to freeze.
+ *
+ * Finds the file associated with @token and transitions it from the PREPARED
+ * to the FROZEN state by invoking its handler's ->freeze() callback. This is
+ * typically used for final, "blackout window" state saving for a specific
+ * file.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int luo_file_freeze(u64 token)
+{
+ struct luo_file *luo_file;
+ int ret;
+
+ luo_state_read_enter();
+ luo_file = xa_load(&luo_files_xa_out, token);
+ if (!luo_file) {
+ ret = -ENOENT;
+ goto out_unlock;
+ }
+
+ ret = luo_files_freeze_one(luo_file);
+out_unlock:
+ luo_state_read_exit();
+ return ret;
+}
+
+int luo_file_cancel(u64 token)
+{
+ struct luo_file *luo_file;
+ int ret = 0;
+
+ luo_state_read_enter();
+ luo_file = xa_load(&luo_files_xa_out, token);
+ if (!luo_file) {
+ ret = -ENOENT;
+ goto out_unlock;
+ }
+
+ luo_files_cancel_one(luo_file);
+out_unlock:
+ luo_state_read_exit();
+ return ret;
+}
+
+/**
+ * luo_file_finish - Clean-up a single restored file after live update.
+ * @token: The token of the file to finalize.
+ *
+ * This function is called in the new kernel after a live update, typically
+ * after a file has been restored via luo_retrieve_file() and is no longer
+ * needed by the userspace agent in its preserved state. It invokes the
+ * handler's ->finish() callback, allowing for any final cleanup of the
+ * preserved state associated with this specific file.
+ *
+ * This must be called when LUO is in the UPDATED state.
+ *
+ * Return: 0 on success, -ENOENT if the token is not found, -EBUSY if not
+ * in the UPDATED state.
+ */
+int luo_file_finish(u64 token)
+{
+ struct luo_file *luo_file;
+ int ret = 0;
+
+ luo_state_read_enter();
+ if (!liveupdate_state_updated()) {
+ pr_warn("finish can only be done in UPDATED state\n");
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+ luo_file = xa_load(&luo_files_xa_in, token);
+ if (!luo_file) {
+ ret = -ENOENT;
+ goto out_unlock;
+ }
+
+ luo_files_finish_one(luo_file);
+out_unlock:
+ luo_state_read_exit();
+ return ret;
+}
+
/**
* luo_retrieve_file - Find a registered file instance by its token.
* @token: The unique token of the file instance to retrieve.
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 189e032d7738..01bd0d3b023b 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -8,6 +8,8 @@
#ifndef _LINUX_LUO_INTERNAL_H
#define _LINUX_LUO_INTERNAL_H
+#include <uapi/linux/liveupdate.h>
+
/*
* Handles a deserialization failure: devices and memory is in unpredictable
* state.
@@ -39,4 +41,10 @@ int luo_register_file(u64 token, int fd);
int luo_unregister_file(u64 token);
void luo_unregister_all_files(void);
+int luo_file_get_state(u64 token, enum liveupdate_state *statep, bool incoming);
+int luo_file_prepare(u64 token);
+int luo_file_freeze(u64 token);
+int luo_file_cancel(u64 token);
+int luo_file_finish(u64 token);
+
#endif /* _LINUX_LUO_INTERNAL_H */
diff --git a/kernel/liveupdate/luo_ioctl.c b/kernel/liveupdate/luo_ioctl.c
index 7ca33d1c868f..4c0f6708e411 100644
--- a/kernel/liveupdate/luo_ioctl.c
+++ b/kernel/liveupdate/luo_ioctl.c
@@ -127,6 +127,48 @@ static int luo_ioctl_set_event(struct luo_ucmd *ucmd)
return ret;
}
+static int luo_ioctl_get_fd_state(struct luo_ucmd *ucmd)
+{
+ struct liveupdate_ioctl_get_fd_state *argp = ucmd->cmd;
+ enum liveupdate_state state;
+ int ret;
+
+ ret = luo_file_get_state(argp->token, &state, !!argp->incoming);
+ if (ret)
+ return ret;
+
+ argp->state = state;
+ if (copy_to_user(ucmd->ubuffer, argp, ucmd->user_size))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int luo_ioctl_set_fd_event(struct luo_ucmd *ucmd)
+{
+ struct liveupdate_ioctl_set_fd_event *argp = ucmd->cmd;
+ int ret;
+
+ switch (argp->event) {
+ case LIVEUPDATE_PREPARE:
+ ret = luo_file_prepare(argp->token);
+ break;
+ case LIVEUPDATE_FREEZE:
+ ret = luo_file_freeze(argp->token);
+ break;
+ case LIVEUPDATE_FINISH:
+ ret = luo_file_finish(argp->token);
+ break;
+ case LIVEUPDATE_CANCEL:
+ ret = luo_file_cancel(argp->token);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
static int luo_open(struct inode *inodep, struct file *filep)
{
if (atomic_cmpxchg(&luo_device_in_use, 0, 1))
@@ -149,6 +191,8 @@ union ucmd_buffer {
struct liveupdate_ioctl_fd_restore restore;
struct liveupdate_ioctl_get_state state;
struct liveupdate_ioctl_set_event event;
+ struct liveupdate_ioctl_get_fd_state fd_state;
+ struct liveupdate_ioctl_set_fd_event fd_event;
};
struct luo_ioctl_op {
@@ -179,6 +223,10 @@ static const struct luo_ioctl_op luo_ioctl_ops[] = {
struct liveupdate_ioctl_get_state, state),
IOCTL_OP(LIVEUPDATE_IOCTL_SET_EVENT, luo_ioctl_set_event,
struct liveupdate_ioctl_set_event, event),
+ IOCTL_OP(LIVEUPDATE_IOCTL_GET_FD_STATE, luo_ioctl_get_fd_state,
+ struct liveupdate_ioctl_get_fd_state, token),
+ IOCTL_OP(LIVEUPDATE_IOCTL_SET_FD_EVENT, luo_ioctl_set_fd_event,
+ struct liveupdate_ioctl_set_fd_event, token),
};
static long luo_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
--
2.50.1.565.gc32cd1483b-goog
^ permalink raw reply related
* [PATCH v3 17/30] liveupdate: luo_files: luo_ioctl: Unregister all FDs on device close
From: Pasha Tatashin @ 2025-08-07 1:44 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, changyuanl, pasha.tatashin, rppt,
dmatlack, rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda,
aliceryhl, masahiroy, akpm, tj, yoann.congal, mmaurer,
roman.gushchin, chenridong, axboe, mark.rutland, jannh,
vincent.guittot, hannes, dan.j.williams, david, joel.granados,
rostedt, anna.schumaker, song, zhangguopeng, linux, linux-kernel,
linux-doc, linux-mm, gregkh, tglx, mingo, bp, dave.hansen, x86,
hpa, rafael, dakr, bartosz.golaszewski, cw00.choi, myungjoo.ham,
yesanishhere, Jonathan.Cameron, quic_zijuhu, aleksander.lobakin,
ira.weiny, andriy.shevchenko, leon, lukas, bhelgaas, wagi,
djeffery, stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu
In-Reply-To: <20250807014442.3829950-1-pasha.tatashin@soleen.com>
Currently, a file descriptor registered for preservation via the remains
globally registered with LUO until it is explicitly unregistered. This
creates a potential for resource leaks into the next kernel if the
userspace agent crashes or exits without proper cleanup before a live
update is fully initiated.
This patch ties the lifetime of FD preservation requests to the lifetime
of the open file descriptor for /dev/liveupdate, creating an implicit
"session".
When the /dev/liveupdate file descriptor is closed (either explicitly
via close() or implicitly on process exit/crash), the .release
handler, luo_release(), is now called. This handler invokes the new
function luo_unregister_all_files(), which iterates through all FDs
that were preserved through that session and unregisters them.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/liveupdate/luo_files.c | 19 +++++++++++++++++++
kernel/liveupdate/luo_internal.h | 1 +
kernel/liveupdate/luo_ioctl.c | 1 +
3 files changed, 21 insertions(+)
diff --git a/kernel/liveupdate/luo_files.c b/kernel/liveupdate/luo_files.c
index 33577c9e9a64..63f8b086b785 100644
--- a/kernel/liveupdate/luo_files.c
+++ b/kernel/liveupdate/luo_files.c
@@ -721,6 +721,25 @@ int luo_unregister_file(u64 token)
return ret;
}
+/**
+ * luo_unregister_all_files - Unpreserve all currently registered files.
+ *
+ * Iterates through all file descriptors currently registered for preservation
+ * and unregisters them, freeing all associated resources. This is typically
+ * called when LUO agent exits.
+ */
+void luo_unregister_all_files(void)
+{
+ struct luo_file *luo_file;
+ unsigned long token;
+
+ luo_state_read_enter();
+ xa_for_each(&luo_files_xa_out, token, luo_file)
+ __luo_unregister_file(token);
+ luo_state_read_exit();
+ WARN_ON_ONCE(atomic64_read(&luo_files_count) != 0);
+}
+
/**
* luo_retrieve_file - Find a registered file instance by its token.
* @token: The unique token of the file instance to retrieve.
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 5692196fd425..189e032d7738 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -37,5 +37,6 @@ void luo_do_subsystems_cancel_calls(void);
int luo_retrieve_file(u64 token, struct file **filep);
int luo_register_file(u64 token, int fd);
int luo_unregister_file(u64 token);
+void luo_unregister_all_files(void);
#endif /* _LINUX_LUO_INTERNAL_H */
diff --git a/kernel/liveupdate/luo_ioctl.c b/kernel/liveupdate/luo_ioctl.c
index 6f61569c94e8..7ca33d1c868f 100644
--- a/kernel/liveupdate/luo_ioctl.c
+++ b/kernel/liveupdate/luo_ioctl.c
@@ -137,6 +137,7 @@ static int luo_open(struct inode *inodep, struct file *filep)
static int luo_release(struct inode *inodep, struct file *filep)
{
+ luo_unregister_all_files();
atomic_set(&luo_device_in_use, 0);
return 0;
--
2.50.1.565.gc32cd1483b-goog
^ permalink raw reply related
* [PATCH v3 16/30] liveupdate: luo_ioctl: add userpsace interface
From: Pasha Tatashin @ 2025-08-07 1:44 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, changyuanl, pasha.tatashin, rppt,
dmatlack, rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda,
aliceryhl, masahiroy, akpm, tj, yoann.congal, mmaurer,
roman.gushchin, chenridong, axboe, mark.rutland, jannh,
vincent.guittot, hannes, dan.j.williams, david, joel.granados,
rostedt, anna.schumaker, song, zhangguopeng, linux, linux-kernel,
linux-doc, linux-mm, gregkh, tglx, mingo, bp, dave.hansen, x86,
hpa, rafael, dakr, bartosz.golaszewski, cw00.choi, myungjoo.ham,
yesanishhere, Jonathan.Cameron, quic_zijuhu, aleksander.lobakin,
ira.weiny, andriy.shevchenko, leon, lukas, bhelgaas, wagi,
djeffery, stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu
In-Reply-To: <20250807014442.3829950-1-pasha.tatashin@soleen.com>
Introduce the user-space interface for the Live Update Orchestrator
via ioctl commands, enabling external control over the live update
process and management of preserved resources.
The idea is that there is going to be a single userspace agent driving
the live update, therefore, only a single process can ever hold this
device opened at a time.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
include/uapi/linux/liveupdate.h | 243 ++++++++++++++++++++++++++++++++
kernel/liveupdate/luo_ioctl.c | 200 ++++++++++++++++++++++++++
2 files changed, 443 insertions(+)
diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
index 3cb09b2c4353..37ec5656443b 100644
--- a/include/uapi/linux/liveupdate.h
+++ b/include/uapi/linux/liveupdate.h
@@ -14,6 +14,32 @@
#include <linux/ioctl.h>
#include <linux/types.h>
+/**
+ * DOC: General ioctl format
+ *
+ * The ioctl interface follows a general format to allow for extensibility. Each
+ * ioctl is passed in a structure pointer as the argument providing the size of
+ * the structure in the first u32. The kernel checks that any structure space
+ * beyond what it understands is 0. This allows userspace to use the backward
+ * compatible portion while consistently using the newer, larger, structures.
+ *
+ * ioctls use a standard meaning for common errnos:
+ *
+ * - ENOTTY: The IOCTL number itself is not supported at all
+ * - E2BIG: The IOCTL number is supported, but the provided structure has
+ * non-zero in a part the kernel does not understand.
+ * - EOPNOTSUPP: The IOCTL number is supported, and the structure is
+ * understood, however a known field has a value the kernel does not
+ * understand or support.
+ * - EINVAL: Everything about the IOCTL was understood, but a field is not
+ * correct.
+ * - ENOENT: An ID or IOVA provided does not exist.
+ * - ENOMEM: Out of memory.
+ * - EOVERFLOW: Mathematics overflowed.
+ *
+ * As well as additional errnos, within specific ioctls.
+ */
+
/**
* enum liveupdate_state - Defines the possible states of the live update
* orchestrator.
@@ -91,4 +117,221 @@ enum liveupdate_event {
LIVEUPDATE_CANCEL = 3,
};
+/* The ioctl type, documented in ioctl-number.rst */
+#define LIVEUPDATE_IOCTL_TYPE 0xBA
+
+/* The ioctl commands */
+enum {
+ LIVEUPDATE_CMD_BASE = 0x00,
+ LIVEUPDATE_CMD_FD_PRESERVE = LIVEUPDATE_CMD_BASE,
+ LIVEUPDATE_CMD_FD_UNPRESERVE = 0x01,
+ LIVEUPDATE_CMD_FD_RESTORE = 0x02,
+ LIVEUPDATE_CMD_GET_STATE = 0x03,
+ LIVEUPDATE_CMD_SET_EVENT = 0x04,
+};
+
+/**
+ * struct liveupdate_ioctl_fd_preserve - ioctl(LIVEUPDATE_IOCTL_FD_PRESERVE)
+ * @size: Input; sizeof(struct liveupdate_ioctl_fd_preserve)
+ * @fd: Input; The user-space file descriptor to be preserved.
+ * @token: Input; An opaque, unique token for preserved resource.
+ *
+ * Holds parameters for preserving Validate and initiate preservation for a file
+ * descriptor.
+ *
+ * User sets the @fd field identifying the file descriptor to preserve
+ * (e.g., memfd, kvm, iommufd, VFIO). The kernel validates if this FD type
+ * and its dependencies are supported for preservation. If validation passes,
+ * the kernel marks the FD internally and *initiates the process* of preparing
+ * its state for saving. The actual snapshotting of the state typically occurs
+ * during the subsequent %LIVEUPDATE_IOCTL_PREPARE execution phase, though
+ * some finalization might occur during freeze.
+ * On successful validation and initiation, the kernel uses the @token
+ * field with an opaque identifier representing the resource being preserved.
+ * This token confirms the FD is targeted for preservation and is required for
+ * the subsequent %LIVEUPDATE_IOCTL_FD_RESTORE call after the live update.
+ *
+ * Return: 0 on success (validation passed, preservation initiated), negative
+ * error code on failure (e.g., unsupported FD type, dependency issue,
+ * validation failed).
+ */
+struct liveupdate_ioctl_fd_preserve {
+ __u32 size;
+ __s32 fd;
+ __aligned_u64 token;
+};
+
+#define LIVEUPDATE_IOCTL_FD_PRESERVE \
+ _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_FD_PRESERVE)
+
+/**
+ * struct liveupdate_ioctl_fd_unpreserve - ioctl(LIVEUPDATE_IOCTL_FD_UNPRESERVE)
+ * @size: Input; sizeof(struct liveupdate_ioctl_fd_unpreserve)
+ * @token: Input; A token for resource to be unpreserved.
+ *
+ * Remove a file descriptor from the preservation list.
+ *
+ * Allows user space to explicitly remove a file descriptor from the set of
+ * items marked as potentially preservable. User space provides a @token that
+ * was previously used by a successful %LIVEUPDATE_IOCTL_FD_PRESERVE call
+ * (potentially from a prior, possibly cancelled, live update attempt). The
+ * kernel reads the token value from the provided user-space address.
+ *
+ * On success, the kernel removes the corresponding entry (identified by the
+ * token value read from the user pointer) from its internal preservation list.
+ * The provided @token (representing the now-removed entry) becomes invalid
+ * after this call.
+ *
+ * Return: 0 on success, negative error code on failure (e.g., -EBUSY or -EINVAL
+ * if bad address provided, invalid token value read, token not found).
+ */
+struct liveupdate_ioctl_fd_unpreserve {
+ __u32 size;
+ __aligned_u64 token;
+};
+
+#define LIVEUPDATE_IOCTL_FD_UNPRESERVE \
+ _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_FD_UNPRESERVE)
+
+/**
+ * struct liveupdate_ioctl_fd_restore - ioctl(LIVEUPDATE_IOCTL_FD_RESTORE)
+ * @size: Input; sizeof(struct liveupdate_ioctl_fd_restore)
+ * @fd: Output; The new file descriptor representing the fully restored
+ * kernel resource.
+ * @token: Input; An opaque, token that was used to preserve the resource.
+ *
+ * Restore a previously preserved file descriptor.
+ *
+ * User sets the @token field to the value obtained from a successful
+ * %LIVEUPDATE_IOCTL_FD_PRESERVE call before the live update. On success,
+ * the kernel restores the state (saved during the PREPARE/FREEZE phases)
+ * associated with the token and populates the @fd field with a new file
+ * descriptor referencing the restored resource in the current (new) kernel.
+ * This operation must be performed *before* signaling completion via
+ * %LIVEUPDATE_IOCTL_FINISH.
+ *
+ * Return: 0 on success, negative error code on failure (e.g., invalid token).
+ */
+struct liveupdate_ioctl_fd_restore {
+ __u32 size;
+ __s32 fd;
+ __aligned_u64 token;
+};
+
+#define LIVEUPDATE_IOCTL_FD_RESTORE \
+ _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_FD_RESTORE)
+
+/**
+ * struct liveupdate_ioctl_get_state - ioctl(LIVEUPDATE_IOCTL_GET_STATE)
+ * @size: Input; sizeof(struct liveupdate_ioctl_get_state)
+ * @state: Output; The current live update state.
+ *
+ * Query the current state of the live update orchestrator.
+ *
+ * The kernel fills the @state with the current
+ * state of the live update subsystem. Possible states are:
+ *
+ * - %LIVEUPDATE_STATE_NORMAL: Default state; no live update operation is
+ * currently in progress.
+ * - %LIVEUPDATE_STATE_PREPARED: The preparation phase (triggered by
+ * %LIVEUPDATE_PREPARE) has completed
+ * successfully. The system is ready for the
+ * reboot transition. Note that some
+ * device operations (e.g., unbinding, new DMA
+ * mappings) might be restricted in this state.
+ * - %LIVEUPDATE_STATE_UPDATED: The system has successfully rebooted into the
+ * new kernel via live update. It is now running
+ * the new kernel code and is awaiting the
+ * completion signal from user space via
+ * %LIVEUPDATE_FINISH after restoration tasks are
+ * done.
+ *
+ * See the definition of &enum liveupdate_state for more details on each state.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+struct liveupdate_ioctl_get_state {
+ __u32 size;
+ __u32 state;
+};
+
+#define LIVEUPDATE_IOCTL_GET_STATE \
+ _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_GET_STATE)
+
+/**
+ * struct liveupdate_ioctl_set_event - ioctl(LIVEUPDATE_IOCTL_SET_EVENT)
+ * @size: Input; sizeof(struct liveupdate_ioctl_set_event)
+ * @event: Input; The live update event.
+ *
+ * Notify live update orchestrator about global event, that causes a state
+ * transition.
+ *
+ * Event, can be one of the following:
+ *
+ * - %LIVEUPDATE_PREPARE: Initiates the live update preparation phase. This
+ * typically triggers the saving process for items marked
+ * via the PRESERVE ioctls. This typically occurs
+ * *before* the "blackout window", while user
+ * applications (e.g., VMs) may still be running. Kernel
+ * subsystems receiving the %LIVEUPDATE_PREPARE event
+ * should serialize necessary state. This command does
+ * not transfer data.
+ * - %LIVEUPDATE_FINISH: Signal restoration completion and triggercleanup.
+ *
+ * Signals that user space has completed all necessary
+ * restoration actions in the new kernel (after a live
+ * update reboot). Calling this ioctl triggers the
+ * cleanup phase: any resources that were successfully
+ * preserved but were *not* subsequently restored
+ * (reclaimed) via the RESTORE ioctls will have their
+ * preserved state discarded and associated kernel
+ * resources released. Involved devices may be reset. All
+ * desired restorations *must* be completed *before*
+ * this. Kernel callbacks for the %LIVEUPDATE_FINISH
+ * event must not fail. Successfully completing this
+ * phase transitions the system state from
+ * %LIVEUPDATE_STATE_UPDATED back to
+ * %LIVEUPDATE_STATE_NORMAL. This command does
+ * not transfer data.
+ * - %LIVEUPDATE_CANCEL: Cancel the live update preparation phase.
+ *
+ * Notifies the live update subsystem to abort the
+ * preparation sequence potentially initiated by
+ * %LIVEUPDATE_PREPARE event.
+ *
+ * When triggered, subsystems receiving the
+ * %LIVEUPDATE_CANCEL event should revert any state
+ * changes or actions taken specifically for the aborted
+ * prepare phase (e.g., discard partially serialized
+ * state). The kernel releases resources allocated
+ * specifically for this *aborted preparation attempt*.
+ *
+ * This operation cancels the current *attempt* to
+ * prepare for a live update but does **not** remove
+ * previously validated items from the internal list
+ * of potentially preservable resources. Consequently,
+ * preservation tokens previously used by successful
+ * %LIVEUPDATE_IOCTL_FD_PRESERVE or calls **remain
+ * valid** as identifiers for those potentially
+ * preservable resources. However, since the system state
+ * returns towards %LIVEUPDATE_STATE_NORMAL, user space
+ * must initiate a new live update sequence (starting
+ * with %LIVEUPDATE_PREPARE) to proceed with an update
+ * using these (or other) tokens.
+ *
+ * This command does not transfer data. Kernel callbacks
+ * for the %LIVEUPDATE_CANCEL event must not fail.
+ *
+ * See the definition of &enum liveupdate_event for more details on each state.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+struct liveupdate_ioctl_set_event {
+ __u32 size;
+ __u32 event;
+};
+
+#define LIVEUPDATE_IOCTL_SET_EVENT \
+ _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SET_EVENT)
+
#endif /* _UAPI_LIVEUPDATE_H */
diff --git a/kernel/liveupdate/luo_ioctl.c b/kernel/liveupdate/luo_ioctl.c
index 3df1ec9fbe57..6f61569c94e8 100644
--- a/kernel/liveupdate/luo_ioctl.c
+++ b/kernel/liveupdate/luo_ioctl.c
@@ -5,6 +5,25 @@
* Pasha Tatashin <pasha.tatashin@soleen.com>
*/
+/**
+ * DOC: LUO ioctl Interface
+ *
+ * The IOCTL user-space control interface for the LUO subsystem.
+ * It registers a character device, typically found at ``/dev/liveupdate``,
+ * which allows a userspace agent to manage the LUO state machine and its
+ * associated resources, such as preservable file descriptors.
+ *
+ * To ensure that the state machine is controlled by a single entity, access
+ * to this device is exclusive: only one process is permitted to have
+ * ``/dev/liveupdate`` open at any given time. Subsequent open attempts will
+ * fail with -EBUSY until the first process closes its file descriptor.
+ * This singleton model simplifies state management by preventing conflicting
+ * commands from multiple userspace agents.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/atomic.h>
#include <linux/errno.h>
#include <linux/file.h>
#include <linux/fs.h>
@@ -17,8 +36,189 @@
#include <uapi/linux/liveupdate.h>
#include "luo_internal.h"
+static atomic_t luo_device_in_use = ATOMIC_INIT(0);
+
+struct luo_ucmd {
+ void __user *ubuffer;
+ u32 user_size;
+ void *cmd;
+};
+
+static int luo_ioctl_fd_preserve(struct luo_ucmd *ucmd)
+{
+ struct liveupdate_ioctl_fd_preserve *argp = ucmd->cmd;
+ int ret;
+
+ ret = luo_register_file(argp->token, argp->fd);
+ if (!ret)
+ return ret;
+
+ if (copy_to_user(ucmd->ubuffer, argp, ucmd->user_size))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int luo_ioctl_fd_unpreserve(struct luo_ucmd *ucmd)
+{
+ struct liveupdate_ioctl_fd_unpreserve *argp = ucmd->cmd;
+
+ return luo_unregister_file(argp->token);
+}
+
+static int luo_ioctl_fd_restore(struct luo_ucmd *ucmd)
+{
+ struct liveupdate_ioctl_fd_restore *argp = ucmd->cmd;
+ struct file *file;
+ int ret;
+
+ argp->fd = get_unused_fd_flags(O_CLOEXEC);
+ if (argp->fd < 0) {
+ pr_err("Failed to allocate new fd: %d\n", argp->fd);
+ return argp->fd;
+ }
+
+ ret = luo_retrieve_file(argp->token, &file);
+ if (ret < 0) {
+ put_unused_fd(argp->fd);
+
+ return ret;
+ }
+
+ fd_install(argp->fd, file);
+
+ if (copy_to_user(ucmd->ubuffer, argp, ucmd->user_size))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int luo_ioctl_get_state(struct luo_ucmd *ucmd)
+{
+ struct liveupdate_ioctl_get_state *argp = ucmd->cmd;
+
+ argp->state = liveupdate_get_state();
+
+ if (copy_to_user(ucmd->ubuffer, argp, ucmd->user_size))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int luo_ioctl_set_event(struct luo_ucmd *ucmd)
+{
+ struct liveupdate_ioctl_set_event *argp = ucmd->cmd;
+ int ret;
+
+ switch (argp->event) {
+ case LIVEUPDATE_PREPARE:
+ ret = luo_prepare();
+ break;
+ case LIVEUPDATE_FINISH:
+ ret = luo_finish();
+ break;
+ case LIVEUPDATE_CANCEL:
+ ret = luo_cancel();
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int luo_open(struct inode *inodep, struct file *filep)
+{
+ if (atomic_cmpxchg(&luo_device_in_use, 0, 1))
+ return -EBUSY;
+
+ return 0;
+}
+
+static int luo_release(struct inode *inodep, struct file *filep)
+{
+ atomic_set(&luo_device_in_use, 0);
+
+ return 0;
+}
+
+union ucmd_buffer {
+ struct liveupdate_ioctl_fd_preserve preserve;
+ struct liveupdate_ioctl_fd_unpreserve unpreserve;
+ struct liveupdate_ioctl_fd_restore restore;
+ struct liveupdate_ioctl_get_state state;
+ struct liveupdate_ioctl_set_event event;
+};
+
+struct luo_ioctl_op {
+ unsigned int size;
+ unsigned int min_size;
+ unsigned int ioctl_num;
+ int (*execute)(struct luo_ucmd *ucmd);
+};
+
+#define IOCTL_OP(_ioctl, _fn, _struct, _last) \
+ [_IOC_NR(_ioctl) - LIVEUPDATE_CMD_BASE] = { \
+ .size = sizeof(_struct) + \
+ BUILD_BUG_ON_ZERO(sizeof(union ucmd_buffer) < \
+ sizeof(_struct)), \
+ .min_size = offsetofend(_struct, _last), \
+ .ioctl_num = _ioctl, \
+ .execute = _fn, \
+ }
+
+static const struct luo_ioctl_op luo_ioctl_ops[] = {
+ IOCTL_OP(LIVEUPDATE_IOCTL_FD_PRESERVE, luo_ioctl_fd_preserve,
+ struct liveupdate_ioctl_fd_preserve, token),
+ IOCTL_OP(LIVEUPDATE_IOCTL_FD_UNPRESERVE, luo_ioctl_fd_unpreserve,
+ struct liveupdate_ioctl_fd_unpreserve, token),
+ IOCTL_OP(LIVEUPDATE_IOCTL_FD_RESTORE, luo_ioctl_fd_restore,
+ struct liveupdate_ioctl_fd_restore, token),
+ IOCTL_OP(LIVEUPDATE_IOCTL_GET_STATE, luo_ioctl_get_state,
+ struct liveupdate_ioctl_get_state, state),
+ IOCTL_OP(LIVEUPDATE_IOCTL_SET_EVENT, luo_ioctl_set_event,
+ struct liveupdate_ioctl_set_event, event),
+};
+
+static long luo_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
+{
+ const struct luo_ioctl_op *op;
+ struct luo_ucmd ucmd = {};
+ union ucmd_buffer buf;
+ unsigned int nr;
+ int ret;
+
+ nr = _IOC_NR(cmd);
+ if (nr < LIVEUPDATE_CMD_BASE ||
+ (nr - LIVEUPDATE_CMD_BASE) >= ARRAY_SIZE(luo_ioctl_ops)) {
+ return -EINVAL;
+ }
+
+ ucmd.ubuffer = (void __user *)arg;
+ ret = get_user(ucmd.user_size, (u32 __user *)ucmd.ubuffer);
+ if (ret)
+ return ret;
+
+ op = &luo_ioctl_ops[nr - LIVEUPDATE_CMD_BASE];
+ if (op->ioctl_num != cmd)
+ return -ENOIOCTLCMD;
+ if (ucmd.user_size < op->min_size)
+ return -EINVAL;
+
+ ucmd.cmd = &buf;
+ ret = copy_struct_from_user(ucmd.cmd, op->size, ucmd.ubuffer,
+ ucmd.user_size);
+ if (ret)
+ return ret;
+
+ return op->execute(&ucmd);
+}
+
static const struct file_operations fops = {
.owner = THIS_MODULE,
+ .open = luo_open,
+ .release = luo_release,
+ .unlocked_ioctl = luo_ioctl,
};
static struct miscdevice liveupdate_miscdev = {
--
2.50.1.565.gc32cd1483b-goog
^ permalink raw reply related
* [PATCH v3 15/30] liveupdate: luo_files: implement file systems callbacks
From: Pasha Tatashin @ 2025-08-07 1:44 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, changyuanl, pasha.tatashin, rppt,
dmatlack, rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda,
aliceryhl, masahiroy, akpm, tj, yoann.congal, mmaurer,
roman.gushchin, chenridong, axboe, mark.rutland, jannh,
vincent.guittot, hannes, dan.j.williams, david, joel.granados,
rostedt, anna.schumaker, song, zhangguopeng, linux, linux-kernel,
linux-doc, linux-mm, gregkh, tglx, mingo, bp, dave.hansen, x86,
hpa, rafael, dakr, bartosz.golaszewski, cw00.choi, myungjoo.ham,
yesanishhere, Jonathan.Cameron, quic_zijuhu, aleksander.lobakin,
ira.weiny, andriy.shevchenko, leon, lukas, bhelgaas, wagi,
djeffery, stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu
In-Reply-To: <20250807014442.3829950-1-pasha.tatashin@soleen.com>
Implements the core logic within luo_files.c to invoke the prepare,
reboot, finish, and cancel callbacks for preserved file instances,
replacing the previous stub implementations. It also handles
the persistence and retrieval of the u64 data payload associated with
each file via the LUO FDT.
This completes the core mechanism enabling registered files handlers to actively
manage file state across the live update transition using the LUO framework.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/liveupdate/luo_files.c | 191 +++++++++++++++++++++++++++++++++-
1 file changed, 188 insertions(+), 3 deletions(-)
diff --git a/kernel/liveupdate/luo_files.c b/kernel/liveupdate/luo_files.c
index 4b7568d0f0f0..33577c9e9a64 100644
--- a/kernel/liveupdate/luo_files.c
+++ b/kernel/liveupdate/luo_files.c
@@ -326,32 +326,190 @@ static int luo_files_fdt_setup(void)
return ret;
}
+static int luo_files_prepare_one(struct luo_file *h)
+{
+ int ret = 0;
+
+ guard(mutex)(&h->mutex);
+ if (h->state == LIVEUPDATE_STATE_NORMAL) {
+ if (h->fh->ops->prepare) {
+ ret = h->fh->ops->prepare(h->fh, h->file,
+ &h->private_data);
+ }
+ if (!ret)
+ h->state = LIVEUPDATE_STATE_PREPARED;
+ } else {
+ WARN_ON_ONCE(h->state != LIVEUPDATE_STATE_PREPARED &&
+ h->state != LIVEUPDATE_STATE_FROZEN);
+ }
+
+ return ret;
+}
+
+static int luo_files_freeze_one(struct luo_file *h)
+{
+ int ret = 0;
+
+ guard(mutex)(&h->mutex);
+ if (h->state == LIVEUPDATE_STATE_PREPARED) {
+ if (h->fh->ops->freeze) {
+ ret = h->fh->ops->freeze(h->fh, h->file,
+ &h->private_data);
+ }
+ if (!ret)
+ h->state = LIVEUPDATE_STATE_FROZEN;
+ } else {
+ WARN_ON_ONCE(h->state != LIVEUPDATE_STATE_FROZEN);
+ }
+
+ return ret;
+}
+
+static void luo_files_finish_one(struct luo_file *h)
+{
+ guard(mutex)(&h->mutex);
+ if (h->state == LIVEUPDATE_STATE_UPDATED) {
+ if (h->fh->ops->finish) {
+ h->fh->ops->finish(h->fh, h->file, h->private_data,
+ h->reclaimed);
+ }
+ h->state = LIVEUPDATE_STATE_NORMAL;
+ } else {
+ WARN_ON_ONCE(h->state != LIVEUPDATE_STATE_NORMAL);
+ }
+}
+
+static void luo_files_cancel_one(struct luo_file *h)
+{
+ int ret;
+
+ guard(mutex)(&h->mutex);
+ if (h->state == LIVEUPDATE_STATE_NORMAL)
+ return;
+
+ ret = WARN_ON_ONCE(h->state != LIVEUPDATE_STATE_PREPARED &&
+ h->state != LIVEUPDATE_STATE_FROZEN);
+ if (ret)
+ return;
+
+ if (h->fh->ops->cancel)
+ h->fh->ops->cancel(h->fh, h->file, h->private_data);
+ h->private_data = 0;
+ h->state = LIVEUPDATE_STATE_NORMAL;
+}
+
+static void __luo_files_cancel(struct luo_file *boundary_file)
+{
+ unsigned long token;
+ struct luo_file *h;
+
+ xa_for_each(&luo_files_xa_out, token, h) {
+ if (h == boundary_file)
+ break;
+
+ luo_files_cancel_one(h);
+ }
+ luo_files_fdt_cleanup();
+}
+
+static int luo_files_commit_data_to_fdt(void)
+{
+ int node_offset, ret;
+ unsigned long token;
+ char token_str[19];
+ struct luo_file *h;
+
+ guard(rwsem_read)(&luo_file_fdt_rwsem);
+ xa_for_each(&luo_files_xa_out, token, h) {
+ snprintf(token_str, sizeof(token_str), "%#0llx", (u64)token);
+ node_offset = fdt_subnode_offset(luo_file_fdt_out,
+ 0,
+ token_str);
+ ret = fdt_setprop(luo_file_fdt_out, node_offset, "data",
+ &h->private_data, sizeof(h->private_data));
+ if (ret < 0) {
+ pr_err("Failed to set data property for token %s: %s\n",
+ token_str, fdt_strerror(ret));
+ return -ENOSPC;
+ }
+ }
+
+ return 0;
+}
+
static int luo_files_prepare(struct liveupdate_subsystem *h, u64 *data)
{
+ unsigned long token;
+ struct luo_file *luo_file;
int ret;
ret = luo_files_fdt_setup();
if (ret)
return ret;
- scoped_guard(rwsem_read, &luo_file_fdt_rwsem)
- *data = __pa(luo_file_fdt_out);
+ xa_for_each(&luo_files_xa_out, token, luo_file) {
+ ret = luo_files_prepare_one(luo_file);
+ if (ret < 0) {
+ pr_err("Prepare failed for file token %#0llx handler '%s' [%d]\n",
+ (u64)token, luo_file->fh->compatible, ret);
+ __luo_files_cancel(luo_file);
+
+ return ret;
+ }
+ }
+
+ ret = luo_files_commit_data_to_fdt();
+ if (ret) {
+ __luo_files_cancel(NULL);
+ } else {
+ scoped_guard(rwsem_read, &luo_file_fdt_rwsem)
+ *data = __pa(luo_file_fdt_out);
+ }
return ret;
}
static int luo_files_freeze(struct liveupdate_subsystem *h, u64 *data)
{
- return 0;
+ unsigned long token;
+ struct luo_file *luo_file;
+ int ret;
+
+ xa_for_each(&luo_files_xa_out, token, luo_file) {
+ ret = luo_files_freeze_one(luo_file);
+ if (ret < 0) {
+ pr_err("Freeze callback failed for file token %#0llx handler '%s' [%d]\n",
+ (u64)token, luo_file->fh->compatible, ret);
+ __luo_files_cancel(luo_file);
+
+ return ret;
+ }
+ }
+
+ ret = luo_files_commit_data_to_fdt();
+ if (ret)
+ __luo_files_cancel(NULL);
+
+ return ret;
}
static void luo_files_finish(struct liveupdate_subsystem *h, u64 data)
{
+ unsigned long token;
+ struct luo_file *luo_file;
+
luo_files_recreate_luo_files_xa_in();
+ xa_for_each(&luo_files_xa_in, token, luo_file) {
+ luo_files_finish_one(luo_file);
+ mutex_destroy(&luo_file->mutex);
+ kfree(luo_file);
+ }
+ xa_destroy(&luo_files_xa_in);
}
static void luo_files_cancel(struct liveupdate_subsystem *h, u64 data)
{
+ __luo_files_cancel(NULL);
}
static void luo_files_boot(struct liveupdate_subsystem *h, u64 fdt_pa)
@@ -484,6 +642,27 @@ int luo_register_file(u64 token, int fd)
return ret;
}
+static void luo_files_fdt_remove_node(u64 token)
+{
+ char token_str[19];
+ int offset, ret;
+
+ guard(rwsem_write)(&luo_file_fdt_rwsem);
+ if (!luo_file_fdt_out)
+ return;
+
+ snprintf(token_str, sizeof(token_str), "%#0llx", token);
+ offset = fdt_subnode_offset(luo_file_fdt_out, 0, token_str);
+ if (offset < 0)
+ return;
+
+ ret = fdt_del_node(luo_file_fdt_out, offset);
+ if (ret < 0) {
+ pr_warn("LUO Files: Failed to delete FDT node for token %s: %s\n",
+ token_str, fdt_strerror(ret));
+ }
+}
+
static int __luo_unregister_file(u64 token)
{
struct luo_file *luo_file;
@@ -492,6 +671,12 @@ static int __luo_unregister_file(u64 token)
if (!luo_file)
return -ENOENT;
+ if (luo_file->state == LIVEUPDATE_STATE_FROZEN ||
+ luo_file->state == LIVEUPDATE_STATE_PREPARED) {
+ luo_files_cancel_one(luo_file);
+ luo_files_fdt_remove_node(token);
+ }
+
fput(luo_file->file);
mutex_destroy(&luo_file->mutex);
kfree(luo_file);
--
2.50.1.565.gc32cd1483b-goog
^ permalink raw reply related
* [PATCH v3 14/30] liveupdate: luo_files: add infrastructure for FDs
From: Pasha Tatashin @ 2025-08-07 1:44 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, changyuanl, pasha.tatashin, rppt,
dmatlack, rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda,
aliceryhl, masahiroy, akpm, tj, yoann.congal, mmaurer,
roman.gushchin, chenridong, axboe, mark.rutland, jannh,
vincent.guittot, hannes, dan.j.williams, david, joel.granados,
rostedt, anna.schumaker, song, zhangguopeng, linux, linux-kernel,
linux-doc, linux-mm, gregkh, tglx, mingo, bp, dave.hansen, x86,
hpa, rafael, dakr, bartosz.golaszewski, cw00.choi, myungjoo.ham,
yesanishhere, Jonathan.Cameron, quic_zijuhu, aleksander.lobakin,
ira.weiny, andriy.shevchenko, leon, lukas, bhelgaas, wagi,
djeffery, stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu
In-Reply-To: <20250807014442.3829950-1-pasha.tatashin@soleen.com>
Introduce the framework within LUO to support preserving specific types
of file descriptors across a live update transition. This allows
stateful FDs (like memfds or vfio FDs used by VMs) to be recreated in
the new kernel.
Note: The core logic for iterating through the luo_files_list and
invoking the handler callbacks (prepare, freeze, cancel, finish)
within luo_do_files_*_calls, as well as managing the u64 data
persistence via the FDT for individual files, is currently implemented
as stubs in this patch. This patch sets up the registration, FDT layout,
and retrieval framework.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
include/linux/liveupdate.h | 73 ++++
kernel/liveupdate/Makefile | 1 +
kernel/liveupdate/luo_files.c | 677 +++++++++++++++++++++++++++++++
kernel/liveupdate/luo_internal.h | 4 +
4 files changed, 755 insertions(+)
create mode 100644 kernel/liveupdate/luo_files.c
diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
index 4c378a986cfe..72786482ca48 100644
--- a/include/linux/liveupdate.h
+++ b/include/linux/liveupdate.h
@@ -13,6 +13,66 @@
#include <uapi/linux/liveupdate.h>
struct liveupdate_subsystem;
+struct liveupdate_file_handler;
+struct file;
+
+/**
+ * struct liveupdate_file_ops - Callbacks for live-updatable files.
+ * @prepare: Optional. Saves state for a specific file instance @file,
+ * before update, potentially returning value via @data.
+ * Returns 0 on success, negative errno on failure.
+ * @freeze: Optional. Performs final actions just before kernel
+ * transition, potentially reading/updating the handle via
+ * @data.
+ * Returns 0 on success, negative errno on failure.
+ * @cancel: Optional. Cleans up state/resources if update is aborted
+ * after prepare/freeze succeeded, using the @data handle (by
+ * value) from the successful prepare. Returns void.
+ * @finish: Optional. Performs final cleanup in the new kernel using the
+ * preserved @data handle (by value). Returns void.
+ * @retrieve: Retrieve the preserved file. Must be called before finish.
+ * @can_preserve: callback to determine if @file can be preserved by this
+ * handler.
+ * Return bool (true if preservable, false otherwise).
+ * @owner: Module reference
+ */
+struct liveupdate_file_ops {
+ int (*prepare)(struct liveupdate_file_handler *handler,
+ struct file *file, u64 *data);
+ int (*freeze)(struct liveupdate_file_handler *handler,
+ struct file *file, u64 *data);
+ void (*cancel)(struct liveupdate_file_handler *handler,
+ struct file *file, u64 data);
+ void (*finish)(struct liveupdate_file_handler *handler,
+ struct file *file, u64 data, bool reclaimed);
+ int (*retrieve)(struct liveupdate_file_handler *handler,
+ u64 data, struct file **file);
+ bool (*can_preserve)(struct liveupdate_file_handler *handler,
+ struct file *file);
+ struct module *owner;
+};
+
+/**
+ * struct liveupdate_file_handler - Represents a handler for a live-updatable
+ * file type.
+ * @ops: Callback functions
+ * @compatible: The compatibility string (e.g., "memfd-v1", "vfiofd-v1")
+ * that uniquely identifies the file type this handler supports.
+ * This is matched against the compatible string associated with
+ * individual &struct liveupdate_file instances.
+ * @list: used for linking this handler instance into a global list of
+ * registered file handlers.
+ *
+ * Modules that want to support live update for specific file types should
+ * register an instance of this structure. LUO uses this registration to
+ * determine if a given file can be preserved and to find the appropriate
+ * operations to manage its state across the update.
+ */
+struct liveupdate_file_handler {
+ const struct liveupdate_file_ops *ops;
+ const char *compatible;
+ struct list_head list;
+};
/**
* struct liveupdate_subsystem_ops - LUO events callback functions
@@ -83,6 +143,9 @@ int liveupdate_register_subsystem(struct liveupdate_subsystem *h);
int liveupdate_unregister_subsystem(struct liveupdate_subsystem *h);
int liveupdate_get_subsystem_data(struct liveupdate_subsystem *h, u64 *data);
+int liveupdate_register_file_handler(struct liveupdate_file_handler *h);
+int liveupdate_unregister_file_handler(struct liveupdate_file_handler *h);
+
#else /* CONFIG_LIVEUPDATE */
static inline int liveupdate_reboot(void)
@@ -126,5 +189,15 @@ static inline int liveupdate_get_subsystem_data(struct liveupdate_subsystem *h,
return -ENODATA;
}
+static inline int liveupdate_register_file_handler(struct liveupdate_file_handler *h)
+{
+ return 0;
+}
+
+static inline int liveupdate_unregister_file_handler(struct liveupdate_file_handler *h)
+{
+ return 0;
+}
+
#endif /* CONFIG_LIVEUPDATE */
#endif /* _LINUX_LIVEUPDATE_H */
diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile
index 47e9ad56675b..c67fa2797796 100644
--- a/kernel/liveupdate/Makefile
+++ b/kernel/liveupdate/Makefile
@@ -5,6 +5,7 @@
luo-y := \
luo_core.o \
+ luo_files.o \
luo_ioctl.o \
luo_subsystems.o
diff --git a/kernel/liveupdate/luo_files.c b/kernel/liveupdate/luo_files.c
new file mode 100644
index 000000000000..4b7568d0f0f0
--- /dev/null
+++ b/kernel/liveupdate/luo_files.c
@@ -0,0 +1,677 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (c) 2025, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@soleen.com>
+ */
+
+/**
+ * DOC: LUO file descriptors
+ *
+ * LUO provides the infrastructure necessary to preserve
+ * specific types of stateful file descriptors across a kernel live
+ * update transition. The primary goal is to allow workloads, such as virtual
+ * machines using vfio, memfd, or iommufd to retain access to their essential
+ * resources without interruption after the underlying kernel is updated.
+ *
+ * The framework operates based on handler registration and instance tracking:
+ *
+ * 1. Handler Registration: Kernel modules responsible for specific file
+ * types (e.g., memfd, vfio) register a &struct liveupdate_file_handler
+ * handler. This handler contains callbacks
+ * (&liveupdate_file_handler.ops->prepare,
+ * &liveupdate_file_handler.ops->freeze,
+ * &liveupdate_file_handler.ops->finish, etc.) and a unique 'compatible' string
+ * identifying the file type. Registration occurs via
+ * liveupdate_register_file_handler().
+ *
+ * 2. File Instance Tracking: When a potentially preservable file needs to be
+ * managed for live update, the core LUO logic (luo_register_file()) finds a
+ * compatible registered handler using its
+ * &liveupdate_file_handler.ops->can_preserve callback. If found, an internal
+ * &struct luo_file instance is created, assigned a unique u64 'token', and
+ * added to a list.
+ *
+ * 3. State Persistence (FDT): During the LUO prepare/freeze phases, the
+ * registered handler callbacks are invoked for each tracked file instance.
+ * These callbacks can generate a u64 data payload representing the minimal
+ * state needed for restoration. This payload, along with the handler's
+ * compatible string and the unique token, is stored in a dedicated
+ * '/file-descriptors' node within the main LUO FDT blob passed via
+ * Kexec Handover (KHO).
+ *
+ * 4. Restoration: In the new kernel, the LUO framework parses the incoming
+ * FDT to reconstruct the list of &struct luo_file instances. When the
+ * original owner requests the file, luo_retrieve_file() uses the corresponding
+ * handler's &liveupdate_file_handler.ops->retrieve callback, passing the
+ * persisted u64 data, to recreate or find the appropriate &struct file object.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/atomic.h>
+#include <linux/err.h>
+#include <linux/file.h>
+#include <linux/kexec_handover.h>
+#include <linux/libfdt.h>
+#include <linux/liveupdate.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/rwsem.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/xarray.h>
+#include "luo_internal.h"
+
+#define LUO_FILES_NODE_NAME "file-descriptors"
+#define LUO_FILES_COMPATIBLE "file-descriptors-v1"
+
+static DEFINE_XARRAY(luo_files_xa_in);
+static DEFINE_XARRAY(luo_files_xa_out);
+static bool luo_files_xa_in_recreated;
+
+/* Registered files. */
+static DECLARE_RWSEM(luo_register_file_list_rwsem);
+static LIST_HEAD(luo_register_file_list);
+
+static DECLARE_RWSEM(luo_file_fdt_rwsem);
+static void *luo_file_fdt_out;
+static void *luo_file_fdt_in;
+
+static size_t luo_file_fdt_out_size;
+
+static atomic64_t luo_files_count;
+
+/**
+ * struct luo_file - Represents a file descriptor instance preserved
+ * across live update.
+ * @fh: Pointer to the &struct liveupdate_file_handler containing
+ * the implementation of prepare, freeze, cancel, and finish
+ * operations specific to this file's type.
+ * @file: A pointer to the kernel's &struct file object representing
+ * the open file descriptor that is being preserved.
+ * @private_data: Internal storage used by the live update core framework
+ * between phases.
+ * @reclaimed: Flag indicating whether this preserved file descriptor has
+ * been successfully 'reclaimed' (e.g., requested via an ioctl)
+ * by user-space or the owning kernel subsystem in the new
+ * kernel after the live update.
+ * @state: The current state of file descriptor, it is allowed to
+ * prepare, freeze, and finish FDs before the global state
+ * switch.
+ * @mutex: Lock to protect FD state, and allow independently to change
+ * the FD state compared to global state.
+ *
+ * This structure holds the necessary callbacks and context for managing a
+ * specific open file descriptor throughout the different phases of a live
+ * update process. Instances of this structure are typically allocated,
+ * populated with file-specific details (&file, &arg, callbacks, compatibility
+ * string, token), and linked into a central list managed by the LUO. The
+ * private_data field is used internally by the core logic to store state
+ * between phases.
+ */
+struct luo_file {
+ struct liveupdate_file_handler *fh;
+ struct file *file;
+ u64 private_data;
+ bool reclaimed;
+ enum liveupdate_state state;
+ struct mutex mutex;
+};
+
+static void luo_files_recreate_luo_files_xa_in(void)
+{
+ const char *node_name, *fdt_compat_str;
+ struct liveupdate_file_handler *fh;
+ struct luo_file *luo_file;
+ const void *data_ptr;
+ int file_node_offset;
+ int ret = 0;
+
+ guard(rwsem_read)(&luo_file_fdt_rwsem);
+ if (luo_files_xa_in_recreated || !luo_file_fdt_in)
+ return;
+
+ /* Take write in order to guarantee that we re-create list once */
+ guard(rwsem_write)(&luo_register_file_list_rwsem);
+ if (luo_files_xa_in_recreated)
+ return;
+
+ fdt_for_each_subnode(file_node_offset, luo_file_fdt_in, 0) {
+ bool handler_found = false;
+ u64 token;
+
+ node_name = fdt_get_name(luo_file_fdt_in, file_node_offset,
+ NULL);
+ if (!node_name) {
+ luo_restore_fail("FDT subnode at offset %d: Cannot get name\n",
+ file_node_offset);
+ }
+
+ ret = kstrtou64(node_name, 0, &token);
+ if (ret < 0) {
+ luo_restore_fail("FDT node '%s': Failed to parse token\n",
+ node_name);
+ }
+
+ if (xa_load(&luo_files_xa_in, token)) {
+ luo_restore_fail("Duplicate token %llu found in incoming FDT for file descriptors.\n",
+ token);
+ }
+
+ fdt_compat_str = fdt_getprop(luo_file_fdt_in, file_node_offset,
+ "compatible", NULL);
+ if (!fdt_compat_str) {
+ luo_restore_fail("FDT node '%s': Missing 'compatible' property\n",
+ node_name);
+ }
+
+ data_ptr = fdt_getprop(luo_file_fdt_in, file_node_offset, "data",
+ NULL);
+ if (!data_ptr) {
+ luo_restore_fail("Can't recover property 'data' for FDT node '%s'\n",
+ node_name);
+ }
+
+ list_for_each_entry(fh, &luo_register_file_list, list) {
+ if (!strcmp(fh->compatible, fdt_compat_str)) {
+ handler_found = true;
+ break;
+ }
+ }
+
+ if (!handler_found) {
+ luo_restore_fail("FDT node '%s': No registered handler for compatible '%s'\n",
+ node_name, fdt_compat_str);
+ }
+
+ luo_file = kmalloc(sizeof(*luo_file),
+ GFP_KERNEL | __GFP_NOFAIL);
+ luo_file->fh = fh;
+ luo_file->file = NULL;
+ memcpy(&luo_file->private_data, data_ptr, sizeof(u64));
+ luo_file->reclaimed = false;
+ mutex_init(&luo_file->mutex);
+ luo_file->state = LIVEUPDATE_STATE_UPDATED;
+ ret = xa_err(xa_store(&luo_files_xa_in, token, luo_file,
+ GFP_KERNEL | __GFP_NOFAIL));
+ if (ret < 0) {
+ luo_restore_fail("Failed to store luo_file for token %llu in XArray: %d\n",
+ token, ret);
+ }
+ }
+ luo_files_xa_in_recreated = true;
+}
+
+static size_t luo_files_fdt_size(void)
+{
+ u64 num_files = atomic64_read(&luo_files_count);
+
+ /* Estimate a 1K overhead, + 128 bytes per file entry */
+ return PAGE_SIZE << get_order(SZ_1K + (num_files * 128));
+}
+
+static void luo_files_fdt_cleanup(void)
+{
+ WARN_ON_ONCE(kho_unpreserve_phys(__pa(luo_file_fdt_out),
+ luo_file_fdt_out_size));
+
+ free_pages((unsigned long)luo_file_fdt_out,
+ get_order(luo_file_fdt_out_size));
+
+ luo_file_fdt_out_size = 0;
+ luo_file_fdt_out = NULL;
+}
+
+static int luo_files_to_fdt(struct xarray *files_xa_out)
+{
+ const u64 zero_data = 0;
+ unsigned long token;
+ struct luo_file *h;
+ char token_str[19];
+ int ret = 0;
+
+ xa_for_each(files_xa_out, token, h) {
+ snprintf(token_str, sizeof(token_str), "%#0llx", (u64)token);
+
+ ret = fdt_begin_node(luo_file_fdt_out, token_str);
+ if (ret < 0)
+ break;
+
+ ret = fdt_property_string(luo_file_fdt_out, "compatible",
+ h->fh->compatible);
+ if (ret < 0) {
+ fdt_end_node(luo_file_fdt_out);
+ break;
+ }
+
+ ret = fdt_property_u64(luo_file_fdt_out, "data", zero_data);
+ if (ret < 0) {
+ fdt_end_node(luo_file_fdt_out);
+ break;
+ }
+
+ ret = fdt_end_node(luo_file_fdt_out);
+ if (ret < 0)
+ break;
+ }
+
+ return ret;
+}
+
+static int luo_files_fdt_setup(void)
+{
+ int ret;
+
+ guard(rwsem_write)(&luo_file_fdt_rwsem);
+ luo_file_fdt_out_size = luo_files_fdt_size();
+ luo_file_fdt_out = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO,
+ get_order(luo_file_fdt_out_size));
+ if (!luo_file_fdt_out) {
+ pr_err("Failed to allocate FDT memory (%zu bytes)\n",
+ luo_file_fdt_out_size);
+ luo_file_fdt_out_size = 0;
+ return -ENOMEM;
+ }
+
+ ret = kho_preserve_phys(__pa(luo_file_fdt_out), luo_file_fdt_out_size);
+ if (ret) {
+ pr_err("Failed to kho preserve FDT memory (%zu bytes)\n",
+ luo_file_fdt_out_size);
+ luo_file_fdt_out_size = 0;
+ luo_file_fdt_out = NULL;
+ return ret;
+ }
+
+ ret = fdt_create(luo_file_fdt_out, luo_file_fdt_out_size);
+ if (ret < 0)
+ goto exit_cleanup;
+
+ ret = fdt_finish_reservemap(luo_file_fdt_out);
+ if (ret < 0)
+ goto exit_finish;
+
+ ret = fdt_begin_node(luo_file_fdt_out, LUO_FILES_NODE_NAME);
+ if (ret < 0)
+ goto exit_finish;
+
+ ret = fdt_property_string(luo_file_fdt_out, "compatible",
+ LUO_FILES_COMPATIBLE);
+ if (ret < 0)
+ goto exit_end_node;
+
+ ret = luo_files_to_fdt(&luo_files_xa_out);
+ if (ret < 0)
+ goto exit_end_node;
+
+ ret = fdt_end_node(luo_file_fdt_out);
+ if (ret < 0)
+ goto exit_finish;
+
+ ret = fdt_finish(luo_file_fdt_out);
+ if (ret < 0)
+ goto exit_cleanup;
+
+ return 0;
+
+exit_end_node:
+ fdt_end_node(luo_file_fdt_out);
+exit_finish:
+ fdt_finish(luo_file_fdt_out);
+exit_cleanup:
+ pr_err("Failed to setup FDT: %s (ret %d)\n", fdt_strerror(ret), ret);
+ luo_files_fdt_cleanup();
+
+ return ret;
+}
+
+static int luo_files_prepare(struct liveupdate_subsystem *h, u64 *data)
+{
+ int ret;
+
+ ret = luo_files_fdt_setup();
+ if (ret)
+ return ret;
+
+ scoped_guard(rwsem_read, &luo_file_fdt_rwsem)
+ *data = __pa(luo_file_fdt_out);
+
+ return ret;
+}
+
+static int luo_files_freeze(struct liveupdate_subsystem *h, u64 *data)
+{
+ return 0;
+}
+
+static void luo_files_finish(struct liveupdate_subsystem *h, u64 data)
+{
+ luo_files_recreate_luo_files_xa_in();
+}
+
+static void luo_files_cancel(struct liveupdate_subsystem *h, u64 data)
+{
+}
+
+static void luo_files_boot(struct liveupdate_subsystem *h, u64 fdt_pa)
+{
+ int ret;
+
+ ret = fdt_node_check_compatible(__va(fdt_pa), 0,
+ LUO_FILES_COMPATIBLE);
+ if (ret) {
+ luo_restore_fail("FDT '%s' is incompatible with '%s' [%d]\n",
+ LUO_FILES_NODE_NAME, LUO_FILES_COMPATIBLE,
+ ret);
+ }
+ scoped_guard(rwsem_write, &luo_file_fdt_rwsem)
+ luo_file_fdt_in = __va(fdt_pa);
+}
+
+static const struct liveupdate_subsystem_ops luo_file_subsys_ops = {
+ .prepare = luo_files_prepare,
+ .freeze = luo_files_freeze,
+ .cancel = luo_files_cancel,
+ .boot = luo_files_boot,
+ .finish = luo_files_finish,
+ .owner = THIS_MODULE,
+};
+
+static struct liveupdate_subsystem luo_file_subsys = {
+ .ops = &luo_file_subsys_ops,
+ .name = LUO_FILES_NODE_NAME,
+};
+
+static int __init luo_files_startup(void)
+{
+ int ret;
+
+ if (!liveupdate_enabled())
+ return 0;
+
+ ret = liveupdate_register_subsystem(&luo_file_subsys);
+ if (ret) {
+ pr_warn("Failed to register luo_file subsystem [%d]\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+late_initcall(luo_files_startup);
+
+/**
+ * luo_register_file - Register a file descriptor for live update management.
+ * @token: Token value for this file descriptor.
+ * @fd: file descriptor to be preserved.
+ *
+ * Context: Must be called when LUO is in 'normal' state.
+ *
+ * Return: 0 on success. Negative errno on failure.
+ */
+int luo_register_file(u64 token, int fd)
+{
+ struct liveupdate_file_handler *fh;
+ struct luo_file *luo_file;
+ bool found = false;
+ int ret = -ENOENT;
+ struct file *file;
+
+ file = fget(fd);
+ if (!file) {
+ pr_err("Bad file descriptor\n");
+ return -EBADF;
+ }
+
+ luo_state_read_enter();
+ if (!liveupdate_state_normal() && !liveupdate_state_updated()) {
+ pr_warn("File can be registered only in normal or updated state\n");
+ luo_state_read_exit();
+ fput(file);
+ return -EBUSY;
+ }
+
+ guard(rwsem_read)(&luo_register_file_list_rwsem);
+ list_for_each_entry(fh, &luo_register_file_list, list) {
+ if (fh->ops->can_preserve(fh, file)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ goto exit_unlock;
+
+ luo_file = kmalloc(sizeof(*luo_file), GFP_KERNEL);
+ if (!luo_file) {
+ ret = -ENOMEM;
+ goto exit_unlock;
+ }
+
+ luo_file->private_data = 0;
+ luo_file->reclaimed = false;
+
+ luo_file->file = file;
+ luo_file->fh = fh;
+ mutex_init(&luo_file->mutex);
+ luo_file->state = LIVEUPDATE_STATE_NORMAL;
+
+ if (xa_load(&luo_files_xa_out, token)) {
+ ret = -EEXIST;
+ pr_warn("Token %llu is already taken\n", token);
+ mutex_destroy(&luo_file->mutex);
+ kfree(luo_file);
+ goto exit_unlock;
+ }
+
+ ret = xa_err(xa_store(&luo_files_xa_out, token, luo_file,
+ GFP_KERNEL));
+ if (ret < 0) {
+ pr_warn("Failed to store file for token %llu in XArray: %d\n",
+ token, ret);
+ mutex_destroy(&luo_file->mutex);
+ kfree(luo_file);
+ goto exit_unlock;
+ }
+ atomic64_inc(&luo_files_count);
+
+exit_unlock:
+ luo_state_read_exit();
+
+ if (ret)
+ fput(file);
+
+ return ret;
+}
+
+static int __luo_unregister_file(u64 token)
+{
+ struct luo_file *luo_file;
+
+ luo_file = xa_erase(&luo_files_xa_out, token);
+ if (!luo_file)
+ return -ENOENT;
+
+ fput(luo_file->file);
+ mutex_destroy(&luo_file->mutex);
+ kfree(luo_file);
+ atomic64_dec(&luo_files_count);
+
+ return 0;
+}
+
+/**
+ * luo_unregister_file - Unregister a file instance using its token.
+ * @token: The unique token of the file instance to unregister.
+ *
+ * Finds the &struct luo_file associated with the @token in the
+ * global list and removes it. This function *only* removes the entry from the
+ * list; it does *not* free the memory allocated for the &struct luo_file
+ * itself. The caller is responsible for freeing the structure after this
+ * function returns successfully.
+ *
+ * Context: Can be called when a preserved file descriptor is closed or
+ * no longer needs live update management.
+ *
+ * Return: 0 on success. Negative errno on failure.
+ */
+int luo_unregister_file(u64 token)
+{
+ int ret = 0;
+
+ luo_state_read_enter();
+ if (!liveupdate_state_normal() && !liveupdate_state_updated()) {
+ pr_warn("File can be unregistered only in normal or updates state\n");
+ luo_state_read_exit();
+ return -EBUSY;
+ }
+
+ ret = __luo_unregister_file(token);
+ if (ret) {
+ pr_warn("Failed to unregister: token %llu not found.\n",
+ token);
+ }
+ luo_state_read_exit();
+
+ return ret;
+}
+
+/**
+ * luo_retrieve_file - Find a registered file instance by its token.
+ * @token: The unique token of the file instance to retrieve.
+ * @filep: Output parameter. On success (return value 0), this will point
+ * to the retrieved "struct file".
+ *
+ * Searches the global list for a &struct luo_file matching the @token. Uses a
+ * read lock, allowing concurrent retrievals.
+ *
+ * Return: 0 on success. Negative errno on failure.
+ */
+int luo_retrieve_file(u64 token, struct file **filep)
+{
+ struct luo_file *luo_file;
+ int ret = 0;
+
+ luo_files_recreate_luo_files_xa_in();
+ luo_state_read_enter();
+ if (!liveupdate_state_updated()) {
+ pr_warn("File can be retrieved only in updated state\n");
+ luo_state_read_exit();
+ return -EBUSY;
+ }
+
+ luo_file = xa_load(&luo_files_xa_in, token);
+ if (luo_file && !luo_file->reclaimed) {
+ scoped_guard(mutex, &luo_file->mutex) {
+ if (!luo_file->reclaimed) {
+ luo_file->reclaimed = true;
+ ret = luo_file->fh->ops->retrieve(luo_file->fh,
+ luo_file->private_data,
+ filep);
+ if (!ret)
+ luo_file->file = *filep;
+ }
+ }
+ } else if (luo_file && luo_file->reclaimed) {
+ pr_err("The file descriptor for token %lld has already been retrieved\n",
+ token);
+ ret = -EINVAL;
+ } else {
+ ret = -ENOENT;
+ }
+
+ luo_state_read_exit();
+
+ return ret;
+}
+
+/**
+ * liveupdate_register_file_handler - Register a file handler with LUO.
+ * @fh: Pointer to a caller-allocated &struct liveupdate_file_handler.
+ * The caller must initialize this structure, including a unique
+ * 'compatible' string and a valid 'fh' callbacks. This function adds the
+ * handler to the global list of supported file handlers.
+ *
+ * Context: Typically called during module initialization for file types that
+ * support live update preservation.
+ *
+ * Return: 0 on success. Negative errno on failure.
+ */
+int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
+{
+ struct liveupdate_file_handler *fh_iter;
+ int ret = 0;
+
+ luo_state_read_enter();
+ if (!liveupdate_state_normal() && !liveupdate_state_updated()) {
+ luo_state_read_exit();
+ return -EBUSY;
+ }
+
+ guard(rwsem_write)(&luo_register_file_list_rwsem);
+ list_for_each_entry(fh_iter, &luo_register_file_list, list) {
+ if (!strcmp(fh_iter->compatible, fh->compatible)) {
+ pr_err("File handler registration failed: Compatible string '%s' already registered.\n",
+ fh->compatible);
+ ret = -EEXIST;
+ goto exit_unlock;
+ }
+ }
+
+ if (!try_module_get(fh->ops->owner)) {
+ pr_warn("File handler '%s' unable to get reference.\n",
+ fh->compatible);
+ ret = -EAGAIN;
+ goto exit_unlock;
+ }
+
+ INIT_LIST_HEAD(&fh->list);
+ list_add_tail(&fh->list, &luo_register_file_list);
+
+exit_unlock:
+ luo_state_read_exit();
+
+ return ret;
+}
+
+/**
+ * liveupdate_unregister_file - Unregister a file handler.
+ * @fh: Pointer to the specific &struct liveupdate_file_handler instance
+ * that was previously returned by or passed to
+ * liveupdate_register_file_handler.
+ *
+ * Removes the specified handler instance @fh from the global list of
+ * registered file handlers. This function only removes the entry from the
+ * list; it does not free the memory associated with @fh itself. The caller
+ * is responsible for freeing the structure memory after this function returns
+ * successfully.
+ *
+ * Return: 0 on success. Negative errno on failure.
+ */
+int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
+{
+ unsigned long token;
+ struct luo_file *h;
+ int ret = 0;
+
+ luo_state_read_enter();
+ if (!liveupdate_state_normal() && !liveupdate_state_updated()) {
+ luo_state_read_exit();
+ return -EBUSY;
+ }
+
+ guard(rwsem_write)(&luo_register_file_list_rwsem);
+
+ xa_for_each(&luo_files_xa_out, token, h) {
+ if (h->fh == fh) {
+ luo_state_read_exit();
+ return -EBUSY;
+ }
+ }
+
+ list_del_init(&fh->list);
+ luo_state_read_exit();
+ module_put(fh->ops->owner);
+
+ return ret;
+}
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 40bfbe279d34..5692196fd425 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -34,4 +34,8 @@ int luo_do_subsystems_freeze_calls(void);
void luo_do_subsystems_finish_calls(void);
void luo_do_subsystems_cancel_calls(void);
+int luo_retrieve_file(u64 token, struct file **filep);
+int luo_register_file(u64 token, int fd);
+int luo_unregister_file(u64 token);
+
#endif /* _LINUX_LUO_INTERNAL_H */
--
2.50.1.565.gc32cd1483b-goog
^ permalink raw reply related
* [PATCH v3 13/30] liveupdate: luo_subsystems: implement subsystem callbacks
From: Pasha Tatashin @ 2025-08-07 1:44 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, changyuanl, pasha.tatashin, rppt,
dmatlack, rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda,
aliceryhl, masahiroy, akpm, tj, yoann.congal, mmaurer,
roman.gushchin, chenridong, axboe, mark.rutland, jannh,
vincent.guittot, hannes, dan.j.williams, david, joel.granados,
rostedt, anna.schumaker, song, zhangguopeng, linux, linux-kernel,
linux-doc, linux-mm, gregkh, tglx, mingo, bp, dave.hansen, x86,
hpa, rafael, dakr, bartosz.golaszewski, cw00.choi, myungjoo.ham,
yesanishhere, Jonathan.Cameron, quic_zijuhu, aleksander.lobakin,
ira.weiny, andriy.shevchenko, leon, lukas, bhelgaas, wagi,
djeffery, stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu
In-Reply-To: <20250807014442.3829950-1-pasha.tatashin@soleen.com>
Implement the core logic within luo_subsystems.c to handle the
invocation of registered subsystem callbacks and manage the persistence
of their state via the LUO FDT. This replaces the stub implementations
from the previous patch.
This completes the core mechanism enabling subsystems to actively
participate in the LUO state machine, execute phase-specific logic, and
persist/restore a u64 state across the live update transition
using the FDT.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/liveupdate/luo_subsystems.c | 167 ++++++++++++++++++++++++++++-
1 file changed, 164 insertions(+), 3 deletions(-)
diff --git a/kernel/liveupdate/luo_subsystems.c b/kernel/liveupdate/luo_subsystems.c
index 69f00d5c000e..ebb7c0db08f3 100644
--- a/kernel/liveupdate/luo_subsystems.c
+++ b/kernel/liveupdate/luo_subsystems.c
@@ -101,8 +101,81 @@ void __init luo_subsystems_startup(void *fdt)
luo_fdt_in = fdt;
}
+static void __luo_do_subsystems_cancel_calls(struct liveupdate_subsystem *boundary_subsystem)
+{
+ struct liveupdate_subsystem *subsystem;
+
+ list_for_each_entry(subsystem, &luo_subsystems_list, list) {
+ if (subsystem == boundary_subsystem)
+ break;
+
+ if (subsystem->ops->cancel) {
+ subsystem->ops->cancel(subsystem,
+ subsystem->private_data);
+ }
+ subsystem->private_data = 0;
+ }
+}
+
+static void luo_subsystems_retrieve_data_from_fdt(void)
+{
+ struct liveupdate_subsystem *subsystem;
+ int node_offset, prop_len;
+ const void *prop;
+
+ if (!luo_fdt_in)
+ return;
+
+ node_offset = fdt_subnode_offset(luo_fdt_in, 0,
+ LUO_SUBSYSTEMS_NODE_NAME);
+ list_for_each_entry(subsystem, &luo_subsystems_list, list) {
+ prop = fdt_getprop(luo_fdt_in, node_offset,
+ subsystem->name, &prop_len);
+
+ if (!prop || prop_len != sizeof(u64)) {
+ luo_restore_fail("In FDT node '/%s' can't find property '%s': %s\n",
+ LUO_SUBSYSTEMS_NODE_NAME,
+ subsystem->name,
+ fdt_strerror(node_offset));
+ }
+ memcpy(&subsystem->private_data, prop, sizeof(u64));
+ }
+}
+
+static int luo_subsystems_commit_data_to_fdt(void)
+{
+ struct liveupdate_subsystem *subsystem;
+ int ret, node_offset;
+
+ node_offset = fdt_subnode_offset(luo_fdt_out, 0,
+ LUO_SUBSYSTEMS_NODE_NAME);
+ list_for_each_entry(subsystem, &luo_subsystems_list, list) {
+ ret = fdt_setprop(luo_fdt_out, node_offset, subsystem->name,
+ &subsystem->private_data, sizeof(u64));
+ if (ret < 0) {
+ pr_err("Failed to set FDT property for subsystem '%s' %s\n",
+ subsystem->name, fdt_strerror(ret));
+ return -ENOENT;
+ }
+ }
+
+ return 0;
+}
+
static int luo_get_subsystem_data(struct liveupdate_subsystem *h, u64 *data)
{
+ int node_offset, prop_len;
+ const void *prop;
+
+ node_offset = fdt_subnode_offset(luo_fdt_in, 0,
+ LUO_SUBSYSTEMS_NODE_NAME);
+ prop = fdt_getprop(luo_fdt_in, node_offset, h->name, &prop_len);
+ if (!prop || prop_len != sizeof(u64)) {
+ luo_state_read_exit();
+ return -ENOENT;
+ }
+ memcpy(data, prop, sizeof(u64));
+
return 0;
}
@@ -121,7 +194,30 @@ static int luo_get_subsystem_data(struct liveupdate_subsystem *h, u64 *data)
*/
int luo_do_subsystems_prepare_calls(void)
{
- return 0;
+ struct liveupdate_subsystem *subsystem;
+ int ret;
+
+ guard(mutex)(&luo_subsystem_list_mutex);
+ list_for_each_entry(subsystem, &luo_subsystems_list, list) {
+ if (!subsystem->ops->prepare)
+ continue;
+
+ ret = subsystem->ops->prepare(subsystem,
+ &subsystem->private_data);
+ if (ret < 0) {
+ pr_err("Subsystem '%s' prepare callback failed [%d]\n",
+ subsystem->name, ret);
+ __luo_do_subsystems_cancel_calls(subsystem);
+
+ return ret;
+ }
+ }
+
+ ret = luo_subsystems_commit_data_to_fdt();
+ if (ret)
+ __luo_do_subsystems_cancel_calls(NULL);
+
+ return ret;
}
/**
@@ -139,7 +235,30 @@ int luo_do_subsystems_prepare_calls(void)
*/
int luo_do_subsystems_freeze_calls(void)
{
- return 0;
+ struct liveupdate_subsystem *subsystem;
+ int ret;
+
+ guard(mutex)(&luo_subsystem_list_mutex);
+ list_for_each_entry(subsystem, &luo_subsystems_list, list) {
+ if (!subsystem->ops->freeze)
+ continue;
+
+ ret = subsystem->ops->freeze(subsystem,
+ &subsystem->private_data);
+ if (ret < 0) {
+ pr_err("Subsystem '%s' freeze callback failed [%d]\n",
+ subsystem->name, ret);
+ __luo_do_subsystems_cancel_calls(subsystem);
+
+ return ret;
+ }
+ }
+
+ ret = luo_subsystems_commit_data_to_fdt();
+ if (ret)
+ __luo_do_subsystems_cancel_calls(NULL);
+
+ return ret;
}
/**
@@ -150,6 +269,18 @@ int luo_do_subsystems_freeze_calls(void)
*/
void luo_do_subsystems_finish_calls(void)
{
+ struct liveupdate_subsystem *subsystem;
+
+ guard(mutex)(&luo_subsystem_list_mutex);
+ luo_subsystems_retrieve_data_from_fdt();
+
+ list_for_each_entry(subsystem, &luo_subsystems_list, list) {
+ if (subsystem->ops->finish) {
+ subsystem->ops->finish(subsystem,
+ subsystem->private_data);
+ }
+ subsystem->private_data = 0;
+ }
}
/**
@@ -163,6 +294,9 @@ void luo_do_subsystems_finish_calls(void)
*/
void luo_do_subsystems_cancel_calls(void)
{
+ guard(mutex)(&luo_subsystem_list_mutex);
+ __luo_do_subsystems_cancel_calls(NULL);
+ luo_subsystems_commit_data_to_fdt();
}
/**
@@ -285,7 +419,34 @@ int liveupdate_unregister_subsystem(struct liveupdate_subsystem *h)
return ret;
}
+/**
+ * liveupdate_get_subsystem_data - Retrieve raw private data for a subsystem
+ * from FDT.
+ * @h: Pointer to the liveupdate_subsystem structure representing the
+ * subsystem instance. The 'name' field is used to find the property.
+ * @data: Output pointer where the subsystem's raw private u64 data will be
+ * stored via memcpy.
+ *
+ * Reads the 8-byte data property associated with the subsystem @h->name
+ * directly from the '/subsystems' node within the globally accessible
+ * 'luo_fdt_in' blob. Returns appropriate error codes if inputs are invalid, or
+ * nodes/properties are missing or invalid.
+ *
+ * Return: 0 on success. -ENOENT on error.
+ */
int liveupdate_get_subsystem_data(struct liveupdate_subsystem *h, u64 *data)
{
- return 0;
+ int ret;
+
+ luo_state_read_enter();
+ if (WARN_ON_ONCE(!luo_fdt_in || !liveupdate_state_updated())) {
+ luo_state_read_exit();
+ return -ENOENT;
+ }
+
+ scoped_guard(mutex, &luo_subsystem_list_mutex)
+ ret = luo_get_subsystem_data(h, data);
+ luo_state_read_exit();
+
+ return ret;
}
--
2.50.1.565.gc32cd1483b-goog
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox