928 lines · 30.8 KB · C
RawDownload
// 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.