Re: [PATCH v2 1/2] liveupdate: Add LIVEUPDATE_SESSION_FREEZE ioctl to freeze sessions early

From: Pasha Tatashin

Date: Sun Oct 25 2026 - 21:10:52 EST


On Fri, Oct 23, 2026 at 3:27 AM Mike Rapoport <rppt@xxxxxxxxxx> wrote:
>
> On Tue, Oct 20, 2026 at 10:47:03AM -0400, Pasha Tatashin wrote:
> > Currently, LUO sessions are frozen automatically only inside the
> > reboot(LINUX_REBOOT_CMD_KEXEC) pathway, where luo_session_serialize()
> > iterates through all outgoing sessions under global write locks and
> > freezes them serially during shutdown. Allowing userspace to decide when
> > to freeze a session once it is done with it (and to freeze multiple
> > sessions in parallel, since luo_session_freeze() only acquires the
> > per-session mutex) avoids postponing this work to kexec shutdown and
> > reduces blackout time.
> >
> > Introduce LIVEUPDATE_SESSION_FREEZE on session file descriptors to freeze
> > a session early. The ioctl runs .freeze() across all preserved files in the
> > session and serializes their metadata. Once frozen, no additional files can
> > be added to the session, and subsequent freeze calls return -EBUSY. Freeze
> > is only valid on outgoing sessions and returns -EINVAL on incoming ones.
> >
> > During kexec, sessions that were already frozen early are skipped, while
> > sessions that did not invoke this ioctl auto-freeze as before. If an
> > early-frozen session is released before kexec, its files are rolled back
> > via .unfreeze() before being unpreserved.
> >
> > Signed-off-by: Pasha Tatashin <pasha.tatashin@xxxxxxxxxx>
> > ---
> > include/linux/liveupdate.h | 3 +-
> > include/uapi/linux/liveupdate.h | 25 ++++++++++
> > kernel/liveupdate/luo_file.c | 73 +++++++++++++---------------
> > kernel/liveupdate/luo_internal.h | 9 ++--
> > kernel/liveupdate/luo_session.c | 82 +++++++++++++++++++++++++-------
> > mm/memfd_luo.c | 5 +-
> > 6 files changed, 134 insertions(+), 63 deletions(-)
> >
> > diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
> > index 6051abc0612c..851692571e13 100644
> > --- a/include/linux/liveupdate.h
> > +++ b/include/linux/liveupdate.h
> > @@ -59,7 +59,8 @@ struct liveupdate_file_op_args {
> > * compatible with the given file.
> > * @preserve: Required. Performs state-saving for the file.
> > * @unpreserve: Required. Cleans up any resources allocated by @preserve.
> > - * @freeze: Optional. Final actions just before kernel transition.
> > + * @freeze: Optional. Prepares final state before kexec (via
> > + * LIVEUPDATE_SESSION_FREEZE or during reboot).
> > * @unfreeze: Optional. Undo freeze operations.
> > * @retrieve: Required. Restores the file in the new kernel.
> > * @can_finish: Optional. Check if this FD can finish, i.e. all restoration
> > diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
> > index 4043d4038712..1644c502521e 100644
> > --- a/include/uapi/linux/liveupdate.h
> > +++ b/include/uapi/linux/liveupdate.h
> > @@ -60,6 +60,7 @@ enum {
> > LIVEUPDATE_CMD_SESSION_RETRIEVE_FD = 0x41,
> > LIVEUPDATE_CMD_SESSION_FINISH = 0x42,
> > LIVEUPDATE_CMD_SESSION_GET_NAME = 0x43,
> > + LIVEUPDATE_CMD_SESSION_FREEZE = 0x44,
> > };
> >
> > /**
> > @@ -236,4 +237,28 @@ struct liveupdate_session_get_name {
> > #define LIVEUPDATE_SESSION_GET_NAME \
> > _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SESSION_GET_NAME)
> >
> > +/**
> > + * struct liveupdate_session_freeze - ioctl(LIVEUPDATE_SESSION_FREEZE)
> > + * @size: Input; sizeof(struct liveupdate_session_freeze)
> > + * @reserved: Input; Must be zero. Reserved for future use.
> > + *
> > + * Freezes the session early from userspace before the reboot kexec phase.
> > + *
> > + * When this ioctl is called, the kernel executes the .freeze() callback on all
> > + * preserved files in the session and serializes their metadata. Once frozen,
> > + * no further files can be preserved in the session.
> > + *
> > + * During the subsequent reboot kexec, this session is recognized as already
>
> Nit: kexec reboot

Ok

>
> > + * frozen and is skipped from the late auto freeze.
> > + *
> > + * Return: 0 on success, negative error code on failure.
> > + */
> > +struct liveupdate_session_freeze {
> > + __u32 size;
> > + __u32 reserved;
> > +};
> > +
> > +#define LIVEUPDATE_SESSION_FREEZE \
> > + _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SESSION_FREEZE)
> > +
> > #endif /* _UAPI_LIVEUPDATE_H */
> > diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
> > index a39992da504f..7f79890beb31 100644
> > --- a/kernel/liveupdate/luo_file.c
> > +++ b/kernel/liveupdate/luo_file.c
> > @@ -31,9 +31,9 @@
> > * - unpreserve(): Cleans up any resources allocated by .preserve(), called
> > * if the preservation process is aborted before the reboot (i.e. session is
> > * closed).
> > - * - freeze(): A final pre-reboot opportunity to prepare the state for kexec.
> > - * We are already in reboot syscall, and therefore userspace cannot mutate
> > - * the file anymore.
> > + * - freeze(): Prepares the final state for kexec. Invoked either early via
>
> Nit: I'd drop "early", it seems relevant only to the current change, but
> will be more confusing than useful later.

Good point, will do.

>
> > + * the LIVEUPDATE_SESSION_FREEZE ioctl once userspace is done modifying
> > + * the session's files, or during the reboot() syscall.
> > * - unfreeze(): Undoes the actions of .freeze(), called if the live update
> > * is aborted after the freeze phase.
> > * - retrieve(): Reconstructs the file in the new kernel from the preserved
> > @@ -48,7 +48,8 @@
> > * handler, calls its .preserve() operation, and creates an internal &struct
> > * luo_file to track the live state.
> > *
> > - * 2. Freeze (Pre-Reboot): Just before the kexec, luo_file_freeze() is called.
> > + * 2. Freeze (Pre-Reboot): Either explicitly via LIVEUPDATE_SESSION_FREEZE or
> > + * just before the kexec during reboot(), luo_file_freeze() is called.
> > * It iterates through all preserved files, calls their respective .freeze()
> > * operation, and serializes their final metadata (compatible string, token,
> > * and data handle) into a contiguous memory block for KHO.
> > @@ -74,15 +75,16 @@
> > *
> > * 1. Abort Before Reboot: If the userspace agent aborts the live update
> > * process before calling reboot (e.g., by closing the session file
> > - * descriptor), the session's release handler calls
> > - * luo_file_unpreserve_files(). This invokes the .unpreserve() callback on
> > - * all preserved files, ensuring all allocated resources are cleaned up and
> > - * returning the system to a clean state.
> > + * descriptor), the session's release handler unfreezes the files if the
> > + * session was already frozen and calls luo_file_unpreserve_files(). This
> > + * invokes the .unpreserve() callback on all preserved files, ensuring all
> > + * allocated resources are cleaned up and returning the system to a clean
> > + * state.
> > *
> > - * 2. Freeze Failure: During the reboot() syscall, if any handler's .freeze()
> > - * op fails, the .unfreeze() op is invoked on all previously *successful*
> > - * freezes to roll back their state. The reboot() syscall then returns an
> > - * error to userspace, canceling the live update.
> > + * 2. Freeze Failure: During LIVEUPDATE_SESSION_FREEZE or the reboot() syscall,
> > + * if any handler's .freeze() op fails, the .unfreeze() op is invoked on all
> > + * previously *successful* freezes to roll back their state, and an error is
> > + * returned to userspace.
>
> I think we still want to say that freeze failure at reboot cancels live
> update.

OK

>
> > *
> > * 3. Finish Failure: In the new kernel, if a handler's .finish() op fails,
> > * the luo_file_finish() operation is aborted. LUO retains ownership of
> > @@ -440,12 +442,11 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
> >
> > /**
> > * luo_file_freeze - Freezes all preserved files and serializes their metadata.
> > - * @file_set: The file_set whose files are to be frozen.
> > - * @file_set_ser: Where to put the serialized file_set.
> > + * @file_set: The file_set whose files are to be frozen.
> > *
> > - * This function is called from the reboot() syscall path, just before the
> > - * kernel transitions to the new image via kexec. Its purpose is to perform the
> > - * final preparation and serialization of all preserved files in the file_set.
> > + * This function is called from LIVEUPDATE_SESSION_FREEZE or from the reboot()
> > + * syscall path. Its purpose is to perform the preparation and serialization of
>
> Nit: ^ final?

OK

>
> > + * all preserved files in the file_set.
> > *
> > * It iterates through each preserved file in FIFO order (the order of
> > * preservation) and performs two main actions:
> > @@ -457,22 +458,20 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
> > *
> > * 2. Serializes Metadata: After a successful freeze, it copies the final file
> > * metadata—the handler's compatible string, the user token, and the final
> > - * private data handle—into the pre-allocated contiguous memory buffer
> > - * (file_set->files) that will be handed over to the next kernel via KHO.
> > + * private data handle—into the pre-allocated serialization blocks
> > + * (file_set->block_set) that will be handed over to the next kernel via KHO.
> > *
> > * Error Handling (Rollback):
> > * This function is atomic. If any handler's .freeze() operation fails, the
> > - * entire live update is aborted. The __luo_file_unfreeze() helper is
> > - * immediately called to invoke the .unfreeze() op on all files that were
> > - * successfully frozen before the point of failure, rolling them back to a
> > - * running state. The function then returns an error, causing the reboot()
> > - * syscall to fail.
> > + * __luo_file_unfreeze() helper is immediately called to invoke the .unfreeze()
> > + * op on all files that were successfully frozen before the point of failure,
> > + * rolling them back to an unfrozen state, and an error is returned to the
> > + * caller.
>
> Don't you want to say that reboot is canceled here too?

Sure

>
> > *
> > - * Context: Called only from the liveupdate_reboot() path.
> > + * Context: Called from LIVEUPDATE_SESSION_FREEZE or the liveupdate_reboot() path.
> > * Return: 0 on success, or a negative errno on failure.
> > */
> > -int luo_file_freeze(struct luo_file_set *file_set,
> > - struct luo_file_set_ser *file_set_ser)
> > +int luo_file_freeze(struct luo_file_set *file_set)
> > {
> > struct luo_file *luo_file;
> > struct kho_block_set_it it;
> > @@ -506,9 +505,6 @@ int luo_file_freeze(struct luo_file_set *file_set,
> > file_ser->token = luo_file->token;
> > }
> >
> > - file_set_ser->count = file_set->count;
> > - file_set_ser->files = kho_block_set_head_pa(&file_set->block_set);
> > -
> > return 0;
> >
> > err_unfreeze:
> > @@ -519,28 +515,25 @@ int luo_file_freeze(struct luo_file_set *file_set,
> >
> > /**
> > * luo_file_unfreeze - Unfreezes all files in a file_set and clear serialization
> > - * @file_set: The file_set whose files are to be unfrozen.
> > - * @file_set_ser: Serialized file_set.
> > + * @file_set: The file_set whose files are to be unfrozen.
> > *
> > * This function rolls back the state of all files in a file_set after the
> > - * freeze phase has begun but must be aborted. It is the counterpart to
> > - * luo_file_freeze().
> > + * freeze phase has begun. It is the counterpart to luo_file_freeze().
> > *
> > * It invokes the __luo_file_unfreeze() helper with a NULL argument, which
> > - * signals the helper to iterate through all files in the file_set and call
> > - * their respective .unfreeze() handler callbacks.
> > + * signals the helper to iterate through all files in the file_set, call their
> > + * respective .unfreeze() handler callbacks, and clear the serialized blocks.
> > *
> > * Context: This is called when the live update is aborted during
> > - * the reboot() syscall, after luo_file_freeze() has been called.
> > + * the reboot() syscall, after luo_file_freeze() has been called,
> > + * or when an early-frozen session is released before kexec.
> > */
> > -void luo_file_unfreeze(struct luo_file_set *file_set,
> > - struct luo_file_set_ser *file_set_ser)
> > +void luo_file_unfreeze(struct luo_file_set *file_set)
>
> I went several time back and forth trying to understand why we remove
> *file_set_ser here.
>
> Maybe add a preparation patch that does the renames and moves file_set_ser
> handling?

Yes, this is a good suggestion. I will add another patch to rename,
and change the argument.

>
> > {
> > if (!file_set->count)
> > return;
> >
> > __luo_file_unfreeze(file_set, NULL);
> > - memset(file_set_ser, 0, sizeof(*file_set_ser));
> > }
> >
> > /**
> > diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
> > index dac6644bfb18..b3fc3dcf5f36 100644
> > --- a/kernel/liveupdate/luo_internal.h
> > +++ b/kernel/liveupdate/luo_internal.h
> > @@ -64,6 +64,8 @@ struct luo_file_set {
> > * previous kernel) sessions.
> > * @retrieved: A boolean flag indicating whether this session has been
> > * retrieved by a consumer in the new kernel.
> > + * @frozen: A boolean flag indicating whether this session has been
> > + * frozen early from userspace.
>
> s/early/explicitly

OK

>
> > * @file_set: A set of files that belong to this session.
> > * @mutex: protects fields in the luo_session.
> > */
> > @@ -71,6 +73,7 @@ struct luo_session {
> > char name[LIVEUPDATE_SESSION_NAME_LENGTH];
> > struct list_head list;
> > bool retrieved;
> > + bool frozen;
> > struct luo_file_set file_set;
> > struct mutex mutex;
> > };
>
> --
> Sincerely yours,
> Mike.