| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright 2023 Red Hat |
| */ |
| |
| #include <linux/atomic.h> |
| #include <linux/bitops.h> |
| #include <linux/completion.h> |
| #include <linux/delay.h> |
| #include <linux/device-mapper.h> |
| #include <linux/err.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/spinlock.h> |
| |
| #include "admin-state.h" |
| #include "block-map.h" |
| #include "completion.h" |
| #include "constants.h" |
| #include "data-vio.h" |
| #include "dedupe.h" |
| #include "dump.h" |
| #include "encodings.h" |
| #include "errors.h" |
| #include "flush.h" |
| #include "io-submitter.h" |
| #include "logger.h" |
| #include "memory-alloc.h" |
| #include "message-stats.h" |
| #include "recovery-journal.h" |
| #include "repair.h" |
| #include "slab-depot.h" |
| #include "status-codes.h" |
| #include "string-utils.h" |
| #include "thread-device.h" |
| #include "thread-registry.h" |
| #include "thread-utils.h" |
| #include "types.h" |
| #include "vdo.h" |
| #include "vio.h" |
| |
| enum admin_phases { |
| GROW_LOGICAL_PHASE_START, |
| GROW_LOGICAL_PHASE_GROW_BLOCK_MAP, |
| GROW_LOGICAL_PHASE_END, |
| GROW_LOGICAL_PHASE_ERROR, |
| GROW_PHYSICAL_PHASE_START, |
| GROW_PHYSICAL_PHASE_COPY_SUMMARY, |
| GROW_PHYSICAL_PHASE_UPDATE_COMPONENTS, |
| GROW_PHYSICAL_PHASE_USE_NEW_SLABS, |
| GROW_PHYSICAL_PHASE_END, |
| GROW_PHYSICAL_PHASE_ERROR, |
| LOAD_PHASE_START, |
| LOAD_PHASE_LOAD_DEPOT, |
| LOAD_PHASE_MAKE_DIRTY, |
| LOAD_PHASE_PREPARE_TO_ALLOCATE, |
| LOAD_PHASE_SCRUB_SLABS, |
| LOAD_PHASE_DATA_REDUCTION, |
| LOAD_PHASE_FINISHED, |
| LOAD_PHASE_DRAIN_JOURNAL, |
| LOAD_PHASE_WAIT_FOR_READ_ONLY, |
| PRE_LOAD_PHASE_START, |
| PRE_LOAD_PHASE_LOAD_COMPONENTS, |
| PRE_LOAD_PHASE_END, |
| PREPARE_GROW_PHYSICAL_PHASE_START, |
| RESUME_PHASE_START, |
| RESUME_PHASE_ALLOW_READ_ONLY_MODE, |
| RESUME_PHASE_DEDUPE, |
| RESUME_PHASE_DEPOT, |
| RESUME_PHASE_JOURNAL, |
| RESUME_PHASE_BLOCK_MAP, |
| RESUME_PHASE_LOGICAL_ZONES, |
| RESUME_PHASE_PACKER, |
| RESUME_PHASE_FLUSHER, |
| RESUME_PHASE_DATA_VIOS, |
| RESUME_PHASE_END, |
| SUSPEND_PHASE_START, |
| SUSPEND_PHASE_PACKER, |
| SUSPEND_PHASE_DATA_VIOS, |
| SUSPEND_PHASE_DEDUPE, |
| SUSPEND_PHASE_FLUSHES, |
| SUSPEND_PHASE_LOGICAL_ZONES, |
| SUSPEND_PHASE_BLOCK_MAP, |
| SUSPEND_PHASE_JOURNAL, |
| SUSPEND_PHASE_DEPOT, |
| SUSPEND_PHASE_READ_ONLY_WAIT, |
| SUSPEND_PHASE_WRITE_SUPER_BLOCK, |
| SUSPEND_PHASE_END, |
| }; |
| |
| static const char * const ADMIN_PHASE_NAMES[] = { |
| "GROW_LOGICAL_PHASE_START", |
| "GROW_LOGICAL_PHASE_GROW_BLOCK_MAP", |
| "GROW_LOGICAL_PHASE_END", |
| "GROW_LOGICAL_PHASE_ERROR", |
| "GROW_PHYSICAL_PHASE_START", |
| "GROW_PHYSICAL_PHASE_COPY_SUMMARY", |
| "GROW_PHYSICAL_PHASE_UPDATE_COMPONENTS", |
| "GROW_PHYSICAL_PHASE_USE_NEW_SLABS", |
| "GROW_PHYSICAL_PHASE_END", |
| "GROW_PHYSICAL_PHASE_ERROR", |
| "LOAD_PHASE_START", |
| "LOAD_PHASE_LOAD_DEPOT", |
| "LOAD_PHASE_MAKE_DIRTY", |
| "LOAD_PHASE_PREPARE_TO_ALLOCATE", |
| "LOAD_PHASE_SCRUB_SLABS", |
| "LOAD_PHASE_DATA_REDUCTION", |
| "LOAD_PHASE_FINISHED", |
| "LOAD_PHASE_DRAIN_JOURNAL", |
| "LOAD_PHASE_WAIT_FOR_READ_ONLY", |
| "PRE_LOAD_PHASE_START", |
| "PRE_LOAD_PHASE_LOAD_COMPONENTS", |
| "PRE_LOAD_PHASE_END", |
| "PREPARE_GROW_PHYSICAL_PHASE_START", |
| "RESUME_PHASE_START", |
| "RESUME_PHASE_ALLOW_READ_ONLY_MODE", |
| "RESUME_PHASE_DEDUPE", |
| "RESUME_PHASE_DEPOT", |
| "RESUME_PHASE_JOURNAL", |
| "RESUME_PHASE_BLOCK_MAP", |
| "RESUME_PHASE_LOGICAL_ZONES", |
| "RESUME_PHASE_PACKER", |
| "RESUME_PHASE_FLUSHER", |
| "RESUME_PHASE_DATA_VIOS", |
| "RESUME_PHASE_END", |
| "SUSPEND_PHASE_START", |
| "SUSPEND_PHASE_PACKER", |
| "SUSPEND_PHASE_DATA_VIOS", |
| "SUSPEND_PHASE_DEDUPE", |
| "SUSPEND_PHASE_FLUSHES", |
| "SUSPEND_PHASE_LOGICAL_ZONES", |
| "SUSPEND_PHASE_BLOCK_MAP", |
| "SUSPEND_PHASE_JOURNAL", |
| "SUSPEND_PHASE_DEPOT", |
| "SUSPEND_PHASE_READ_ONLY_WAIT", |
| "SUSPEND_PHASE_WRITE_SUPER_BLOCK", |
| "SUSPEND_PHASE_END", |
| }; |
| |
| /* If we bump this, update the arrays below */ |
| #define TABLE_VERSION 4 |
| |
| /* arrays for handling different table versions */ |
| static const u8 REQUIRED_ARGC[] = { 10, 12, 9, 7, 6 }; |
| /* pool name no longer used. only here for verification of older versions */ |
| static const u8 POOL_NAME_ARG_INDEX[] = { 8, 10, 8 }; |
| |
| /* |
| * Track in-use instance numbers using a flat bit array. |
| * |
| * O(n) run time isn't ideal, but if we have 1000 VDO devices in use simultaneously we still only |
| * need to scan 16 words, so it's not likely to be a big deal compared to other resource usage. |
| */ |
| |
| /* |
| * This minimum size for the bit array creates a numbering space of 0-999, which allows |
| * successive starts of the same volume to have different instance numbers in any |
| * reasonably-sized test. Changing instances on restart allows vdoMonReport to detect that |
| * the ephemeral stats have reset to zero. |
| */ |
| #define BIT_COUNT_MINIMUM 1000 |
| /* Grow the bit array by this many bits when needed */ |
| #define BIT_COUNT_INCREMENT 100 |
| |
| struct instance_tracker { |
| unsigned int bit_count; |
| unsigned long *words; |
| unsigned int count; |
| unsigned int next; |
| }; |
| |
| static DEFINE_MUTEX(instances_lock); |
| static struct instance_tracker instances; |
| |
| /** |
| * free_device_config() - Free a device config created by parse_device_config(). |
| * @config: The config to free. |
| */ |
| static void free_device_config(struct device_config *config) |
| { |
| if (config == NULL) |
| return; |
| |
| if (config->owned_device != NULL) |
| dm_put_device(config->owning_target, config->owned_device); |
| |
| vdo_free(config->parent_device_name); |
| vdo_free(config->original_string); |
| |
| /* Reduce the chance a use-after-free (as in BZ 1669960) happens to work. */ |
| memset(config, 0, sizeof(*config)); |
| vdo_free(config); |
| } |
| |
| /** |
| * get_version_number() - Decide the version number from argv. |
| * |
| * @argc: The number of table values. |
| * @argv: The array of table values. |
| * @error_ptr: A pointer to return a error string in. |
| * @version_ptr: A pointer to return the version. |
| * |
| * Return: VDO_SUCCESS or an error code. |
| */ |
| static int get_version_number(int argc, char **argv, char **error_ptr, |
| unsigned int *version_ptr) |
| { |
| /* version, if it exists, is in a form of V<n> */ |
| if (sscanf(argv[0], "V%u", version_ptr) == 1) { |
| if (*version_ptr < 1 || *version_ptr > TABLE_VERSION) { |
| *error_ptr = "Unknown version number detected"; |
| return VDO_BAD_CONFIGURATION; |
| } |
| } else { |
| /* V0 actually has no version number in the table string */ |
| *version_ptr = 0; |
| } |
| |
| /* |
| * V0 and V1 have no optional parameters. There will always be a parameter for thread |
| * config, even if it's a "." to show it's an empty list. |
| */ |
| if (*version_ptr <= 1) { |
| if (argc != REQUIRED_ARGC[*version_ptr]) { |
| *error_ptr = "Incorrect number of arguments for version"; |
| return VDO_BAD_CONFIGURATION; |
| } |
| } else if (argc < REQUIRED_ARGC[*version_ptr]) { |
| *error_ptr = "Incorrect number of arguments for version"; |
| return VDO_BAD_CONFIGURATION; |
| } |
| |
| if (*version_ptr != TABLE_VERSION) { |
| vdo_log_warning("Detected version mismatch between kernel module and tools kernel: %d, tool: %d", |
| TABLE_VERSION, *version_ptr); |
| vdo_log_warning("Please consider upgrading management tools to match kernel."); |
| } |
| return VDO_SUCCESS; |
| } |
| |
| /* Free a list of non-NULL string pointers, and then the list itself. */ |
| static void free_string_array(char **string_array) |
| { |
| unsigned int offset; |
| |
| for (offset = 0; string_array[offset] != NULL; offset++) |
| vdo_free(string_array[offset]); |
| vdo_free(string_array); |
| } |
| |
| /* |
| * Split the input string into substrings, separated at occurrences of the indicated character, |
| * returning a null-terminated list of string pointers. |
| * |
| * The string pointers and the pointer array itself should both be freed with vdo_free() when no |
| * longer needed. This can be done with vdo_free_string_array (below) if the pointers in the array |
| * are not changed. Since the array and copied strings are allocated by this function, it may only |
| * be used in contexts where allocation is permitted. |
| * |
| * Empty substrings are not ignored; that is, returned substrings may be empty strings if the |
| * separator occurs twice in a row. |
| */ |
| static int split_string(const char *string, char separator, char ***substring_array_ptr) |
| { |
| unsigned int current_substring = 0, substring_count = 1; |
| const char *s; |
| char **substrings; |
| int result; |
| ptrdiff_t length; |
| |
| for (s = string; *s != 0; s++) { |
| if (*s == separator) |
| substring_count++; |
| } |
| |
| result = vdo_allocate(substring_count + 1, char *, "string-splitting array", |
| &substrings); |
| if (result != VDO_SUCCESS) |
| return result; |
| |
| for (s = string; *s != 0; s++) { |
| if (*s == separator) { |
| ptrdiff_t length = s - string; |
| |
| result = vdo_allocate(length + 1, char, "split string", |
| &substrings[current_substring]); |
| if (result != VDO_SUCCESS) { |
| free_string_array(substrings); |
| return result; |
| } |
| /* |
| * Trailing NUL is already in place after allocation; deal with the zero or |
| * more non-NUL bytes in the string. |
| */ |
| if (length > 0) |
| memcpy(substrings[current_substring], string, length); |
| string = s + 1; |
| current_substring++; |
| BUG_ON(current_substring >= substring_count); |
| } |
| } |
| /* Process final string, with no trailing separator. */ |
| BUG_ON(current_substring != (substring_count - 1)); |
| length = strlen(string); |
| |
| result = vdo_allocate(length + 1, char, "split string", |
| &substrings[current_substring]); |
| if (result != VDO_SUCCESS) { |
| free_string_array(substrings); |
| return result; |
| } |
| memcpy(substrings[current_substring], string, length); |
| current_substring++; |
| /* substrings[current_substring] is NULL already */ |
| *substring_array_ptr = substrings; |
| return VDO_SUCCESS; |
| } |
| |
| /* |
| * Join the input substrings into one string, joined with the indicated character, returning a |
| * string. array_length is a bound on the number of valid elements in substring_array, in case it |
| * is not NULL-terminated. |
| */ |
| static int join_strings(char **substring_array, size_t array_length, char separator, |
| char **string_ptr) |
| { |
| size_t string_length = 0; |
| size_t i; |
| int result; |
| char *output, *current_position; |
| |
| for (i = 0; (i < array_length) && (substring_array[i] != NULL); i++) |
| string_length += strlen(substring_array[i]) + 1; |
| |
| result = vdo_allocate(string_length, char, __func__, &output); |
| if (result != VDO_SUCCESS) |
| return result; |
| |
| current_position = &output[0]; |
| |
| for (i = 0; (i < array_length) && (substring_array[i] != NULL); i++) { |
| current_position = vdo_append_to_buffer(current_position, |
| output + string_length, "%s", |
| substring_array[i]); |
| *current_position = separator; |
| current_position++; |
| } |
| |
| /* We output one too many separators; replace the last with a zero byte. */ |
| if (current_position != output) |
| *(current_position - 1) = '\0'; |
| |
| *string_ptr = output; |
| return VDO_SUCCESS; |
| } |
| |
| /** |
| * parse_bool() - Parse a two-valued option into a bool. |
| * @bool_str: The string value to convert to a bool. |
| * @true_str: The string value which should be converted to true. |
| * @false_str: The string value which should be converted to false. |
| * @bool_ptr: A pointer to return the bool value in. |
| * |
| * Return: VDO_SUCCESS or an error if bool_str is neither true_str nor false_str. |
| */ |
| static inline int __must_check parse_bool(const char *bool_str, const char *true_str, |
| const char *false_str, bool *bool_ptr) |
| { |
| bool value = false; |
| |
| if (strcmp(bool_str, true_str) == 0) |
| value = true; |
| else if (strcmp(bool_str, false_str) == 0) |
| value = false; |
| else |
| return VDO_BAD_CONFIGURATION; |
| |
| *bool_ptr = value; |
| return VDO_SUCCESS; |
| } |
| |
| /** |
| * process_one_thread_config_spec() - Process one component of a thread parameter configuration |
| * string and update the configuration data structure. |
| * @thread_param_type: The type of thread specified. |
| * @count: The thread count requested. |
| * @config: The configuration data structure to update. |
| * |
| * If the thread count requested is invalid, a message is logged and -EINVAL returned. If the |
| * thread name is unknown, a message is logged but no error is returned. |
| * |
| * Return: VDO_SUCCESS or -EINVAL |
| */ |
| static int process_one_thread_config_spec(const char *thread_param_type, |
| unsigned int count, |
| struct thread_count_config *config) |
| { |
| /* Handle limited thread parameters */ |
| if (strcmp(thread_param_type, "bioRotationInterval") == 0) { |
| if (count == 0) { |
| vdo_log_error("thread config string error: 'bioRotationInterval' of at least 1 is required"); |
| return -EINVAL; |
| } else if (count > VDO_BIO_ROTATION_INTERVAL_LIMIT) { |
| vdo_log_error("thread config string error: 'bioRotationInterval' cannot be higher than %d", |
| VDO_BIO_ROTATION_INTERVAL_LIMIT); |
| return -EINVAL; |
| } |
| config->bio_rotation_interval = count; |
| return VDO_SUCCESS; |
| } |
| if (strcmp(thread_param_type, "logical") == 0) { |
| if (count > MAX_VDO_LOGICAL_ZONES) { |
| vdo_log_error("thread config string error: at most %d 'logical' threads are allowed", |
| MAX_VDO_LOGICAL_ZONES); |
| return -EINVAL; |
| } |
| config->logical_zones = count; |
| return VDO_SUCCESS; |
| } |
| if (strcmp(thread_param_type, "physical") == 0) { |
| if (count > MAX_VDO_PHYSICAL_ZONES) { |
| vdo_log_error("thread config string error: at most %d 'physical' threads are allowed", |
| MAX_VDO_PHYSICAL_ZONES); |
| return -EINVAL; |
| } |
| config->physical_zones = count; |
| return VDO_SUCCESS; |
| } |
| /* Handle other thread count parameters */ |
| if (count > MAXIMUM_VDO_THREADS) { |
| vdo_log_error("thread config string error: at most %d '%s' threads are allowed", |
| MAXIMUM_VDO_THREADS, thread_param_type); |
| return -EINVAL; |
| } |
| if (strcmp(thread_param_type, "hash") == 0) { |
| config->hash_zones = count; |
| return VDO_SUCCESS; |
| } |
| if (strcmp(thread_param_type, "cpu") == 0) { |
| if (count == 0) { |
| vdo_log_error("thread config string error: at least one 'cpu' thread required"); |
| return -EINVAL; |
| } |
| config->cpu_threads = count; |
| return VDO_SUCCESS; |
| } |
| if (strcmp(thread_param_type, "ack") == 0) { |
| config->bio_ack_threads = count; |
| return VDO_SUCCESS; |
| } |
| if (strcmp(thread_param_type, "bio") == 0) { |
| if (count == 0) { |
| vdo_log_error("thread config string error: at least one 'bio' thread required"); |
| return -EINVAL; |
| } |
| config->bio_threads = count; |
| return VDO_SUCCESS; |
| } |
| |
| /* |
| * Don't fail, just log. This will handle version mismatches between user mode tools and |
| * kernel. |
| */ |
| vdo_log_info("unknown thread parameter type \"%s\"", thread_param_type); |
| return VDO_SUCCESS; |
| } |
| |
| /** |
| * parse_one_thread_config_spec() - Parse one component of a thread parameter configuration string |
| * and update the configuration data structure. |
| * @spec: The thread parameter specification string. |
| * @config: The configuration data to be updated. |
| */ |
| static int parse_one_thread_config_spec(const char *spec, |
| struct thread_count_config *config) |
| { |
| unsigned int count; |
| char **fields; |
| int result; |
| |
| result = split_string(spec, '=', &fields); |
| if (result != VDO_SUCCESS) |
| return result; |
| |
| if ((fields[0] == NULL) || (fields[1] == NULL) || (fields[2] != NULL)) { |
| vdo_log_error("thread config string error: expected thread parameter assignment, saw \"%s\"", |
| spec); |
| free_string_array(fields); |
| return -EINVAL; |
| } |
| |
| result = kstrtouint(fields[1], 10, &count); |
| if (result) { |
| vdo_log_error("thread config string error: integer value needed, found \"%s\"", |
| fields[1]); |
| free_string_array(fields); |
| return result; |
| } |
| |
| result = process_one_thread_config_spec(fields[0], count, config); |
| free_string_array(fields); |
| return result; |
| } |
| |
| /** |
| * parse_thread_config_string() - Parse the configuration string passed and update the specified |
| * counts and other parameters of various types of threads to be |
| * created. |
| * @string: Thread parameter configuration string. |
| * @config: The thread configuration data to update. |
| * |
| * The configuration string should contain one or more comma-separated specs of the form |
| * "typename=number"; the supported type names are "cpu", "ack", "bio", "bioRotationInterval", |
| * "logical", "physical", and "hash". |
| * |
| * If an error occurs during parsing of a single key/value pair, we deem it serious enough to stop |
| * further parsing. |
| * |
| * This function can't set the "reason" value the caller wants to pass back, because we'd want to |
| * format it to say which field was invalid, and we can't allocate the "reason" strings |
| * dynamically. So if an error occurs, we'll log the details and pass back an error. |
| * |
| * Return: VDO_SUCCESS or -EINVAL or -ENOMEM |
| */ |
| static int parse_thread_config_string(const char *string, |
| struct thread_count_config *config) |
| { |
| int result = VDO_SUCCESS; |
| char **specs; |
| |
| if (strcmp(".", string) != 0) { |
| unsigned int i; |
| |
| result = split_string(string, ',', &specs); |
| if (result != VDO_SUCCESS) |
| return result; |
| |
| for (i = 0; specs[i] != NULL; i++) { |
| result = parse_one_thread_config_spec(specs[i], config); |
| if (result != VDO_SUCCESS) |
| break; |
| } |
| free_string_array(specs); |
| } |
| return result; |
| } |
| |
| /** |
| * process_one_key_value_pair() - Process one component of an optional parameter string and update |
| * the configuration data structure. |
| * @key: The optional parameter key name. |
| * @value: The optional parameter value. |
| * @config: The configuration data structure to update. |
| * |
| * If the value requested is invalid, a message is logged and -EINVAL returned. If the key is |
| * unknown, a message is logged but no error is returned. |
| * |
| * Return: VDO_SUCCESS or -EINVAL |
| */ |
| static int process_one_key_value_pair(const char *key, unsigned int value, |
| struct device_config *config) |
| { |
| /* Non thread optional parameters */ |
| if (strcmp(key, "maxDiscard") == 0) { |
| if (value == 0) { |
| vdo_log_error("optional parameter error: at least one max discard block required"); |
| return -EINVAL; |
| } |
| /* Max discard sectors in blkdev_issue_discard is UINT_MAX >> 9 */ |
| if (value > (UINT_MAX / VDO_BLOCK_SIZE)) { |
| vdo_log_error("optional parameter error: at most %d max discard blocks are allowed", |
| UINT_MAX / VDO_BLOCK_SIZE); |
| return -EINVAL; |
| } |
| config->max_discard_blocks = value; |
| return VDO_SUCCESS; |
| } |
| /* Handles unknown key names */ |
| return process_one_thread_config_spec(key, value, &config->thread_counts); |
| } |
| |
| /** |
| * parse_one_key_value_pair() - Parse one key/value pair and update the configuration data |
| * structure. |
| * @key: The optional key name. |
| * @value: The optional value. |
| * @config: The configuration data to be updated. |
| * |
| * Return: VDO_SUCCESS or error. |
| */ |
| static int parse_one_key_value_pair(const char *key, const char *value, |
| struct device_config *config) |
| { |
| unsigned int count; |
| int result; |
| |
| if (strcmp(key, "deduplication") == 0) |
| return parse_bool(value, "on", "off", &config->deduplication); |
| |
| if (strcmp(key, "compression") == 0) |
| return parse_bool(value, "on", "off", &config->compression); |
| |
| /* The remaining arguments must have integral values. */ |
| result = kstrtouint(value, 10, &count); |
| if (result) { |
| vdo_log_error("optional config string error: integer value needed, found \"%s\"", |
| value); |
| return result; |
| } |
| return process_one_key_value_pair(key, count, config); |
| } |
| |
| /** |
| * parse_key_value_pairs() - Parse all key/value pairs from a list of arguments. |
| * @argc: The total number of arguments in list. |
| * @argv: The list of key/value pairs. |
| * @config: The device configuration data to update. |
| * |
| * If an error occurs during parsing of a single key/value pair, we deem it serious enough to stop |
| * further parsing. |
| * |
| * This function can't set the "reason" value the caller wants to pass back, because we'd want to |
| * format it to say which field was invalid, and we can't allocate the "reason" strings |
| * dynamically. So if an error occurs, we'll log the details and return the error. |
| * |
| * Return: VDO_SUCCESS or error |
| */ |
| static int parse_key_value_pairs(int argc, char **argv, struct device_config *config) |
| { |
| int result = VDO_SUCCESS; |
| |
| while (argc) { |
| result = parse_one_key_value_pair(argv[0], argv[1], config); |
| if (result != VDO_SUCCESS) |
| break; |
| |
| argc -= 2; |
| argv += 2; |
| } |
| |
| return result; |
| } |
| |
| /** |
| * parse_optional_arguments() - Parse the configuration string passed in for optional arguments. |
| * @arg_set: The structure holding the arguments to parse. |
| * @error_ptr: Pointer to a buffer to hold the error string. |
| * @config: Pointer to device configuration data to update. |
| * |
| * For V0/V1 configurations, there will only be one optional parameter; the thread configuration. |
| * The configuration string should contain one or more comma-separated specs of the form |
| * "typename=number"; the supported type names are "cpu", "ack", "bio", "bioRotationInterval", |
| * "logical", "physical", and "hash". |
| * |
| * For V2 configurations and beyond, there could be any number of arguments. They should contain |
| * one or more key/value pairs separated by a space. |
| * |
| * Return: VDO_SUCCESS or error |
| */ |
| static int parse_optional_arguments(struct dm_arg_set *arg_set, char **error_ptr, |
| struct device_config *config) |
| { |
| int result = VDO_SUCCESS; |
| |
| if (config->version == 0 || config->version == 1) { |
| result = parse_thread_config_string(arg_set->argv[0], |
| &config->thread_counts); |
| if (result != VDO_SUCCESS) { |
| *error_ptr = "Invalid thread-count configuration"; |
| return VDO_BAD_CONFIGURATION; |
| } |
| } else { |
| if ((arg_set->argc % 2) != 0) { |
| *error_ptr = "Odd number of optional arguments given but they should be <key> <value> pairs"; |
| return VDO_BAD_CONFIGURATION; |
| } |
| result = parse_key_value_pairs(arg_set->argc, arg_set->argv, config); |
| if (result != VDO_SUCCESS) { |
| *error_ptr = "Invalid optional argument configuration"; |
| return VDO_BAD_CONFIGURATION; |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * handle_parse_error() - Handle a parsing error. |
| * @config: The config to free. |
| * @error_ptr: A place to store a constant string about the error. |
| * @error_str: A constant string to store in error_ptr. |
| */ |
| static void handle_parse_error(struct device_config *config, char **error_ptr, |
| char *error_str) |
| { |
| free_device_config(config); |
| *error_ptr = error_str; |
| } |
| |
| /** |
| * parse_device_config() - Convert the dmsetup table into a struct device_config. |
| * @argc: The number of table values. |
| * @argv: The array of table values. |
| * @ti: The target structure for this table. |
| * @config_ptr: A pointer to return the allocated config. |
| * |
| * Return: VDO_SUCCESS or an error code. |
| */ |
| static int parse_device_config(int argc, char **argv, struct dm_target *ti, |
| struct device_config **config_ptr) |
| { |
| bool enable_512e; |
| size_t logical_bytes = to_bytes(ti->len); |
| struct dm_arg_set arg_set; |
| char **error_ptr = &ti->error; |
| struct device_config *config = NULL; |
| int result; |
| |
| if ((logical_bytes % VDO_BLOCK_SIZE) != 0) { |
| handle_parse_error(config, error_ptr, |
| "Logical size must be a multiple of 4096"); |
| return VDO_BAD_CONFIGURATION; |
| } |
| |
| if (argc == 0) { |
| handle_parse_error(config, error_ptr, "Incorrect number of arguments"); |
| return VDO_BAD_CONFIGURATION; |
| } |
| |
| result = vdo_allocate(1, struct device_config, "device_config", &config); |
| if (result != VDO_SUCCESS) { |
| handle_parse_error(config, error_ptr, |
| "Could not allocate config structure"); |
| return VDO_BAD_CONFIGURATION; |
| } |
| |
| config->owning_target = ti; |
| config->logical_blocks = logical_bytes / VDO_BLOCK_SIZE; |
| INIT_LIST_HEAD(&config->config_list); |
| |
| /* Save the original string. */ |
| result = join_strings(argv, argc, ' ', &config->original_string); |
| if (result != VDO_SUCCESS) { |
| handle_parse_error(config, error_ptr, "Could not populate string"); |
| return VDO_BAD_CONFIGURATION; |
| } |
| |
| vdo_log_info("table line: %s", config->original_string); |
| |
| config->thread_counts = (struct thread_count_config) { |
| .bio_ack_threads = 1, |
| .bio_threads = DEFAULT_VDO_BIO_SUBMIT_QUEUE_COUNT, |
| .bio_rotation_interval = DEFAULT_VDO_BIO_SUBMIT_QUEUE_ROTATE_INTERVAL, |
| .cpu_threads = 1, |
| .logical_zones = 0, |
| .physical_zones = 0, |
| .hash_zones = 0, |
| }; |
| config->max_discard_blocks = 1; |
| config->deduplication = true; |
| config->compression = false; |
| |
| arg_set.argc = argc; |
| arg_set.argv = argv; |
| |
| result = get_version_number(argc, argv, error_ptr, &config->version); |
| if (result != VDO_SUCCESS) { |
| /* get_version_number sets error_ptr itself. */ |
| handle_parse_error(config, error_ptr, *error_ptr); |
| return result; |
| } |
| /* Move the arg pointer forward only if the argument was there. */ |
| if (config->version >= 1) |
| dm_shift_arg(&arg_set); |
| |
| result = vdo_duplicate_string(dm_shift_arg(&arg_set), "parent device name", |
| &config->parent_device_name); |
| if (result != VDO_SUCCESS) { |
| handle_parse_error(config, error_ptr, |
| "Could not copy parent device name"); |
| return VDO_BAD_CONFIGURATION; |
| } |
| |
| /* Get the physical blocks, if known. */ |
| if (config->version >= 1) { |
| result = kstrtoull(dm_shift_arg(&arg_set), 10, &config->physical_blocks); |
| if (result != VDO_SUCCESS) { |
| handle_parse_error(config, error_ptr, |
| "Invalid physical block count"); |
| return VDO_BAD_CONFIGURATION; |
| } |
| } |
| |
| /* Get the logical block size and validate */ |
| result = parse_bool(dm_shift_arg(&arg_set), "512", "4096", &enable_512e); |
| if (result != VDO_SUCCESS) { |
| handle_parse_error(config, error_ptr, "Invalid logical block size"); |
| return VDO_BAD_CONFIGURATION; |
| } |
| config->logical_block_size = (enable_512e ? 512 : 4096); |
| |
| /* Skip past the two no longer used read cache options. */ |
| if (config->version <= 1) |
| dm_consume_args(&arg_set, 2); |
| |
| /* Get the page cache size. */ |
| result = kstrtouint(dm_shift_arg(&arg_set), 10, &config->cache_size); |
| if (result != VDO_SUCCESS) { |
| handle_parse_error(config, error_ptr, |
| "Invalid block map page cache size"); |
| return VDO_BAD_CONFIGURATION; |
| } |
| |
| /* Get the block map era length. */ |
| result = kstrtouint(dm_shift_arg(&arg_set), 10, &config->block_map_maximum_age); |
| if (result != VDO_SUCCESS) { |
| handle_parse_error(config, error_ptr, "Invalid block map maximum age"); |
| return VDO_BAD_CONFIGURATION; |
| } |
| |
| /* Skip past the no longer used MD RAID5 optimization mode */ |
| if (config->version <= 2) |
| dm_consume_args(&arg_set, 1); |
| |
| /* Skip past the no longer used write policy setting */ |
| if (config->version <= 3) |
| dm_consume_args(&arg_set, 1); |
| |
| /* Skip past the no longer used pool name for older table lines */ |
| if (config->version <= 2) { |
| /* |
| * Make sure the enum to get the pool name from argv directly is still in sync with |
| * the parsing of the table line. |
| */ |
| if (&arg_set.argv[0] != &argv[POOL_NAME_ARG_INDEX[config->version]]) { |
| handle_parse_error(config, error_ptr, |
| "Pool name not in expected location"); |
| return VDO_BAD_CONFIGURATION; |
| } |
| dm_shift_arg(&arg_set); |
| } |
| |
| /* Get the optional arguments and validate. */ |
| result = parse_optional_arguments(&arg_set, error_ptr, config); |
| if (result != VDO_SUCCESS) { |
| /* parse_optional_arguments sets error_ptr itself. */ |
| handle_parse_error(config, error_ptr, *error_ptr); |
| return result; |
| } |
| |
| /* |
| * Logical, physical, and hash zone counts can all be zero; then we get one thread doing |
| * everything, our older configuration. If any zone count is non-zero, the others must be |
| * as well. |
| */ |
| if (((config->thread_counts.logical_zones == 0) != |
| (config->thread_counts.physical_zones == 0)) || |
| ((config->thread_counts.physical_zones == 0) != |
| (config->thread_counts.hash_zones == 0))) { |
| handle_parse_error(config, error_ptr, |
| "Logical, physical, and hash zones counts must all be zero or all non-zero"); |
| return VDO_BAD_CONFIGURATION; |
| } |
| |
| if (config->cache_size < |
| (2 * MAXIMUM_VDO_USER_VIOS * config->thread_counts.logical_zones)) { |
| handle_parse_error(config, error_ptr, |
| "Insufficient block map cache for logical zones"); |
| return VDO_BAD_CONFIGURATION; |
| } |
| |
| result = dm_get_device(ti, config->parent_device_name, |
| dm_table_get_mode(ti->table), &config->owned_device); |
| if (result != 0) { |
| vdo_log_error("couldn't open device \"%s\": error %d", |
| config->parent_device_name, result); |
| handle_parse_error(config, error_ptr, "Unable to open storage device"); |
| return VDO_BAD_CONFIGURATION; |
| } |
| |
| if (config->version == 0) { |
| u64 device_size = bdev_nr_bytes(config->owned_device->bdev); |
| |
| config->physical_blocks = device_size / VDO_BLOCK_SIZE; |
| } |
| |
| *config_ptr = config; |
| return result; |
| } |
| |
| static struct vdo *get_vdo_for_target(struct dm_target *ti) |
| { |
| return ((struct device_config *) ti->private)->vdo; |
| } |
| |
| |
| static int vdo_map_bio(struct dm_target *ti, struct bio *bio) |
| { |
| struct vdo *vdo = get_vdo_for_target(ti); |
| struct vdo_work_queue *current_work_queue; |
| const struct admin_state_code *code = vdo_get_admin_state_code(&vdo->admin.state); |
| |
| VDO_ASSERT_LOG_ONLY(code->normal, "vdo should not receive bios while in state %s", |
| code->name); |
| |
| /* Count all incoming bios. */ |
| vdo_count_bios(&vdo->stats.bios_in, bio); |
| |
| |
| /* Handle empty bios. Empty flush bios are not associated with a vio. */ |
| if ((bio_op(bio) == REQ_OP_FLUSH) || ((bio->bi_opf & REQ_PREFLUSH) != 0)) { |
| vdo_launch_flush(vdo, bio); |
| return DM_MAPIO_SUBMITTED; |
| } |
| |
| /* This could deadlock, */ |
| current_work_queue = vdo_get_current_work_queue(); |
| BUG_ON((current_work_queue != NULL) && |
| (vdo == vdo_get_work_queue_owner(current_work_queue)->vdo)); |
| vdo_launch_bio(vdo->data_vio_pool, bio); |
| return DM_MAPIO_SUBMITTED; |
| } |
| |
| static void vdo_io_hints(struct dm_target *ti, struct queue_limits *limits) |
| { |
| struct vdo *vdo = get_vdo_for_target(ti); |
| |
| limits->logical_block_size = vdo->device_config->logical_block_size; |
| limits->physical_block_size = VDO_BLOCK_SIZE; |
| |
| /* The minimum io size for random io */ |
| limits->io_min = VDO_BLOCK_SIZE; |
| /* The optimal io size for streamed/sequential io */ |
| limits->io_opt = VDO_BLOCK_SIZE; |
| |
| /* |
| * Sets the maximum discard size that will be passed into VDO. This value comes from a |
| * table line value passed in during dmsetup create. |
| * |
| * The value 1024 is the largest usable value on HD systems. A 2048 sector discard on a |
| * busy HD system takes 31 seconds. We should use a value no higher than 1024, which takes |
| * 15 to 16 seconds on a busy HD system. However, using large values results in 120 second |
| * blocked task warnings in kernel logs. In order to avoid these warnings, we choose to |
| * use the smallest reasonable value. |
| * |
| * The value is used by dm-thin to determine whether to pass down discards. The block layer |
| * splits large discards on this boundary when this is set. |
| */ |
| limits->max_hw_discard_sectors = |
| (vdo->device_config->max_discard_blocks * VDO_SECTORS_PER_BLOCK); |
| |
| /* |
| * Force discards to not begin or end with a partial block by stating the granularity is |
| * 4k. |
| */ |
| limits->discard_granularity = VDO_BLOCK_SIZE; |
| } |
| |
| static int vdo_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn, |
| void *data) |
| { |
| struct device_config *config = get_vdo_for_target(ti)->device_config; |
| |
| return fn(ti, config->owned_device, 0, |
| config->physical_blocks * VDO_SECTORS_PER_BLOCK, data); |
| } |
| |
| /* |
| * Status line is: |
| * <device> <operating mode> <in recovery> <index state> <compression state> |
| * <used physical blocks> <total physical blocks> |
| */ |
| |
| static void vdo_status(struct dm_target *ti, status_type_t status_type, |
| unsigned int status_flags, char *result, unsigned int maxlen) |
| { |
| struct vdo *vdo = get_vdo_for_target(ti); |
| struct vdo_statistics *stats; |
| struct device_config *device_config; |
| /* N.B.: The DMEMIT macro uses the variables named "sz", "result", "maxlen". */ |
| int sz = 0; |
| |
| switch (status_type) { |
| case STATUSTYPE_INFO: |
| /* Report info for dmsetup status */ |
| mutex_lock(&vdo->stats_mutex); |
| vdo_fetch_statistics(vdo, &vdo->stats_buffer); |
| stats = &vdo->stats_buffer; |
| |
| DMEMIT("/dev/%pg %s %s %s %s %llu %llu", |
| vdo_get_backing_device(vdo), stats->mode, |
| stats->in_recovery_mode ? "recovering" : "-", |
| vdo_get_dedupe_index_state_name(vdo->hash_zones), |
| vdo_get_compressing(vdo) ? "online" : "offline", |
| stats->data_blocks_used + stats->overhead_blocks_used, |
| stats->physical_blocks); |
| mutex_unlock(&vdo->stats_mutex); |
| break; |
| |
| case STATUSTYPE_TABLE: |
| /* Report the string actually specified in the beginning. */ |
| device_config = (struct device_config *) ti->private; |
| DMEMIT("%s", device_config->original_string); |
| break; |
| |
| case STATUSTYPE_IMA: |
| /* FIXME: We ought to be more detailed here, but this is what thin does. */ |
| *result = '\0'; |
| break; |
| } |
| } |
| |
| static block_count_t __must_check get_underlying_device_block_count(const struct vdo *vdo) |
| { |
| return bdev_nr_bytes(vdo_get_backing_device(vdo)) / VDO_BLOCK_SIZE; |
| } |
| |
| static int __must_check process_vdo_message_locked(struct vdo *vdo, unsigned int argc, |
| char **argv) |
| { |
| if ((argc == 2) && (strcasecmp(argv[0], "compression") == 0)) { |
| if (strcasecmp(argv[1], "on") == 0) { |
| vdo_set_compressing(vdo, true); |
| return 0; |
| } |
| |
| if (strcasecmp(argv[1], "off") == 0) { |
| vdo_set_compressing(vdo, false); |
| return 0; |
| } |
| |
| vdo_log_warning("invalid argument '%s' to dmsetup compression message", |
| argv[1]); |
| return -EINVAL; |
| } |
| |
| vdo_log_warning("unrecognized dmsetup message '%s' received", argv[0]); |
| return -EINVAL; |
| } |
| |
| /* |
| * If the message is a dump, just do it. Otherwise, check that no other message is being processed, |
| * and only proceed if so. |
| * Returns -EBUSY if another message is being processed |
| */ |
| static int __must_check process_vdo_message(struct vdo *vdo, unsigned int argc, |
| char **argv) |
| { |
| int result; |
| |
| /* |
| * All messages which may be processed in parallel with other messages should be handled |
| * here before the atomic check below. Messages which should be exclusive should be |
| * processed in process_vdo_message_locked(). |
| */ |
| |
| /* Dump messages should always be processed */ |
| if (strcasecmp(argv[0], "dump") == 0) |
| return vdo_dump(vdo, argc, argv, "dmsetup message"); |
| |
| if (argc == 1) { |
| if (strcasecmp(argv[0], "dump-on-shutdown") == 0) { |
| vdo->dump_on_shutdown = true; |
| return 0; |
| } |
| |
| /* Index messages should always be processed */ |
| if ((strcasecmp(argv[0], "index-close") == 0) || |
| (strcasecmp(argv[0], "index-create") == 0) || |
| (strcasecmp(argv[0], "index-disable") == 0) || |
| (strcasecmp(argv[0], "index-enable") == 0)) |
| return vdo_message_dedupe_index(vdo->hash_zones, argv[0]); |
| } |
| |
| if (atomic_cmpxchg(&vdo->processing_message, 0, 1) != 0) |
| return -EBUSY; |
| |
| result = process_vdo_message_locked(vdo, argc, argv); |
| |
| /* Pairs with the implicit barrier in cmpxchg just above */ |
| smp_wmb(); |
| atomic_set(&vdo->processing_message, 0); |
| return result; |
| } |
| |
| static int vdo_message(struct dm_target *ti, unsigned int argc, char **argv, |
| char *result_buffer, unsigned int maxlen) |
| { |
| struct registered_thread allocating_thread, instance_thread; |
| struct vdo *vdo; |
| int result; |
| |
| if (argc == 0) { |
| vdo_log_warning("unspecified dmsetup message"); |
| return -EINVAL; |
| } |
| |
| vdo = get_vdo_for_target(ti); |
| vdo_register_allocating_thread(&allocating_thread, NULL); |
| vdo_register_thread_device_id(&instance_thread, &vdo->instance); |
| |
| /* |
| * Must be done here so we don't map return codes. The code in dm-ioctl expects a 1 for a |
| * return code to look at the buffer and see if it is full or not. |
| */ |
| if ((argc == 1) && (strcasecmp(argv[0], "stats") == 0)) { |
| vdo_write_stats(vdo, result_buffer, maxlen); |
| result = 1; |
| } else if ((argc == 1) && (strcasecmp(argv[0], "config") == 0)) { |
| vdo_write_config(vdo, &result_buffer, &maxlen); |
| result = 1; |
| } else { |
| result = vdo_status_to_errno(process_vdo_message(vdo, argc, argv)); |
| } |
| |
| vdo_unregister_thread_device_id(); |
| vdo_unregister_allocating_thread(); |
| return result; |
| } |
| |
| static void configure_target_capabilities(struct dm_target *ti) |
| { |
| ti->discards_supported = 1; |
| ti->flush_supported = true; |
| ti->num_discard_bios = 1; |
| ti->num_flush_bios = 1; |
| |
| /* |
| * If this value changes, please make sure to update the value for max_discard_sectors |
| * accordingly. |
| */ |
| BUG_ON(dm_set_target_max_io_len(ti, VDO_SECTORS_PER_BLOCK) != 0); |
| } |
| |
| /* |
| * Implements vdo_filter_fn. |
| */ |
| static bool vdo_uses_device(struct vdo *vdo, const void *context) |
| { |
| const struct device_config *config = context; |
| |
| return vdo_get_backing_device(vdo)->bd_dev == config->owned_device->bdev->bd_dev; |
| } |
| |
| /** |
| * get_thread_id_for_phase() - Get the thread id for the current phase of the admin operation in |
| * progress. |
| */ |
| static thread_id_t __must_check get_thread_id_for_phase(struct vdo *vdo) |
| { |
| switch (vdo->admin.phase) { |
| case RESUME_PHASE_PACKER: |
| case RESUME_PHASE_FLUSHER: |
| case SUSPEND_PHASE_PACKER: |
| case SUSPEND_PHASE_FLUSHES: |
| return vdo->thread_config.packer_thread; |
| |
| case RESUME_PHASE_DATA_VIOS: |
| case SUSPEND_PHASE_DATA_VIOS: |
| return vdo->thread_config.cpu_thread; |
| |
| case LOAD_PHASE_DRAIN_JOURNAL: |
| case RESUME_PHASE_JOURNAL: |
| case SUSPEND_PHASE_JOURNAL: |
| return vdo->thread_config.journal_thread; |
| |
| default: |
| return vdo->thread_config.admin_thread; |
| } |
| } |
| |
| static struct vdo_completion *prepare_admin_completion(struct vdo *vdo, |
| vdo_action_fn callback, |
| vdo_action_fn error_handler) |
| { |
| struct vdo_completion *completion = &vdo->admin.completion; |
| |
| /* |
| * We can't use vdo_prepare_completion_for_requeue() here because we don't want to reset |
| * any error in the completion. |
| */ |
| completion->callback = callback; |
| completion->error_handler = error_handler; |
| completion->callback_thread_id = get_thread_id_for_phase(vdo); |
| completion->requeue = true; |
| return completion; |
| } |
| |
| /** |
| * advance_phase() - Increment the phase of the current admin operation and prepare the admin |
| * completion to run on the thread for the next phase. |
| * @vdo: The on which an admin operation is being performed |
| * |
| * Return: The current phase |
| */ |
| static u32 advance_phase(struct vdo *vdo) |
| { |
| u32 phase = vdo->admin.phase++; |
| |
| vdo->admin.completion.callback_thread_id = get_thread_id_for_phase(vdo); |
| vdo->admin.completion.requeue = true; |
| return phase; |
| } |
| |
| /* |
| * Perform an administrative operation (load, suspend, grow logical, or grow physical). This method |
| * should not be called from vdo threads. |
| */ |
| static int perform_admin_operation(struct vdo *vdo, u32 starting_phase, |
| vdo_action_fn callback, vdo_action_fn error_handler, |
| const char *type) |
| { |
| int result; |
| struct vdo_administrator *admin = &vdo->admin; |
| |
| if (atomic_cmpxchg(&admin->busy, 0, 1) != 0) { |
| return vdo_log_error_strerror(VDO_COMPONENT_BUSY, |
| "Can't start %s operation, another operation is already in progress", |
| type); |
| } |
| |
| admin->phase = starting_phase; |
| reinit_completion(&admin->callback_sync); |
| vdo_reset_completion(&admin->completion); |
| vdo_launch_completion(prepare_admin_completion(vdo, callback, error_handler)); |
| |
| /* |
| * Using the "interruptible" interface means that Linux will not log a message when we wait |
| * for more than 120 seconds. |
| */ |
| while (wait_for_completion_interruptible(&admin->callback_sync)) { |
| /* However, if we get a signal in a user-mode process, we could spin... */ |
| fsleep(1000); |
| } |
| |
| result = admin->completion.result; |
| /* pairs with implicit barrier in cmpxchg above */ |
| smp_wmb(); |
| atomic_set(&admin->busy, 0); |
| return result; |
| } |
| |
| /* Assert that we are operating on the correct thread for the current phase. */ |
| static void assert_admin_phase_thread(struct vdo *vdo, const char *what) |
| { |
| VDO_ASSERT_LOG_ONLY(vdo_get_callback_thread_id() == get_thread_id_for_phase(vdo), |
| "%s on correct thread for %s", what, |
| ADMIN_PHASE_NAMES[vdo->admin.phase]); |
| } |
| |
| /** |
| * finish_operation_callback() - Callback to finish an admin operation. |
| * @completion: The admin_completion. |
| */ |
| static void finish_operation_callback(struct vdo_completion *completion) |
| { |
| struct vdo_administrator *admin = &completion->vdo->admin; |
| |
| vdo_finish_operation(&admin->state, completion->result); |
| complete(&admin->callback_sync); |
| } |
| |
| /** |
| * decode_from_super_block() - Decode the VDO state from the super block and validate that it is |
| * correct. |
| * @vdo: The vdo being loaded. |
| * |
| * On error from this method, the component states must be destroyed explicitly. If this method |
| * returns successfully, the component states must not be destroyed. |
| * |
| * Return: VDO_SUCCESS or an error. |
| */ |
| static int __must_check decode_from_super_block(struct vdo *vdo) |
| { |
| const struct device_config *config = vdo->device_config; |
| int result; |
| |
| result = vdo_decode_component_states(vdo->super_block.buffer, &vdo->geometry, |
| &vdo->states); |
| if (result != VDO_SUCCESS) |
| return result; |
| |
| vdo_set_state(vdo, vdo->states.vdo.state); |
| vdo->load_state = vdo->states.vdo.state; |
| |
| /* |
| * If the device config specifies a larger logical size than was recorded in the super |
| * block, just accept it. |
| */ |
| if (vdo->states.vdo.config.logical_blocks < config->logical_blocks) { |
| vdo_log_warning("Growing logical size: a logical size of %llu blocks was specified, but that differs from the %llu blocks configured in the vdo super block", |
| (unsigned long long) config->logical_blocks, |
| (unsigned long long) vdo->states.vdo.config.logical_blocks); |
| vdo->states.vdo.config.logical_blocks = config->logical_blocks; |
| } |
| |
| result = vdo_validate_component_states(&vdo->states, vdo->geometry.nonce, |
| config->physical_blocks, |
| config->logical_blocks); |
| if (result != VDO_SUCCESS) |
| return result; |
| |
| vdo->layout = vdo->states.layout; |
| return VDO_SUCCESS; |
| } |
| |
| /** |
| * decode_vdo() - Decode the component data portion of a super block and fill in the corresponding |
| * portions of the vdo being loaded. |
| * @vdo: The vdo being loaded. |
| * |
| * This will also allocate the recovery journal and slab depot. If this method is called with an |
| * asynchronous layer (i.e. a thread config which specifies at least one base thread), the block |
| * map and packer will be constructed as well. |
| * |
| * Return: VDO_SUCCESS or an error. |
| */ |
| static int __must_check decode_vdo(struct vdo *vdo) |
| { |
| block_count_t maximum_age, journal_length; |
| struct partition *partition; |
| int result; |
| |
| result = decode_from_super_block(vdo); |
| if (result != VDO_SUCCESS) { |
| vdo_destroy_component_states(&vdo->states); |
| return result; |
| } |
| |
| maximum_age = vdo_convert_maximum_age(vdo->device_config->block_map_maximum_age); |
| journal_length = |
| vdo_get_recovery_journal_length(vdo->states.vdo.config.recovery_journal_size); |
| if (maximum_age > (journal_length / 2)) { |
| return vdo_log_error_strerror(VDO_BAD_CONFIGURATION, |
| "maximum age: %llu exceeds limit %llu", |
| (unsigned long long) maximum_age, |
| (unsigned long long) (journal_length / 2)); |
| } |
| |
| if (maximum_age == 0) { |
| return vdo_log_error_strerror(VDO_BAD_CONFIGURATION, |
| "maximum age must be greater than 0"); |
| } |
| |
| result = vdo_enable_read_only_entry(vdo); |
| if (result != VDO_SUCCESS) |
| return result; |
| |
| partition = vdo_get_known_partition(&vdo->layout, |
| VDO_RECOVERY_JOURNAL_PARTITION); |
| result = vdo_decode_recovery_journal(vdo->states.recovery_journal, |
| vdo->states.vdo.nonce, vdo, partition, |
| vdo->states.vdo.complete_recoveries, |
| vdo->states.vdo.config.recovery_journal_size, |
| &vdo->recovery_journal); |
| if (result != VDO_SUCCESS) |
| return result; |
| |
| partition = vdo_get_known_partition(&vdo->layout, VDO_SLAB_SUMMARY_PARTITION); |
| result = vdo_decode_slab_depot(vdo->states.slab_depot, vdo, partition, |
| &vdo->depot); |
| if (result != VDO_SUCCESS) |
| return result; |
| |
| result = vdo_decode_block_map(vdo->states.block_map, |
| vdo->states.vdo.config.logical_blocks, vdo, |
| vdo->recovery_journal, vdo->states.vdo.nonce, |
| vdo->device_config->cache_size, maximum_age, |
| &vdo->block_map); |
| if (result != VDO_SUCCESS) |
| return result; |
| |
| result = vdo_make_physical_zones(vdo, &vdo->physical_zones); |
| if (result != VDO_SUCCESS) |
| return result; |
| |
| /* The logical zones depend on the physical zones already existing. */ |
| result = vdo_make_logical_zones(vdo, &vdo->logical_zones); |
| if (result != VDO_SUCCESS) |
| return result; |
| |
| return vdo_make_hash_zones(vdo, &vdo->hash_zones); |
| } |
| |
| /** |
| * pre_load_callback() - Callback to initiate a pre-load, registered in vdo_initialize(). |
| * @completion: The admin completion. |
| */ |
| static void pre_load_callback(struct vdo_completion *completion) |
| { |
| struct vdo *vdo = completion->vdo; |
| int result; |
| |
| assert_admin_phase_thread(vdo, __func__); |
| |
| switch (advance_phase(vdo)) { |
| case PRE_LOAD_PHASE_START: |
| result = vdo_start_operation(&vdo->admin.state, |
| VDO_ADMIN_STATE_PRE_LOADING); |
| if (result != VDO_SUCCESS) { |
| vdo_continue_completion(completion, result); |
| return; |
| } |
| |
| vdo_load_super_block(vdo, completion); |
| return; |
| |
| case PRE_LOAD_PHASE_LOAD_COMPONENTS: |
| vdo_continue_completion(completion, decode_vdo(vdo)); |
| return; |
| |
| case PRE_LOAD_PHASE_END: |
| break; |
| |
| default: |
| vdo_set_completion_result(completion, UDS_BAD_STATE); |
| } |
| |
| finish_operation_callback(completion); |
| } |
| |
| static void release_instance(unsigned int instance) |
| { |
| mutex_lock(&instances_lock); |
| if (instance >= instances.bit_count) { |
| VDO_ASSERT_LOG_ONLY(false, |
| "instance number %u must be less than bit count %u", |
| instance, instances.bit_count); |
| } else if (test_bit(instance, instances.words) == 0) { |
| VDO_ASSERT_LOG_ONLY(false, "instance number %u must be allocated", instance); |
| } else { |
| __clear_bit(instance, instances.words); |
| instances.count -= 1; |
| } |
| mutex_unlock(&instances_lock); |
| } |
| |
| static void set_device_config(struct dm_target *ti, struct vdo *vdo, |
| struct device_config *config) |
| { |
| list_del_init(&config->config_list); |
| list_add_tail(&config->config_list, &vdo->device_config_list); |
| config->vdo = vdo; |
| ti->private = config; |
| configure_target_capabilities(ti); |
| } |
| |
| static int vdo_initialize(struct dm_target *ti, unsigned int instance, |
| struct device_config *config) |
| { |
| struct vdo *vdo; |
| int result; |
| u64 block_size = VDO_BLOCK_SIZE; |
| u64 logical_size = to_bytes(ti->len); |
| block_count_t logical_blocks = logical_size / block_size; |
| |
| vdo_log_info("loading device '%s'", vdo_get_device_name(ti)); |
| vdo_log_debug("Logical block size = %llu", (u64) config->logical_block_size); |
| vdo_log_debug("Logical blocks = %llu", logical_blocks); |
| vdo_log_debug("Physical block size = %llu", (u64) block_size); |
| vdo_log_debug("Physical blocks = %llu", config->physical_blocks); |
| vdo_log_debug("Block map cache blocks = %u", config->cache_size); |
| vdo_log_debug("Block map maximum age = %u", config->block_map_maximum_age); |
| vdo_log_debug("Deduplication = %s", (config->deduplication ? "on" : "off")); |
| vdo_log_debug("Compression = %s", (config->compression ? "on" : "off")); |
| |
| vdo = vdo_find_matching(vdo_uses_device, config); |
| if (vdo != NULL) { |
| vdo_log_error("Existing vdo already uses device %s", |
| vdo->device_config->parent_device_name); |
| ti->error = "Cannot share storage device with already-running VDO"; |
| return VDO_BAD_CONFIGURATION; |
| } |
| |
| result = vdo_make(instance, config, &ti->error, &vdo); |
| if (result != VDO_SUCCESS) { |
| vdo_log_error("Could not create VDO device. (VDO error %d, message %s)", |
| result, ti->error); |
| vdo_destroy(vdo); |
| return result; |
| } |
| |
| result = perform_admin_operation(vdo, PRE_LOAD_PHASE_START, pre_load_callback, |
| finish_operation_callback, "pre-load"); |
| if (result != VDO_SUCCESS) { |
| ti->error = ((result == VDO_INVALID_ADMIN_STATE) ? |
| "Pre-load is only valid immediately after initialization" : |
| "Cannot load metadata from device"); |
| vdo_log_error("Could not start VDO device. (VDO error %d, message %s)", |
| result, ti->error); |
| vdo_destroy(vdo); |
| return result; |
| } |
| |
| set_device_config(ti, vdo, config); |
| vdo->device_config = config; |
| return VDO_SUCCESS; |
| } |
| |
| /* Implements vdo_filter_fn. */ |
| static bool __must_check vdo_is_named(struct vdo *vdo, const void *context) |
| { |
| struct dm_target *ti = vdo->device_config->owning_target; |
| const char *device_name = vdo_get_device_name(ti); |
| |
| return strcmp(device_name, context) == 0; |
| } |
| |
| /** |
| * get_bit_array_size() - Return the number of bytes needed to store a bit array of the specified |
| * capacity in an array of unsigned longs. |
| * @bit_count: The number of bits the array must hold. |
| * |
| * Return: the number of bytes needed for the array representation. |
| */ |
| static size_t get_bit_array_size(unsigned int bit_count) |
| { |
| /* Round up to a multiple of the word size and convert to a byte count. */ |
| return (BITS_TO_LONGS(bit_count) * sizeof(unsigned long)); |
| } |
| |
| /** |
| * grow_bit_array() - Re-allocate the bitmap word array so there will more instance numbers that |
| * can be allocated. |
| * |
| * Since the array is initially NULL, this also initializes the array the first time we allocate an |
| * instance number. |
| * |
| * Return: VDO_SUCCESS or an error code from the allocation |
| */ |
| static int grow_bit_array(void) |
| { |
| unsigned int new_count = max(instances.bit_count + BIT_COUNT_INCREMENT, |
| (unsigned int) BIT_COUNT_MINIMUM); |
| unsigned long *new_words; |
| int result; |
| |
| result = vdo_reallocate_memory(instances.words, |
| get_bit_array_size(instances.bit_count), |
| get_bit_array_size(new_count), |
| "instance number bit array", &new_words); |
| if (result != VDO_SUCCESS) |
| return result; |
| |
| instances.bit_count = new_count; |
| instances.words = new_words; |
| return VDO_SUCCESS; |
| } |
| |
| /** |
| * allocate_instance() - Allocate an instance number. |
| * @instance_ptr: A point to hold the instance number |
| * |
| * Return: VDO_SUCCESS or an error code |
| * |
| * This function must be called while holding the instances lock. |
| */ |
| static int allocate_instance(unsigned int *instance_ptr) |
| { |
| unsigned int instance; |
| int result; |
| |
| /* If there are no unallocated instances, grow the bit array. */ |
| if (instances.count >= instances.bit_count) { |
| result = grow_bit_array(); |
| if (result != VDO_SUCCESS) |
| return result; |
| } |
| |
| /* |
| * There must be a zero bit somewhere now. Find it, starting just after the last instance |
| * allocated. |
| */ |
| instance = find_next_zero_bit(instances.words, instances.bit_count, |
| instances.next); |
| if (instance >= instances.bit_count) { |
| /* Nothing free after next, so wrap around to instance zero. */ |
| instance = find_first_zero_bit(instances.words, instances.bit_count); |
| result = VDO_ASSERT(instance < instances.bit_count, |
| "impossibly, no zero bit found"); |
| if (result != VDO_SUCCESS) |
| return result; |
| } |
| |
| __set_bit(instance, instances.words); |
| instances.count++; |
| instances.next = instance + 1; |
| *instance_ptr = instance; |
| return VDO_SUCCESS; |
| } |
| |
| static int construct_new_vdo_registered(struct dm_target *ti, unsigned int argc, |
| char **argv, unsigned int instance) |
| { |
| int result; |
| struct device_config *config; |
| |
| result = parse_device_config(argc, argv, ti, &config); |
| if (result != VDO_SUCCESS) { |
| vdo_log_error_strerror(result, "parsing failed: %s", ti->error); |
| release_instance(instance); |
| return -EINVAL; |
| } |
| |
| /* Beyond this point, the instance number will be cleaned up for us if needed */ |
| result = vdo_initialize(ti, instance, config); |
| if (result != VDO_SUCCESS) { |
| release_instance(instance); |
| free_device_config(config); |
| return vdo_status_to_errno(result); |
| } |
| |
| return VDO_SUCCESS; |
| } |
| |
| static int construct_new_vdo(struct dm_target *ti, unsigned int argc, char **argv) |
| { |
| int result; |
| unsigned int instance; |
| struct registered_thread instance_thread; |
| |
| mutex_lock(&instances_lock); |
| result = allocate_instance(&instance); |
| mutex_unlock(&instances_lock); |
| if (result != VDO_SUCCESS) |
| return -ENOMEM; |
| |
| vdo_register_thread_device_id(&instance_thread, &instance); |
| result = construct_new_vdo_registered(ti, argc, argv, instance); |
| vdo_unregister_thread_device_id(); |
| return result; |
| } |
| |
| /** |
| * check_may_grow_physical() - Callback to check that we're not in recovery mode, used in |
| * vdo_prepare_to_grow_physical(). |
| * @completion: The admin completion. |
| */ |
| static void check_may_grow_physical(struct vdo_completion *completion) |
| { |
| struct vdo *vdo = completion->vdo; |
| |
| assert_admin_phase_thread(vdo, __func__); |
| |
| /* These checks can only be done from a vdo thread. */ |
| if (vdo_is_read_only(vdo)) |
| vdo_set_completion_result(completion, VDO_READ_ONLY); |
| |
| if (vdo_in_recovery_mode(vdo)) |
| vdo_set_completion_result(completion, VDO_RETRY_AFTER_REBUILD); |
| |
| finish_operation_callback(completion); |
| } |
| |
| static block_count_t get_partition_size(struct layout *layout, enum partition_id id) |
| { |
| return vdo_get_known_partition(layout, id)->count; |
| } |
| |
| /** |
| * grow_layout() - Make the layout for growing a vdo. |
| * @vdo: The vdo preparing to grow. |
| * @old_size: The current size of the vdo. |
| * @new_size: The size to which the vdo will be grown. |
| * |
| * Return: VDO_SUCCESS or an error code. |
| */ |
| static int grow_layout(struct vdo *vdo, block_count_t old_size, block_count_t new_size) |
| { |
| int result; |
| block_count_t min_new_size; |
| |
| if (vdo->next_layout.size == new_size) { |
| /* We are already prepared to grow to the new size, so we're done. */ |
| return VDO_SUCCESS; |
| } |
| |
| /* Make a copy completion if there isn't one */ |
| if (vdo->partition_copier == NULL) { |
| vdo->partition_copier = dm_kcopyd_client_create(NULL); |
| if (IS_ERR(vdo->partition_copier)) { |
| result = PTR_ERR(vdo->partition_copier); |
| vdo->partition_copier = NULL; |
| return result; |
| } |
| } |
| |
| /* Free any unused preparation. */ |
| vdo_uninitialize_layout(&vdo->next_layout); |
| |
| /* |
| * Make a new layout with the existing partition sizes for everything but the slab depot |
| * partition. |
| */ |
| result = vdo_initialize_layout(new_size, vdo->layout.start, |
| get_partition_size(&vdo->layout, |
| VDO_BLOCK_MAP_PARTITION), |
| get_partition_size(&vdo->layout, |
| VDO_RECOVERY_JOURNAL_PARTITION), |
| get_partition_size(&vdo->layout, |
| VDO_SLAB_SUMMARY_PARTITION), |
| &vdo->next_layout); |
| if (result != VDO_SUCCESS) { |
| dm_kcopyd_client_destroy(vdo_forget(vdo->partition_copier)); |
| return result; |
| } |
| |
| /* Ensure the new journal and summary are entirely within the added blocks. */ |
| min_new_size = (old_size + |
| get_partition_size(&vdo->next_layout, |
| VDO_SLAB_SUMMARY_PARTITION) + |
| get_partition_size(&vdo->next_layout, |
| VDO_RECOVERY_JOURNAL_PARTITION)); |
| if (min_new_size > new_size) { |
| /* Copying the journal and summary would destroy some old metadata. */ |
| vdo_uninitialize_layout(&vdo->next_layout); |
| dm_kcopyd_client_destroy(vdo_forget(vdo->partition_copier)); |
| return VDO_INCREMENT_TOO_SMALL; |
| } |
| |
| return VDO_SUCCESS; |
| } |
| |
| static int prepare_to_grow_physical(struct vdo *vdo, block_count_t new_physical_blocks) |
| { |
| int result; |
| block_count_t current_physical_blocks = vdo->states.vdo.config.physical_blocks; |
| |
| vdo_log_info("Preparing to resize physical to %llu", |
| (unsigned long long) new_physical_blocks); |
| VDO_ASSERT_LOG_ONLY((new_physical_blocks > current_physical_blocks), |
| "New physical size is larger than current physical size"); |
| result = perform_admin_operation(vdo, PREPARE_GROW_PHYSICAL_PHASE_START, |
| check_may_grow_physical, |
| finish_operation_callback, |
| "prepare grow-physical"); |
| if (result != VDO_SUCCESS) |
| return result; |
| |
| result = grow_layout(vdo, current_physical_blocks, new_physical_blocks); |
| if (result != VDO_SUCCESS) |
| return result; |
| |
| result = vdo_prepare_to_grow_slab_depot(vdo->depot, |
| vdo_get_known_partition(&vdo->next_layout, |
| VDO_SLAB_DEPOT_PARTITION)); |
| if (result != VDO_SUCCESS) { |
| vdo_uninitialize_layout(&vdo->next_layout); |
| return result; |
| } |
| |
| vdo_log_info("Done preparing to resize physical"); |
| return VDO_SUCCESS; |
| } |
| |
| /** |
| * validate_new_device_config() - Check whether a new device config represents a valid modification |
| * to an existing config. |
| * @to_validate: The new config to validate. |
| * @config: The existing config. |
| * @may_grow: Set to true if growing the logical and physical size of the vdo is currently |
| * permitted. |
| * @error_ptr: A pointer to hold the reason for any error. |
| * |
| * Return: VDO_SUCCESS or an error. |
| */ |
| static int validate_new_device_config(struct device_config *to_validate, |
| struct device_config *config, bool may_grow, |
| char **error_ptr) |
| { |
| if (to_validate->owning_target->begin != config->owning_target->begin) { |
| *error_ptr = "Starting sector cannot change"; |
| return VDO_PARAMETER_MISMATCH; |
| } |
| |
| if (to_validate->logical_block_size != config->logical_block_size) { |
| *error_ptr = "Logical block size cannot change"; |
| return VDO_PARAMETER_MISMATCH; |
| } |
| |
| if (to_validate->logical_blocks < config->logical_blocks) { |
| *error_ptr = "Can't shrink VDO logical size"; |
| return VDO_PARAMETER_MISMATCH; |
| } |
| |
| if (to_validate->cache_size != config->cache_size) { |
| *error_ptr = "Block map cache size cannot change"; |
| return VDO_PARAMETER_MISMATCH; |
| } |
| |
| if (to_validate->block_map_maximum_age != config->block_map_maximum_age) { |
| *error_ptr = "Block map maximum age cannot change"; |
| return VDO_PARAMETER_MISMATCH; |
| } |
| |
| if (memcmp(&to_validate->thread_counts, &config->thread_counts, |
| sizeof(struct thread_count_config)) != 0) { |
| *error_ptr = "Thread configuration cannot change"; |
| return VDO_PARAMETER_MISMATCH; |
| } |
| |
| if (to_validate->physical_blocks < config->physical_blocks) { |
| *error_ptr = "Removing physical storage from a VDO is not supported"; |
| return VDO_NOT_IMPLEMENTED; |
| } |
| |
| if (!may_grow && (to_validate->physical_blocks > config->physical_blocks)) { |
| *error_ptr = "VDO physical size may not grow in current state"; |
| return VDO_NOT_IMPLEMENTED; |
| } |
| |
| return VDO_SUCCESS; |
| } |
| |
| static int prepare_to_modify(struct dm_target *ti, struct device_config *config, |
| struct vdo *vdo) |
| { |
| int result; |
| bool may_grow = (vdo_get_admin_state(vdo) != VDO_ADMIN_STATE_PRE_LOADED); |
| |
| result = validate_new_device_config(config, vdo->device_config, may_grow, |
| &ti->error); |
| if (result != VDO_SUCCESS) |
| return -EINVAL; |
| |
| if (config->logical_blocks > vdo->device_config->logical_blocks) { |
| block_count_t logical_blocks = vdo->states.vdo.config.logical_blocks; |
| |
| vdo_log_info("Preparing to resize logical to %llu", |
| (unsigned long long) config->logical_blocks); |
| VDO_ASSERT_LOG_ONLY((config->logical_blocks > logical_blocks), |
| "New logical size is larger than current size"); |
| |
| result = vdo_prepare_to_grow_block_map(vdo->block_map, |
| config->logical_blocks); |
| if (result != VDO_SUCCESS) { |
| ti->error = "Device vdo_prepare_to_grow_logical failed"; |
| return result; |
| } |
| |
| vdo_log_info("Done preparing to resize logical"); |
| } |
| |
| if (config->physical_blocks > vdo->device_config->physical_blocks) { |
| result = prepare_to_grow_physical(vdo, config->physical_blocks); |
| if (result != VDO_SUCCESS) { |
| if (result == VDO_PARAMETER_MISMATCH) { |
| /* |
| * If we don't trap this case, vdo_status_to_errno() will remap |
| * it to -EIO, which is misleading and ahistorical. |
| */ |
| result = -EINVAL; |
| } |
| |
| if (result == VDO_TOO_MANY_SLABS) |
| ti->error = "Device vdo_prepare_to_grow_physical failed (specified physical size too big based on formatted slab size)"; |
| else |
| ti->error = "Device vdo_prepare_to_grow_physical failed"; |
| |
| return result; |
| } |
| } |
| |
| if (strcmp(config->parent_device_name, vdo->device_config->parent_device_name) != 0) { |
| const char *device_name = vdo_get_device_name(config->owning_target); |
| |
| vdo_log_info("Updating backing device of %s from %s to %s", device_name, |
| vdo->device_config->parent_device_name, |
| config->parent_device_name); |
| } |
| |
| return VDO_SUCCESS; |
| } |
| |
| static int update_existing_vdo(const char *device_name, struct dm_target *ti, |
| unsigned int argc, char **argv, struct vdo *vdo) |
| { |
| int result; |
| struct device_config *config; |
| |
| result = parse_device_config(argc, argv, ti, &config); |
| if (result != VDO_SUCCESS) |
| return -EINVAL; |
| |
| vdo_log_info("preparing to modify device '%s'", device_name); |
| result = prepare_to_modify(ti, config, vdo); |
| if (result != VDO_SUCCESS) { |
| free_device_config(config); |
| return vdo_status_to_errno(result); |
| } |
| |
| set_device_config(ti, vdo, config); |
| return VDO_SUCCESS; |
| } |
| |
| static int vdo_ctr(struct dm_target *ti, unsigned int argc, char **argv) |
| { |
| int result; |
| struct registered_thread allocating_thread, instance_thread; |
| const char *device_name; |
| struct vdo *vdo; |
| |
| vdo_register_allocating_thread(&allocating_thread, NULL); |
| device_name = vdo_get_device_name(ti); |
| vdo = vdo_find_matching(vdo_is_named, device_name); |
| if (vdo == NULL) { |
| result = construct_new_vdo(ti, argc, argv); |
| } else { |
| vdo_register_thread_device_id(&instance_thread, &vdo->instance); |
| result = update_existing_vdo(device_name, ti, argc, argv, vdo); |
| vdo_unregister_thread_device_id(); |
| } |
| |
| vdo_unregister_allocating_thread(); |
| return result; |
| } |
| |
| static void vdo_dtr(struct dm_target *ti) |
| { |
| struct device_config *config = ti->private; |
| struct vdo *vdo = vdo_forget(config->vdo); |
| |
| list_del_init(&config->config_list); |
| if (list_empty(&vdo->device_config_list)) { |
| const char *device_name; |
| |
| /* This was the last config referencing the VDO. Free it. */ |
| unsigned int instance = vdo->instance; |
| struct registered_thread allocating_thread, instance_thread; |
| |
| vdo_register_thread_device_id(&instance_thread, &instance); |
| vdo_register_allocating_thread(&allocating_thread, NULL); |
| |
| device_name = vdo_get_device_name(ti); |
| vdo_log_info("stopping device '%s'", device_name); |
| if (vdo->dump_on_shutdown) |
| vdo_dump_all(vdo, "device shutdown"); |
| |
| vdo_destroy(vdo_forget(vdo)); |
| vdo_log_info("device '%s' stopped", device_name); |
| vdo_unregister_thread_device_id(); |
| vdo_unregister_allocating_thread(); |
| release_instance(instance); |
| } else if (config == vdo->device_config) { |
| /* |
| * The VDO still references this config. Give it a reference to a config that isn't |
| * being destroyed. |
| */ |
| vdo->device_config = list_first_entry(&vdo->device_config_list, |
| struct device_config, config_list); |
| } |
| |
| free_device_config(config); |
| ti->private = NULL; |
| } |
| |
| static void vdo_presuspend(struct dm_target *ti) |
| { |
| get_vdo_for_target(ti)->suspend_type = |
| (dm_noflush_suspending(ti) ? VDO_ADMIN_STATE_SUSPENDING : VDO_ADMIN_STATE_SAVING); |
| } |
| |
| /** |
| * write_super_block_for_suspend() - Update the VDO state and save the super block. |
| * @completion: The admin completion |
| */ |
| static void write_super_block_for_suspend(struct vdo_completion *completion) |
| { |
| struct vdo *vdo = completion->vdo; |
| |
| switch (vdo_get_state(vdo)) { |
| case VDO_DIRTY: |
| case VDO_NEW: |
| vdo_set_state(vdo, VDO_CLEAN); |
| break; |
| |
| case VDO_CLEAN: |
| case VDO_READ_ONLY_MODE: |
| case VDO_FORCE_REBUILD: |
| case VDO_RECOVERING: |
| case VDO_REBUILD_FOR_UPGRADE: |
| break; |
| |
| case VDO_REPLAYING: |
| default: |
| vdo_continue_completion(completion, UDS_BAD_STATE); |
| return; |
| } |
| |
| vdo_save_components(vdo, completion); |
| } |
| |
| /** |
| * suspend_callback() - Callback to initiate a suspend, registered in vdo_postsuspend(). |
| * @completion: The sub-task completion. |
| */ |
| static void suspend_callback(struct vdo_completion *completion) |
| { |
| struct vdo *vdo = completion->vdo; |
| struct admin_state *state = &vdo->admin.state; |
| int result; |
| |
| assert_admin_phase_thread(vdo, __func__); |
| |
| switch (advance_phase(vdo)) { |
| case SUSPEND_PHASE_START: |
| if (vdo_get_admin_state_code(state)->quiescent) { |
| /* Already suspended */ |
| break; |
| } |
| |
| vdo_continue_completion(completion, |
| vdo_start_operation(state, vdo->suspend_type)); |
| return; |
| |
| case SUSPEND_PHASE_PACKER: |
| /* |
| * If the VDO was already resumed from a prior suspend while read-only, some of the |
| * components may not have been resumed. By setting a read-only error here, we |
| * guarantee that the result of this suspend will be VDO_READ_ONLY and not |
| * VDO_INVALID_ADMIN_STATE in that case. |
| */ |
| if (vdo_in_read_only_mode(vdo)) |
| vdo_set_completion_result(completion, VDO_READ_ONLY); |
| |
| vdo_drain_packer(vdo->packer, completion); |
| return; |
| |
| case SUSPEND_PHASE_DATA_VIOS: |
| drain_data_vio_pool(vdo->data_vio_pool, completion); |
| return; |
| |
| case SUSPEND_PHASE_DEDUPE: |
| vdo_drain_hash_zones(vdo->hash_zones, completion); |
| return; |
| |
| case SUSPEND_PHASE_FLUSHES: |
| vdo_drain_flusher(vdo->flusher, completion); |
| return; |
| |
| case SUSPEND_PHASE_LOGICAL_ZONES: |
| /* |
| * Attempt to flush all I/O before completing post suspend work. We believe a |
| * suspended device is expected to have persisted all data written before the |
| * suspend, even if it hasn't been flushed yet. |
| */ |
| result = vdo_synchronous_flush(vdo); |
| if (result != VDO_SUCCESS) |
| vdo_enter_read_only_mode(vdo, result); |
| |
| vdo_drain_logical_zones(vdo->logical_zones, |
| vdo_get_admin_state_code(state), completion); |
| return; |
| |
| case SUSPEND_PHASE_BLOCK_MAP: |
| vdo_drain_block_map(vdo->block_map, vdo_get_admin_state_code(state), |
| completion); |
| return; |
| |
| case SUSPEND_PHASE_JOURNAL: |
| vdo_drain_recovery_journal(vdo->recovery_journal, |
| vdo_get_admin_state_code(state), completion); |
| return; |
| |
| case SUSPEND_PHASE_DEPOT: |
| vdo_drain_slab_depot(vdo->depot, vdo_get_admin_state_code(state), |
| completion); |
| return; |
| |
| case SUSPEND_PHASE_READ_ONLY_WAIT: |
| vdo_wait_until_not_entering_read_only_mode(completion); |
| return; |
| |
| case SUSPEND_PHASE_WRITE_SUPER_BLOCK: |
| if (vdo_is_state_suspending(state) || (completion->result != VDO_SUCCESS)) { |
| /* If we didn't save the VDO or there was an error, we're done. */ |
| break; |
| } |
| |
| write_super_block_for_suspend(completion); |
| return; |
| |
| case SUSPEND_PHASE_END: |
| break; |
| |
| default: |
| vdo_set_completion_result(completion, UDS_BAD_STATE); |
| } |
| |
| finish_operation_callback(completion); |
| } |
| |
| static void vdo_postsuspend(struct dm_target *ti) |
| { |
| struct vdo *vdo = get_vdo_for_target(ti); |
| struct registered_thread instance_thread; |
| const char *device_name; |
| int result; |
| |
| vdo_register_thread_device_id(&instance_thread, &vdo->instance); |
| device_name = vdo_get_device_name(vdo->device_config->owning_target); |
| vdo_log_info("suspending device '%s'", device_name); |
| |
| /* |
| * It's important to note any error here does not actually stop device-mapper from |
| * suspending the device. All this work is done post suspend. |
| */ |
| result = perform_admin_operation(vdo, SUSPEND_PHASE_START, suspend_callback, |
| suspend_callback, "suspend"); |
| |
| if ((result == VDO_SUCCESS) || (result == VDO_READ_ONLY)) { |
| /* |
| * Treat VDO_READ_ONLY as a success since a read-only suspension still leaves the |
| * VDO suspended. |
| */ |
| vdo_log_info("device '%s' suspended", device_name); |
| } else if (result == VDO_INVALID_ADMIN_STATE) { |
| vdo_log_error("Suspend invoked while in unexpected state: %s", |
| vdo_get_admin_state(vdo)->name); |
| } else { |
| vdo_log_error_strerror(result, "Suspend of device '%s' failed", |
| device_name); |
| } |
| |
| vdo_unregister_thread_device_id(); |
| } |
| |
| /** |
| * was_new() - Check whether the vdo was new when it was loaded. |
| * @vdo: The vdo to query. |
| * |
| * Return: true if the vdo was new. |
| */ |
| static bool was_new(const struct vdo *vdo) |
| { |
| return (vdo->load_state == VDO_NEW); |
| } |
| |
| /** |
| * requires_repair() - Check whether a vdo requires recovery or rebuild. |
| * @vdo: The vdo to query. |
| * |
| * Return: true if the vdo must be repaired. |
| */ |
| static bool __must_check requires_repair(const struct vdo *vdo) |
| { |
| switch (vdo_get_state(vdo)) { |
| case VDO_DIRTY: |
| case VDO_FORCE_REBUILD: |
| case VDO_REPLAYING: |
| case VDO_REBUILD_FOR_UPGRADE: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| /** |
| * get_load_type() - Determine how the slab depot was loaded. |
| * @vdo: The vdo. |
| * |
| * Return: How the depot was loaded. |
| */ |
| static enum slab_depot_load_type get_load_type(struct vdo *vdo) |
| { |
| if (vdo_state_requires_read_only_rebuild(vdo->load_state)) |
| return VDO_SLAB_DEPOT_REBUILD_LOAD; |
| |
| if (vdo_state_requires_recovery(vdo->load_state)) |
| return VDO_SLAB_DEPOT_RECOVERY_LOAD; |
| |
| return VDO_SLAB_DEPOT_NORMAL_LOAD; |
| } |
| |
| /** |
| * load_callback() - Callback to do the destructive parts of loading a VDO. |
| * @completion: The sub-task completion. |
| */ |
| static void load_callback(struct vdo_completion *completion) |
| { |
| struct vdo *vdo = completion->vdo; |
| int result; |
| |
| assert_admin_phase_thread(vdo, __func__); |
| |
| switch (advance_phase(vdo)) { |
| case LOAD_PHASE_START: |
| result = vdo_start_operation(&vdo->admin.state, VDO_ADMIN_STATE_LOADING); |
| if (result != VDO_SUCCESS) { |
| vdo_continue_completion(completion, result); |
| return; |
| } |
| |
| /* Prepare the recovery journal for new entries. */ |
| vdo_open_recovery_journal(vdo->recovery_journal, vdo->depot, |
| vdo->block_map); |
| vdo_allow_read_only_mode_entry(completion); |
| return; |
| |
| case LOAD_PHASE_LOAD_DEPOT: |
| vdo_set_dedupe_state_normal(vdo->hash_zones); |
| if (vdo_is_read_only(vdo)) { |
| /* |
| * In read-only mode we don't use the allocator and it may not even be |
| * readable, so don't bother trying to load it. |
| */ |
| vdo_set_completion_result(completion, VDO_READ_ONLY); |
| break; |
| } |
| |
| if (requires_repair(vdo)) { |
| vdo_repair(completion); |
| return; |
| } |
| |
| vdo_load_slab_depot(vdo->depot, |
| (was_new(vdo) ? VDO_ADMIN_STATE_FORMATTING : |
| VDO_ADMIN_STATE_LOADING), |
| completion, NULL); |
| return; |
| |
| case LOAD_PHASE_MAKE_DIRTY: |
| vdo_set_state(vdo, VDO_DIRTY); |
| vdo_save_components(vdo, completion); |
| return; |
| |
| case LOAD_PHASE_PREPARE_TO_ALLOCATE: |
| vdo_initialize_block_map_from_journal(vdo->block_map, |
| vdo->recovery_journal); |
| vdo_prepare_slab_depot_to_allocate(vdo->depot, get_load_type(vdo), |
| completion); |
| return; |
| |
| case LOAD_PHASE_SCRUB_SLABS: |
| if (vdo_state_requires_recovery(vdo->load_state)) |
| vdo_enter_recovery_mode(vdo); |
| |
| vdo_scrub_all_unrecovered_slabs(vdo->depot, completion); |
| return; |
| |
| case LOAD_PHASE_DATA_REDUCTION: |
| WRITE_ONCE(vdo->compressing, vdo->device_config->compression); |
| if (vdo->device_config->deduplication) { |
| /* |
| * Don't try to load or rebuild the index first (and log scary error |
| * messages) if this is known to be a newly-formatted volume. |
| */ |
| vdo_start_dedupe_index(vdo->hash_zones, was_new(vdo)); |
| } |
| |
| vdo->allocations_allowed = false; |
| fallthrough; |
| |
| case LOAD_PHASE_FINISHED: |
| break; |
| |
| case LOAD_PHASE_DRAIN_JOURNAL: |
| vdo_drain_recovery_journal(vdo->recovery_journal, VDO_ADMIN_STATE_SAVING, |
| completion); |
| return; |
| |
| case LOAD_PHASE_WAIT_FOR_READ_ONLY: |
| /* Avoid an infinite loop */ |
| completion->error_handler = NULL; |
| vdo->admin.phase = LOAD_PHASE_FINISHED; |
| vdo_wait_until_not_entering_read_only_mode(completion); |
| return; |
| |
| default: |
| vdo_set_completion_result(completion, UDS_BAD_STATE); |
| } |
| |
| finish_operation_callback(completion); |
| } |
| |
| /** |
| * handle_load_error() - Handle an error during the load operation. |
| * @completion: The admin completion. |
| * |
| * If at all possible, brings the vdo online in read-only mode. This handler is registered in |
| * vdo_preresume_registered(). |
| */ |
| static void handle_load_error(struct vdo_completion *completion) |
| { |
| struct vdo *vdo = completion->vdo; |
| |
| if (vdo_requeue_completion_if_needed(completion, |
| vdo->thread_config.admin_thread)) |
| return; |
| |
| if (vdo_state_requires_read_only_rebuild(vdo->load_state) && |
| (vdo->admin.phase == LOAD_PHASE_MAKE_DIRTY)) { |
| vdo_log_error_strerror(completion->result, "aborting load"); |
| vdo->admin.phase = LOAD_PHASE_DRAIN_JOURNAL; |
| load_callback(vdo_forget(completion)); |
| return; |
| } |
| |
| if ((completion->result == VDO_UNSUPPORTED_VERSION) && |
| (vdo->admin.phase == LOAD_PHASE_MAKE_DIRTY)) { |
| vdo_log_error("Aborting load due to unsupported version"); |
| vdo->admin.phase = LOAD_PHASE_FINISHED; |
| load_callback(completion); |
| return; |
| } |
| |
| vdo_log_error_strerror(completion->result, |
| "Entering read-only mode due to load error"); |
| vdo->admin.phase = LOAD_PHASE_WAIT_FOR_READ_ONLY; |
| vdo_enter_read_only_mode(vdo, completion->result); |
| completion->result = VDO_READ_ONLY; |
| load_callback(completion); |
| } |
| |
| /** |
| * write_super_block_for_resume() - Update the VDO state and save the super block. |
| * @completion: The admin completion |
| */ |
| static void write_super_block_for_resume(struct vdo_completion *completion) |
| { |
| struct vdo *vdo = completion->vdo; |
| |
| switch (vdo_get_state(vdo)) { |
| case VDO_CLEAN: |
| case VDO_NEW: |
| vdo_set_state(vdo, VDO_DIRTY); |
| vdo_save_components(vdo, completion); |
| return; |
| |
| case VDO_DIRTY: |
| case VDO_READ_ONLY_MODE: |
| case VDO_FORCE_REBUILD: |
| case VDO_RECOVERING: |
| case VDO_REBUILD_FOR_UPGRADE: |
| /* No need to write the super block in these cases */ |
| vdo_launch_completion(completion); |
| return; |
| |
| case VDO_REPLAYING: |
| default: |
| vdo_continue_completion(completion, UDS_BAD_STATE); |
| } |
| } |
| |
| /** |
| * resume_callback() - Callback to resume a VDO. |
| * @completion: The admin completion. |
| */ |
| static void resume_callback(struct vdo_completion *completion) |
| { |
| struct vdo *vdo = completion->vdo; |
| int result; |
| |
| assert_admin_phase_thread(vdo, __func__); |
| |
| switch (advance_phase(vdo)) { |
| case RESUME_PHASE_START: |
| result = vdo_start_operation(&vdo->admin.state, |
| VDO_ADMIN_STATE_RESUMING); |
| if (result != VDO_SUCCESS) { |
| vdo_continue_completion(completion, result); |
| return; |
| } |
| |
| write_super_block_for_resume(completion); |
| return; |
| |
| case RESUME_PHASE_ALLOW_READ_ONLY_MODE: |
| vdo_allow_read_only_mode_entry(completion); |
| return; |
| |
| case RESUME_PHASE_DEDUPE: |
| vdo_resume_hash_zones(vdo->hash_zones, completion); |
| return; |
| |
| case RESUME_PHASE_DEPOT: |
| vdo_resume_slab_depot(vdo->depot, completion); |
| return; |
| |
| case RESUME_PHASE_JOURNAL: |
| vdo_resume_recovery_journal(vdo->recovery_journal, completion); |
| return; |
| |
| case RESUME_PHASE_BLOCK_MAP: |
| vdo_resume_block_map(vdo->block_map, completion); |
| return; |
| |
| case RESUME_PHASE_LOGICAL_ZONES: |
| vdo_resume_logical_zones(vdo->logical_zones, completion); |
| return; |
| |
| case RESUME_PHASE_PACKER: |
| { |
| bool was_enabled = vdo_get_compressing(vdo); |
| bool enable = vdo->device_config->compression; |
| |
| if (enable != was_enabled) |
| WRITE_ONCE(vdo->compressing, enable); |
| vdo_log_info("compression is %s", (enable ? "enabled" : "disabled")); |
| |
| vdo_resume_packer(vdo->packer, completion); |
| return; |
| } |
| |
| case RESUME_PHASE_FLUSHER: |
| vdo_resume_flusher(vdo->flusher, completion); |
| return; |
| |
| case RESUME_PHASE_DATA_VIOS: |
| resume_data_vio_pool(vdo->data_vio_pool, completion); |
| return; |
| |
| case RESUME_PHASE_END: |
| break; |
| |
| default: |
| vdo_set_completion_result(completion, UDS_BAD_STATE); |
| } |
| |
| finish_operation_callback(completion); |
| } |
| |
| /** |
| * grow_logical_callback() - Callback to initiate a grow logical. |
| * @completion: The admin completion. |
| * |
| * Registered in perform_grow_logical(). |
| */ |
| static void grow_logical_callback(struct vdo_completion *completion) |
| { |
| struct vdo *vdo = completion->vdo; |
| int result; |
| |
| assert_admin_phase_thread(vdo, __func__); |
| |
| switch (advance_phase(vdo)) { |
| case GROW_LOGICAL_PHASE_START: |
| if (vdo_is_read_only(vdo)) { |
| vdo_log_error_strerror(VDO_READ_ONLY, |
| "Can't grow logical size of a read-only VDO"); |
| vdo_set_completion_result(completion, VDO_READ_ONLY); |
| break; |
| } |
| |
| result = vdo_start_operation(&vdo->admin.state, |
| VDO_ADMIN_STATE_SUSPENDED_OPERATION); |
| if (result != VDO_SUCCESS) { |
| vdo_continue_completion(completion, result); |
| return; |
| } |
| |
| vdo->states.vdo.config.logical_blocks = vdo->block_map->next_entry_count; |
| vdo_save_components(vdo, completion); |
| return; |
| |
| case GROW_LOGICAL_PHASE_GROW_BLOCK_MAP: |
| vdo_grow_block_map(vdo->block_map, completion); |
| return; |
| |
| case GROW_LOGICAL_PHASE_END: |
| break; |
| |
| case GROW_LOGICAL_PHASE_ERROR: |
| vdo_enter_read_only_mode(vdo, completion->result); |
| break; |
| |
| default: |
| vdo_set_completion_result(completion, UDS_BAD_STATE); |
| } |
| |
| finish_operation_callback(completion); |
| } |
| |
| /** |
| * handle_logical_growth_error() - Handle an error during the grow physical process. |
| * @completion: The admin completion. |
| */ |
| static void handle_logical_growth_error(struct vdo_completion *completion) |
| { |
| struct vdo *vdo = completion->vdo; |
| |
| if (vdo->admin.phase == GROW_LOGICAL_PHASE_GROW_BLOCK_MAP) { |
| /* |
| * We've failed to write the new size in the super block, so set our in memory |
| * config back to the old size. |
| */ |
| vdo->states.vdo.config.logical_blocks = vdo->block_map->entry_count; |
| vdo_abandon_block_map_growth(vdo->block_map); |
| } |
| |
| vdo->admin.phase = GROW_LOGICAL_PHASE_ERROR; |
| grow_logical_callback(completion); |
| } |
| |
| /** |
| * perform_grow_logical() - Grow the logical size of the vdo. |
| * @vdo: The vdo to grow. |
| * @new_logical_blocks: The size to which the vdo should be grown. |
| * |
| * Context: This method may only be called when the vdo has been suspended and must not be called |
| * from a base thread. |
| * |
| * Return: VDO_SUCCESS or an error. |
| */ |
| static int perform_grow_logical(struct vdo *vdo, block_count_t new_logical_blocks) |
| { |
| int result; |
| |
| if (vdo->device_config->logical_blocks == new_logical_blocks) { |
| /* |
| * A table was loaded for which we prepared to grow, but a table without that |
| * growth was what we are resuming with. |
| */ |
| vdo_abandon_block_map_growth(vdo->block_map); |
| return VDO_SUCCESS; |
| } |
| |
| vdo_log_info("Resizing logical to %llu", |
| (unsigned long long) new_logical_blocks); |
| if (vdo->block_map->next_entry_count != new_logical_blocks) |
| return VDO_PARAMETER_MISMATCH; |
| |
| result = perform_admin_operation(vdo, GROW_LOGICAL_PHASE_START, |
| grow_logical_callback, |
| handle_logical_growth_error, "grow logical"); |
| if (result != VDO_SUCCESS) |
| return result; |
| |
| vdo_log_info("Logical blocks now %llu", (unsigned long long) new_logical_blocks); |
| return VDO_SUCCESS; |
| } |
| |
| static void copy_callback(int read_err, unsigned long write_err, void *context) |
| { |
| struct vdo_completion *completion = context; |
| int result = (((read_err == 0) && (write_err == 0)) ? VDO_SUCCESS : -EIO); |
| |
| vdo_continue_completion(completion, result); |
| } |
| |
| static void partition_to_region(struct partition *partition, struct vdo *vdo, |
| struct dm_io_region *region) |
| { |
| physical_block_number_t pbn = partition->offset - vdo->geometry.bio_offset; |
| |
| *region = (struct dm_io_region) { |
| .bdev = vdo_get_backing_device(vdo), |
| .sector = pbn * VDO_SECTORS_PER_BLOCK, |
| .count = partition->count * VDO_SECTORS_PER_BLOCK, |
| }; |
| } |
| |
| /** |
| * copy_partition() - Copy a partition from the location specified in the current layout to that in |
| * the next layout. |
| * @vdo: The vdo preparing to grow. |
| * @id: The ID of the partition to copy. |
| * @parent: The completion to notify when the copy is complete. |
| */ |
| static void copy_partition(struct vdo *vdo, enum partition_id id, |
| struct vdo_completion *parent) |
| { |
| struct dm_io_region read_region, write_regions[1]; |
| struct partition *from = vdo_get_known_partition(&vdo->layout, id); |
| struct partition *to = vdo_get_known_partition(&vdo->next_layout, id); |
| |
| partition_to_region(from, vdo, &read_region); |
| partition_to_region(to, vdo, &write_regions[0]); |
| dm_kcopyd_copy(vdo->partition_copier, &read_region, 1, write_regions, 0, |
| copy_callback, parent); |
| } |
| |
| /** |
| * grow_physical_callback() - Callback to initiate a grow physical. |
| * @completion: The admin completion. |
| * |
| * Registered in perform_grow_physical(). |
| */ |
| static void grow_physical_callback(struct vdo_completion *completion) |
| { |
| struct vdo *vdo = completion->vdo; |
| int result; |
| |
| assert_admin_phase_thread(vdo, __func__); |
| |
| switch (advance_phase(vdo)) { |
| case GROW_PHYSICAL_PHASE_START: |
| if (vdo_is_read_only(vdo)) { |
| vdo_log_error_strerror(VDO_READ_ONLY, |
| "Can't grow physical size of a read-only VDO"); |
| vdo_set_completion_result(completion, VDO_READ_ONLY); |
| break; |
| } |
| |
| result = vdo_start_operation(&vdo->admin.state, |
| VDO_ADMIN_STATE_SUSPENDED_OPERATION); |
| if (result != VDO_SUCCESS) { |
| vdo_continue_completion(completion, result); |
| return; |
| } |
| |
| /* Copy the journal into the new layout. */ |
| copy_partition(vdo, VDO_RECOVERY_JOURNAL_PARTITION, completion); |
| return; |
| |
| case GROW_PHYSICAL_PHASE_COPY_SUMMARY: |
| copy_partition(vdo, VDO_SLAB_SUMMARY_PARTITION, completion); |
| return; |
| |
| case GROW_PHYSICAL_PHASE_UPDATE_COMPONENTS: |
| vdo_uninitialize_layout(&vdo->layout); |
| vdo->layout = vdo->next_layout; |
| vdo_forget(vdo->next_layout.head); |
| vdo->states.vdo.config.physical_blocks = vdo->layout.size; |
| vdo_update_slab_depot_size(vdo->depot); |
| vdo_save_components(vdo, completion); |
| return; |
| |
| case GROW_PHYSICAL_PHASE_USE_NEW_SLABS: |
| vdo_use_new_slabs(vdo->depot, completion); |
| return; |
| |
| case GROW_PHYSICAL_PHASE_END: |
| vdo->depot->summary_origin = |
| vdo_get_known_partition(&vdo->layout, |
| VDO_SLAB_SUMMARY_PARTITION)->offset; |
| vdo->recovery_journal->origin = |
| vdo_get_known_partition(&vdo->layout, |
| VDO_RECOVERY_JOURNAL_PARTITION)->offset; |
| break; |
| |
| case GROW_PHYSICAL_PHASE_ERROR: |
| vdo_enter_read_only_mode(vdo, completion->result); |
| break; |
| |
| default: |
| vdo_set_completion_result(completion, UDS_BAD_STATE); |
| } |
| |
| vdo_uninitialize_layout(&vdo->next_layout); |
| finish_operation_callback(completion); |
| } |
| |
| /** |
| * handle_physical_growth_error() - Handle an error during the grow physical process. |
| * @completion: The sub-task completion. |
| */ |
| static void handle_physical_growth_error(struct vdo_completion *completion) |
| { |
| completion->vdo->admin.phase = GROW_PHYSICAL_PHASE_ERROR; |
| grow_physical_callback(completion); |
| } |
| |
| /** |
| * perform_grow_physical() - Grow the physical size of the vdo. |
| * @vdo: The vdo to resize. |
| * @new_physical_blocks: The new physical size in blocks. |
| * |
| * Context: This method may only be called when the vdo has been suspended and must not be called |
| * from a base thread. |
| * |
| * Return: VDO_SUCCESS or an error. |
| */ |
| static int perform_grow_physical(struct vdo *vdo, block_count_t new_physical_blocks) |
| { |
| int result; |
| block_count_t new_depot_size, prepared_depot_size; |
| block_count_t old_physical_blocks = vdo->states.vdo.config.physical_blocks; |
| |
| /* Skip any noop grows. */ |
| if (old_physical_blocks == new_physical_blocks) |
| return VDO_SUCCESS; |
| |
| if (new_physical_blocks != vdo->next_layout.size) { |
| /* |
| * Either the VDO isn't prepared to grow, or it was prepared to grow to a different |
| * size. Doing this check here relies on the fact that the call to this method is |
| * done under the dmsetup message lock. |
| */ |
| vdo_uninitialize_layout(&vdo->next_layout); |
| vdo_abandon_new_slabs(vdo->depot); |
| return VDO_PARAMETER_MISMATCH; |
| } |
| |
| /* Validate that we are prepared to grow appropriately. */ |
| new_depot_size = |
| vdo_get_known_partition(&vdo->next_layout, VDO_SLAB_DEPOT_PARTITION)->count; |
| prepared_depot_size = (vdo->depot->new_slabs == NULL) ? 0 : vdo->depot->new_size; |
| if (prepared_depot_size != new_depot_size) |
| return VDO_PARAMETER_MISMATCH; |
| |
| result = perform_admin_operation(vdo, GROW_PHYSICAL_PHASE_START, |
| grow_physical_callback, |
| handle_physical_growth_error, "grow physical"); |
| if (result != VDO_SUCCESS) |
| return result; |
| |
| vdo_log_info("Physical block count was %llu, now %llu", |
| (unsigned long long) old_physical_blocks, |
| (unsigned long long) new_physical_blocks); |
| return VDO_SUCCESS; |
| } |
| |
| /** |
| * apply_new_vdo_configuration() - Attempt to make any configuration changes from the table being |
| * resumed. |
| * @vdo: The vdo being resumed. |
| * @config: The new device configuration derived from the table with which the vdo is being |
| * resumed. |
| * |
| * Return: VDO_SUCCESS or an error. |
| */ |
| static int __must_check apply_new_vdo_configuration(struct vdo *vdo, |
| struct device_config *config) |
| { |
| int result; |
| |
| result = perform_grow_logical(vdo, config->logical_blocks); |
| if (result != VDO_SUCCESS) { |
| vdo_log_error("grow logical operation failed, result = %d", result); |
| return result; |
| } |
| |
| result = perform_grow_physical(vdo, config->physical_blocks); |
| if (result != VDO_SUCCESS) |
| vdo_log_error("resize operation failed, result = %d", result); |
| |
| return result; |
| } |
| |
| static int vdo_preresume_registered(struct dm_target *ti, struct vdo *vdo) |
| { |
| struct device_config *config = ti->private; |
| const char *device_name = vdo_get_device_name(ti); |
| block_count_t backing_blocks; |
| int result; |
| |
| backing_blocks = get_underlying_device_block_count(vdo); |
| if (backing_blocks < config->physical_blocks) { |
| /* FIXME: can this still happen? */ |
| vdo_log_error("resume of device '%s' failed: backing device has %llu blocks but VDO physical size is %llu blocks", |
| device_name, (unsigned long long) backing_blocks, |
| (unsigned long long) config->physical_blocks); |
| return -EINVAL; |
| } |
| |
| if (vdo_get_admin_state(vdo) == VDO_ADMIN_STATE_PRE_LOADED) { |
| vdo_log_info("starting device '%s'", device_name); |
| result = perform_admin_operation(vdo, LOAD_PHASE_START, load_callback, |
| handle_load_error, "load"); |
| if (result == VDO_UNSUPPORTED_VERSION) { |
| /* |
| * A component version is not supported. This can happen when the |
| * recovery journal metadata is in an old version format. Abort the |
| * load without saving the state. |
| */ |
| vdo->suspend_type = VDO_ADMIN_STATE_SUSPENDING; |
| perform_admin_operation(vdo, SUSPEND_PHASE_START, |
| suspend_callback, suspend_callback, |
| "suspend"); |
| return result; |
| } |
| |
| if ((result != VDO_SUCCESS) && (result != VDO_READ_ONLY)) { |
| /* |
| * Something has gone very wrong. Make sure everything has drained and |
| * leave the device in an unresumable state. |
| */ |
| vdo_log_error_strerror(result, |
| "Start failed, could not load VDO metadata"); |
| vdo->suspend_type = VDO_ADMIN_STATE_STOPPING; |
| perform_admin_operation(vdo, SUSPEND_PHASE_START, |
| suspend_callback, suspend_callback, |
| "suspend"); |
| return result; |
| } |
| |
| /* Even if the VDO is read-only, it is now able to handle read requests. */ |
| vdo_log_info("device '%s' started", device_name); |
| } |
| |
| vdo_log_info("resuming device '%s'", device_name); |
| |
| /* If this fails, the VDO was not in a state to be resumed. This should never happen. */ |
| result = apply_new_vdo_configuration(vdo, config); |
| BUG_ON(result == VDO_INVALID_ADMIN_STATE); |
| |
| /* |
| * Now that we've tried to modify the vdo, the new config *is* the config, whether the |
| * modifications worked or not. |
| */ |
| vdo->device_config = config; |
| |
| /* |
| * Any error here is highly unexpected and the state of the vdo is questionable, so we mark |
| * it read-only in memory. Because we are suspended, the read-only state will not be |
| * written to disk. |
| */ |
| if (result != VDO_SUCCESS) { |
| vdo_log_error_strerror(result, |
| "Commit of modifications to device '%s' failed", |
| device_name); |
| vdo_enter_read_only_mode(vdo, result); |
| return result; |
| } |
| |
| if (vdo_get_admin_state(vdo)->normal) { |
| /* The VDO was just started, so we don't need to resume it. */ |
| return VDO_SUCCESS; |
| } |
| |
| result = perform_admin_operation(vdo, RESUME_PHASE_START, resume_callback, |
| resume_callback, "resume"); |
| BUG_ON(result == VDO_INVALID_ADMIN_STATE); |
| if (result == VDO_READ_ONLY) { |
| /* Even if the vdo is read-only, it has still resumed. */ |
| result = VDO_SUCCESS; |
| } |
| |
| if (result != VDO_SUCCESS) |
| vdo_log_error("resume of device '%s' failed with error: %d", device_name, |
| result); |
| |
| return result; |
| } |
| |
| static int vdo_preresume(struct dm_target *ti) |
| { |
| struct registered_thread instance_thread; |
| struct vdo *vdo = get_vdo_for_target(ti); |
| int result; |
| |
| vdo_register_thread_device_id(&instance_thread, &vdo->instance); |
| result = vdo_preresume_registered(ti, vdo); |
| if ((result == VDO_PARAMETER_MISMATCH) || (result == VDO_INVALID_ADMIN_STATE) || |
| (result == VDO_UNSUPPORTED_VERSION)) |
| result = -EINVAL; |
| vdo_unregister_thread_device_id(); |
| return vdo_status_to_errno(result); |
| } |
| |
| static void vdo_resume(struct dm_target *ti) |
| { |
| struct registered_thread instance_thread; |
| |
| vdo_register_thread_device_id(&instance_thread, |
| &get_vdo_for_target(ti)->instance); |
| vdo_log_info("device '%s' resumed", vdo_get_device_name(ti)); |
| vdo_unregister_thread_device_id(); |
| } |
| |
| /* |
| * If anything changes that affects how user tools will interact with vdo, update the version |
| * number and make sure documentation about the change is complete so tools can properly update |
| * their management code. |
| */ |
| static struct target_type vdo_target_bio = { |
| .features = DM_TARGET_SINGLETON, |
| .name = "vdo", |
| .version = { 9, 1, 0 }, |
| .module = THIS_MODULE, |
| .ctr = vdo_ctr, |
| .dtr = vdo_dtr, |
| .io_hints = vdo_io_hints, |
| .iterate_devices = vdo_iterate_devices, |
| .map = vdo_map_bio, |
| .message = vdo_message, |
| .status = vdo_status, |
| .presuspend = vdo_presuspend, |
| .postsuspend = vdo_postsuspend, |
| .preresume = vdo_preresume, |
| .resume = vdo_resume, |
| }; |
| |
| static bool dm_registered; |
| |
| static void vdo_module_destroy(void) |
| { |
| vdo_log_debug("unloading"); |
| |
| if (dm_registered) |
| dm_unregister_target(&vdo_target_bio); |
| |
| VDO_ASSERT_LOG_ONLY(instances.count == 0, |
| "should have no instance numbers still in use, but have %u", |
| instances.count); |
| vdo_free(instances.words); |
| memset(&instances, 0, sizeof(struct instance_tracker)); |
| } |
| |
| static int __init vdo_init(void) |
| { |
| int result = 0; |
| |
| /* Memory tracking must be initialized first for accurate accounting. */ |
| vdo_memory_init(); |
| vdo_initialize_threads_mutex(); |
| vdo_initialize_thread_device_registry(); |
| vdo_initialize_device_registry_once(); |
| |
| /* Add VDO errors to the set of errors registered by the indexer. */ |
| result = vdo_register_status_codes(); |
| if (result != VDO_SUCCESS) { |
| vdo_log_error("vdo_register_status_codes failed %d", result); |
| vdo_module_destroy(); |
| return result; |
| } |
| |
| result = dm_register_target(&vdo_target_bio); |
| if (result < 0) { |
| vdo_log_error("dm_register_target failed %d", result); |
| vdo_module_destroy(); |
| return result; |
| } |
| dm_registered = true; |
| |
| return result; |
| } |
| |
| static void __exit vdo_exit(void) |
| { |
| vdo_module_destroy(); |
| /* Memory tracking cleanup must be done last. */ |
| vdo_memory_exit(); |
| } |
| |
| module_init(vdo_init); |
| module_exit(vdo_exit); |
| |
| module_param_named(log_level, vdo_log_level, uint, 0644); |
| MODULE_PARM_DESC(log_level, "Log level for log messages"); |
| |
| MODULE_DESCRIPTION(DM_NAME " target for transparent deduplication"); |
| MODULE_AUTHOR("Red Hat, Inc."); |
| MODULE_LICENSE("GPL"); |