| // 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; | |
| } | |
| /** | |
| * 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. | |
| * | |
| * 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(). | |
| * | |
| * 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. | |
| * | |
| * Context: This is called when the live update is aborted during | |
| * the reboot() syscall, after luo_file_freeze() has been called. | |
| */ | |
| void luo_file_unfreeze(struct luo_file_set *file_set, | |
| struct luo_file_set_ser *file_set_ser) | |
| { | |
| if (!file_set->count) | |
| return; | |
| __luo_file_unfreeze(file_set, NULL); | |
| memset(file_set_ser, 0, sizeof(*file_set_ser)); | |
| } | |
| /** | |
| * luo_retrieve_file - Restores a preserved file from a file_set by its token. | |
| * @file_set: The file_set from which to retrieve the file. | |
| * @token: The unique token identifying the file to be restored. | |
| * @filep: Output parameter; on success, this is populated with a pointer | |
| * to the newly retrieved 'struct file'. | |
| * | |
| * This function is the primary mechanism for recreating a file in the new | |
| * kernel after a live update. It searches the file_set's list of deserialized | |
| * files for an entry matching the provided @token. | |
| * | |
| * The operation is idempotent: if a file has already been successfully | |
| * retrieved, this function will simply return a pointer to the existing | |
| * 'struct file' and report success without re-executing the retrieve | |
| * operation. This is handled by checking the 'retrieved' flag under a lock. | |
| * | |
| * File retrieval can happen in any order; it is not bound by the order of | |
| * preservation. | |
| * | |
| * Context: Can be called from an ioctl or other in-kernel code in the new | |
| * kernel. | |
| * Return: 0 on success. Returns a negative errno on failure: | |
| * -ENOENT if no file with the matching token is found. | |
| * Any error code returned by the handler's .retrieve() op. | |
| */ | |
| int luo_retrieve_file(struct luo_file_set *file_set, u64 token, | |
| struct file **filep) | |
| { | |
| struct liveupdate_file_op_args args = {0}; | |
| struct luo_file *luo_file; | |
| bool found = false; | |
| int err; | |
| if (list_empty(&file_set->files_list)) | |
| return -ENOENT; | |
| list_for_each_entry(luo_file, &file_set->files_list, list) { | |
| if (luo_file->token == token) { | |
| found = true; | |
| break; | |
| } | |
| } | |
| if (!found) | |
| return -ENOENT; | |
| guard(mutex)(&luo_file->mutex); | |
| if (luo_file->retrieve_status < 0) { | |
| /* Retrieve was attempted and it failed. Return the error code. */ | |
| return luo_file->retrieve_status; | |
| } | |
| if (luo_file->retrieve_status > 0) { | |
| /* | |
| * Someone is asking for this file again, so get a reference | |
| * for them. | |
| */ | |
| get_file(luo_file->file); | |
| *filep = luo_file->file; | |
| return 0; | |
| } | |
| args.handler = luo_file->fh; | |
| args.serialized_data = luo_file->serialized_data; | |
| err = luo_file->fh->ops->retrieve(&args); | |
| if (err) { | |
| /* Keep the error code for later use. */ | |
| luo_file->retrieve_status = err; | |
| return err; | |
| } | |
| luo_file->file = args.file; | |
| /* Get reference so we can keep this file in LUO until finish */ | |
| get_file(luo_file->file); | |
| WARN_ON(xa_insert(&luo_preserved_files, | |
| luo_get_id(luo_file->fh, luo_file->file), | |
| luo_file->file, GFP_KERNEL)); | |
| *filep = luo_file->file; | |
| luo_file->retrieve_status = 1; | |
| return 0; | |
| } | |
| static int luo_file_can_finish_one(struct luo_file_set *file_set, | |
| struct luo_file *luo_file) | |
| { | |
| bool can_finish = true; | |
| guard(mutex)(&luo_file->mutex); | |
| if (luo_file->fh->ops->can_finish) { | |
| 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.retrieve_status = luo_file->retrieve_status; | |
| can_finish = luo_file->fh->ops->can_finish(&args); | |
| } | |
| return can_finish ? 0 : -EBUSY; | |
| } | |
| static void luo_file_finish_one(struct luo_file_set *file_set, | |
| struct luo_file *luo_file) | |
| { | |
| struct liveupdate_file_op_args args = {0}; | |
| guard(mutex)(&luo_file->mutex); | |
| args.handler = luo_file->fh; | |
| args.file = luo_file->file; | |
| args.serialized_data = luo_file->serialized_data; | |
| args.retrieve_status = luo_file->retrieve_status; | |
| luo_file->fh->ops->finish(&args); | |
| luo_flb_file_finish(luo_file->fh); | |
| } | |
| /** | |
| * luo_file_finish - Completes the lifecycle for all files in a file_set. | |
| * @file_set: The file_set to be finalized. | |
| * | |
| * This function orchestrates the final teardown of a live update file_set in | |
| * the new kernel. It should be called after all necessary files have been | |
| * retrieved and the userspace agent is ready to release the preserved state. | |
| * | |
| * The function iterates through all tracked files. For each file, it performs | |
| * the following sequence of cleanup actions: | |
| * | |
| * 1. If file is not yet retrieved, retrieves it, and calls can_finish() on | |
| * every file in the file_set. If all can_finish return true, continue to | |
| * finish. | |
| * 2. Calls the handler's .finish() callback (via luo_file_finish_one) to | |
| * allow for final resource cleanup within the handler. | |
| * 3. Releases LUO's ownership reference on the 'struct file' via fput(). This | |
| * is the counterpart to the get_file() call in luo_retrieve_file(). | |
| * 4. Removes the 'struct luo_file' from the file_set's internal list. | |
| * 5. Frees the memory for the 'struct luo_file' instance itself. | |
| * | |
| * After successfully finishing all individual files, it frees the | |
| * contiguous memory block that was used to transfer the serialized metadata | |
| * from the previous kernel. | |
| * | |
| * Error Handling (Atomic Failure): | |
| * This operation is atomic. If any handler's .can_finish() op fails, the entire | |
| * function aborts immediately and returns an error. | |
| * | |
| * Context: Can be called from an ioctl handler in the new kernel. | |
| * Return: 0 on success, or a negative errno on failure. | |
| */ | |
| int luo_file_finish(struct luo_file_set *file_set) | |
| { | |
| struct list_head *files_list = &file_set->files_list; | |
| struct luo_file *luo_file; | |
| int err; | |
| if (!file_set->count) | |
| return 0; | |
| list_for_each_entry(luo_file, files_list, list) { | |
| err = luo_file_can_finish_one(file_set, luo_file); | |
| if (err) | |
| return err; | |
| } | |
| while (!list_empty(&file_set->files_list)) { | |
| luo_file = list_last_entry(&file_set->files_list, | |
| struct luo_file, list); | |
| luo_file_finish_one(file_set, luo_file); | |
| if (luo_file->file) { | |
| xa_erase(&luo_preserved_files, | |
| luo_get_id(luo_file->fh, luo_file->file)); | |
| fput(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); | |
| mutex_destroy(&luo_file->mutex); | |
| kfree(luo_file); | |
| } | |
| kho_block_set_destroy(&file_set->block_set); | |
| return 0; | |
| } | |
| static int luo_file_deserialize_one(struct luo_file_set *file_set, | |
| struct luo_file_ser *ser) | |
| { | |
| struct liveupdate_file_handler *fh; | |
| bool handler_found = false; | |
| struct luo_file *luo_file; | |
| down_read(&luo_register_rwlock); | |
| list_private_for_each_entry(fh, &luo_file_handler_list, list) { | |
| if (!strcmp(fh->compatible, ser->compatible)) { | |
| if (try_module_get(fh->ops->owner)) | |
| handler_found = true; | |
| break; | |
| } | |
| } | |
| up_read(&luo_register_rwlock); | |
| if (!handler_found) { | |
| pr_warn("No registered handler for compatible '%.*s'\n", | |
| (int)sizeof(ser->compatible), | |
| ser->compatible); | |
| return -ENOENT; | |
| } | |
| luo_file = kzalloc_obj(*luo_file); | |
| if (!luo_file) { | |
| module_put(fh->ops->owner); | |
| return -ENOMEM; | |
| } | |
| luo_file->fh = fh; | |
| luo_file->file = NULL; | |
| luo_file->serialized_data = ser->data; | |
| luo_file->token = ser->token; | |
| mutex_init(&luo_file->mutex); | |
| list_add_tail(&luo_file->list, &file_set->files_list); | |
| return 0; | |
| } | |
| /** | |
| * luo_file_deserialize - Reconstructs the list of preserved files in the new kernel. | |
| * @file_set: The incoming file_set to fill with deserialized data. | |
| * @file_set_ser: Serialized KHO file_set data from the previous kernel. | |
| * | |
| * This function is called during the early boot process of the new kernel. It | |
| * takes the raw, contiguous memory block of 'struct luo_file_ser' entries, | |
| * provided by the previous kernel, and transforms it back into a live, | |
| * in-memory linked list of 'struct luo_file' instances. | |
| * | |
| * For each serialized entry, it performs the following steps: | |
| * 1. Reads the 'compatible' string. | |
| * 2. Searches the global list of registered file handlers for one that | |
| * matches the compatible string. | |
| * 3. Allocates a new 'struct luo_file'. | |
| * 4. Populates the new structure with the deserialized data (token, private | |
| * data handle) and links it to the found handler. The 'file' pointer is | |
| * initialized to NULL, as the file has not been retrieved yet. | |
| * 5. Adds the new 'struct luo_file' to the file_set's files_list. | |
| * | |
| * This prepares the file_set for userspace, which can later call | |
| * luo_retrieve_file() to restore the actual file descriptors. | |
| * | |
| * Context: Called from session deserialization. | |
| */ | |
| int luo_file_deserialize(struct luo_file_set *file_set, | |
| struct luo_file_set_ser *file_set_ser) | |
| { | |
| struct luo_file_ser *file_ser; | |
| struct kho_block_set_it it; | |
| int err; | |
| if (!file_set_ser->files) { | |
| WARN_ON(file_set_ser->count); | |
| return 0; | |
| } | |
| file_set->count = 0; | |
| err = kho_block_set_restore(&file_set->block_set, file_set_ser->files); | |
| if (err) | |
| return err; | |
| /* | |
| * Note on error handling: | |
| * | |
| * If deserialization fails (e.g., allocation failure or corrupt data), | |
| * we intentionally skip cleanup of files that were already restored. | |
| * | |
| * A partial failure leaves the preserved state inconsistent. | |
| * Implementing a safe "undo" to unwind complex dependencies (sessions, | |
| * files, hardware state) is error-prone and provides little value, as | |
| * the system is effectively in a broken state. | |
| * | |
| * We treat these resources as leaked. The expected recovery path is for | |
| * userspace to detect the failure and trigger a reboot, which will | |
| * reliably reset devices and reclaim memory. | |
| */ | |
| kho_block_set_it_init(&it, &file_set->block_set); | |
| while ((file_ser = kho_block_set_it_read_entry(&it))) { | |
| err = luo_file_deserialize_one(file_set, file_ser); | |
| if (err) | |
| goto err_destroy_blocks; | |
| file_set->count++; | |
| } | |
| if (file_set->count != file_set_ser->count) { | |
| pr_warn("File count mismatch: expected %llu, found %llu\n", | |
| file_set_ser->count, file_set->count); | |
| err = -EINVAL; | |
| goto err_destroy_blocks; | |
| } | |
| return 0; | |
| err_destroy_blocks: | |
| while (!list_empty(&file_set->files_list)) { | |
| struct luo_file *luo_file; | |
| luo_file = list_first_entry(&file_set->files_list, | |
| struct luo_file, list); | |
| list_del(&luo_file->list); | |
| module_put(luo_file->fh->ops->owner); | |
| mutex_destroy(&luo_file->mutex); | |
| kfree(luo_file); | |
| } | |
| file_set->count = 0; | |
| kho_block_set_destroy(&file_set->block_set); | |
| return err; | |
| } | |
| void luo_file_set_init(struct luo_file_set *file_set) | |
| { | |
| INIT_LIST_HEAD(&file_set->files_list); | |
| kho_block_set_init(&file_set->block_set, sizeof(struct luo_file_ser)); | |
| } | |
| void luo_file_set_destroy(struct luo_file_set *file_set) | |
| { | |
| WARN_ON(file_set->count); | |
| WARN_ON(!list_empty(&file_set->files_list)); | |
| WARN_ON(!kho_block_set_is_empty(&file_set->block_set)); | |
| } | |
| /** | |
| * 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 err; | |
| if (!liveupdate_enabled()) | |
| return -EOPNOTSUPP; | |
| /* Sanity check that all required callbacks are set */ | |
| if (!fh->ops->preserve || !fh->ops->unpreserve || !fh->ops->retrieve || | |
| !fh->ops->finish || !fh->ops->can_preserve) { | |
| return -EINVAL; | |
| } | |
| down_write(&luo_register_rwlock); | |
| /* Check for duplicate compatible strings */ | |
| list_private_for_each_entry(fh_iter, &luo_file_handler_list, list) { | |
| if (!strcmp(fh_iter->compatible, fh->compatible)) { | |
| pr_err("File handler registration failed: Compatible string '%s' already registered.\n", | |
| fh->compatible); | |
| err = -EEXIST; | |
| goto err_unlock; | |
| } | |
| } | |
| INIT_LIST_HEAD(&ACCESS_PRIVATE(fh, flb_list)); | |
| INIT_LIST_HEAD(&ACCESS_PRIVATE(fh, list)); | |
| list_add_tail(&ACCESS_PRIVATE(fh, list), &luo_file_handler_list); | |
| up_write(&luo_register_rwlock); | |
| liveupdate_test_register(fh); | |
| return 0; | |
| err_unlock: | |
| up_write(&luo_register_rwlock); | |
| return err; | |
| } | |
| /** | |
| * liveupdate_unregister_file_handler - Unregister a liveupdate file handler | |
| * @fh: The file handler to unregister | |
| * | |
| * Unregisters the file handler from the liveupdate core. This function | |
| * reverses the operations of liveupdate_register_file_handler(). | |
| */ | |
| void liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh) | |
| { | |
| if (!liveupdate_enabled()) | |
| return; | |
| guard(rwsem_write)(&luo_register_rwlock); | |
| luo_flb_unregister_all(fh); | |
| list_del(&ACCESS_PRIVATE(fh, list)); | |
| } |