/* Copyright (C) 2005 MySQL AB

   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; either version 2 of the License, or
   (at your option) any later version.

   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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

#include "mysql_priv.h"
#include <my_pthread.h>
#define REPORT_TO_LOG  1
#define REPORT_TO_USER 2

char *opt_plugin_dir_ptr;
char opt_plugin_dir[FN_REFLEN];
LEX_STRING plugin_type_names[]=
{
  { STRING_WITH_LEN("UDF") },
  { STRING_WITH_LEN("STORAGE ENGINE") },
  { STRING_WITH_LEN("FTPARSER") }
};
static const char *plugin_interface_version_sym=
                   "_mysql_plugin_interface_version_";
static const char *plugin_declarations_sym= "_mysql_plugin_declarations_";
static int min_plugin_interface_version= 0x0000;
/* Note that 'int version' must be the first field of every plugin
   sub-structure (plugin->info).
*/
static int min_plugin_info_interface_version[MYSQL_MAX_PLUGIN_TYPE_NUM]=
{
  0x0000,
  0x0000,
  0x0000
};
static int cur_plugin_info_interface_version[MYSQL_MAX_PLUGIN_TYPE_NUM]=
{
  0x0000, /* UDF: not implemented */
  MYSQL_HANDLERTON_INTERFACE_VERSION,
  MYSQL_FTPARSER_INTERFACE_VERSION
};
static DYNAMIC_ARRAY plugin_dl_array;
static DYNAMIC_ARRAY plugin_array;
static HASH plugin_hash[MYSQL_MAX_PLUGIN_TYPE_NUM];
static rw_lock_t THR_LOCK_plugin;
static bool initialized= 0;

static struct st_plugin_dl *plugin_dl_find(LEX_STRING *dl)
{
  uint i;
  DBUG_ENTER("plugin_dl_find");
  for (i= 0; i < plugin_dl_array.elements; i++)
  {
    struct st_plugin_dl *tmp= dynamic_element(&plugin_dl_array, i,
                                              struct st_plugin_dl *);
    if (tmp->ref_count &&
        ! my_strnncoll(files_charset_info,
                       (const uchar *)dl->str, dl->length,
                       (const uchar *)tmp->dl.str, tmp->dl.length))
      DBUG_RETURN(tmp);
  }
  DBUG_RETURN(0);
}


static st_plugin_dl *plugin_dl_insert_or_reuse(struct st_plugin_dl *plugin_dl)
{
  uint i;
  DBUG_ENTER("plugin_dl_insert_or_reuse");
  for (i= 0; i < plugin_dl_array.elements; i++)
  {
    struct st_plugin_dl *tmp= dynamic_element(&plugin_dl_array, i,
                                              struct st_plugin_dl *);
    if (! tmp->ref_count)
    {
      memcpy(tmp, plugin_dl, sizeof(struct st_plugin_dl));
      DBUG_RETURN(tmp);
    }
  }
  if (insert_dynamic(&plugin_dl_array, (gptr)plugin_dl))
    DBUG_RETURN(0);
  DBUG_RETURN(dynamic_element(&plugin_dl_array, plugin_dl_array.elements - 1,
                              struct st_plugin_dl *));
}


static st_plugin_dl *plugin_dl_add(LEX_STRING *dl, int report)
{
#ifdef HAVE_DLOPEN
  char dlpath[FN_REFLEN];
  uint plugin_dir_len, dummy_errors;
  struct st_plugin_dl *tmp, plugin_dl;
  void *sym;
  DBUG_ENTER("plugin_dl_add");
  plugin_dir_len= strlen(opt_plugin_dir);
  /*
    Ensure that the dll doesn't have a path.
    This is done to ensure that only approved libraries from the
    plugin directory are used (to make this even remotely secure).
  */
  if (my_strchr(files_charset_info, dl->str, dl->str + dl->length, FN_LIBCHAR) ||
      dl->length > NAME_LEN ||
      plugin_dir_len + dl->length + 1 >= FN_REFLEN)
  {
    if (report & REPORT_TO_USER)
      my_error(ER_UDF_NO_PATHS, MYF(0));
    if (report & REPORT_TO_LOG)
      sql_print_error(ER(ER_UDF_NO_PATHS));
    DBUG_RETURN(0);
  }
  /* If this dll is already loaded just increase ref_count. */
  if ((tmp= plugin_dl_find(dl)))
  {
    tmp->ref_count++;
    DBUG_RETURN(tmp);
  }
  /* Compile dll path */
  strxnmov(dlpath, sizeof(dlpath) - 1, opt_plugin_dir, "/", dl->str, NullS);
  plugin_dl.ref_count= 1;
  /* Open new dll handle */
  if (!(plugin_dl.handle= dlopen(dlpath, RTLD_NOW)))
  {
    if (report & REPORT_TO_USER)
      my_error(ER_CANT_OPEN_LIBRARY, MYF(0), dlpath, errno, dlerror());
    if (report & REPORT_TO_LOG)
      sql_print_error(ER(ER_CANT_OPEN_LIBRARY), dlpath, errno, dlerror());
    DBUG_RETURN(0);
  }
  /* Determine interface version */
  if (!(sym= dlsym(plugin_dl.handle, plugin_interface_version_sym)))
  {
    dlclose(plugin_dl.handle);
    if (report & REPORT_TO_USER)
      my_error(ER_CANT_FIND_DL_ENTRY, MYF(0), plugin_interface_version_sym);
    if (report & REPORT_TO_LOG)
      sql_print_error(ER(ER_CANT_FIND_DL_ENTRY), plugin_interface_version_sym);
    DBUG_RETURN(0);
  }
  plugin_dl.version= *(int *)sym;
  /* Versioning */
  if (plugin_dl.version < min_plugin_interface_version ||
      (plugin_dl.version >> 8) > (MYSQL_PLUGIN_INTERFACE_VERSION >> 8))
  {
    dlclose(plugin_dl.handle);
    if (report & REPORT_TO_USER)
      my_error(ER_CANT_OPEN_LIBRARY, MYF(0), dlpath, 0,
               "plugin interface version mismatch");
    if (report & REPORT_TO_LOG)
      sql_print_error(ER(ER_CANT_OPEN_LIBRARY), dlpath, 0,
                      "plugin interface version mismatch");
    DBUG_RETURN(0);
  }
  /* Find plugin declarations */
  if (!(sym= dlsym(plugin_dl.handle, plugin_declarations_sym)))
  {
    dlclose(plugin_dl.handle);
    if (report & REPORT_TO_USER)
      my_error(ER_CANT_FIND_DL_ENTRY, MYF(0), plugin_declarations_sym);
    if (report & REPORT_TO_LOG)
      sql_print_error(ER(ER_CANT_FIND_DL_ENTRY), plugin_declarations_sym);
    DBUG_RETURN(0);
  }
  plugin_dl.plugins= (struct st_mysql_plugin *)sym;
  /* Duplicate and convert dll name */
  plugin_dl.dl.length= dl->length * files_charset_info->mbmaxlen + 1;
  if (! (plugin_dl.dl.str= my_malloc(plugin_dl.dl.length, MYF(0))))
  {
    dlclose(plugin_dl.handle);
    if (report & REPORT_TO_USER)
      my_error(ER_OUTOFMEMORY, MYF(0), plugin_dl.dl.length);
    if (report & REPORT_TO_LOG)
      sql_print_error(ER(ER_OUTOFMEMORY), plugin_dl.dl.length);
    DBUG_RETURN(0);
  }
  plugin_dl.dl.length= copy_and_convert(plugin_dl.dl.str, plugin_dl.dl.length,
    files_charset_info, dl->str, dl->length, system_charset_info,
    &dummy_errors);
  plugin_dl.dl.str[plugin_dl.dl.length]= 0;
  /* Add this dll to array */
  if (! (tmp= plugin_dl_insert_or_reuse(&plugin_dl)))
  {
    dlclose(plugin_dl.handle);
    my_free(plugin_dl.dl.str, MYF(0));
    if (report & REPORT_TO_USER)
      my_error(ER_OUTOFMEMORY, MYF(0), sizeof(struct st_plugin_dl));
    if (report & REPORT_TO_LOG)
      sql_print_error(ER(ER_OUTOFMEMORY), sizeof(struct st_plugin_dl));
    DBUG_RETURN(0);
  }
  DBUG_RETURN(tmp);
#else
  DBUG_ENTER("plugin_dl_add");
  if (report & REPORT_TO_USER)
    my_error(ER_FEATURE_DISABLED, MYF(0), "plugin", "HAVE_DLOPEN");
  if (report & REPORT_TO_LOG)
    sql_print_error(ER(ER_FEATURE_DISABLED), "plugin", "HAVE_DLOPEN");
  DBUG_RETURN(0);
#endif
}


static void plugin_dl_del(LEX_STRING *dl)
{
#ifdef HAVE_DLOPEN
  uint i;
  DBUG_ENTER("plugin_dl_del");
  for (i= 0; i < plugin_dl_array.elements; i++)
  {
    struct st_plugin_dl *tmp= dynamic_element(&plugin_dl_array, i,
                                              struct st_plugin_dl *);
    if (tmp->ref_count &&
        ! my_strnncoll(files_charset_info,
                       (const uchar *)dl->str, dl->length,
                       (const uchar *)tmp->dl.str, tmp->dl.length))
    {
      /* Do not remove this element, unless no other plugin uses this dll. */
      if (! --tmp->ref_count)
      {
        dlclose(tmp->handle);
        my_free(tmp->dl.str, MYF(0));
        bzero(tmp, sizeof(struct st_plugin_dl));
      }
      break;
    }
  }
  DBUG_VOID_RETURN;
#endif
}


static struct st_plugin_int *plugin_find_internal(LEX_STRING *name, int type)
{
  uint i;
  DBUG_ENTER("plugin_find_internal");
  if (! initialized)
    DBUG_RETURN(0);
  if (type == MYSQL_ANY_PLUGIN)
  {
    for (i= 0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++)
    {
      struct st_plugin_int *plugin= (st_plugin_int *)
        hash_search(&plugin_hash[i], (const byte *)name->str, name->length);
      if (plugin) 
        DBUG_RETURN(plugin);
    }
  }
  else
    DBUG_RETURN((st_plugin_int *)
        hash_search(&plugin_hash[type], (const byte *)name->str, name->length));
  DBUG_RETURN(0);
}


my_bool plugin_is_ready(LEX_STRING *name, int type)
{
  my_bool rc= FALSE;
  struct st_plugin_int *plugin;
  DBUG_ENTER("plugin_is_ready");
  rw_rdlock(&THR_LOCK_plugin);
  if ((plugin= plugin_find_internal(name, type)) &&
      plugin->state == PLUGIN_IS_READY)
    rc= TRUE;
  rw_unlock(&THR_LOCK_plugin);
  DBUG_RETURN(rc);
}


struct st_plugin_int *plugin_lock(LEX_STRING *name, int type)
{
  struct st_plugin_int *rc;
  DBUG_ENTER("plugin_lock");
  rw_wrlock(&THR_LOCK_plugin);
  if ((rc= plugin_find_internal(name, type)))
  {
    if (rc->state == PLUGIN_IS_READY)
      rc->ref_count++;
    else
      rc= 0;
  }
  rw_unlock(&THR_LOCK_plugin);
  DBUG_RETURN(rc);
}


static st_plugin_int *plugin_insert_or_reuse(struct st_plugin_int *plugin)
{
  uint i;
  DBUG_ENTER("plugin_insert_or_reuse");
  for (i= 0; i < plugin_array.elements; i++)
  {
    struct st_plugin_int *tmp= dynamic_element(&plugin_array, i,
                                               struct st_plugin_int *);
    if (tmp->state == PLUGIN_IS_FREED)
    {
      memcpy(tmp, plugin, sizeof(struct st_plugin_int));
      DBUG_RETURN(tmp);
    }
  }
  if (insert_dynamic(&plugin_array, (gptr)plugin))
    DBUG_RETURN(0);
  DBUG_RETURN(dynamic_element(&plugin_array, plugin_array.elements - 1,
                              struct st_plugin_int *));
}


static my_bool plugin_add(LEX_STRING *name, LEX_STRING *dl, int report)
{
  struct st_plugin_int tmp;
  struct st_mysql_plugin *plugin;
  DBUG_ENTER("plugin_add");
  if (plugin_find_internal(name, MYSQL_ANY_PLUGIN))
  {
    if (report & REPORT_TO_USER)
      my_error(ER_UDF_EXISTS, MYF(0), name->str);
    if (report & REPORT_TO_LOG)
      sql_print_error(ER(ER_UDF_EXISTS), name->str);
    DBUG_RETURN(TRUE);
  }
  if (! (tmp.plugin_dl= plugin_dl_add(dl, report)))
    DBUG_RETURN(TRUE);
  /* Find plugin by name */
  for (plugin= tmp.plugin_dl->plugins; plugin->info; plugin++)
  {
    uint name_len= strlen(plugin->name);
    if (plugin->type >= 0 && plugin->type < MYSQL_MAX_PLUGIN_TYPE_NUM &&
        ! my_strnncoll(system_charset_info,
                       (const uchar *)name->str, name->length,
                       (const uchar *)plugin->name,
                       name_len))
    {
      struct st_plugin_int *tmp_plugin_ptr;
      if (*(int*)plugin->info <
          min_plugin_info_interface_version[plugin->type] ||
          ((*(int*)plugin->info) >> 8) >
          (cur_plugin_info_interface_version[plugin->type] >> 8))
      {
        char buf[256];
        strxnmov(buf, sizeof(buf) - 1, "API version for ",
                 plugin_type_names[plugin->type].str,
                 " plugin is too different", NullS);
        if (report & REPORT_TO_USER)
          my_error(ER_CANT_OPEN_LIBRARY, MYF(0), dl->str, 0, buf);
        if (report & REPORT_TO_LOG)
          sql_print_error(ER(ER_CANT_OPEN_LIBRARY), dl->str, 0, buf);
        goto err;
      }
      tmp.plugin= plugin;
      tmp.name.str= (char *)plugin->name;
      tmp.name.length= name_len;
      tmp.ref_count= 0;
      tmp.state= PLUGIN_IS_UNINITIALIZED;
      if (! (tmp_plugin_ptr= plugin_insert_or_reuse(&tmp)))
      {
        if (report & REPORT_TO_USER)
          my_error(ER_OUTOFMEMORY, MYF(0), sizeof(struct st_plugin_int));
        if (report & REPORT_TO_LOG)
          sql_print_error(ER(ER_OUTOFMEMORY), sizeof(struct st_plugin_int));
        goto err;
      }
      if (my_hash_insert(&plugin_hash[plugin->type], (byte*)tmp_plugin_ptr))
      {
        tmp_plugin_ptr->state= PLUGIN_IS_FREED;
        if (report & REPORT_TO_USER)
          my_error(ER_OUTOFMEMORY, MYF(0), sizeof(struct st_plugin_int));
        if (report & REPORT_TO_LOG)
          sql_print_error(ER(ER_OUTOFMEMORY), sizeof(struct st_plugin_int));
        goto err;
      }
      DBUG_RETURN(FALSE);
    }
  }
  if (report & REPORT_TO_USER)
    my_error(ER_CANT_FIND_DL_ENTRY, MYF(0), name->str);
  if (report & REPORT_TO_LOG)
    sql_print_error(ER(ER_CANT_FIND_DL_ENTRY), name->str);
err:
  plugin_dl_del(dl);
  DBUG_RETURN(TRUE);
}


static void plugin_del(LEX_STRING *name)
{
  uint i;
  struct st_plugin_int *plugin;
  DBUG_ENTER("plugin_del");
  if ((plugin= plugin_find_internal(name, MYSQL_ANY_PLUGIN)))
  {
    hash_delete(&plugin_hash[plugin->plugin->type], (byte*)plugin);
    plugin_dl_del(&plugin->plugin_dl->dl);
    plugin->state= PLUGIN_IS_FREED;
  }
  DBUG_VOID_RETURN;
}


void plugin_unlock(struct st_plugin_int *plugin)
{
  DBUG_ENTER("plugin_unlock");
  rw_wrlock(&THR_LOCK_plugin);
  DBUG_ASSERT(plugin && plugin->ref_count);
  plugin->ref_count--;
  if (plugin->state == PLUGIN_IS_DELETED && ! plugin->ref_count)
  {
    if (plugin->plugin->deinit)
      plugin->plugin->deinit();
    plugin_del(&plugin->name);
  }
  rw_unlock(&THR_LOCK_plugin);
  DBUG_VOID_RETURN;
}


static int plugin_initialize(struct st_plugin_int *plugin)
{
  DBUG_ENTER("plugin_initialize");
  
  if (plugin->plugin->init)
  {
    if (plugin->plugin->init())
    {
      sql_print_error("Plugin '%s' init function returned error.",
                      plugin->name.str);
      DBUG_PRINT("warning", ("Plugin '%s' init function returned error.",
                             plugin->name.str))
      goto err;
    }
  }
  
  switch (plugin->plugin->type)
  {
  case MYSQL_STORAGE_ENGINE_PLUGIN:
    if (ha_initialize_handlerton((handlerton*) plugin->plugin->info))
    {
      sql_print_error("Plugin '%s' handlerton init returned error.",
                      plugin->name.str);
      DBUG_PRINT("warning", ("Plugin '%s' handlerton init returned error.",
                             plugin->name.str))
      goto err;
    }
    break;
  default:
    break;
  }

  DBUG_RETURN(0);
err:
  DBUG_RETURN(1);
}

static void plugin_call_initializer(void)
{
  uint i;
  DBUG_ENTER("plugin_call_initializer");
  for (i= 0; i < plugin_array.elements; i++)
  {
    struct st_plugin_int *tmp= dynamic_element(&plugin_array, i,
                                               struct st_plugin_int *);
    if (tmp->state == PLUGIN_IS_UNINITIALIZED)
    {
      if (plugin_initialize(tmp))
        plugin_del(&tmp->name);
      else
        tmp->state= PLUGIN_IS_READY;
    }
  }
  DBUG_VOID_RETURN;
}


static void plugin_call_deinitializer(void)
{
  uint i;
  DBUG_ENTER("plugin_call_deinitializer");
  for (i= 0; i < plugin_array.elements; i++)
  {
    struct st_plugin_int *tmp= dynamic_element(&plugin_array, i,
                                               struct st_plugin_int *);
    if (tmp->state == PLUGIN_IS_READY)
    {
      if (tmp->plugin->deinit)
      {
        DBUG_PRINT("info", ("Deinitializing plugin: '%s'", tmp->name.str));
        if (tmp->plugin->deinit())
        {
          DBUG_PRINT("warning", ("Plugin '%s' deinit function returned error.",
                                 tmp->name.str))
        }
      }
      tmp->state= PLUGIN_IS_UNINITIALIZED;
    }
  }
  DBUG_VOID_RETURN;
}


static byte *get_hash_key(const byte *buff, uint *length,
                   my_bool not_used __attribute__((unused)))
{
  struct st_plugin_int *plugin= (st_plugin_int *)buff;
  *length= (uint)plugin->name.length;
  return((byte *)plugin->name.str);
}


int plugin_init(void)
{
  int i;
  DBUG_ENTER("plugin_init");

  if (initialized)
    DBUG_RETURN(0);

  my_rwlock_init(&THR_LOCK_plugin, NULL);

  if (my_init_dynamic_array(&plugin_dl_array,
                            sizeof(struct st_plugin_dl),16,16) ||
      my_init_dynamic_array(&plugin_array,
                            sizeof(struct st_plugin_int),16,16))
    goto err;

  for (i= 0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++)
  {
    if (hash_init(&plugin_hash[i], system_charset_info, 16, 0, 0,
                  get_hash_key, NULL, 0))
      goto err;
  }
  
  initialized= 1;

  DBUG_RETURN(0);
  
err:
  DBUG_RETURN(1);
}


my_bool plugin_register_builtin(struct st_mysql_plugin *plugin)
{
  struct st_plugin_int tmp;
  DBUG_ENTER("plugin_register_builtin");

  tmp.plugin= plugin;
  tmp.name.str= (char *)plugin->name;
  tmp.name.length= strlen(plugin->name);
  tmp.state= PLUGIN_IS_UNINITIALIZED;

  /* Cannot be unloaded */
  tmp.ref_count= 1;
  tmp.plugin_dl= 0;

  if (insert_dynamic(&plugin_array, (gptr)&tmp))
    DBUG_RETURN(1);

  if (my_hash_insert(&plugin_hash[plugin->type],
                     (byte*)dynamic_element(&plugin_array,
                                            plugin_array.elements - 1,
                                            struct st_plugin_int *)))
    DBUG_RETURN(1);

  DBUG_RETURN(0);
}


void plugin_load(void)
{
  TABLE_LIST tables;
  TABLE *table;
  READ_RECORD read_record_info;
  int error, i;
  MEM_ROOT mem;
  THD *new_thd;
  DBUG_ENTER("plugin_load");
  
  DBUG_ASSERT(initialized);
  
  if (!(new_thd= new THD))
  {
    sql_print_error("Can't allocate memory for plugin structures");
    delete new_thd;
    DBUG_VOID_RETURN;
  }
  init_sql_alloc(&mem, 1024, 0);
  new_thd->thread_stack= (char*) &tables;
  new_thd->store_globals();
  new_thd->db= my_strdup("mysql", MYF(0));
  new_thd->db_length= 5;
  bzero((gptr)&tables, sizeof(tables));
  tables.alias= tables.table_name= (char*)"plugin";
  tables.lock_type= TL_READ;
  tables.db= new_thd->db;
  if (simple_open_n_lock_tables(new_thd, &tables))
  {
    DBUG_PRINT("error",("Can't open plugin table"));
    sql_print_error("Can't open the mysql.plugin table. Please run the mysql_install_db script to create it.");
    goto end;
  }
  table= tables.table;
  init_read_record(&read_record_info, new_thd, table, NULL, 1, 0);
  while (!(error= read_record_info.read_record(&read_record_info)))
  {
    DBUG_PRINT("info", ("init plugin record"));
    LEX_STRING name, dl;
    name.str= get_field(&mem, table->field[0]);
    name.length= strlen(name.str);
    dl.str= get_field(&mem, table->field[1]);
    dl.length= strlen(dl.str);
    if (plugin_add(&name, &dl, REPORT_TO_LOG))
      DBUG_PRINT("warning", ("Couldn't load plugin named '%s' with soname '%s'.",
                             name.str, dl.str));
  }
  plugin_call_initializer();
  if (error > 0)
    sql_print_error(ER(ER_GET_ERRNO), my_errno);
  end_read_record(&read_record_info);
  new_thd->version--; // Force close to free memory
end:
  free_root(&mem, MYF(0));
  close_thread_tables(new_thd);
  delete new_thd;
  /* Remember that we don't have a THD */
  my_pthread_setspecific_ptr(THR_THD, 0);
  DBUG_VOID_RETURN;
}


void plugin_free(void)
{
  uint i;
  DBUG_ENTER("plugin_free");
  plugin_call_deinitializer();
  for (i= 0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++)
    hash_free(&plugin_hash[i]);
  delete_dynamic(&plugin_array);
  for (i= 0; i < plugin_dl_array.elements; i++)
  {
    struct st_plugin_dl *tmp= dynamic_element(&plugin_dl_array, i,
                                              struct st_plugin_dl *);
#ifdef HAVE_DLOPEN
    if (tmp->handle)
    {
      dlclose(tmp->handle);
      my_free(tmp->dl.str, MYF(0));
    }
#endif
  }
  delete_dynamic(&plugin_dl_array);
  if (initialized)
  {
    initialized= 0;
    rwlock_destroy(&THR_LOCK_plugin);
  }
  DBUG_VOID_RETURN;
}


my_bool mysql_install_plugin(THD *thd, LEX_STRING *name, LEX_STRING *dl)
{
  TABLE_LIST tables;
  TABLE *table;
  int error;
  struct st_plugin_int *tmp;
  DBUG_ENTER("mysql_install_plugin");

  bzero(&tables, sizeof(tables));
  tables.db= (char *)"mysql";
  tables.table_name= tables.alias= (char *)"plugin";
  if (check_table_access(thd, INSERT_ACL, &tables, 0))
    DBUG_RETURN(TRUE);
    
  /* need to open before acquiring THR_LOCK_plugin or it will deadlock */
  if (! (table = open_ltable(thd, &tables, TL_WRITE)))
    DBUG_RETURN(TRUE);

  rw_wrlock(&THR_LOCK_plugin);
  if (plugin_add(name, dl, REPORT_TO_USER))
    goto err;
  tmp= plugin_find_internal(name, MYSQL_ANY_PLUGIN);
  
  if (plugin_initialize(tmp))
  {
    my_error(ER_CANT_INITIALIZE_UDF, MYF(0), name->str,
             "Plugin initialization function failed.");
    goto err;
  }

  tmp->state= PLUGIN_IS_READY;

  restore_record(table, s->default_values);
  table->field[0]->store(name->str, name->length, system_charset_info);
  table->field[1]->store(dl->str, dl->length, files_charset_info);
  error= table->file->ha_write_row(table->record[0]);
  if (error)
  {
    table->file->print_error(error, MYF(0));
    goto deinit;
  }
  
  rw_unlock(&THR_LOCK_plugin);
  DBUG_RETURN(FALSE);
deinit:
  if (tmp->plugin->deinit)
    tmp->plugin->deinit();
err:
  plugin_del(name);
  rw_unlock(&THR_LOCK_plugin);
  DBUG_RETURN(TRUE);
}


my_bool mysql_uninstall_plugin(THD *thd, LEX_STRING *name)
{
  TABLE *table;
  TABLE_LIST tables;
  struct st_plugin_int *plugin;
  DBUG_ENTER("mysql_uninstall_plugin");

  bzero(&tables, sizeof(tables));
  tables.db= (char *)"mysql";
  tables.table_name= tables.alias= (char *)"plugin";

  /* need to open before acquiring THR_LOCK_plugin or it will deadlock */
  if (! (table= open_ltable(thd, &tables, TL_WRITE)))
    DBUG_RETURN(TRUE);

  rw_wrlock(&THR_LOCK_plugin);
  if (!(plugin= plugin_find_internal(name, MYSQL_ANY_PLUGIN)))
  {
    my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "PLUGIN", name->str);
    goto err;
  }
  if (!plugin->plugin_dl)
  {
    push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, 0,
                 "Built-in plugins cannot be deleted,.");
    my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "PLUGIN", name->str);
    goto err;
  }
  
  if (plugin->ref_count)
  {
    plugin->state= PLUGIN_IS_DELETED;
    push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, 0,
                 "Plugin is not deleted, waiting on tables.");
  }
  else
  {
    if (plugin->plugin->deinit)
      plugin->plugin->deinit();
    plugin_del(name);
  }

  table->field[0]->store(name->str, name->length, system_charset_info);
  table->file->extra(HA_EXTRA_RETRIEVE_ALL_COLS);
  if (! table->file->index_read_idx(table->record[0], 0,
                                    (byte *)table->field[0]->ptr,
                                    table->key_info[0].key_length,
                                    HA_READ_KEY_EXACT))
  {
    int error;
    if ((error= table->file->ha_delete_row(table->record[0])))
    {
      table->file->print_error(error, MYF(0));
      goto err;
    }
  }
  rw_unlock(&THR_LOCK_plugin);
  DBUG_RETURN(FALSE);
err:
  rw_unlock(&THR_LOCK_plugin);
  DBUG_RETURN(TRUE);
}


my_bool plugin_foreach(THD *thd, plugin_foreach_func *func,
                       int type, void *arg)
{
  uint idx;
  struct st_plugin_int *plugin;
  DBUG_ENTER("mysql_uninstall_plugin");
  rw_rdlock(&THR_LOCK_plugin);
  
  if (type == MYSQL_ANY_PLUGIN)
  {
    for (idx= 0; idx < plugin_array.elements; idx++)
    {
      plugin= dynamic_element(&plugin_array, idx, struct st_plugin_int *);
      
      /* FREED records may have garbage pointers */
      if ((plugin->state != PLUGIN_IS_FREED) &&
          func(thd, plugin, arg))
        goto err;
    }
  }
  else
  {
    HASH *hash= &plugin_hash[type];
    for (idx= 0; idx < hash->records; idx++)
    {
      plugin= (struct st_plugin_int *) hash_element(hash, idx);
      if ((plugin->state != PLUGIN_IS_FREED) &&
          (plugin->state != PLUGIN_IS_DELETED) &&
          func(thd, plugin, arg))
        goto err;
    }
  }

  rw_unlock(&THR_LOCK_plugin);
  DBUG_RETURN(FALSE);
err:
  rw_unlock(&THR_LOCK_plugin);
  DBUG_RETURN(TRUE);
}