/* Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
   Copyright (c) 2011, 2018, 2026 MariaDB Corporation
   Copyright (c) 2024, Väinö Mäkelä

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1335  USA */

#define VER "1.0"
#include <my_global.h>
#include "mariadbd_options.h"
#include <my_global.h>
#include <my_sys.h>
#include <my_dir.h>
#include <mysql/psi/mysql_file.h>
#include <m_string.h>
#include <my_getopt.h>
#include <my_default.h>
#include <mysql_version.h>
#include <stdarg.h>
#include <welcome_copyright_notice.h>

#if!defined (X_OK) && defined(_WIN32)
#define X_OK 0              /* Test existence of file with access() */
#endif


#define load_default_groups mariadbd_groups
#include <mysqld_default_groups.h>
#undef load_default_groups

const char *mysqld_groups[]=
{
  "mysqld", "server", 0, 0
};

const char *client_server_group= "client-server";

/* The following is from my_getopt.c */
static const char *special_opt_prefix[]=
{"skip", "disable", "enable", "maximum", "loose", "autoset", 0};
static const uint special_opt_prefix_lengths[]=
{ 4,      7,         6,        7,         5,       7,        0};

#ifndef WEXITSTATUS
# ifdef _WIN32
#  define WEXITSTATUS(stat_val) (stat_val)
# else
#  define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
# endif
#endif

struct upgrade_ctx
{
  MEM_ROOT *alloc;
  TYPELIB *group;                /* Default groups and extra groups */
  DYNAMIC_ARRAY updated_files;
  my_bool failed;
};


enum edit_mode
{
  EDIT_MODE_NONE,
  EDIT_MODE_REMOVE,
  EDIT_MODE_COMMENT,
  EDIT_MODE_INLINE,
  EDIT_MODE_LAST,
};

static const char *edit_mode_values[]=
{
  "", "remove", "comment", "inline", "last", NullS
};
static TYPELIB edit_mode_typelib=
{array_elements(edit_mode_values) - 1, "", edit_mode_values, NULL, NULL};

enum from_mode
{
  FROM_MYSQL,
  FROM_MARIADB,
};

static const char *from_mode_values[]=
{
  "MySQL", "MariaDB", NullS
};
static TYPELIB from_mode_typelib=
{array_elements(from_mode_values) - 1, "", from_mode_values, NULL, NULL};

struct RENAMED_OPTIONS
{
  const char *from, *to;
};

struct RENAMED_OPTIONS renamed_options[]=
{
  {"innodb_stats_sample_pages", "innodb_stats_transient_sample_pages" },
  {NullS, NullS},
};


LEX_CSTRING mysql_name=   {STRING_WITH_LEN("mysql")};
LEX_CSTRING mysqld_name=  {STRING_WITH_LEN("mysqld")};
LEX_CSTRING mariadb_name= {STRING_WITH_LEN("mariadb")};

static const char *opt_error_group="mysqld-5.7";
static const char *opt_unsupported_group="options-not-recognized-by-mariadbd";
static my_bool opt_update;
static my_bool opt_backup;
static my_bool opt_print;
static my_bool opt_no_myisam_options;
static my_bool opt_add_skip_slave_start;
static my_bool opt_add_mariadb_replication_compatibility;
static my_bool opt_add_mysql_replication_compatibility;
static my_bool opt_add_client_server_group;
static my_bool opt_silent;
static my_bool opt_update_paths;
static my_bool opt_copy_mysqld_groups;
static my_bool add_new_options_once=0, new_options_added= 0;
static uint audit_plugin_warning= 0;
static const char **groups_to_use;
static const char *opt_mariadbd= "";
static ulong opt_edit_mode, opt_convert_from;
static size_t global_copied_lines=0, global_unsupported_lines= 0;
static size_t global_mariadbd_additions= 0;

static PSI_memory_key key_memory_upgrade_config;
#ifdef HAVE_PSI_FILE_INTERFACE
static PSI_file_key key_file_cnf;
#endif
static size_t global_update_count= 0;
static my_bool give_error_for_missing_files= 0;
static my_bool opt_mariadbd_testing;
static const char *mariadb_replication_compatibility_comment=
  "# The following options enable MySQL to replicate to MariaDB";
static const char *mysql_replication_compatibility_comment=
  "# The following options enable MariaDB to replicate to MySQL";

/* Options found that require special handling */
static longlong value_port;
static char *value_socket= 0;
static my_bool found_skip_slave_start= FALSE;
static my_bool found_innodb_log_files_in_group= 0;
static my_bool found_innodb_log_file_size= 0;
static my_bool found_character_set_server= 0;
static my_bool found_client_server_group= 0;
static my_bool found_mariadb_replication_compatibility= 0;
static my_bool found_mysql_replication_compatibility= 0;

static uint value_innodb_log_files_in_group= 0;
static ulonglong value_innodb_log_file_size= 0;
static char suffix_innodb_log_file_size;


#ifdef _WIN32
static const char *f_extensions[]= { ".ini", ".cnf", 0 };
#else
static const char *f_extensions[]= { ".cnf", 0 };
#endif

static uint count_array_elements(DYNAMIC_ARRAY *array);
static my_bool delete_line(DYNAMIC_ARRAY *array, char *line);

/**
  Remove the -upgrade-config-orig backups of the updated files if
  successful and replace the updated files with the backups if not.
*/

static int finish_updated_files(struct upgrade_ctx *ctx, my_bool success)
{
  size_t i;
  if (!opt_silent && success && ctx->updated_files.elements)
    fputs("Updated files:\n", stdout);
  for (i= 0; i < ctx->updated_files.elements; i++)
  {
    char *name = *(char **)dynamic_array_ptr(&ctx->updated_files, i);
    const char *suffix = "-upgrade-config-orig";
    size_t orig_len = strlen(name) + strlen(suffix) + 1;
    char *orig = my_malloc(key_memory_upgrade_config, orig_len, MYF(MY_WME));
    if (!orig)
      return 1;
    strmov(strmov(orig, name), suffix);
    if (success)
    {
      fprintf(stdout, "%s\n", name);
      my_delete(orig, MYF(0));
    }
    else
    {
      if (my_redel(name, orig, 0, MYF(MY_WME | MY_REDEL_MAKE_BACKUP)))
      {
        my_free(orig);
        return 1;
      }
    }
    my_free(orig);
    my_free(name);
  }
  delete_dynamic(&ctx->updated_files);
  return 0;
}


/**
   Run a command using the shell, storing its output in the supplied dynamic
   string.
*/
static int run_command(char* cmd,
                       DYNAMIC_STRING *ds_res)
{
  char buf[512]= {0};
  FILE *res_file;
  int error;

  if (!(res_file= my_popen(cmd, "r")))
  {
    fprintf(stderr, "popen(\"%s\", \"r\") failed\n", cmd);
    return -1;
  }

  while (fgets(buf, sizeof(buf), res_file))
  {
#ifdef _WIN32
    /* Strip '\r' off newlines. */
    size_t len = strlen(buf);
    if (len > 1 && buf[len - 2] == '\r' && buf[len - 1] == '\n')
    {
      buf[len - 2] = '\n';
      buf[len - 1] = 0;
    }
#endif
    dynstr_append(ds_res, buf);
  }

  error= my_pclose(res_file);
  return WEXITSTATUS(error);
}


/**
   Run `mariadbd --help --verbose` with the supplied arguments and write its
   stderr output to ds_res.
*/
static int run_mariadbd(const char *mariadbd_path,
                        DYNAMIC_STRING *ds_res,
                        const char **defaults_args,
                        int defaults_args_count)
{
  int ret;
  int i;
  DYNAMIC_STRING ds_cmdline;

  if (init_dynamic_string(&ds_cmdline, IF_WIN("\"", ""), FN_REFLEN, FN_REFLEN))
  {
    fputs("Out of memory\n", stderr);
    return -1;
  }

  dynstr_append_os_quoted(&ds_cmdline, mariadbd_path, NullS);
  dynstr_append(&ds_cmdline, " ");

  for (i= 0; i < defaults_args_count; i++)
  {
    dynstr_append_os_quoted(&ds_cmdline, defaults_args[i], NullS);
    dynstr_append(&ds_cmdline, " ");
  }

  dynstr_append(&ds_cmdline, "--help ");
  dynstr_append(&ds_cmdline, "--verbose ");
  dynstr_append(&ds_cmdline, "--silent-startup ");
  dynstr_append(&ds_cmdline, "--log-warnings=0 ");
  dynstr_append(&ds_cmdline, "2>&1 ");
  dynstr_append(&ds_cmdline, IF_WIN("1>NUL", "1>/dev/null"));

#ifdef _WIN32
  dynstr_append(&ds_cmdline, "\"");
#endif

  ret= run_command(ds_cmdline.str, ds_res);
  dynstr_free(&ds_cmdline);
  return ret;
}


/**
   Test whether mariadbd can be launched with --no-defaults.
*/
static int test_mariadbd(const char *mariadbd_name)
{
  DYNAMIC_STRING ds_tmp;
  const char *defaults_argument = "--no-defaults";
  int err= 0;

  if (init_dynamic_string(&ds_tmp, "", 32, 32))
  {
    fputs("Out of memory\n", stderr);
    return -1;
  }

  if (run_mariadbd(mariadbd_name,
                   &ds_tmp,
                   &defaults_argument,
                   1))
  {
    fprintf(stderr, "Can't execute %s\n", mariadbd_name);
    err= 1;
  }

  dynstr_free(&ds_tmp);
  return err;
}


static int compare_options(const void *a, const void *b)
{
  const char *first= *(const char **)a;
  const char *second= *(const char **)b;
  return strcmp(first, second);
}


static my_bool mariadbd_option_exists(const char *option)
{
  return bsearch(&option, mariadbd_valid_options,
                 array_elements(mariadbd_valid_options),
                 sizeof(mariadbd_valid_options[0]),
                 compare_options) != NULL;
}


static my_bool mariadbd_valid_enum_value(const char *option, const char *value)
{
  const char **option_ptr= bsearch(&option, mariadbd_enum_options,
                                   array_elements(mariadbd_enum_options),
                                   sizeof(mariadbd_enum_options[0]),
                                   compare_options);
  longlong val;
  int error;
  if (!option_ptr)
    return TRUE;
  if (find_type(value,
                mariadbd_enum_typelibs[option_ptr - mariadbd_enum_options],
                FIND_TYPE_BASIC) != 0)
    return TRUE;
  val= my_strtoll10(value, (char**) 0, &error);
  return (error == 0 && val < mariadbd_enum_typelibs[option_ptr -
                                                     mariadbd_enum_options]->count);
}


/**
   Check whether the given value is a valid set value for the given
   option.
   Returns 0 on success and the first invalid set index on
   failure. If the option is not a set option, returns 0.
*/

static int mariadbd_check_set_value(const char *option, const char *value)
{
  const char **option_ptr= bsearch(&option, mariadbd_set_options,
                                   array_elements(mariadbd_set_options),
                                   sizeof(mariadbd_set_options[0]),
                                   compare_options);
  TYPELIB *typelib;
  int error_pos= 0;
  if (!option_ptr)
    return 0;
  typelib = mariadbd_set_typelibs[option_ptr - mariadbd_set_options];
  find_typeset(value, typelib, &error_pos);
  if (error_pos)
  {
    ulonglong num;
    char *endptr;

    if (!my_strcasecmp_latin1(value, "all"))
      return 0;

    num= strtol(value, &endptr, 10);
    if (!*endptr)
    {
      if ((num >> 1) >= (1ULL << (typelib->count - 1)))
        return 1;
      return 0;
    }
  }
  return error_pos;
}


enum plugin_check_result
{
  PLUGINS_OK,
  AUDIT_PLUGIN,
};

static enum plugin_check_result check_plugins(const char *option,
                                              const char *value)
{
  if (!strcmp(option, "plugin_load") || !strcmp(option, "plugin_load_add"))
  {
    if (!strcmp(value, "audit_log"))
      return AUDIT_PLUGIN;
    return PLUGINS_OK;
  }
  return PLUGINS_OK;
}


static my_bool check_plugin_option(struct upgrade_ctx *ctx,
                                   const char *option,
                                   const char *value,
                                   char *name,
                                   uint line)
{

  enum plugin_check_result plugin_check_result;

  if ((plugin_check_result= check_plugins(option, value)) != PLUGINS_OK)
  {
    if (!opt_print)
    {
      switch (plugin_check_result) {
      case PLUGINS_OK: break;               /* Impossible */
      case AUDIT_PLUGIN:
        if (!audit_plugin_warning++ && !opt_silent)
          fprintf(stdout, "In %s at line %d: Please replace audit_log "
                  "with the server_audit plugin\n", name, line);
      }
      return 1;
    }
  }
  return 0;
}

/*
  Skip over keyword and get argument after keyword

  SYNOPSIS
  get_argument()
  keyword		Include directive keyword
  kwlen		Length of keyword
  ptr			Pointer to the keword in the line under process
  line			line number

  RETURN
  0	error
  #	Returns pointer to the argument after the keyword.
*/

static char *get_argument(const char *keyword, size_t kwlen,
                          char *ptr, char *name, uint line)
{
  char *end;

  /* Skip over "include / includedir keyword" and following whitespace */

  for (ptr+= kwlen - 1;
       my_isspace(&my_charset_latin1, ptr[0]);
       ptr++)
  {}

  /*
    Trim trailing whitespace from directory name
    The -1 below is for the newline added by fgets()
    Note that my_isspace() is true for \r and \n
  */
  for (end= ptr + strlen(ptr) - 1;
       my_isspace(&my_charset_latin1, *(end - 1));
       end--)
  {}
  end[0]= 0;                                    /* Cut off end space */

  /* Print error msg if there is nothing after !include* directive */
  if (end <= ptr)
  {
    fprintf(stderr,
	    "error: Wrong '!%s' directive in config file: %s at line %d\n",
	    keyword, name, line);
    return 0;
  }
  return ptr;
}


static char *find_end_comment(char *ptr)
{
  char quote= 0;	/* we are inside quote marks */
  char escape= 0;	/* symbol is protected by escape chagacter */

  for (; *ptr; ptr++)
  {
    if ((*ptr == '\'' || *ptr == '\"') && !escape)
    {
      if (!quote)
	quote= *ptr;
      else if (quote == *ptr)
	quote= 0;
    }
    /* We are not inside a string */
    if (!quote && *ptr == '#')
      return ptr;
    escape= (quote && *ptr == '\\' && !escape);
  }
  return ptr;
}

struct generator
{
  /* the memory root for allocating line strings */
  MEM_ROOT alloc;
  /* array of strings for the main content */
  DYNAMIC_ARRAY main;
  /*
    array of strings for options that should be added to the error_group
    section
  */
  DYNAMIC_ARRAY old_version;
  /*
    array of strings for options that should be added to the unsupported_group
    section
  */
  DYNAMIC_ARRAY unsupported_version;
  /* array of strings to be added to the mariadbd section */
  DYNAMIC_ARRAY mariadbd_additions;
  /* array of strings to be added to the client_server section */
  DYNAMIC_ARRAY client_server;

  struct upgrade_ctx *ctx;          /* Current context */

  const char *name;                 /* config file name */
  int start_of_group;               /* Start of current group */
  int mariadbd_group_start;         /* Start of last mariadbd group */
  uint line;                        /* Current line number */
  /*
    one past the last index of the original mariadbd section in the main array
  */
  int mariadbd_group_end;
  /* the current group from which options are read */
  const char *input_group;
  /*
    The current group in the generated output. This may be different from
    input_group if lines are moved between groups.
  */
  char *output_group;
  /* whether last main line is empty or not */
  my_bool prev_empty;
  /* If we should copy [mysqld-xxx] options to [mariadbd] */
  my_bool copy_code_to_mariadb;
  /* If current file is valid */
  my_bool file_valid;
};

static void generator_init(struct generator *generator)
{
  init_alloc_root(key_memory_upgrade_config, &generator->alloc, 512, 0, MYF(0));
  init_dynamic_array2(key_memory_upgrade_config,
                      &generator->main,
                      sizeof(char *),
                      NULL,
                      0,
                      0,
                      MYF(0));
  init_dynamic_array2(key_memory_upgrade_config,
                      &generator->old_version,
                      sizeof(char *),
                      NULL,
                      0,
                      0,
                      MYF(0));
  init_dynamic_array2(key_memory_upgrade_config,
                      &generator->unsupported_version,
                      sizeof(char *),
                      NULL,
                      0,
                      0,
                      MYF(0));
  init_dynamic_array2(key_memory_upgrade_config,
                      &generator->mariadbd_additions,
                      sizeof(char *),
                      NULL,
                      0,
                      0,
                      MYF(0));
  init_dynamic_array2(key_memory_upgrade_config,
                      &generator->client_server,
                      sizeof(char *),
                      NULL,
                      0,
                      0,
                      MYF(0));
  generator->mariadbd_group_end= -1;
  generator->input_group= NULL;
  generator->output_group= NULL;
  /* don't add extra newlines to the beginning */
  generator->prev_empty= TRUE;
  generator->copy_code_to_mariadb= 0;
}


/*
  Delete data in mariadbd_additions that was found in last [mariadbd] group

  This is needed to ensure that we don't generate new lines into the
  [mariadbd] group if we run the program twice on it
*/

static void delete_dup_data_from_mariadbd_group(struct generator *generator)
{
  if (generator->mariadbd_group_end >= 0)
  {
    for (int i= generator->mariadbd_group_start;
         i < generator->mariadbd_group_end;
         i++)
    {
      char *element= *(char **) dynamic_array_ptr(&generator->main, i);
      delete_line(&generator->mariadbd_additions, element);
    }
  }
}


static my_bool change_output_group(struct generator *generator,
                                   const char *group)
{
  char *group_dupe= my_strdup(key_memory_upgrade_config, group, MYF(0));
  if (!group_dupe)
    return TRUE;
  my_free(generator->output_group);
  generator->output_group= group_dupe;
  return FALSE;
}

/*
  Generate output lines for one section
  Returns number of lines added
*/


static int generate_write_section(FILE *target_file,
                                   struct generator *generator,
                                   DYNAMIC_ARRAY *array,
                                   const char *group_name)
{
  int lines= 0;
  if (array->elements > 0)
  {
    if (!generator->prev_empty)
      fputc('\n', target_file);
    fprintf(target_file, "[%s]\n", group_name);
    change_output_group(generator, group_name);
    for (uint i= 0; i < array->elements; i++)
    {
      char *element= *(char **)dynamic_array_ptr(array, i);
      if (element)
      {
        fputs(element, target_file);
        lines++;
      }
    }
    generator->prev_empty= 0;
  }
  return lines;
}


/*
  Generate output lines
  Returns true if file changed
*/

static my_bool generator_write(MYSQL_FILE *f, struct generator *generator)
{
  FILE *target_file= f ? f->m_file : stdout;
  size_t i;
  size_t lines_handled= 0;
  size_t new_lines;

  delete_dup_data_from_mariadbd_group(generator);

  if (generator->mariadbd_group_end >= 0)
  {
    /*
      There was a mariadbd group in the file. Put all generated mariadbd
      options into this group.
    */
    for (i= 0; i < (size_t)generator->mariadbd_group_end; i++)
    {
      char *element= *(char **) dynamic_array_ptr(&generator->main, i);
      fputs(element, target_file);
    }
    for (i= 0; i < generator->mariadbd_additions.elements; i++)
    {
      char *element= (*(char **)
                      dynamic_array_ptr(&generator->mariadbd_additions, i));
      if (element)                       /* Ignore deleted elements */
      {
        fputs(element, target_file);
        generator->prev_empty= element[0] == '\n';
        lines_handled++;
      }
    }
    for (i= (size_t)generator->mariadbd_group_end;
         i < generator->main.elements;
         i++)
    {
      char *element= *(char **) dynamic_array_ptr(&generator->main, i);
      fputs(element, target_file);
      generator->prev_empty= element[0] == '\n';
    }
  }
  else
  {
    for (i= 0; i < generator->main.elements; i++)
    {
      char *element= *(char **) dynamic_array_ptr(&generator->main, i);
      fputs(element, target_file);
      generator->prev_empty= element[0] == '\n';
    }

    if (count_array_elements(&generator->mariadbd_additions) > 0)
      lines_handled+= generate_write_section(target_file,
                                             generator,
                                             &generator->mariadbd_additions,
                                             "mariadbd");
  }
  generate_write_section(target_file, generator,
                         &generator->old_version, opt_error_group);
  generate_write_section(target_file, generator,
                         &generator->unsupported_version,
                         opt_unsupported_group);
  generate_write_section(target_file, generator,
                         &generator->client_server,
                         client_server_group);

  new_lines= (lines_handled + generator->old_version.elements +
              generator->unsupported_version.elements +
              generator->client_server.elements);
  if (new_lines)
  {
    global_update_count+= new_lines;
    generator->copy_code_to_mariadb= 0;
  }
  return new_lines != 0;
}


static void generator_count_lines(struct generator *generator)
{
  int mariadbd_additions;

  delete_dup_data_from_mariadbd_group(generator);
  mariadbd_additions= count_array_elements(&generator->mariadbd_additions);
  global_mariadbd_additions+= mariadbd_additions;
  global_update_count+= (mariadbd_additions +
                         generator->old_version.elements +
                         generator->unsupported_version.elements +
                         generator->client_server.elements);
  global_copied_lines+= generator->old_version.elements;
  global_unsupported_lines+= generator->unsupported_version.elements;
}


static void report_line_changes()
{
  if (!opt_print && !opt_silent && opt_edit_mode == EDIT_MODE_NONE)
  {
    if (global_mariadbd_additions)
      fprintf(stdout, "%lld line/lines would be copied to [mariadbd] group\n",
              (longlong) global_mariadbd_additions);
    if (global_copied_lines > 0)
      fprintf(stdout, "%lld line/lines would be copied to [%s] group\n",
              (longlong) global_copied_lines, opt_error_group);
    if (global_unsupported_lines)
      fprintf(stdout, "%lld line/lines would be copied to [%s] group\n",
              (longlong) global_unsupported_lines, opt_unsupported_group);
  }
}


static void generator_free(struct generator *generator)
{
  free_root(&generator->alloc, MYF(0));
  delete_dynamic(&generator->main);
  delete_dynamic(&generator->old_version);
  delete_dynamic(&generator->unsupported_version);
  delete_dynamic(&generator->mariadbd_additions);
  delete_dynamic(&generator->client_server);
  my_free(generator->output_group);
}


/* Called on include of new default file */

static void generator_reset(struct generator *generator)
{
  generator->main.elements= 0;
  generator->old_version.elements= 0;
  generator->unsupported_version.elements= 0;
  generator->mariadbd_additions.elements= 0;
  generator->mariadbd_group_end= -1;
}


static char *print_alloc(MEM_ROOT *alloc, const char *format, va_list args)
{
  int length;
  char *p= NULL;
  va_list args2;
  va_copy(args2, args);

  length= vsnprintf(p, 0, format, args) + 1;
  p= alloc_root(alloc, length);
  if (!p)
    return NULL;
  vsnprintf(p, length, format, args2);
  va_end(args2);
  return p;
}

static my_bool vadd_line(MEM_ROOT *alloc, DYNAMIC_ARRAY *array,
                         const char *format, va_list args)
{
  char *p;
  p= print_alloc(alloc, format, args);
  if (!p)
    return TRUE;
  return insert_dynamic(array, &p);
}

static my_bool add_line(MEM_ROOT *alloc, DYNAMIC_ARRAY *array,
                        const char *format, ...)
{
  va_list args;
  my_bool res;
  va_start(args, format);
  res= vadd_line(alloc, array, format, args);
  va_end(args);
  return res;
}

/*
  Delete elements from array if it already exits.

  Delete is done by setting pointer to string element to 0;
*/

static my_bool delete_line(DYNAMIC_ARRAY *array, char *line)
{
  for (uint i= 0; i < array->elements ; i++)
  {
    char *element= *dynamic_element(array, i, char**);
    if (element && !strcmp(element, line))
    {
      *((char**) dynamic_array_ptr(array, i))= NullS;
      return 1;
    }
  }
  return 0;
}


/* Count not null elements in the array */

static uint count_array_elements(DYNAMIC_ARRAY *array)
{
  uint count= 0;
  for (uint i= 0; i < array->elements ; i++)
    if (*dynamic_element(array, i, char**))
      count++;
  return count;
}

enum line_type
{
  LINE_TYPE_EMPTY,
  LINE_TYPE_OPTION,
  LINE_TYPE_OTHER,
  LINE_TYPE_PASSTHROUGH
};


static my_bool add_main_line(struct generator *generator, enum line_type type,
                             const char *format, ...)
{
  va_list args;
  my_bool res;
  /* Ensure that --print without edit doesn't loose space */
  my_bool delay_empty= (type == LINE_TYPE_EMPTY &&
                        opt_edit_mode != EDIT_MODE_NONE);

  /* generator->input_group is empty for comments at top of include files */
  if (!delay_empty && generator->input_group &&
      (!generator->output_group ||
       strcmp(generator->input_group, generator->output_group)))
  {
    if (add_line(&generator->alloc, &generator->main,
                 generator->prev_empty ? "[%s]\n" : "\n[%s]\n",
                 generator->input_group))
      return TRUE;
    if (change_output_group(generator, generator->input_group))
      return TRUE;
    generator->start_of_group= (int) generator->main.elements;
  }
  va_start(args, format);
  res= vadd_line(&generator->alloc, &generator->main, format, args);
  va_end(args);
  if (!res)
  {
    if (type == LINE_TYPE_OPTION &&
        !strcmp(generator->input_group, "mariadbd"))
    {
      generator->mariadbd_group_start= generator->start_of_group;
      generator->mariadbd_group_end=   (int) generator->main.elements;
    }

    generator->prev_empty= (type == LINE_TYPE_EMPTY);
  }
  return res;
}


/*
  Change file paths from /mysql ending with /, \, . space or \n to /mariadbd

  If string contains 'from' return allocated string otherwise return
  NullS
*/

char *change_file_path(const char *line, LEX_CSTRING *from, LEX_CSTRING *to)
{
  const char *pos;

  for (pos= line ; (pos= strstr(pos, from->str)); pos++)
  {
    char end= pos[from->length];
    if (pos != line && (pos[-1] == '/' || pos[-1] == '\\') &&
        (end == pos[-1] || end == 0 || my_isspace(&my_charset_latin1, end) ||
         end == '.'))
    {
      /* Found path, change 'from' to 'to' */
      char *path= my_malloc(key_memory_upgrade_config,
                            strlen(line) - from->length + to->length + 1,
                            MYF(MY_WME));
      if (!path)
        break;
      memcpy(path, line, (size_t) (pos - line));
      strxmov(path + (pos - line), to->str, pos + from->length, NullS);
      return path;
    }
  }
  return NullS;
}



/* Change path with warning */

static char *path_changed(struct generator *generator, const char *line)
{
  char *new_line;
  if (!(new_line= change_file_path(line, &mysql_name, &mariadb_name)) &&
      !(new_line= change_file_path(line, &mysqld_name, &mariadb_name)))
    return 0;

  if (!opt_print && opt_edit_mode == EDIT_MODE_NONE)
  {
    fprintf(stdout, "In %s at line %d: '%.*s' would be "
            "replaced with '%.*s'\n", generator->name, generator->line,
            (int) strlen(line)-1, line,
            (int) strlen(new_line)-1, new_line);
    global_update_count++;
    // generator->ctx->failed= 1;
  }
  return new_line;
}


/*
  find new name of renamed option
*/

const char *renamed_option(const char *option)
{
  struct RENAMED_OPTIONS *options;
  for (options= renamed_options; options->from ; options++)
    if (!strcmp(option, options->from))
      return options->to;
  return 0;
}


void error_unknown_option(struct generator *generator,
                          struct upgrade_ctx *ctx,
                          const char *option, const char *value,
                          const char *org_line)
{
  const char *renamed;

  if (!opt_print && opt_edit_mode == EDIT_MODE_NONE &&
      !generator->copy_code_to_mariadb)
  {
    if ((renamed= renamed_option(option)))
    {
      if (value)
        fprintf(stdout, "In %s at line %u: '%.*s' would be "
                "replaced with '%s=%.*s'\n", generator->name, generator->line,
                (int) strlen(org_line)-1, org_line, renamed,
                (int) strlen(value)-1, value);
      else
        fprintf(stdout, "In %s at line %u: '%.*s' would be "
                "replaced with '%s'\n", generator->name, generator->line,
                (int) strlen(org_line)-1, org_line, renamed);
    }
    else
    {
      fprintf(stdout, "In %s at line %u: Invalid option %s\n", generator->name,
              generator->line, option);
    }
    // ctx->failed= 1;
    global_update_count++;
  }
  if (!strncmp(option, STRING_WITH_LEN("audit_log")) &&
      !audit_plugin_warning++ && !opt_silent)
    fprintf(stdout, "Warning: Please replace audit_log with the server_audit "
            "plugin\n");
}


/*
  Store value for socket= option if used
  @return 1 if socket found
  @return 0 no socket.
*/

my_bool check_if_socket(const char *option, const char *value)
{
  if (!strcmp(option, "socket"))
  {
    const char *end;
    my_free(value_socket);

    /* The string may have an '\n' if paths has changed */
    if (!(end= strchr(value, '\n')))
      end= strend(value);
    value_socket= my_strndup(key_memory_upgrade_config, value, (end-value),
                             MYF(MY_WME));
    return 1;                                   /* Socket found */
  }
  return 0;
}

/**
   Write the given line to generator output as specified by opt_edit_mode.
   Returns false on success and true on failure.
*/
static my_bool generator_add_line(struct generator *generator, const char *line,
                                  enum line_type type, my_bool is_valid)
{
  if (generator->copy_code_to_mariadb && is_valid && type != LINE_TYPE_EMPTY)
  {
    /* Copy lines from [mysqld-XXX] group to [mariadbd] */
    const char *pos= line, *new_line= 0;
    if (opt_update_paths &&
        (new_line= path_changed(generator, line)))
      pos= new_line;
    add_line(&generator->alloc, &generator->mariadbd_additions, "%s", pos);
    my_free((char*) new_line);
  }
  if (generator->copy_code_to_mariadb)
    is_valid= 1;                /* Print line to [mysqld-XXX] groups */

  switch (opt_edit_mode) {
  case EDIT_MODE_REMOVE:
    global_update_count+= MY_TEST(!is_valid);
    return is_valid ? add_main_line(generator, type, "%s", line) : FALSE;
  case EDIT_MODE_LAST:
    if (is_valid || type == LINE_TYPE_EMPTY)
      return add_main_line(generator, type, "%s", line);
    if (strcmp(generator->input_group, "mariadbd"))
      return add_line(&generator->alloc, &generator->old_version, "%s", line);
    return add_line(&generator->alloc, &generator->unsupported_version, "%s",
                    line);
  case EDIT_MODE_COMMENT:
    global_update_count+= MY_TEST(!is_valid);
    return add_main_line(generator, type, is_valid ? "%s" : "#%s", line);
  case EDIT_MODE_INLINE:
  {
    const char *old_input_group= generator->input_group;
    my_bool res= TRUE;
    global_update_count+= MY_TEST(!is_valid);
    if (!is_valid)
      generator->input_group= (strcmp(generator->input_group, "mariadbd") ?
                               opt_error_group :
                               opt_unsupported_group);
    if (!add_main_line(generator, is_valid ? type : LINE_TYPE_OTHER, "%s",
                       line))
      res= FALSE;
    generator->input_group= old_input_group;
    return res;
  }
  case EDIT_MODE_NONE:
    return opt_print ? add_main_line(generator, type, "%s", line) : 0;
  }
  return TRUE;
}


static int process_default_file_with_ext(struct upgrade_ctx *ctx,
                                         const char *dir, const char *ext,
                                         const char *config_file,
                                         int recursion_level)
{
  char name[FN_REFLEN + 10], buff[4096], curr_gr[4096], *ptr, *end, **tmp_ext;
  char tmp_name[FN_REFLEN + 30], restored_name[FN_REFLEN + 30];
  char option_buff[4096+2], conv_option[128], tmp[FN_REFLEN];
  char *value, *option, *option_value_start, *new_line, *end_of_option;
  const char *renamed;
  static const char includedir_keyword[]= "includedir";
  static const char include_keyword[]= "include";
  const int max_recursion_level= 10;
  MYSQL_FILE *fp;
  MYSQL_FILE *tmp_fp= NULL;
  my_bool group_is_mariadbd= FALSE;
  my_bool group_is_mysqld= FALSE;
  uint option_start;
  enum { NONE, PARSE, SKIP } found_group= NONE;
  my_bool skip_prefix= 0, generator_inited= 0;
  size_t i;
  MY_DIR *search_dir;
  FILEINFO *search_file;
  struct generator generator;

  if (recursion_level == 0)
  {
    value_port= 0;
    my_free(value_socket);
    value_socket= 0;
    found_skip_slave_start= FALSE;
    found_innodb_log_files_in_group= 0;
    found_innodb_log_file_size= 0;
    found_character_set_server= 0;
    value_innodb_log_files_in_group= 0;
    value_innodb_log_file_size= 0;
    suffix_innodb_log_file_size= 0;
    found_mariadb_replication_compatibility= 0;
    found_mysql_replication_compatibility= 0;
    found_client_server_group= 0;
  }

  if (safe_strlen(dir) + strlen(config_file) >= FN_REFLEN-3)
    return 0;					/* Ignore wrong paths */
  if (dir)
  {
    end=convert_dirname(name, dir, NullS);
    if (dir[0] == FN_HOMELIB)		/* Add . to filenames in home */
      *end++='.';
    strxmov(end,config_file,ext,NullS);
  }
  else
  {
    strmov(name,config_file);
  }
  fn_format(name,name,"","",4);
#if !defined(_WIN32)
  {
    MY_STAT stat_info;
    if (!my_stat(name,&stat_info,
                 MYF(give_error_for_missing_files ? MY_WME : 0)))
      return 1;
    /*
      Ignore world-writable regular files (exceptions apply).
      This is mainly done to protect us to not read a file that may be
      modified by anyone.

      Also check access so that read only mounted (EROFS)
      or immutable files (EPERM) that are suitable protections.

      The main case we are allowing is a container readonly volume mount
      from a filesystem that doesn't have unix permissions. This will
      have a 0777 permission and access will set errno = EROFS.

      Note if a ROFS has a file with permissions 04n6, access sets errno
      EACCESS, rather the ROFS, so in this case we'll error, even though
      the ROFS is protecting the file.

      An ideal, race free, implementation would do fstat / fstatvfs / ioctl
      for permission, read only filesystem, and immutability resprectively.
    */
    if ((stat_info.st_mode & S_IWOTH) &&
	(stat_info.st_mode & S_IFMT) == S_IFREG &&
	(access(name, W_OK) == 0 || (errno != EROFS && errno != EPERM)))
    {
      fprintf(stderr, "Warning: World-writable config file '%s' is ignored\n",
              name);
      return 0;
    }
  }
#endif
  if (!(fp= mysql_file_fopen(key_file_cnf, name, O_RDONLY, MYF(MY_WME))))
    return 1;					/* Ignore wrong files */

  if (opt_update)
  {
    strmov(strmov(tmp_name, name), "-upgrade-config");
    strmov(strmov(restored_name, name), "-upgrade-config-orig");
    if (!(tmp_fp= mysql_file_fopen(key_file_cnf, tmp_name,
                                   O_RDWR | O_CREAT | O_TRUNC, MYF(0))))
    {
      fprintf(stderr, "error: Failed to open %s for writing: %s\n", tmp_name,
              strerror(errno));
      goto err;
    }
  }

  generator_init(&generator);
  generator_inited= 1;
  generator.name= name;
  generator.ctx= ctx;
  generator.file_valid= TRUE;
  generator.line= 0;

  if (opt_print || (opt_edit_mode != EDIT_MODE_NONE && !opt_update))
    fprintf(stdout, "### File %s:\n", name);
  while (mysql_file_fgets(buff, sizeof(buff) - 3, fp))
  {
    my_bool line_valid= TRUE;
    int invalid_set_index;
    generator.line++;
    /* Ignore comment and empty lines */
    for (ptr= buff; my_isspace(&my_charset_latin1, *ptr); ptr++)
    {}

    /* Ensure line ends with \n (delete_line code depends one this) */
    end= strend(ptr);
    if (end != ptr && end[-1] != '\n')
    {
      end[0]= '\n';
      end[1]= 0;
    }

    if (*ptr == '#' || *ptr == ';' || !*ptr)
    {
      if (is_prefix(ptr, mariadb_replication_compatibility_comment))
      {
        /* File is already updated */
        found_mariadb_replication_compatibility= 0;
      }
      if (is_prefix(ptr, mysql_replication_compatibility_comment))
      {
        /* File is already updated */
        found_mysql_replication_compatibility= 0;
      }
      generator_add_line(&generator, buff,
                         *ptr ? LINE_TYPE_OTHER : LINE_TYPE_EMPTY, line_valid);
      continue;
    }

    /* Configuration File Directives */
    if (*ptr == '!' || *ptr == '?')
    {
      /* Write out current cached items before include directive */
      my_bool prev_empty= generator.prev_empty;
      if (opt_print || opt_update)
      {
        if (generator_write(tmp_fp, &generator))
          generator.file_valid= FALSE;
      }
      else
        generator_count_lines(&generator);
      generator_reset(&generator);
      /* Ensure that when using --print we get !include next */
      if (prev_empty && !generator.prev_empty)
        generator_add_line(&generator, "\n", LINE_TYPE_EMPTY, 1);
      generator_add_line(&generator, buff, LINE_TYPE_PASSTHROUGH, 1);
      generator_write(tmp_fp, &generator);
      generator_reset(&generator);

      if (recursion_level >= max_recursion_level)
      {
        for (end= ptr + strlen(ptr) - 1;
             my_isspace(&my_charset_latin1, *(end - 1));
             end--)
        {}
        end[0]= 0;
        fprintf(stderr,
                "Warning: skipping '%s' directive as maximum include"
                "recursion level was reached in file %s at line %d\n",
                ptr, name, generator.line);
        continue;
      }

      /* skip over `!' and following whitespace */
      for (++ptr; my_isspace(&my_charset_latin1, ptr[0]); ptr++)
      {}

      if ((!strncmp(ptr, includedir_keyword,
                    sizeof(includedir_keyword) - 1)) &&
          my_isspace(&my_charset_latin1, ptr[sizeof(includedir_keyword) - 1]))
      {
        if (!(ptr= get_argument(includedir_keyword,
                                sizeof(includedir_keyword),
                                ptr, name, generator.line)))
          goto err;

        if (!(search_dir= my_dir(ptr, MYF(MY_WME | MY_WANT_SORT))))
          goto err;

        for (i= 0; i < search_dir->number_of_files; i++)
        {
          search_file= search_dir->dir_entry + i;
          ext= fn_ext2(search_file->name);

          /* check extension */
          for (tmp_ext= (char**) f_extensions; *tmp_ext; tmp_ext++)
          {
            if (!strcmp(ext, *tmp_ext))
              break;
          }

          if (*tmp_ext)
          {
            fn_format(tmp, search_file->name, ptr, "",
                      MY_UNPACK_FILENAME | MY_SAFE_PATH);

            process_default_file_with_ext(ctx, "", "", tmp,
                                          recursion_level + 1);
            if (opt_print || (opt_edit_mode != EDIT_MODE_NONE && !opt_update))
              fprintf(stdout, "### Back in %s:\n", name);
          }
        }

        my_dirend(search_dir);
      }
      else if ((!strncmp(ptr, include_keyword, sizeof(include_keyword) - 1)) &&
               my_isspace(&my_charset_latin1, ptr[sizeof(include_keyword)-1]))
      {
	if (!(ptr= get_argument(include_keyword,
                                sizeof(include_keyword), ptr,
                                name, generator.line)))
	  goto err;

        process_default_file_with_ext(ctx, "", "", ptr, recursion_level + 1);
        if (opt_print || (opt_edit_mode != EDIT_MODE_NONE && !opt_update))
          fprintf(stdout, "### Back in %s:\n", name);
      }
      continue;
    }

    if (*ptr == '[')				/* Group name */
    {
      if (!(end=(char *) strchr(++ptr,']')))
      {
	fprintf(stderr,
		"error: Wrong group definition in config file: %s at line %d\n",
		name, generator.line);
	goto err;
      }
      /* Remove end space */
      for ( ; my_isspace(&my_charset_latin1,end[-1]) ; end--) ;
      end[0]=0;

      strmake(curr_gr, ptr, MY_MIN((size_t) (end-ptr), sizeof(curr_gr)-1));
      group_is_mariadbd= !strcmp(curr_gr, "mariadbd");
      group_is_mysqld= (!strcmp(curr_gr, "mysqld") ||
                        !strcmp(curr_gr, "server"));
      found_client_server_group|= !strcmp(curr_gr, client_server_group);

      generator.copy_code_to_mariadb= 0;
      found_group= find_type(curr_gr, ctx->group, FIND_TYPE_NO_PREFIX)
        ? PARSE : SKIP;
      /* Ensure we process all groups with the mysqld prefix */
      if (found_group == SKIP)
      {
        if (!strncmp(curr_gr, "mysqld-", 7) && opt_copy_mysqld_groups)
        {
          found_group= PARSE;
          /* Copy all valid code from the mysqld- group to the mariadbd group */
          generator.copy_code_to_mariadb= 1;
        }
      }

      generator.input_group= curr_gr;
      if (found_group == PARSE && group_is_mariadbd)
      {
        /**
           Force the mariadbd group to be emitted to allow potentially adding
           new lines to it.
        */
        generator_add_line(&generator, "", LINE_TYPE_OPTION, TRUE);
      }
      continue;
    }
    switch (found_group) {
    case NONE:
      fprintf(stderr,
	      "error: Found option without preceding group in config "
              "file: %s at line: %d\n",
	      name, generator.line);
      goto err;
    case PARSE:
      if (opt_print && opt_edit_mode == EDIT_MODE_NONE)
      {
        generator_add_line(&generator, buff, LINE_TYPE_OPTION, TRUE);
        continue;
      }
      break;
    case SKIP:
      if (group_is_mariadbd &&
          (opt_edit_mode == EDIT_MODE_INLINE ||
           opt_edit_mode == EDIT_MODE_LAST))
      {
        /* Remove lines with the /mysql/ path */
        char *pos= change_file_path(buff, &mariadb_name, &mysql_name);
        if (pos)
        {
          delete_line(&generator.mariadbd_additions, pos);
          my_free(pos);
        }
        delete_line(&generator.mariadbd_additions, buff);
      }
      generator_add_line(&generator, buff, LINE_TYPE_OPTION, line_valid);
      continue;
    }

    end= end_of_option= find_end_comment(ptr);
    if ((value= strchr(ptr, '=')) && value < end_of_option)
      end= value;
    else
      value= 0;

    for ( ; my_isspace(&my_charset_latin1,end[-1]) ; end--)
      ;
    ptr= strmake(option_buff, ptr, (size_t) (end-ptr));

    /* Change '-' to '_' in the option */
    {
      char *from, *from_end, *to;
      from_end= option_buff + sizeof(conv_option)-1;
      for (from= option_buff, to= conv_option;
           *from && from < from_end;
           from++, to++)
        *to= (*from == '-') ? '_' : *from;
      *to= 0;
      option_start= 0;
      skip_prefix= 0;

      /* Ignore some prefix */
      for (uint i= 0; special_opt_prefix[i] ; i++)
      {
        if (!strncmp(conv_option, special_opt_prefix[i],
                     special_opt_prefix_lengths[i]) &&
            conv_option[special_opt_prefix_lengths[i]] == '_' &&
            conv_option[special_opt_prefix_lengths[i]+1])
        {
          option_start= special_opt_prefix_lengths[i]+1;
          skip_prefix= i == 0;                  /* skip- */
          break;
        }
      }
    }
    option= conv_option + option_start;

    if (value)
    {
      /* Remove pre- and end space */
      char *value_end;
      my_bool tmp;

      for (value++ ; my_isspace(&my_charset_latin1,*value); value++) ;
      value_end= end_of_option;
      /*
	We don't have to test for value_end >= value as we know there is
	an '=' before
      */
      for ( ; my_isspace(&my_charset_latin1,value_end[-1]) ; value_end--) ;
      if (value_end < value)			/* Empty string */
	value_end= value;

      /* remove quotes around argument */
      if ((*value == '\"' || *value == '\'') && /* First char is quote */
          (value + 1 < value_end ) && /* String is longer than 1 */
          *value == value_end[-1] ) /* First char is equal to last char */
      {
	value++;
	value_end--;
      }
      *ptr++= 0;
      option_value_start= ptr;

      tmp= mariadbd_option_exists(option);
      if (!tmp && skip_prefix && mariadbd_option_exists(conv_option))
      {
        /* Option only exists with the 'skip-' prefix */
        tmp= 1;
        option= conv_option;                    /* Option with skip- prefix */
      }
      if (!tmp)
      {
        if (generator.copy_code_to_mariadb)               /* [mysqld-#] group */
        {
          /* Changes will be detected in generator_write() */
          generator_add_line(&generator, buff, LINE_TYPE_OPTION, FALSE);
          continue;
        }
        line_valid= FALSE;
        generator.file_valid= FALSE;

        error_unknown_option(&generator, ctx, conv_option, value, buff);
        if (!opt_print && opt_edit_mode == EDIT_MODE_NONE)
          continue;
      }
      for ( ; value != value_end; value++)
      {
	if (*value == '\\' && value != value_end-1)
	{
	  switch(*++value) {
	  case 'n':
	    *ptr++='\n';
	    break;
	  case 't':
	    *ptr++= '\t';
	    break;
	  case 'r':
	    *ptr++ = '\r';
	    break;
	  case 'b':
	    *ptr++ = '\b';
	    break;
	  case 's':
	    *ptr++= ' ';			/* space */
	    break;
	  case '\"':
	    *ptr++= '\"';
	    break;
	  case '\'':
	    *ptr++= '\'';
	    break;
	  case '\\':
	    *ptr++= '\\';
	    break;
	  default:				/* Unknown; Keep '\' */
	    *ptr++= '\\';
	    *ptr++= *value;
	    break;
	  }
	}
	else
	  *ptr++= *value;
      }
      *ptr=0;
      if (!mariadbd_valid_enum_value(option, option_value_start))
      {
        line_valid= FALSE;
        generator.file_valid= FALSE;
        if (!opt_print && opt_edit_mode == EDIT_MODE_NONE)
        {
          fprintf(stdout,
                  "In %s at line %d: Invalid enum value '%s' for option %s\n",
                  name, generator.line, option_value_start, option);
          global_update_count++;
          // ctx->failed= 1;
          continue;
        }
      }
      else if ((invalid_set_index=
                mariadbd_check_set_value(option, option_value_start)))
      {
        line_valid= FALSE;
        generator.file_valid= FALSE;
        if (!opt_print && opt_edit_mode == EDIT_MODE_NONE)
        {
          fprintf(stdout, "In %s at line %d: Invalid set value '%s' at "
                  "index %d for option %s\n",
                  name, generator.line, option_value_start, invalid_set_index,
                  option);
          global_update_count++;
          // ctx->failed= 1;
          continue;
        }
      }
      else if (check_plugin_option(ctx, option, option_value_start, name,
                                   generator.line))
      {
        line_valid= FALSE;
        generator.file_valid= FALSE;
      }
      else if (group_is_mysqld && line_valid &&
               opt_update_paths &&
               (new_line= path_changed(&generator, buff)))
      {
        line_valid= FALSE;

        /* We have alread validated that buff contains an '=' */
        check_if_socket(option, strchr(new_line, '=')+1);
        if ((opt_edit_mode == EDIT_MODE_INLINE ||
             opt_edit_mode == EDIT_MODE_LAST))
        {
          generator_add_line(&generator, buff, LINE_TYPE_OPTION, FALSE);
          add_line(&generator.alloc, &generator.mariadbd_additions, "%s",
                   new_line);
          my_free(new_line);
          continue;
        }
        my_free(new_line);
      }
      else if (!group_is_mariadbd &&
               !strcmp(option, "key_buffer_size") &&
               (opt_no_myisam_options || opt_convert_from == FROM_MYSQL) &&
               ((!opt_print && opt_edit_mode == EDIT_MODE_NONE) ||
                opt_edit_mode == EDIT_MODE_INLINE ||
                opt_edit_mode == EDIT_MODE_LAST))
      {
        generator.file_valid= FALSE;
        if (opt_edit_mode == EDIT_MODE_NONE)
        {
          {
            fprintf(stdout, "In %s at line %d: key_buffer_size needs "
                    "modifications. If you want to git rid of this warning and "
                    "keep as it now, move it to the [mariadbd] group\n",
                    name, generator.line);
            global_update_count++;
          }
          continue;
        }
        generator_add_line(&generator, buff, LINE_TYPE_OPTION, FALSE);
        if (opt_no_myisam_options)
        {
          add_line(&generator.alloc, &generator.mariadbd_additions,
                   "key_buffer_size=64K    # Recommended value if no "
                   "MyISAM tables\n");
          snprintf(conv_option, sizeof(conv_option)-1,
                   "aria_pagecache_buffer_size=%s",
                   option_value_start);
          add_line(&generator.alloc, &generator.mariadbd_additions, "%s\n",
                   conv_option);
          if (!opt_silent)
            fprintf(stdout, "Note: '%s' added to [mariadbd] group\n",
                    conv_option);
        }
        else if (opt_convert_from == FROM_MYSQL)
        {
          add_line(&generator.alloc, &generator.mariadbd_additions, "%s", buff);
          add_line(&generator.alloc, &generator.mariadbd_additions,
                   "#key_buffer_size=64K   "
                   "# Recommended value if no MyISAM tables\n");
          snprintf(conv_option, sizeof(conv_option)-1,
                   "aria_pagecache_buffer_size=%s",
                   option_value_start);
          add_line(&generator.alloc, &generator.mariadbd_additions, "%s\n",
                   conv_option);
          if (!opt_silent)
            fprintf(stdout, "Note: %s added to [mariadbd] group\n",
                    conv_option);
        }
        continue;
      }
      else if (!group_is_mariadbd &&
               !strcmp(option, "innodb_log_files_in_group"))
      {
        found_innodb_log_files_in_group= 1;
        value_innodb_log_files_in_group= atoi(option_value_start);
        if (!value_innodb_log_files_in_group)
          value_innodb_log_files_in_group= 1; /* Ensure it is not 0 */
      }
      else if (!strcmp(option, "innodb_log_file_size"))
      {
        if (group_is_mariadbd)
        {
          /* Already handled. Don't add it again  */
          found_innodb_log_file_size= 0;
          found_innodb_log_files_in_group= 0;
        }
        else
        {
          int error;
          char *int_end= 0;
          value_innodb_log_file_size= my_strtoll10(option_value_start,
                                                   &int_end, &error);
          found_innodb_log_file_size= (value_innodb_log_file_size != 0 &&
                                       error == 0);
          suffix_innodb_log_file_size= *int_end;
        }
      }
      else if (!strcmp(option, "character_set_server") ||
               !strcmp(option, "collation_server"))
      {
        found_character_set_server= 1;
      }
      else if (group_is_mysqld && !strcmp(option, "port"))
      {
        int error;
        char *int_end= 0;
        value_port= my_strtoll10(option_value_start, &int_end, &error);
      }
      else if (group_is_mysqld && check_if_socket(option, option_value_start))
      {
      }
      else if ((renamed= renamed_option(option)))
      {
        generator_add_line(&generator, buff, LINE_TYPE_OPTION, FALSE);
        add_line(&generator.alloc, &generator.mariadbd_additions,
                 "%s=%s\n", renamed, option_value_start);
        continue;
      }
    } /* !value */
    else if (!group_is_mariadbd && !strcmp(option, "skip_slave_start"))
    {
      found_skip_slave_start= TRUE;
    }
    else if (!mariadbd_option_exists(option) &&
             !(skip_prefix && mariadbd_option_exists(conv_option)))
    {
      line_valid= FALSE;
      generator.file_valid= FALSE;

      error_unknown_option(&generator, ctx, conv_option, NullS, buff);

      if (!opt_print && opt_edit_mode == EDIT_MODE_NONE &&
          !generator.copy_code_to_mariadb)
        continue;
    }
    else if ((renamed= renamed_option(option)))
    {
      generator_add_line(&generator, buff, LINE_TYPE_OPTION, FALSE);
      add_line(&generator.alloc, &generator.mariadbd_additions,
               "%s\n", renamed);
      continue;
    }
    generator_add_line(&generator, buff, LINE_TYPE_OPTION, line_valid);
  }
  mysql_file_fclose(fp, MYF(0));
  fp= 0;

  if (recursion_level == 0)
  {
    if (found_innodb_log_file_size && found_innodb_log_files_in_group)
    {
      generator.file_valid= FALSE;
      snprintf(conv_option, sizeof(conv_option)-1,
               "innodb_log_file_size=%llu%c",
               value_innodb_log_file_size * value_innodb_log_files_in_group,
               (my_islower(&my_charset_latin1, suffix_innodb_log_file_size) ||
                my_isupper(&my_charset_latin1, suffix_innodb_log_file_size)) ?
               suffix_innodb_log_file_size : ' ');
      add_line(&generator.alloc, &generator.mariadbd_additions, "%s\n", conv_option);
      if (!opt_silent)
        fprintf(stdout, "Note: '%s' added to [mariadbd] group based on "
                "innodb_log_file_size * innodb_log_files_in_group\n", conv_option);
      found_innodb_log_file_size= found_innodb_log_files_in_group= 0;
    }
    if (opt_add_client_server_group && !found_client_server_group)
    {
      if (value_port)
        add_line(&generator.alloc, &generator.client_server,
                 "port=%lld\n", value_port);
      if (value_socket)
      {
        add_line(&generator.alloc, &generator.client_server,
                 "socket=%s\n", value_socket);
        my_free(value_socket);
        value_socket= 0;
      }
    }
    /* The following options should only be added once */
    if (!add_new_options_once || !new_options_added)
    {
      new_options_added= 1;

      /* Add generated options to mariadbd section */
      if (opt_add_skip_slave_start && !found_skip_slave_start)
      {
        generator.file_valid= FALSE;
        add_line(&generator.alloc, &generator.mariadbd_additions,
                 "skip_slave_start\n");
        found_skip_slave_start= 0;
      }
      if (opt_add_mariadb_replication_compatibility &&
          !found_mariadb_replication_compatibility)
      {
        generator.file_valid= FALSE;
        /* We do this line by line to get correct row numbers for reporting */
        add_line(&generator.alloc, &generator.old_version,
                 "%s\n", mariadb_replication_compatibility_comment);
        add_line(&generator.alloc, &generator.old_version,
                 "loose-binlog_row_value_options=\"\"\n");
        add_line(&generator.alloc, &generator.old_version,
                 "loose-binlog_transaction_compression=0\n");
        add_line(&generator.alloc, &generator.old_version,
                 "# Not required, but recommended for cross_replication\n");
        add_line(&generator.alloc, &generator.old_version,
                 "binlog_format=row\n");
      }
      if (opt_add_mysql_replication_compatibility &&
          !found_mysql_replication_compatibility)
      {
        add_line(&generator.alloc, &generator.mariadbd_additions,
                 "%s\n", mysql_replication_compatibility_comment);
        add_line(&generator.alloc, &generator.mariadbd_additions,
                 "encrypt_binlog=0\n");
        add_line(&generator.alloc, &generator.mariadbd_additions,
                 "log_bin_compress=0\n");
        add_line(&generator.alloc, &generator.mariadbd_additions,
                 "# Not required, but recommended for cross_replication\n");
        add_line(&generator.alloc, &generator.mariadbd_additions,
                 "binlog_format=row\n");
        if (!found_character_set_server)
        {
          add_line(&generator.alloc, &generator.mariadbd_additions,
                   "# Character set to be able to replicate new tables to "
                   "MySQL 8.0\n");
          add_line(&generator.alloc, &generator.mariadbd_additions,
                   "character_set_server=utf8mb4\n");
          add_line(&generator.alloc, &generator.mariadbd_additions,
                   "collation_server=utf8mb4_0900_ai_ci\n");
          if (!opt_silent)
            fprintf(stdout, "Note: Added MySQL default character sets and "
                    "collation to [mariadbd] to be able to replicate to "
                    "MySQL\n");
          found_character_set_server= 0;
        }
      }
    }
  }
  if (opt_print || opt_update)
  {
    if (generator_write(tmp_fp, &generator))
      generator.file_valid= 0;
  }
  else
    generator_count_lines(&generator);
  generator_free(&generator);
  if (recursion_level == 0)
  {
    my_free(value_socket);
    value_socket= 0;
  }

  if (tmp_fp)
  {
    mysql_file_fclose(tmp_fp, MYF(0));
    tmp_fp= 0;
    if (generator.file_valid)
    {
      my_delete(tmp_name, MYF(0));
    }
    else
    {
      myf redel_flags = opt_backup ? MYF(MY_REDEL_MAKE_BACKUP) : MYF(0);
      char *duped_name= my_strdup(key_memory_upgrade_config, name, MYF(0));
      if (!duped_name || insert_dynamic(&ctx->updated_files, &duped_name))
        goto err;
      /*
        Copy the file in case running mariadbd fails and the update must be
        reverted.
      */
      if (my_copy(name, restored_name, MYF(0)))
      {
        fprintf(stderr, "error: Failed to copy %s to %s: %s",
                name, restored_name, strerror(errno));
        return -1;
      }
      if (my_redel(name, tmp_name, time(NULL), redel_flags))
      {
        fprintf(stderr, "error: Failed to rename %s to %s: %s",
                tmp_name, name, strerror(errno));
        return -1;
      }
    }
  }
  if (opt_print || (opt_edit_mode != EDIT_MODE_NONE && !opt_update))
    fputc('\n', stdout);
  return(0);

err:
  if (fp)
    mysql_file_fclose(fp, MYF(0));
  if (tmp_fp)
    mysql_file_fclose(tmp_fp, MYF(0));
  if (generator_inited)
    generator_free(&generator);
  if (recursion_level == 0)
  {
    my_free(value_socket);
    value_socket= 0;
  }
  return -1;					/* Fatal error */
}


static int process_default_file(struct upgrade_ctx *ctx,
                                const char *dir,
                                const char *config_file)
{
  char **ext;
  const char *empty_list[]= { "", 0 };
  my_bool have_ext= fn_ext(config_file)[0] != 0;
  const char **exts_to_use= have_ext ? empty_list : f_extensions;

  for (ext= (char**) exts_to_use; *ext; ext++)
  {
    int error;
    if ((error= process_default_file_with_ext(ctx, dir, *ext, config_file,
                                              0)) < 0)
      return error;
  }
  return 0;
}


static int process_option_files(const char *conf_file,
                                struct upgrade_ctx *ctx,
                                const char **default_directories)
{
  const char **dirs;
  int error= 0;

  if (my_defaults_group_suffix)
  {
    /* Handle --defaults-group-suffix= */
    uint i;
    const char **extra_groups;
    const size_t instance_len= strlen(my_defaults_group_suffix);
    char *ptr;
    TYPELIB *group= ctx->group;

    if (!(extra_groups=
	  (const char**)alloc_root(ctx->alloc,
                                   (2*group->count+1)*sizeof(char*))))
      return 2;

    for (i= 0; i < group->count; i++)
    {
      size_t len;
      extra_groups[i]= group->type_names[i]; /** copy group */

      len= strlen(extra_groups[i]);
      if (!(ptr= alloc_root(ctx->alloc, (uint) (len+instance_len+1))))
        return 2;

      extra_groups[i+group->count]= ptr;

      /** Construct new group */
      memcpy(ptr, extra_groups[i], len);
      memcpy(ptr+len, my_defaults_group_suffix, instance_len+1);
    }

    group->count*= 2;
    group->type_names= extra_groups;
    group->type_names[group->count]= 0;
  }

  if (my_defaults_file)
  {
    if ((error= process_default_file_with_ext(ctx, "", "",
                                              my_defaults_file, 0)) < 0)
      goto err;
    if (error > 0)
    {
      fprintf(stderr, "Could not open required defaults file: %s\n",
              my_defaults_file);
      goto err;
    }
  }
  else if (dirname_length(conf_file) || !default_directories)
  {
    if ((error= process_default_file(ctx, NullS, conf_file)) < 0)
      goto err;
  }
  else
  {
    for (dirs= default_directories ; *dirs; dirs++)
    {
      if (**dirs)
      {
	if (process_default_file(ctx, *dirs, conf_file) < 0)
	  goto err;
      }
      else if (my_defaults_extra_file)
      {
        if ((error= process_default_file_with_ext(ctx, "", "",
                                                  my_defaults_extra_file,
                                                  0)) < 0)
	  goto err;				/* Fatal error */
        if (error > 0)
        {
          fprintf(stderr, "Could not open required defaults file: %s\n",
                  my_defaults_extra_file);
          goto err;
        }
      }
    }
  }

  return 0;

err:
  fprintf(stderr,"Fatal error in defaults handling. Program aborted\n");
  return 1;
}


/*
  Process defaults

  @args default_args        --default options. Sent forward to mariadbd
  @args default_args_count  Number of default options

  @return 0 ok
  @return 1 general error
  @return 2 memory allocation error
*/

static int process_defaults(const char *conf_file,
                            const char **groups,
                            const char **dirs,
                            const char **defaults_args,
                            int defaults_args_count)
{
  int error= 0;
  MEM_ROOT alloc;
  TYPELIB group;
  struct upgrade_ctx ctx;
  char executable[FN_REFLEN];

  init_alloc_root(key_memory_upgrade_config, &alloc, 512, 0, MYF(0));

  group.count= 0;
  group.name= "defaults";
  group.type_names= groups;

  for (; *groups ; groups++)
    group.count++;

  ctx.alloc= &alloc;
  ctx.group= &group;
  init_dynamic_array2(key_memory_upgrade_config,
                      &ctx.updated_files,
                      sizeof(char *),
                      NULL,
                      0,
                      0,
                      MYF(0));
  ctx.failed= 0;

  if ((error= process_option_files(conf_file, &ctx, dirs)))
  {
    finish_updated_files(&ctx, FALSE);
    free_root(&alloc,MYF(0));
    return error;
  }

  if (opt_mariadbd_testing)
  {
    DYNAMIC_STRING mariadbd_output;
    const char *mariadbd_name= IF_WIN("mariadbd.exe", "mariadbd");
    const char *used_name;

    if (opt_mariadbd)
      used_name= opt_mariadbd;
    else
    {
      used_name= mariadbd_name;
      if (!(error= (find_file_in_path(executable, mariadbd_name) != 0)))
        used_name= executable;
    }
    if (!error)
      error= access(used_name, X_OK);

    if (error)
    {
      fprintf(stderr,
              "Cannot execute '%s' to test if the new option files "
              "works (error %d)\n"
              "Try specifying the full path for the MariaDB server with "
              "--mariadbd=...\n",
              used_name, (int) errno);
      finish_updated_files(&ctx, 1);
      return 1;
    }

    if (init_dynamic_string(&mariadbd_output, "", 32, 32))
      goto err;
    if (!test_mariadbd(used_name) &&
        (error= run_mariadbd(used_name, &mariadbd_output, defaults_args,
                             defaults_args_count)))
    {
      if (ctx.updated_files.elements)
        fprintf(stderr, "error: Failed to run %s with the updated files, "
                "reverting\n", used_name);
      else
        fprintf(stderr, "error: Failed to run %s with the original files.\n",
                used_name);
      if (error > 0)
        fprintf(stderr, "mariadbd output:\n%s", mariadbd_output.str);
    }
    dynstr_free(&mariadbd_output);
  }

  if (finish_updated_files(&ctx, error == 0))
    error= 1;
  free_root(&alloc, MYF(0));
  return error;

err:
  finish_updated_files(&ctx, FALSE);
  free_root(&alloc, MYF(0));
  return 1;
}


static const char *config_file="my";         /* Default config file */

enum upgrade_config_options
{
  OPT_UPDATE = 256,
  OPT_BACKUP,
  OPT_ERROR_GROUP,
  OPT_UNSUPPORTED_GROUP,
  OPT_EDIT,
  OPT_PRINT,
  OPT_NO_MYISAM_OPTIONS,
  OPT_ADD_SKIP_SLAVE_START,
  OPT_FIX_ALL,
  OPT_CONVERT_FROM,
  OPT_UPDATE_PATHS,
  OPT_VERIFY,
  OPT_MARIADBD,
  OPT_ADD_MYSQL_REPLICATION_COMPATIBILITY,
  OPT_ADD_MARIADB_REPLICATION_COMPATIBILITY,
  OPT_COPY_MYSQLD_GROUPS,
  OPT_GENERATE_CLIENT_SERVER_GROUP
};

static struct my_option my_long_options[] =
{
  {"add-skip-slave-start", OPT_ADD_SKIP_SLAVE_START,
   "Add skip_slave_start to the [mariadbd] group if not present.",
   &opt_add_skip_slave_start, &opt_add_skip_slave_start,
   0, GET_BOOL, NO_ARG, FALSE, 0, 0, 0, 0, 0},
  {"add-mariadb-replication-compatibility",
   OPT_ADD_MARIADB_REPLICATION_COMPATIBILITY,
   "Add options to [error-group] to enable MySQL to replicate to MariaDB. "
   "Options added are: "
   "binlog-row-value-options=\"\"  binlog_transaction_compression=0 and "
   "binlog_format=row.",
   &opt_add_mariadb_replication_compatibility,
   &opt_add_mariadb_replication_compatibility,
   0, GET_BOOL, NO_ARG, FALSE, 0, 0, 0, 0, 0},
  {"add-mysql-replication-compatibility",
   OPT_ADD_MYSQL_REPLICATION_COMPATIBILITY,
   "Add options to [mariadbd] section to enable MariaDB to replicate to MySQL.",
   &opt_add_mysql_replication_compatibility,
   &opt_add_mysql_replication_compatibility,
   0, GET_BOOL, NO_ARG, FALSE, 0, 0, 0, 0, 0},
  {"add-client-server-group",
   OPT_ADD_MYSQL_REPLICATION_COMPATIBILITY,
   "Add a [client-server] group if it does not exists. This will contain the "
   "last port and socket found in the [mysqld] or [server] groups.",
   &opt_add_client_server_group,
   &opt_add_client_server_group,
   0, GET_BOOL, NO_ARG, FALSE, 0, 0, 0, 0, 0},
  {"backup", OPT_BACKUP,
   "Backup the updated configuration files. The backup file name are of the "
   "type: 'original_name'-timestamp.BAK",
   &opt_backup, &opt_backup, 0, GET_BOOL, NO_ARG, FALSE, 0, 0, 0, 0, 0},
  {"copy-mysqld-groups", OPT_COPY_MYSQLD_GROUPS,
   "Copy valid MariaDB options from [mysqld-###] groups to the [mariadbd] "
   "group.",
   &opt_copy_mysqld_groups, &opt_copy_mysqld_groups, 0, GET_BOOL, NO_ARG, TRUE,
   0, 0, 0, 0, 0},
  {"error-group", OPT_ERROR_GROUP,
   "Group to use for options not supported by MariaDB when using "
   "--edit=[inline|last]. Should be set usually to something like "
   "'mysqld-#.##' when moving from MySQL and 'unknown' when upgrading "
   "MariaDB or if there are already [mariadbd] groups in the config files.",
   &opt_error_group, &opt_error_group,
   0, GET_STR, REQUIRED_ARG, 0,  0, 0, 0, 0, 0},
  {"edit", OPT_EDIT,
   "Select what to do with invalid options. '' (default) no changes. "
   "'remove' removes them. 'comment' comments them with '#'. "
   "inline' adds [error_group] before unknown options. "
   "'last' moves incompatible or mariadbd specific options to the end of "
   "the given file under [error_group], [unsupported_group] or [mariadbd].",
   &opt_edit_mode, &opt_edit_mode, &edit_mode_typelib, GET_ENUM,
   REQUIRED_ARG, EDIT_MODE_NONE, 0, 0, 0, 0, 0},
  {"fix-all", OPT_FIX_ALL,
   "Same as --no-myisam --from=mysql --add-skip-slave-start "
   "--add-mysql-replication-compatibility "
   "--add-mariadb-replication-compatibility --add-client-server-group "
   "--edit=last",
   0, 0, 0, GET_NO_ARG, NO_ARG, FALSE, 0, 0, 0, 0, 0},
  {"from", OPT_CONVERT_FROM,
   "Options are MySQL (default) or MariaDB. Used to specify if your are "
   "upgrading from MySQL or an earlier MariaDB version. "
   "When upgrading from MySQL, only the MySQL groups [mysqld] and [server] "
   "are processed. If MariaDB is specified, all MariaDB entries read by "
   "the current MariaDB version (" MYSQL_SERVER_VERSION ") will be processed. "
   "Note that when upgrading from an earlier MariaDB version, mariadb-upgrade "
   "is the preferred tool! This tool is in this case mainly useful to check "
   "if there is any incompatibilities in the MariaDB configuration files.",
   &opt_convert_from, &opt_convert_from, &from_mode_typelib, GET_ENUM,
   REQUIRED_ARG, FROM_MYSQL, 0, 0, 0, 0, 0},
  {"help", '?', "Display this help message and exit.",
   0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
  {"mariadbd", OPT_MARIADBD,
   "Path to the mariadbd server. Used to verify if the config file changes "
   "succeeded.",
   &opt_mariadbd, &opt_mariadbd, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
  {"no-myisam", OPT_NO_MYISAM_OPTIONS,
   "Assume no usage of MyISAM tables.  Makes the MyISAM buffers very small "
   "to save memory. "
   "Useful when moving to MariaDB when there are no user data in MyISAM "
   "tables.  This option only has an effect if the config file contains "
   "settings of the key-buffer-size variable.",
   &opt_no_myisam_options, &opt_no_myisam_options, 0, GET_BOOL, NO_ARG,
   FALSE, 0, 0, 0, 0, 0},
  {"print", OPT_PRINT, "Print upgraded files to stdout. The 'edit' option "
   "decides the output format.",
   &opt_print, &opt_print, 0, GET_BOOL, NO_ARG, FALSE, 0, 0, 0, 0, 0},
  {"silent", 's', "Print less information",
   &opt_silent, &opt_silent, 0, GET_BOOL, NO_ARG, FALSE, 0, 0, 0, 0, 0},
  {"update", OPT_UPDATE, "Update the configuration files in place.",
   &opt_update, &opt_update, 0, GET_BOOL, NO_ARG, FALSE, 0, 0, 0, 0, 0},
  {"update-paths", OPT_UPDATE_PATHS,
   "When used with --from=mysql, change file paths having /mysql or /mysqld "
   "ending with '/', '.', space, or new line to /mariadbd. This is intended "
   "to be used when you want to run MySQL and MariaDB on the same system.",
   &opt_update_paths, &opt_update_paths,
   0, GET_BOOL, NO_ARG, FALSE, 0, 0, 0, 0, 0},
  {"unsupported-group", OPT_UNSUPPORTED_GROUP,
   "Group to use for options in [mariadbd] group that is not supported by "
   "MariaDB when using '--edit=[inline|last] --from=mariadb'.",
   &opt_unsupported_group, &opt_unsupported_group,
   0, GET_STR, REQUIRED_ARG, 0,  0, 0, 0, 0, 0},
  {"verify", OPT_VERIFY,
   "Verify updates by testing the used config files through mariadbd. "
   "Disabled if --print is used. "
   "Note that verify only works if one uses --defaults-file=# or if one does "
   "not specify any configuration files on the command line. "
   "When using verify the mariadbd option has also to be used.",
   &opt_mariadbd_testing, &opt_mariadbd_testing, 0, GET_BOOL, NO_ARG, FALSE,
   0, 0, 0, 0, 0},
  {"version", 'V', "Output version information and exit.",
   0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
};

static void cleanup_and_exit(int exit_code) __attribute__ ((noreturn));
static void cleanup_and_exit(int exit_code)
{
  my_end(0);
  exit(exit_code);
}


static void print_to_width(const char *line, uint width)
{
  const char *start= line, *space= 0;
  fputs("- ", stdout);

  for (start= line ; *line ; line++)
  {
    if ((uint)(line-start) >= width)
    {
      if (!space)
        space= line;                              /* No space */
      for ( ; start < space ; start++)
        putchar(*start);
      fputs("\n  ", stdout);
      while (*start == ' ')
        start++;
      space= 0;
    }
    if (*line == ' ')
      space= line;
  }
  printf("%s:\n", start);
}


static void pe(const char *example, const char *description)
{
  print_to_width(description, 77);
  fprintf(stdout, "%s %s\n\n",
          my_progname + dirname_length(my_progname),
          example);
}


static void usage() __attribute__ ((noreturn));
static void usage()
{
  print_version();
  puts("This software comes with ABSOLUTELY NO WARRANTY. This is free "
       "software,\nand you are welcome to modify and redistribute it under "
       "the GPL license\n");
  puts("Display or change unrecognized MariaDB options in MariaDB/MySQL\n"
       "configuration files. This is useful when planning an upgrade from\n"
       "MySQL to MariaDB or to check if there are some unsupported\n"
       "options in the option files.\n"
       "This program can print errors, print the changed files, update the\n"
       "configuration files in place.\n"
       "When running with the options:\n"
       "--update --edit=last --error-group=mysqld-5.7\n"
       "The configuration files will be changed so that they should work\n"
       "with both MySQL 5.7 and MariaDB. You should provide the currently\n"
       "used MySQL version if --error-group is used.\n");

  printf("Usage: %s [--print | --update] [OPTIONS] [OPTION_FILES]\n\n",
         my_progname);
  puts("If OPTION_FILES is not provided, all default MariaDB configuration\n"
       "files will be printed/upgraded");

  my_print_default_files(config_file);
  puts("");
  my_print_help(my_long_options);

  puts("");
  puts("If moving from MYSQL, the recommended usage is to use the following "
       "3 steps:");
  puts("");
  puts("- Check if there is anything that needs changing:");
  puts("mariadb-migrate-config-file --from=mysql");
  puts("");
  puts("- Check how the new configuration files would look like:");
  puts("mariadb-migrate-config-file --from=mysql --edit=last --print");
  puts("");
  puts("- Modify all configuration files keeping backups of the originals:");
  puts("mariadb-migrate-config-file --from=mysql --edit=last --update "
       "--backup --error-group=mysqld-5.7\n");

  puts("Examples:");
  pe("", "Go through all my.cnf and included files and print not supported "
     "options. The groups checked are [server], [mysqld] and [mysqld-###]. "
     "Options valid for MariaDB in [mysqld-###] groups are copied to "
     "the [mariadbd] group");
  pe("--from=mariadb",
     "Go through all my.cnf and include files and print options not supported "
     "by the current MariaDB version. All MariaDB groups read by mariadbd are "
     "checked");
  pe("~/.my.cnf", "Print not supported options in ~/.my.cnf");
  pe("--defaults-file=/etc/my.cnf --verify=1 --mariadbd=#path-to-mariadbd#",
     "Go through /etc/my.cnf and print all not supported options. Check also "
     "the by running it through the MariaDB server");
  pe("--print", "Print the current content of all my.cnf and included files "
     "(no changes)");
  pe("--print --edit=last --update-paths",
     "As print, but change all paths from /mysql to /mariadbd. Useful to "
     "check the effect of --update-paths");
  pe("--print --edit=last",
     "Print modified version of all my.cnf and included files where supported "
     "options from [mysqld-###] groups are copied to a [mariadbd] group at "
     "the end of the file. Not supported options from [mysqld] and [server] "
     "are moved to [mysqld-5.7] (default value of 'error_group')");
  pe("--print --edit=inline --error-group=mysqld-8.0",
     "Print modified version of all my.cnf and included files where supported "
     "options from [mysqld-###] groups are copied inline to [mariadbd] "
     "groups. Not supported options in the [mysqld] and [server] groups are "
     "moved to [mysqld-8.0]");
  pe("--print --edit=remove",
     "Print modified version of all my.cnf and includedfiles where supported "
     "options from [mysqld-###] groups are copied to a [mariadbd] group. "
     "Not supported options from [mysqld] and [server] are removed");
  pe("--print --edit=comment",
     "As '--remove', but not supported options are commented");
  pe("--edit=update --edit=inline --backup",
     "Modify all my.cnf and included files. Supported options from "
     "[mysqld-###] groups are copied to [mariadbd] groups. Not supported "
     "options in the [mysqld] and [server] groups are moved to a "
     "[mysqld-5.7] group. "
     "The original of the changed config files are stored as backup files. "
     "If no option files are specified the mariadbd server will be started "
     "using them to verify that the new options files works with it");
  pe("--update --edit=inline --error_group=unknown --from=mariadb",
     "Move all unknown mariadbd options to the group [unknown]");
  pe("--fix-all",
     "Use all common options when converting from MySQL to MariaDB. "
     "See definition for --fix-all for more details");

  my_print_variables(my_long_options);
  cleanup_and_exit(0);
}


static my_bool
get_one_option(const struct my_option *opt __attribute__((unused)),
	       const char *argument __attribute__((unused)),
               const char *filename __attribute__((unused)))
{
  switch (opt->id) {
  case 'I':
  case '?':
    usage();
  case 'V':
    print_version();
    cleanup_and_exit(0);
  case OPT_FIX_ALL:
    opt_no_myisam_options= TRUE;
    opt_add_skip_slave_start= TRUE;
    opt_edit_mode= EDIT_MODE_LAST;
    opt_convert_from= FROM_MYSQL;
    opt_add_mariadb_replication_compatibility= 1;
    opt_add_mysql_replication_compatibility= 1;
    opt_add_client_server_group= 1;
    break;
  }
  return 0;
}


static int get_options(int *argc,char ***argv)
{
  int ho_error;

  if ((ho_error=handle_options(argc, argv, my_long_options, get_one_option)))
    cleanup_and_exit(ho_error);

  if (opt_update && opt_edit_mode == EDIT_MODE_NONE)
  {
    fputs("error: --update requires the '--edit=xxx' option\n", stderr);
    cleanup_and_exit(1);
  }
  if ((!opt_error_group || !opt_unsupported_group) &&
      (opt_edit_mode == EDIT_MODE_INLINE ||
       opt_edit_mode == EDIT_MODE_LAST))
  {
    fputs("error: Selected --edit mode requires --error-group and "
          "--unsupported-group\n", stderr);
    cleanup_and_exit(1);
  }
  if (opt_print && opt_update)
  {
    fputs("error: --print and --update can't be specified simultaneously\n",
          stderr);
    cleanup_and_exit(1);
  }
  if (!opt_print && !opt_update && opt_edit_mode != EDIT_MODE_NONE &&
      !opt_silent)
  {
    puts("Note: edit_mode used without print/update. --print assumed");
    opt_print= 1;
  }
  if (opt_print && opt_edit_mode == EDIT_MODE_NONE && opt_update_paths)
  {
    puts("Note: --print --update-paths without edit mode set will not "
         "show updated paths");
  }
  if (opt_print)
  {
    opt_silent= 1;                /* Don't print notes with --print */
    opt_mariadbd_testing= 0;
  }
  if (opt_update_paths && opt_convert_from != FROM_MYSQL)
    opt_update_paths= 0;

  groups_to_use= (opt_convert_from == FROM_MYSQL ?
                  mysqld_groups : mariadbd_groups);
  return 0;
}


int main(int argc, char **argv)
{
  int count, error= 0, args_used;
  char *tmp_arguments[4];
  char **arguments, **org_argv;
  const char **default_directories;
  MY_INIT(argv[0]);

  org_argv= argv;
  args_used= get_defaults_options(argv);

  /* Copy defaults-xxx arguments & program name */
  count= args_used;
  arguments= tmp_arguments;
  memcpy((char*) arguments, (char*) org_argv, count*sizeof(*org_argv));
  arguments[count]= 0;

  /*
     We already process --defaults* options at the beginning in
     get_defaults_options(). So skip --defaults* options and
     pass remaining options to handle_options().
  */
  org_argv+=args_used-1;
  argc-=args_used-1;

  /* Check out the args */
  if (get_options(&argc, &org_argv))
    cleanup_and_exit(1);

  if (argc)
  {
    give_error_for_missing_files= 1;
    /* We cannot test a list of provided option files */
    opt_mariadbd_testing= 0;
    for (;  argc-- ; org_argv++)
    {
      count= args_used;
      arguments= tmp_arguments;
      if ((error= my_load_defaults(*org_argv, groups_to_use,
                                   &count, &arguments, 0)))
        goto handle_error;
      error= process_defaults(*org_argv, groups_to_use,
                              0,
                              (const char **)(argv + 1),
                              args_used - 1);
      free_defaults(arguments);
      if (error)
        goto handle_error;
    }
  }
  else
  {
    /* Write 'add options' only to the first found config file */
    add_new_options_once= 1;
    if ((error= my_load_defaults(config_file, groups_to_use,
                                 &count, &arguments, &default_directories)))
      goto handle_error;
    /*
      The defaults-xxx arguments are still intact at the beginning of argv and
      can be passed to mariadbd for testing the updated configuration files.
    */
    error= process_defaults(config_file, groups_to_use,
                            default_directories,
                            (const char **)(argv + 1),
                            args_used - 1);
    free_defaults(arguments);
    if (error)
      goto handle_error;
  }

  report_line_changes();

  if (!opt_silent && !opt_print)
  {
    if (global_update_count)
    {
      if (opt_update)
        fprintf(stdout, "%d issue/issues fixed\n", (int) global_update_count);
      else
        fprintf(stdout, "%d issue/issues found\n", (int) global_update_count);
    }
    else
      printf("No issues found\n");
  }
  cleanup_and_exit(error != 0 ||
                   (!opt_update && !opt_print && global_update_count != 0) ?
                   2 : 0);

handle_error:
  DBUG_ASSERT(error);
  if (error == 4)                             /* --print-defaults */
  {
    free_defaults(arguments);
    cleanup_and_exit(0);
  }
  cleanup_and_exit(2);
}
