// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2025, Google LLC. * Pasha Tatashin */ /** * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "luo_internal.h" static LIST_HEAD(luo_file_handler_list); /* 2 4K pages, give space for 128 files per file_set */ #define LUO_FILE_PGCNT 2ul #define LUO_FILE_MAX \ ((LUO_FILE_PGCNT << PAGE_SHIFT) / sizeof(struct luo_file_ser)) /** * 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. * @retrieved: A flag indicating whether a user/kernel in the new kernel has * successfully called retrieve() on this file. This prevents * multiple retrieval attempts. * @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; bool retrieved; struct mutex mutex; struct list_head list; u64 token; }; static int luo_alloc_files_mem(struct luo_file_set *file_set) { size_t size; void *mem; if (file_set->files) return 0; WARN_ON_ONCE(file_set->count); size = LUO_FILE_PGCNT << PAGE_SHIFT; mem = kho_alloc_preserve(size); if (IS_ERR(mem)) return PTR_ERR(mem); file_set->files = mem; return 0; } static void luo_free_files_mem(struct luo_file_set *file_set) { /* If file_set has files, no need to free preservation memory */ if (file_set->count) return; if (!file_set->files) return; kho_unpreserve_free(file_set->files); file_set->files = NULL; } 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. * -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; if (file_set->count == LUO_FILE_MAX) return -ENOSPC; file = fget(fd); if (!file) return -EBADF; err = luo_alloc_files_mem(file_set); if (err) goto err_fput; err = -ENOENT; luo_list_for_each_private(fh, &luo_file_handler_list, list) { if (fh->ops->can_preserve(fh, file)) { err = 0; break; } } /* err is still -ENOENT if no handler was found */ if (err) goto err_free_files_mem; luo_file = kzalloc(sizeof(*luo_file), GFP_KERNEL); if (!luo_file) { err = -ENOMEM; goto err_free_files_mem; } luo_file->file = file; luo_file->fh = fh; luo_file->token = token; luo_file->retrieved = false; 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_free_files_mem: luo_free_files_mem(file_set); err_fput: fput(file); 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); list_del(&luo_file->list); file_set->count--; fput(luo_file->file); mutex_destroy(&luo_file->mutex); kfree(luo_file); } luo_free_files_mem(file_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); } luo_file->serialized_data = 0; } 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); } memset(file_set->files, 0, LUO_FILE_PGCNT << PAGE_SHIFT); } /** * 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_ser *file_ser = file_set->files; struct luo_file *luo_file; int err; int i; if (!file_set->count) return 0; if (WARN_ON(!file_ser)) return -EINVAL; i = 0; list_for_each_entry(luo_file, &file_set->files_list, list) { 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[i].compatible, luo_file->fh->compatible, sizeof(file_ser[i].compatible)); file_ser[i].data = luo_file->serialized_data; file_ser[i].token = luo_file->token; i++; } file_set_ser->count = file_set->count; if (file_set->files) file_set_ser->files = virt_to_phys(file_set->files); 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; 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) break; } if (luo_file->token != token) return -ENOENT; guard(mutex)(&luo_file->mutex); if (luo_file->retrieved) { /* * 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) { luo_file->file = args.file; /* Get reference so we can keep this file in LUO until finish */ get_file(luo_file->file); *filep = luo_file->file; luo_file->retrieved = true; } return err; } 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.retrieved = luo_file->retrieved; 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.retrieved = luo_file->retrieved; luo_file->fh->ops->finish(&args); } /** * 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) fput(luo_file->file); list_del(&luo_file->list); file_set->count--; mutex_destroy(&luo_file->mutex); kfree(luo_file); } if (file_set->files) { kho_restore_free(file_set->files); file_set->files = NULL; } 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; u64 i; if (!file_set_ser->files) { WARN_ON(file_set_ser->count); return 0; } file_set->count = file_set_ser->count; file_set->files = phys_to_virt(file_set_ser->files); /* * 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. */ file_ser = file_set->files; for (i = 0; i < file_set->count; i++) { struct liveupdate_file_handler *fh; bool handler_found = false; struct luo_file *luo_file; luo_list_for_each_private(fh, &luo_file_handler_list, list) { if (!strcmp(fh->compatible, file_ser[i].compatible)) { handler_found = true; break; } } if (!handler_found) { pr_warn("No registered handler for compatible '%s'\n", file_ser[i].compatible); return -ENOENT; } luo_file = kzalloc(sizeof(*luo_file), GFP_KERNEL); if (!luo_file) return -ENOMEM; luo_file->fh = fh; luo_file->file = NULL; luo_file->serialized_data = file_ser[i].data; luo_file->token = file_ser[i].token; luo_file->retrieved = false; mutex_init(&luo_file->mutex); list_add_tail(&luo_file->list, &file_set->files_list); } return 0; } void luo_file_set_init(struct luo_file_set *file_set) { INIT_LIST_HEAD(&file_set->files_list); } void luo_file_set_destroy(struct luo_file_set *file_set) { WARN_ON(file_set->count); WARN_ON(!list_empty(&file_set->files_list)); } /** * 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; } /* * Ensure the system is quiescent (no active sessions). * This prevents registering new handlers while sessions are active or * while deserialization is in progress. */ if (!luo_session_quiesce()) return -EBUSY; /* Check for duplicate compatible strings */ luo_list_for_each_private(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_resume; } } /* Pin the module implementing the handler */ if (!try_module_get(fh->ops->owner)) { err = -EAGAIN; goto err_resume; } INIT_LIST_HEAD(&ACCESS_PRIVATE(fh, list)); list_add_tail(&ACCESS_PRIVATE(fh, list), &luo_file_handler_list); luo_session_resume(); return 0; err_resume: luo_session_resume(); 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(). * * It ensures safe removal by checking that: * No live update session is currently in progress. * * If the unregistration fails, the internal test state is reverted. * * Return: 0 Success. -EOPNOTSUPP when live update is not enabled. -EBUSY A live * update is in progress, can't quiesce live update. */ int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh) { if (!liveupdate_enabled()) return -EOPNOTSUPP; if (!luo_session_quiesce()) return -EBUSY; list_del(&ACCESS_PRIVATE(fh, list)); module_put(fh->ops->owner); luo_session_resume(); return 0; }