Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/md/dm-vdo/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 86 kB image not shown  

Quelle  dm-vdo-target.c   Sprache: C

 
// 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 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");

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=95 H=96 G=95

¤ Dauer der Verarbeitung: 0.23 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.