// 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 to preserve specific, stateful file * descriptors across a kexec-based live update. 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. * * The framework is built around a callback-based handler model and a well- * defined lifecycle for each preserved file. * * Handler Registration: * Kernel modules responsible for a specific file type (e.g., memfd, vfio) * register a &struct liveupdate_file_handler. This handler provides a set of * callbacks that LUO invokes at different stages of the update process, most * notably: * * - can_preserve(): A lightweight check to determine if the handler is * compatible with a given 'struct file'. * - preserve(): The heavyweight operation that saves the file's state and * returns an opaque u64 handle. This is typically performed while the * workload is still active to minimize the downtime during the * actual reboot transition. * - 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. * - 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 * handle. * - finish(): Performs final check and cleanup in the new kernel. After * succesul finish call, LUO gives up ownership to this file. * * File Preservation Lifecycle happy path: * * 1. Preserve (Normal Operation): A userspace agent preserves files one by one * via an ioctl. For each file, luo_preserve_file() finds a compatible * 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. * 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. * * 3. Deserialize: After kexec, luo_file_deserialize() runs when session gets * deserialized (which is when /dev/liveupdate is first opened). It reads the * serialized data from the KHO memory region and reconstructs the in-memory * list of &struct luo_file instances for the new kernel, linking them to * their corresponding handlers. * * 4. Retrieve (New Kernel - Userspace Ready): The userspace agent can now * restore file descriptors by providing a token. luo_retrieve_file() * searches for the matching token, calls the handler's .retrieve() op to * re-create the 'struct file', and returns a new FD. Files can be * retrieved in ANY order. * * 5. Finish (New Kernel - Cleanup): Once a session retrival is complete, * luo_file_finish() is called. It iterates through all files, invokes their * .finish() operations for final cleanup, and releases all associated kernel * resources. * * File Preservation Lifecycle unhappy paths: * * 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. * * 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. * * 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 * all files within that session, including those that were not yet * processed. The userspace agent can attempt to call the finish operation * again later. If the issue cannot be resolved, these resources will be held * by LUO until the next live update cycle, at which point they will be * discarded. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/cleanup.h>#include <linux/compiler.h>#include <linux/err.h>#include <linux/errno.h>#include <linux/file.h>#include <linux/fs.h>#include <linux/io.h>#include <linux/kexec_handover.h>#include <linux/kho/abi/luo.h>#include <linux/list_private.h>#include <linux/liveupdate.h>#include <linux/module.h>#include <linux/sizes.h>#include <linux/xarray.h>#include <linux/slab.h>#include <linux/string.h>#include "luo_internal.h" static LIST_HEAD(luo_file_handler_list); /* Keep track of files being preserved by LUO */static DEFINE_XARRAY(luo_preserved_files); /** * struct luo_file - Represents a single preserved file instance. * @fh: Pointer to the &struct liveupdate_file_handler that manages * this type of file. * @file: Pointer to the kernel's &struct file that is being preserved. * This is NULL in the new kernel until the file is successfully * retrieved. * @serialized_data: The opaque u64 handle to the serialized state of the file. * This handle is passed back to the handler's .freeze(), * .retrieve(), and .finish() callbacks, allowing it to track * and update its serialized state across phases. * @private_data: Pointer to the private data for the file used to hold runtime * state that is not preserved. Set by the handler's .preserve() * callback, and must be freed in the handler's .unpreserve() * callback. * @retrieve_status: Status code indicating whether a user/kernel in the new kernel has * successfully called retrieve() on this file. This prevents * multiple retrieval attempts. A value of 0 means a retrieve() * has not been attempted, a positive value means the retrieve() * was successful, and a negative value means the retrieve() * failed, and the value is the error code of the call. * @mutex: A mutex that protects the fields of this specific instance * (e.g., @retrieved, @file), ensuring that operations like * retrieving or finishing a file are atomic. * @list: The list_head linking this instance into its parent * file_set's list of preserved files. * @token: The user-provided unique token used to identify this file. * * This structure is the core in-kernel representation of a single file being * managed through a live update. An instance is created by luo_preserve_file() * to link a 'struct file' to its corresponding handler, a user-provided token, * and the serialized state handle returned by the handler's .preserve() * operation. * * These instances are tracked in a per-file_set list. The @serialized_data * field, which holds a handle to the file's serialized state, may be updated * during the .freeze() callback before being serialized for the next kernel. * After reboot, these structures are recreated by luo_file_deserialize() and * are finally cleaned up by luo_file_finish(). */struct luo_file { struct liveupdate_file_handler *fh; struct file *file; u64 serialized_data; void *private_data; int retrieve_status; struct mutex mutex; struct list_head list; u64 token;}; static unsigned long luo_get_id(struct liveupdate_file_handler *fh, struct file *file){ return fh->ops->get_id ? fh->ops->get_id(file) : (unsigned long)file;} static bool luo_token_is_used(struct luo_file_set *file_set, u64 token){ struct luo_file *iter; list_for_each_entry(iter, &file_set->files_list, list) { if (iter->token == token) return true; } return false;} /** * luo_preserve_file - Initiate the preservation of a file descriptor. * @file_set: The file_set to which the preserved file will be added. * @token: A unique, user-provided identifier for the file. * @fd: The file descriptor to be preserved. * * This function orchestrates the first phase of preserving a file. Upon entry, * it takes a reference to the 'struct file' via fget(), effectively making LUO * a co-owner of the file. This reference is held until the file is either * unpreserved or successfully finished in the next kernel, preventing the file * from being prematurely destroyed. * * This function orchestrates the first phase of preserving a file. It performs * the following steps: * * 1. Validates that the @token is not already in use within the file_set. * 2. Ensures the file_set's memory for files serialization is allocated * (allocates if needed). * 3. Iterates through registered handlers, calling can_preserve() to find one * compatible with the given @fd. * 4. Calls the handler's .preserve() operation, which saves the file's state * and returns an opaque private data handle. * 5. Adds the new instance to the file_set's internal list. * * On success, LUO takes a reference to the 'struct file' and considers it * under its management until it is unpreserved or finished. * * In case of any failure, all intermediate allocations (file reference, memory * for the 'luo_file' struct, etc.) are cleaned up before returning an error. * * Context: Can be called from an ioctl handler during normal system operation. * Return: 0 on success. Returns a negative errno on failure: * -EEXIST if the token is already used. * -EBUSY if the file descriptor is already preserved by another session. * -EBADF if the file descriptor is invalid. * -ENOSPC if the file_set is full. * -ENOENT if no compatible handler is found. * -ENOMEM on memory allocation failure. * Other erros might be returned by .preserve(). */int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd){ struct liveupdate_file_op_args args = {0}; struct liveupdate_file_handler *fh; struct luo_file *luo_file; struct file *file; int err; if (luo_token_is_used(file_set, token)) return -EEXIST; err = kho_block_set_grow(&file_set->block_set, file_set->count + 1); if (err) return err; file = fget(fd); if (!file) { err = -EBADF; goto err_shrink; } err = -ENOENT; down_read(&luo_register_rwlock); list_private_for_each_entry(fh, &luo_file_handler_list, list) { if (fh->ops->can_preserve(fh, file)) { if (try_module_get(fh->ops->owner)) err = 0; break; } } up_read(&luo_register_rwlock); /* err is still -ENOENT if no handler was found */ if (err) goto err_fput; err = xa_insert(&luo_preserved_files, luo_get_id(fh, file), file, GFP_KERNEL); if (err) goto err_module_put; err = luo_flb_file_preserve(fh); if (err) goto err_erase_xa; luo_file = kzalloc_obj(*luo_file); if (!luo_file) { err = -ENOMEM; goto err_flb_unpreserve; } luo_file->file = file; luo_file->fh = fh; luo_file->token = token; mutex_init(&luo_file->mutex); args.handler = fh; args.file = file; err = fh->ops->preserve(&args); if (err) goto err_kfree; luo_file->serialized_data = args.serialized_data; luo_file->private_data = args.private_data; list_add_tail(&luo_file->list, &file_set->files_list); file_set->count++; return 0; err_kfree: kfree(luo_file);err_flb_unpreserve: luo_flb_file_unpreserve(fh);err_erase_xa: xa_erase(&luo_preserved_files, luo_get_id(fh, file));err_module_put: module_put(fh->ops->owner);err_fput: fput(file);err_shrink: kho_block_set_shrink(&file_set->block_set, file_set->count); return err;} /** * luo_file_unpreserve_files - Unpreserves all files from a file_set. * @file_set: The files to be cleaned up. * * This function serves as the primary cleanup path for a file_set. It is * invoked when the userspace agent closes the file_set's file descriptor. * * For each file, it performs the following cleanup actions: * 1. Calls the handler's .unpreserve() callback to allow the handler to * release any resources it allocated. * 2. Removes the file from the file_set's internal tracking list. * 3. Releases the reference to the 'struct file' that was taken by * luo_preserve_file() via fput(), returning ownership. * 4. Frees the memory associated with the internal 'struct luo_file'. * * After all individual files are unpreserved, it frees the contiguous memory * block that was allocated to hold their serialization data. */void luo_file_unpreserve_files(struct luo_file_set *file_set){ struct luo_file *luo_file; while (!list_empty(&file_set->files_list)) { struct liveupdate_file_op_args args = {0}; luo_file = list_last_entry(&file_set->files_list, struct luo_file, list); args.handler = luo_file->fh; args.file = luo_file->file; args.serialized_data = luo_file->serialized_data; args.private_data = luo_file->private_data; luo_file->fh->ops->unpreserve(&args); luo_flb_file_unpreserve(luo_file->fh); xa_erase(&luo_preserved_files, luo_get_id(luo_file->fh, luo_file->file)); module_put(luo_file->fh->ops->owner); list_del(&luo_file->list); file_set->count--; kho_block_set_shrink(&file_set->block_set, file_set->count); fput(luo_file->file); mutex_destroy(&luo_file->mutex); kfree(luo_file); } kho_block_set_destroy(&file_set->block_set);} static int luo_file_freeze_one(struct luo_file_set *file_set, struct luo_file *luo_file){ int err = 0; guard(mutex)(&luo_file->mutex); if (luo_file->fh->ops->freeze) { struct liveupdate_file_op_args args = {0}; args.handler = luo_file->fh; args.file = luo_file->file; args.serialized_data = luo_file->serialized_data; args.private_data = luo_file->private_data; err = luo_file->fh->ops->freeze(&args); if (!err) luo_file->serialized_data = args.serialized_data; } return err;} static void luo_file_unfreeze_one(struct luo_file_set *file_set, struct luo_file *luo_file){ guard(mutex)(&luo_file->mutex); if (luo_file->fh->ops->unfreeze) { struct liveupdate_file_op_args args = {0}; args.handler = luo_file->fh; args.file = luo_file->file; args.serialized_data = luo_file->serialized_data; args.private_data = luo_file->private_data; luo_file->fh->ops->unfreeze(&args); }} static void __luo_file_unfreeze(struct luo_file_set *file_set, struct luo_file *failed_entry){ struct list_head *files_list = &file_set->files_list; struct luo_file *luo_file; list_for_each_entry(luo_file, files_list, list) { if (luo_file == failed_entry) break; luo_file_unfreeze_one(file_set, luo_file); } kho_block_set_clear(&file_set->block_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. * * 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. * * It iterates through each preserved file in FIFO order (the order of * preservation) and performs two main actions: * * 1. Freezes the File: It calls the handler's .freeze() callback for each * file. This gives the handler a final opportunity to quiesce the device or * prepare its state for the upcoming reboot. The handler may update its * private data handle during this step. * * 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. * * 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. * * Context: Called only from 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){ struct luo_file *luo_file; struct kho_block_set_it it; int err; if (!file_set->count) return 0; kho_block_set_it_init(&it, &file_set->block_set); list_for_each_entry(luo_file, &file_set->files_list, list) { struct luo_file_ser *file_ser = kho_block_set_it_reserve_entry(&it); /* This should not fail normally as blocks were pre-allocated */ if (WARN_ON_ONCE(!file_ser)) { err = -ENOSPC; goto err_unfreeze; } err = luo_file_freeze_one(file_set, luo_file); if (err < 0) { pr_warn("Freeze failed for token[%#0llx] handler[%s] err[%pe]\n", luo_file->token, luo_file->fh->compatible, ERR_PTR(err)); goto err_unfreeze; } strscpy(file_ser->compatible, luo_file->fh->compatible, sizeof(file_ser->compatible)); file_ser->data = luo_file->serialized_data; 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: __luo_file_unfreeze(file_set, luo_file); return err;} Lines 1–500 of 928. Load more
// 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 to preserve specific, stateful file * descriptors across a kexec-based live update. 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. * * The framework is built around a callback-based handler model and a well- * defined lifecycle for each preserved file. * * Handler Registration: * Kernel modules responsible for a specific file type (e.g., memfd, vfio) * register a &struct liveupdate_file_handler. This handler provides a set of * callbacks that LUO invokes at different stages of the update process, most * notably: * * - can_preserve(): A lightweight check to determine if the handler is * compatible with a given 'struct file'. * - preserve(): The heavyweight operation that saves the file's state and * returns an opaque u64 handle. This is typically performed while the * workload is still active to minimize the downtime during the * actual reboot transition. * - 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. * - 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 * handle. * - finish(): Performs final check and cleanup in the new kernel. After * succesul finish call, LUO gives up ownership to this file. * * File Preservation Lifecycle happy path: * * 1. Preserve (Normal Operation): A userspace agent preserves files one by one * via an ioctl. For each file, luo_preserve_file() finds a compatible * 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. * 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. * * 3. Deserialize: After kexec, luo_file_deserialize() runs when session gets * deserialized (which is when /dev/liveupdate is first opened). It reads the * serialized data from the KHO memory region and reconstructs the in-memory * list of &struct luo_file instances for the new kernel, linking them to * their corresponding handlers. * * 4. Retrieve (New Kernel - Userspace Ready): The userspace agent can now * restore file descriptors by providing a token. luo_retrieve_file() * searches for the matching token, calls the handler's .retrieve() op to * re-create the 'struct file', and returns a new FD. Files can be * retrieved in ANY order. * * 5. Finish (New Kernel - Cleanup): Once a session retrival is complete, * luo_file_finish() is called. It iterates through all files, invokes their * .finish() operations for final cleanup, and releases all associated kernel * resources. * * File Preservation Lifecycle unhappy paths: * * 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. * * 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. * * 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 * all files within that session, including those that were not yet * processed. The userspace agent can attempt to call the finish operation * again later. If the issue cannot be resolved, these resources will be held * by LUO until the next live update cycle, at which point they will be * discarded. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/cleanup.h> #include <linux/compiler.h> #include <linux/err.h> #include <linux/errno.h> #include <linux/file.h> #include <linux/fs.h> #include <linux/io.h> #include <linux/kexec_handover.h> #include <linux/kho/abi/luo.h> #include <linux/list_private.h> #include <linux/liveupdate.h> #include <linux/module.h> #include <linux/sizes.h> #include <linux/xarray.h> #include <linux/slab.h> #include <linux/string.h> #include "luo_internal.h" static LIST_HEAD(luo_file_handler_list); /* Keep track of files being preserved by LUO */ static DEFINE_XARRAY(luo_preserved_files); /** * struct luo_file - Represents a single preserved file instance. * @fh: Pointer to the &struct liveupdate_file_handler that manages * this type of file. * @file: Pointer to the kernel's &struct file that is being preserved. * This is NULL in the new kernel until the file is successfully * retrieved. * @serialized_data: The opaque u64 handle to the serialized state of the file. * This handle is passed back to the handler's .freeze(), * .retrieve(), and .finish() callbacks, allowing it to track * and update its serialized state across phases. * @private_data: Pointer to the private data for the file used to hold runtime * state that is not preserved. Set by the handler's .preserve() * callback, and must be freed in the handler's .unpreserve() * callback. * @retrieve_status: Status code indicating whether a user/kernel in the new kernel has * successfully called retrieve() on this file. This prevents * multiple retrieval attempts. A value of 0 means a retrieve() * has not been attempted, a positive value means the retrieve() * was successful, and a negative value means the retrieve() * failed, and the value is the error code of the call. * @mutex: A mutex that protects the fields of this specific instance * (e.g., @retrieved, @file), ensuring that operations like * retrieving or finishing a file are atomic. * @list: The list_head linking this instance into its parent * file_set's list of preserved files. * @token: The user-provided unique token used to identify this file. * * This structure is the core in-kernel representation of a single file being * managed through a live update. An instance is created by luo_preserve_file() * to link a 'struct file' to its corresponding handler, a user-provided token, * and the serialized state handle returned by the handler's .preserve() * operation. * * These instances are tracked in a per-file_set list. The @serialized_data * field, which holds a handle to the file's serialized state, may be updated * during the .freeze() callback before being serialized for the next kernel. * After reboot, these structures are recreated by luo_file_deserialize() and * are finally cleaned up by luo_file_finish(). */ struct luo_file { struct liveupdate_file_handler *fh; struct file *file; u64 serialized_data; void *private_data; int retrieve_status; struct mutex mutex; struct list_head list; u64 token; }; static unsigned long luo_get_id(struct liveupdate_file_handler *fh, struct file *file) { return fh->ops->get_id ? fh->ops->get_id(file) : (unsigned long)file; } static bool luo_token_is_used(struct luo_file_set *file_set, u64 token) { struct luo_file *iter; list_for_each_entry(iter, &file_set->files_list, list) { if (iter->token == token) return true; } return false; } /** * luo_preserve_file - Initiate the preservation of a file descriptor. * @file_set: The file_set to which the preserved file will be added. * @token: A unique, user-provided identifier for the file. * @fd: The file descriptor to be preserved. * * This function orchestrates the first phase of preserving a file. Upon entry, * it takes a reference to the 'struct file' via fget(), effectively making LUO * a co-owner of the file. This reference is held until the file is either * unpreserved or successfully finished in the next kernel, preventing the file * from being prematurely destroyed. * * This function orchestrates the first phase of preserving a file. It performs * the following steps: * * 1. Validates that the @token is not already in use within the file_set. * 2. Ensures the file_set's memory for files serialization is allocated * (allocates if needed). * 3. Iterates through registered handlers, calling can_preserve() to find one * compatible with the given @fd. * 4. Calls the handler's .preserve() operation, which saves the file's state * and returns an opaque private data handle. * 5. Adds the new instance to the file_set's internal list. * * On success, LUO takes a reference to the 'struct file' and considers it * under its management until it is unpreserved or finished. * * In case of any failure, all intermediate allocations (file reference, memory * for the 'luo_file' struct, etc.) are cleaned up before returning an error. * * Context: Can be called from an ioctl handler during normal system operation. * Return: 0 on success. Returns a negative errno on failure: * -EEXIST if the token is already used. * -EBUSY if the file descriptor is already preserved by another session. * -EBADF if the file descriptor is invalid. * -ENOSPC if the file_set is full. * -ENOENT if no compatible handler is found. * -ENOMEM on memory allocation failure. * Other erros might be returned by .preserve(). */ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd) { struct liveupdate_file_op_args args = {0}; struct liveupdate_file_handler *fh; struct luo_file *luo_file; struct file *file; int err; if (luo_token_is_used(file_set, token)) return -EEXIST; err = kho_block_set_grow(&file_set->block_set, file_set->count + 1); if (err) return err; file = fget(fd); if (!file) { err = -EBADF; goto err_shrink; } err = -ENOENT; down_read(&luo_register_rwlock); list_private_for_each_entry(fh, &luo_file_handler_list, list) { if (fh->ops->can_preserve(fh, file)) { if (try_module_get(fh->ops->owner)) err = 0; break; } } up_read(&luo_register_rwlock); /* err is still -ENOENT if no handler was found */ if (err) goto err_fput; err = xa_insert(&luo_preserved_files, luo_get_id(fh, file), file, GFP_KERNEL); if (err) goto err_module_put; err = luo_flb_file_preserve(fh); if (err) goto err_erase_xa; luo_file = kzalloc_obj(*luo_file); if (!luo_file) { err = -ENOMEM; goto err_flb_unpreserve; } luo_file->file = file; luo_file->fh = fh; luo_file->token = token; mutex_init(&luo_file->mutex); args.handler = fh; args.file = file; err = fh->ops->preserve(&args); if (err) goto err_kfree; luo_file->serialized_data = args.serialized_data; luo_file->private_data = args.private_data; list_add_tail(&luo_file->list, &file_set->files_list); file_set->count++; return 0; err_kfree: kfree(luo_file); err_flb_unpreserve: luo_flb_file_unpreserve(fh); err_erase_xa: xa_erase(&luo_preserved_files, luo_get_id(fh, file)); err_module_put: module_put(fh->ops->owner); err_fput: fput(file); err_shrink: kho_block_set_shrink(&file_set->block_set, file_set->count); return err; } /** * luo_file_unpreserve_files - Unpreserves all files from a file_set. * @file_set: The files to be cleaned up. * * This function serves as the primary cleanup path for a file_set. It is * invoked when the userspace agent closes the file_set's file descriptor. * * For each file, it performs the following cleanup actions: * 1. Calls the handler's .unpreserve() callback to allow the handler to * release any resources it allocated. * 2. Removes the file from the file_set's internal tracking list. * 3. Releases the reference to the 'struct file' that was taken by * luo_preserve_file() via fput(), returning ownership. * 4. Frees the memory associated with the internal 'struct luo_file'. * * After all individual files are unpreserved, it frees the contiguous memory * block that was allocated to hold their serialization data. */ void luo_file_unpreserve_files(struct luo_file_set *file_set) { struct luo_file *luo_file; while (!list_empty(&file_set->files_list)) { struct liveupdate_file_op_args args = {0}; luo_file = list_last_entry(&file_set->files_list, struct luo_file, list); args.handler = luo_file->fh; args.file = luo_file->file; args.serialized_data = luo_file->serialized_data; args.private_data = luo_file->private_data; luo_file->fh->ops->unpreserve(&args); luo_flb_file_unpreserve(luo_file->fh); xa_erase(&luo_preserved_files, luo_get_id(luo_file->fh, luo_file->file)); module_put(luo_file->fh->ops->owner); list_del(&luo_file->list); file_set->count--; kho_block_set_shrink(&file_set->block_set, file_set->count); fput(luo_file->file); mutex_destroy(&luo_file->mutex); kfree(luo_file); } kho_block_set_destroy(&file_set->block_set); } static int luo_file_freeze_one(struct luo_file_set *file_set, struct luo_file *luo_file) { int err = 0; guard(mutex)(&luo_file->mutex); if (luo_file->fh->ops->freeze) { struct liveupdate_file_op_args args = {0}; args.handler = luo_file->fh; args.file = luo_file->file; args.serialized_data = luo_file->serialized_data; args.private_data = luo_file->private_data; err = luo_file->fh->ops->freeze(&args); if (!err) luo_file->serialized_data = args.serialized_data; } return err; } static void luo_file_unfreeze_one(struct luo_file_set *file_set, struct luo_file *luo_file) { guard(mutex)(&luo_file->mutex); if (luo_file->fh->ops->unfreeze) { struct liveupdate_file_op_args args = {0}; args.handler = luo_file->fh; args.file = luo_file->file; args.serialized_data = luo_file->serialized_data; args.private_data = luo_file->private_data; luo_file->fh->ops->unfreeze(&args); } } static void __luo_file_unfreeze(struct luo_file_set *file_set, struct luo_file *failed_entry) { struct list_head *files_list = &file_set->files_list; struct luo_file *luo_file; list_for_each_entry(luo_file, files_list, list) { if (luo_file == failed_entry) break; luo_file_unfreeze_one(file_set, luo_file); } kho_block_set_clear(&file_set->block_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. * * 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. * * It iterates through each preserved file in FIFO order (the order of * preservation) and performs two main actions: * * 1. Freezes the File: It calls the handler's .freeze() callback for each * file. This gives the handler a final opportunity to quiesce the device or * prepare its state for the upcoming reboot. The handler may update its * private data handle during this step. * * 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. * * 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. * * Context: Called only from 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) { struct luo_file *luo_file; struct kho_block_set_it it; int err; if (!file_set->count) return 0; kho_block_set_it_init(&it, &file_set->block_set); list_for_each_entry(luo_file, &file_set->files_list, list) { struct luo_file_ser *file_ser = kho_block_set_it_reserve_entry(&it); /* This should not fail normally as blocks were pre-allocated */ if (WARN_ON_ONCE(!file_ser)) { err = -ENOSPC; goto err_unfreeze; } err = luo_file_freeze_one(file_set, luo_file); if (err < 0) { pr_warn("Freeze failed for token[%#0llx] handler[%s] err[%pe]\n", luo_file->token, luo_file->fh->compatible, ERR_PTR(err)); goto err_unfreeze; } strscpy(file_ser->compatible, luo_file->fh->compatible, sizeof(file_ser->compatible)); file_ser->data = luo_file->serialized_data; 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: __luo_file_unfreeze(file_set, luo_file); return err; }