static void prepare_record_for_error_message(int error, TABLE *table)
{
  Field **field_p;
  Field *field;
  uint keynr;
  MY_BITMAP unique_map; /* Fields in offended unique. */
  my_bitmap_map unique_map_buf[bitmap_buffer_size(MAX_FIELDS)];
  DBUG_ENTER("prepare_record_for_error_message");

  /*
    Only duplicate key errors print the key value.
    If storage engine does always read all columns, we have the value alraedy.
  */
  if ((error != HA_ERR_FOUND_DUPP_KEY) ||
      !(table->file->ha_table_flags() & HA_PARTIAL_COLUMN_READ))
    DBUG_VOID_RETURN;

  /*
    Get the number of the offended index.
    We will see MAX_KEY if the engine cannot determine the affected index.
  */
  if (unlikely((keynr= table->file->get_dup_key(error)) >= MAX_KEY))
    DBUG_VOID_RETURN;

  /* Create unique_map with all fields used by that index. */
  my_bitmap_init(&unique_map, unique_map_buf, table->s->fields, FALSE);
  table->mark_index_columns(keynr, &unique_map);

  /* Subtract read_set and write_set. */
  bitmap_subtract(&unique_map, table->read_set);
  bitmap_subtract(&unique_map, table->write_set);

  /*
    If the unique index uses columns that are neither in read_set
    nor in write_set, we must re-read the record.
    Otherwise no need to do anything.
  */
  if (bitmap_is_clear_all(&unique_map))
    DBUG_VOID_RETURN;

  /* Get identifier of last read record into table->file->ref. */
  table->file->position(table->record[0]);
  /* Add all fields used by unique index to read_set. */
  bitmap_union(table->read_set, &unique_map);
  /* Tell the engine about the new set. */
  table->file->column_bitmaps_signal();

  if ((error= table->file->ha_index_or_rnd_end()) ||
      (error= table->file->ha_rnd_init(0)))
  {
    table->file->print_error(error, MYF(0));
    DBUG_VOID_RETURN;
  }

  /* Read record that is identified by table->file->ref. */
  (void) table->file->ha_rnd_pos(table->record[1], table->file->ref);
  /* Copy the newly read columns into the new record. */
  for (field_p= table->field; (field= *field_p); field_p++)
    if (bitmap_is_set(&unique_map, field->field_index))
      field->copy_from_tmp(table->s->rec_buff_length);

  DBUG_VOID_RETURN;
}


bool mysql_prepare_update(THD *thd, TABLE_LIST *table_list,
			 Item **conds, uint order_num, ORDER *order)
{
  Item *fake_conds= 0;
#ifndef NO_EMBEDDED_ACCESS_CHECKS
  TABLE *table= table_list->table;
#endif
  List<Item> all_fields;
  SELECT_LEX *select_lex= &thd->lex->select_lex;
  DBUG_ENTER("mysql_prepare_update");

#ifndef NO_EMBEDDED_ACCESS_CHECKS
  table_list->grant.want_privilege= table->grant.want_privilege=
    (SELECT_ACL & ~table->grant.privilege);
  table_list->register_want_access(SELECT_ACL);
#endif

  thd->lex->allow_sum_func.clear_all();

  DBUG_ASSERT(table_list->table);
  // conds could be cached from previous SP call
  DBUG_ASSERT(!table_list->vers_conditions.need_setup() ||
              !*conds || thd->stmt_arena->is_stmt_execute());
  if (select_lex->vers_setup_conds(thd, table_list))
    DBUG_RETURN(TRUE);

  *conds= select_lex->where;

  /*
    We do not call DT_MERGE_FOR_INSERT because it has no sense for simple
    (not multi-) update
  */
  if (mysql_handle_derived(thd->lex, DT_PREPARE))
    DBUG_RETURN(TRUE);

  if (setup_tables_and_check_access(thd, &select_lex->context,
                                    &select_lex->top_join_list, table_list,
                                    select_lex->leaf_tables,
                                    FALSE, UPDATE_ACL, SELECT_ACL, TRUE) ||
      setup_conds(thd, table_list, select_lex->leaf_tables, conds) ||
      select_lex->setup_ref_array(thd, order_num) ||
      setup_order(thd, select_lex->ref_pointer_array,
		  table_list, all_fields, all_fields, order) ||
      setup_ftfuncs(select_lex))
    DBUG_RETURN(TRUE);

  select_lex->fix_prepare_information(thd, conds, &fake_conds);
  DBUG_RETURN(FALSE);
}

MDEV-28095 crash in multi-update and implicit grouping

disallow implicit grouping in multi-update.
explicit GROUP BY is not allowed by the grammar.


void multi_update::prepare_to_read_rows()
{
  /*
    update column maps now. it cannot be done in ::prepare() before the
    optimizer, because the optimize might reset them (in
    SELECT_LEX::update_used_tables()), it cannot be done in
    ::initialize_tables() after the optimizer, because the optimizer
    might read rows from const tables
  */

  for (TABLE_LIST *tl= update_tables; tl; tl= tl->next_local)
    tl->table->mark_columns_needed_for_update();
}


int multi_update::prepare(List<Item> &not_used_values,
			  SELECT_LEX_UNIT *lex_unit)

{
  TABLE_LIST *table_ref;
  SQL_I_List<TABLE_LIST> update;
  table_map tables_to_update;
  Item_field *item;
  List_iterator_fast<Item> field_it(*fields);
  List_iterator_fast<Item> value_it(*values);
  uint i, max_fields;
  uint leaf_table_count= 0;
  List_iterator<TABLE_LIST> ti(updated_leaves);
  DBUG_ENTER("multi_update::prepare");

  if (prepared)
    DBUG_RETURN(0);
  prepared= true;

  thd->count_cuted_fields= CHECK_FIELD_WARN;
  thd->cuted_fields=0L;
  THD_STAGE_INFO(thd, stage_updating_main_table);

  tables_to_update= get_table_map(fields);

  if (!tables_to_update)
  {
    my_message(ER_NO_TABLES_USED, ER_THD(thd, ER_NO_TABLES_USED), MYF(0));
    DBUG_RETURN(1);
  }

  /*
    We gather the set of columns read during evaluation of SET expression in
    TABLE::tmp_set by pointing TABLE::read_set to it and then restore it after
    setup_fields().
  */
  while ((table_ref= ti++))
  {
    if (table_ref->is_jtbm())
      continue;

    TABLE *table= table_ref->table;
    if (tables_to_update & table->map)
    {
      DBUG_ASSERT(table->read_set == &table->def_read_set);
      table->read_set= &table->tmp_set;
      bitmap_clear_all(table->read_set);
    }
  }

  /*
    We have to check values after setup_tables to get covering_keys right in
    reference tables
  */

  int error= setup_fields(thd, Ref_ptr_array(),
                          *values, MARK_COLUMNS_READ, 0, NULL, 0);

  ti.rewind();
  while ((table_ref= ti++))
  {
    if (table_ref->is_jtbm())
      continue;

    TABLE *table= table_ref->table;
    if (tables_to_update & table->map)
    {
      table->read_set= &table->def_read_set;
      bitmap_union(table->read_set, &table->tmp_set);
    }
  }
  if (unlikely(error))
    DBUG_RETURN(1);

  /*
    Save tables beeing updated in update_tables
    update_table->shared is position for table
    Don't use key read on tables that are updated
  */

  update.empty();
  ti.rewind();
  while ((table_ref= ti++))
  {
    /* TODO: add support of view of join support */
    if (table_ref->is_jtbm())
      continue;
    TABLE *table=table_ref->table;
    leaf_table_count++;
    if (tables_to_update & table->map)
    {
      TABLE_LIST *tl= (TABLE_LIST*) thd->memdup(table_ref,
						sizeof(*tl));
      if (!tl)
	DBUG_RETURN(1);
      update.link_in_list(tl, &tl->next_local);
      tl->shared= table_count++;
      table->no_keyread=1;
      table->covering_keys.clear_all();
      table->pos_in_table_list= tl;
      table->prepare_triggers_for_update_stmt_or_event();
      table->reset_default_fields();
    }
  }

  table_count=  update.elements;
  update_tables= update.first;

  tmp_tables = (TABLE**) thd->calloc(sizeof(TABLE *) * table_count);
  tmp_table_param = (TMP_TABLE_PARAM*) thd->calloc(sizeof(TMP_TABLE_PARAM) *
						   table_count);
  fields_for_table= (List_item **) thd->alloc(sizeof(List_item *) *
					      table_count);
  values_for_table= (List_item **) thd->alloc(sizeof(List_item *) *
					      table_count);
  if (unlikely(thd->is_fatal_error))
    DBUG_RETURN(1);
  for (i=0 ; i < table_count ; i++)
  {
    fields_for_table[i]= new List_item;
    values_for_table[i]= new List_item;
  }
  if (unlikely(thd->is_fatal_error))
    DBUG_RETURN(1);

  /* Split fields into fields_for_table[] and values_by_table[] */

  while ((item= (Item_field *) field_it++))
  {
    Item *value= value_it++;
    uint offset= item->field->table->pos_in_table_list->shared;
    fields_for_table[offset]->push_back(item, thd->mem_root);
    values_for_table[offset]->push_back(value, thd->mem_root);
  }
  if (unlikely(thd->is_fatal_error))
    DBUG_RETURN(1);

  /* Allocate copy fields */
  max_fields=0;
  for (i=0 ; i < table_count ; i++)
  {
    set_if_bigger(max_fields, fields_for_table[i]->elements + leaf_table_count);
    if (fields_for_table[i]->elements)
    {
      TABLE *table= ((Item_field*)(fields_for_table[i]->head()))->field->table;
      switch_to_nullable_trigger_fields(*fields_for_table[i], table);
      switch_to_nullable_trigger_fields(*values_for_table[i], table);
    }
  }
  copy_field= new (thd->mem_root) Copy_field[max_fields];
  DBUG_RETURN(thd->is_fatal_error != 0);
}


multi_update::~multi_update()
{
  TABLE_LIST *table;
  for (table= update_tables ; table; table= table->next_local)
  {
    table->table->no_keyread= 0;
    if (ignore)
      table->table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
  }

  if (tmp_tables)
  {
    for (uint cnt = 0; cnt < table_count; cnt++)
    {
      if (tmp_tables[cnt])
      {
	free_tmp_table(thd, tmp_tables[cnt]);
	tmp_table_param[cnt].cleanup();
      }
    }
  }
  if (copy_field)
    delete [] copy_field;
  thd->count_cuted_fields= CHECK_FIELD_IGNORE;		// Restore this setting
  DBUG_ASSERT(trans_safe || !updated ||
              thd->transaction.all.modified_non_trans_table);
}


bool compare_record(const TABLE *table)
{
  DBUG_ASSERT(records_are_comparable(table));

  if (table->file->ha_table_flags() & HA_PARTIAL_COLUMN_READ ||
      table->s->has_update_default_function)
  {
    /*
      Storage engine may not have read all columns of the record.  Fields
      (including NULL bits) not in the write_set may not have been read and
      can therefore not be compared.
      Or ON UPDATE DEFAULT NOW() could've changed field values, including
      NULL bits.
    */
    for (Field **ptr= table->field ; *ptr != NULL; ptr++)
    {
      Field *field= *ptr;
      if (field->has_explicit_value() && !field->vcol_info)
      {
        if (field->real_maybe_null())
        {
          uchar null_byte_index= (uchar)(field->null_ptr - table->record[0]);

          if (((table->record[0][null_byte_index]) & field->null_bit) !=
              ((table->record[1][null_byte_index]) & field->null_bit))
            return TRUE;
        }
        if (field->cmp_binary_offset(table->s->rec_buff_length))
          return TRUE;
      }
    }
    return FALSE;
  }

  /*
     The storage engine has read all columns, so it's safe to compare all bits
     including those not in the write_set. This is cheaper than the
     field-by-field comparison done above.
  */
  if (table->s->can_cmp_whole_record)
    return cmp_record(table,record[1]);
  /* Compare null bits */
  if (memcmp(table->null_flags,
	     table->null_flags+table->s->rec_buff_length,
	     table->s->null_bytes_for_compare))
    return TRUE;				// Diff in NULL value
  /* Compare updated fields */
  for (Field **ptr= table->field ; *ptr ; ptr++)
  {
    Field *field= *ptr;
    if (field->has_explicit_value() && !field->vcol_info &&
	field->cmp_binary_offset(table->s->rec_buff_length))
      return TRUE;
  }
  return FALSE;
}


multi_update::multi_update(THD *thd_arg, TABLE_LIST *table_list,
                           List<TABLE_LIST> *leaves_list,
			   List<Item> *field_list, List<Item> *value_list,
			   enum enum_duplicates handle_duplicates_arg,
                           bool ignore_arg):
   select_result_interceptor(thd_arg),
   all_tables(table_list), leaves(leaves_list), update_tables(0),
   tmp_tables(0), updated(0), found(0), fields(field_list),
   values(value_list), table_count(0), copy_field(0),
   handle_duplicates(handle_duplicates_arg), do_update(1), trans_safe(1),
   transactional_tables(0), ignore(ignore_arg), error_handled(0), prepared(0),
   updated_sys_ver(0)
{
}


int mysql_multi_update_prepare(THD *thd)
{
  LEX *lex= thd->lex;
  TABLE_LIST *table_list= lex->query_tables;
  TABLE_LIST *tl;
  Multiupdate_prelocking_strategy prelocking_strategy;
  uint table_count= lex->table_count;
  DBUG_ENTER("mysql_multi_update_prepare");

  /*
    Open tables and create derived ones, but do not lock and fill them yet.

    During prepare phase acquire only S metadata locks instead of SW locks to
    keep prepare of multi-UPDATE compatible with concurrent LOCK TABLES WRITE
    and global read lock.

    Don't evaluate any subqueries even if constant, because
    tables aren't locked yet.
  */
  lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_DERIVED;
  if (thd->lex->sql_command == SQLCOM_UPDATE_MULTI)
  {
    if (open_tables(thd, &table_list, &table_count,
        thd->stmt_arena->is_stmt_prepare() ? MYSQL_OPEN_FORCE_SHARED_MDL : 0,
        &prelocking_strategy))
      DBUG_RETURN(TRUE);
  }
  else
  {
    /* following need for prepared statements, to run next time multi-update */
    thd->lex->sql_command= SQLCOM_UPDATE_MULTI;
    prelocking_strategy.reset(thd);
    if (prelocking_strategy.handle_end(thd))
      DBUG_RETURN(TRUE);
  }

  /* now lock and fill tables */
  if (!thd->stmt_arena->is_stmt_prepare() &&
      lock_tables(thd, table_list, table_count, 0))
  {
    DBUG_RETURN(TRUE);
  }

  lex->context_analysis_only&= ~CONTEXT_ANALYSIS_ONLY_DERIVED;

  (void) read_statistics_for_tables_if_needed(thd, table_list);
  /* @todo: downgrade the metadata locks here. */

  /*
    Check that we are not using table that we are updating, but we should
    skip all tables of UPDATE SELECT itself
  */
  lex->select_lex.exclude_from_table_unique_test= TRUE;

  /* We only need SELECT privilege for columns in the values list */
  List_iterator<TABLE_LIST> ti(lex->select_lex.leaf_tables);
  while ((tl= ti++))
  {
    if (tl->is_jtbm())
      continue;
    TABLE *table= tl->table;
    TABLE_LIST *tlist;
    if (!(tlist= tl->top_table())->derived)
    {
      tlist->grant.want_privilege=
        (SELECT_ACL & ~tlist->grant.privilege);
      table->grant.want_privilege= (SELECT_ACL & ~table->grant.privilege);
    }
    DBUG_PRINT("info", ("table: %s  want_privilege: %u", tl->alias.str,
                        (uint) table->grant.want_privilege));
  }
  /*
    Set exclude_from_table_unique_test value back to FALSE. It is needed for
    further check in multi_update::prepare whether to use record cache.
  */
  lex->select_lex.exclude_from_table_unique_test= FALSE;

  if (lex->save_prep_leaf_tables())
    DBUG_RETURN(TRUE);

  DBUG_RETURN (FALSE);
}


void multi_update::update_used_tables()
{
  Item *item;
  List_iterator_fast<Item> it(*values);
  while ((item= it++))
  {
    item->update_used_tables();
  }
}


bool mysql_multi_update(THD *thd, TABLE_LIST *table_list, List<Item> *fields,
                        List<Item> *values, COND *conds, ulonglong options,
                        enum enum_duplicates handle_duplicates,
                        bool ignore, SELECT_LEX_UNIT *unit,
                        SELECT_LEX *select_lex, multi_update **result)
{
  bool res;
  DBUG_ENTER("mysql_multi_update");

  if (!(*result= new (thd->mem_root) multi_update(thd, table_list,
                                 &thd->lex->select_lex.leaf_tables,
                                 fields, values, handle_duplicates, ignore)))
  {
    DBUG_RETURN(TRUE);
  }

  if ((*result)->init(thd))
    DBUG_RETURN(1);

  thd->abort_on_warning= !ignore && thd->is_strict_mode();
  List<Item> total_list;

  if (setup_tables(thd, &select_lex->context, &select_lex->top_join_list,
                   table_list, select_lex->leaf_tables, FALSE, FALSE))
    DBUG_RETURN(1);

  if (select_lex->vers_setup_conds(thd, table_list))
    DBUG_RETURN(1);

  res= mysql_select(thd,
                    table_list, select_lex->with_wild, total_list, conds,
                    select_lex->order_list.elements,
                    select_lex->order_list.first, NULL, NULL, NULL,
                    options | SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK |
                    OPTION_SETUP_TABLES_DONE,
                    *result, unit, select_lex);

  DBUG_PRINT("info",("res: %d  report_error: %d", res, (int) thd->is_error()));
  res|= thd->is_error();
  if (unlikely(res))
    (*result)->abort_result_set();
  else
  {
    if (thd->lex->describe || thd->lex->analyze_stmt)
      res= thd->lex->explain->send_explain(thd);
  }
  thd->abort_on_warning= 0;
  DBUG_RETURN(res);
}


void Multiupdate_prelocking_strategy::reset(THD *thd)
{
  done= false;
  has_prelocking_list= thd->lex->requires_prelocking();
}


bool unsafe_key_update(List<TABLE_LIST> leaves, table_map tables_for_update)
{
  List_iterator_fast<TABLE_LIST> it(leaves), it2(leaves);
  TABLE_LIST *tl, *tl2;

  while ((tl= it++))
  {
    if (!tl->is_jtbm() && (tl->table->map & tables_for_update))
    {
      TABLE *table1= tl->table;
      bool primkey_clustered= (table1->file->primary_key_is_clustered() &&
                               table1->s->primary_key != MAX_KEY);

      bool table_partitioned= false;
#ifdef WITH_PARTITION_STORAGE_ENGINE
      table_partitioned= (table1->part_info != NULL);
#endif

      if (!table_partitioned && !primkey_clustered)
        continue;

      it2.rewind();
      while ((tl2= it2++))
      {
        if (tl2->is_jtbm())
          continue;
        /*
          Look at "next" tables only since all previous tables have
          already been checked
        */
        TABLE *table2= tl2->table;
        if (tl2 != tl &&
            table2->map & tables_for_update && table1->s == table2->s)
        {
          // A table is updated through two aliases
          if (table_partitioned &&
              (partition_key_modified(table1, table1->write_set) ||
               partition_key_modified(table2, table2->write_set)))
          {
            // Partitioned key is updated
            my_error(ER_MULTI_UPDATE_KEY_CONFLICT, MYF(0),
                     tl->top_table()->alias.str,
                     tl2->top_table()->alias.str);
            return true;
          }

          if (primkey_clustered)
          {
            // The primary key can cover multiple columns
            KEY key_info= table1->key_info[table1->s->primary_key];
            KEY_PART_INFO *key_part= key_info.key_part;
            KEY_PART_INFO *key_part_end= key_part + key_info.user_defined_key_parts;

            for (;key_part != key_part_end; ++key_part)
            {
              if (bitmap_is_set(table1->write_set, key_part->fieldnr-1) ||
                  bitmap_is_set(table2->write_set, key_part->fieldnr-1))
              {
                // Clustered primary key is updated
                my_error(ER_MULTI_UPDATE_KEY_CONFLICT, MYF(0),
                         tl->top_table()->alias.str,
                         tl2->top_table()->alias.str);
                return true;
              }
            }
          }
        }
      }
    }
  }
  return false;
}


compress_worker_thread_func(void *arg)
{
	comp_thread_ctxt_t *thd = (comp_thread_ctxt_t *) arg;

	pthread_mutex_lock(&thd->ctrl_mutex);

	pthread_mutex_lock(&thd->data_mutex);

	thd->started = TRUE;
	pthread_cond_signal(&thd->ctrl_cond);

	pthread_mutex_unlock(&thd->ctrl_mutex);

	while (1) {
		thd->data_avail = FALSE;
		pthread_cond_signal(&thd->data_cond);

		while (!thd->data_avail && !thd->cancelled) {
			pthread_cond_wait(&thd->data_cond, &thd->data_mutex);
		}

		if (thd->cancelled)
			break;

		thd->to_len = qlz_compress(thd->from, thd->to, thd->from_len,
					   &thd->state);

		/* qpress uses 0x00010000 as the initial value, but its own
		Adler-32 implementation treats the value differently:
		  1. higher order bits are the sum of all bytes in the sequence
		  2. lower order bits are the sum of resulting values at every
		     step.
		So it's the other way around as compared to zlib's adler32().
		That's why  0x00000001 is being passed here to be compatible
		with qpress implementation. */

		thd->adler = adler32(0x00000001, (uchar *) thd->to,
				     (uInt)thd->to_len);
	}

	pthread_mutex_unlock(&thd->data_mutex);

	return NULL;
}

MDEV-26561 Fix a bug due to unreleased lock

Fix a bug of unreleased lock ctrl_mutex in the method create_worker_threads


compress_write(ds_file_t *file, const uchar *buf, size_t len)
{
	ds_compress_file_t	*comp_file;
	ds_compress_ctxt_t	*comp_ctxt;
	comp_thread_ctxt_t	*threads;
	comp_thread_ctxt_t	*thd;
	uint			nthreads;
	uint			i;
	const char		*ptr;
	ds_file_t		*dest_file;

	comp_file = (ds_compress_file_t *) file->ptr;
	comp_ctxt = comp_file->comp_ctxt;
	dest_file = comp_file->dest_file;

	threads = comp_ctxt->threads;
	nthreads = comp_ctxt->nthreads;

	ptr = (const char *) buf;
	while (len > 0) {
		uint max_thread;

		/* Send data to worker threads for compression */
		for (i = 0; i < nthreads; i++) {
			size_t chunk_len;

			thd = threads + i;

			pthread_mutex_lock(&thd->ctrl_mutex);

			chunk_len = (len > COMPRESS_CHUNK_SIZE) ?
				COMPRESS_CHUNK_SIZE : len;
			thd->from = ptr;
			thd->from_len = chunk_len;

			pthread_mutex_lock(&thd->data_mutex);
			thd->data_avail = TRUE;
			pthread_cond_signal(&thd->data_cond);
			pthread_mutex_unlock(&thd->data_mutex);

			len -= chunk_len;
			if (len == 0) {
				break;
			}
			ptr += chunk_len;
		}

		max_thread = (i < nthreads) ? i :  nthreads - 1;

		/* Reap and stream the compressed data */
		for (i = 0; i <= max_thread; i++) {
			thd = threads + i;

			pthread_mutex_lock(&thd->data_mutex);
			while (thd->data_avail == TRUE) {
				pthread_cond_wait(&thd->data_cond,
						  &thd->data_mutex);
			}

			xb_a(threads[i].to_len > 0);

			if (ds_write(dest_file, "NEWBNEWB", 8) ||
			    write_uint64_le(dest_file,
					    comp_file->bytes_processed)) {
				msg("compress: write to the destination stream "
				    "failed.");
				return 1;
			}

			comp_file->bytes_processed += threads[i].from_len;

			if (write_uint32_le(dest_file, threads[i].adler) ||
			    ds_write(dest_file, threads[i].to,
					   threads[i].to_len)) {
				msg("compress: write to the destination stream "
				    "failed.");
				return 1;
			}

			pthread_mutex_unlock(&threads[i].data_mutex);
			pthread_mutex_unlock(&threads[i].ctrl_mutex);
		}
	}

	return 0;
}


destroy_worker_threads(comp_thread_ctxt_t *threads, uint n)
{
	uint i;

	for (i = 0; i < n; i++) {
		comp_thread_ctxt_t *thd = threads + i;

		pthread_mutex_lock(&thd->data_mutex);
		threads[i].cancelled = TRUE;
		pthread_cond_signal(&thd->data_cond);
		pthread_mutex_unlock(&thd->data_mutex);

		pthread_join(thd->id, NULL);

		pthread_cond_destroy(&thd->data_cond);
		pthread_mutex_destroy(&thd->data_mutex);
		pthread_cond_destroy(&thd->ctrl_cond);
		pthread_mutex_destroy(&thd->ctrl_mutex);

		my_free(thd->to);
	}

	my_free(threads);
}


static bool virtio_net_can_receive(NetClientState *nc)
{
    VirtIONet *n = qemu_get_nic_opaque(nc);
    VirtIODevice *vdev = VIRTIO_DEVICE(n);
    VirtIONetQueue *q = virtio_net_get_subqueue(nc);

    if (!vdev->vm_running) {
        return false;
    }

    if (nc->queue_index >= n->curr_queues) {
        return false;
    }

    if (!virtio_queue_ready(q->rx_vq) ||
        !(vdev->status & VIRTIO_CONFIG_S_DRIVER_OK)) {
        return false;
    }

    return true;
}


static void virtio_net_set_multiqueue(VirtIONet *n, int multiqueue)
{
    int max = multiqueue ? n->max_queues : 1;

    n->multiqueue = multiqueue;
    virtio_net_change_num_queues(n, max);

    virtio_net_set_queues(n);
}


static void virtio_net_set_queues(VirtIONet *n)
{
    int i;
    int r;

    if (n->nic->peer_deleted) {
        return;
    }

    for (i = 0; i < n->max_queues; i++) {
        if (i < n->curr_queues) {
            r = peer_attach(n, i);
            assert(!r);
        } else {
            r = peer_detach(n, i);
            assert(!r);
        }
    }
}


static bool max_queues_gt_1(void *opaque, int version_id)
{
    return VIRTIO_NET(opaque)->max_queues > 1;
}


static void virtio_net_vnet_endian_status(VirtIONet *n, uint8_t status)
{
    VirtIODevice *vdev = VIRTIO_DEVICE(n);
    int queues = n->multiqueue ? n->max_queues : 1;

    if (virtio_net_started(n, status)) {
        /* Before using the device, we tell the network backend about the
         * endianness to use when parsing vnet headers. If the backend
         * can't do it, we fallback onto fixing the headers in the core
         * virtio-net code.
         */
        n->needs_vnet_hdr_swap = virtio_net_set_vnet_endian(vdev, n->nic->ncs,
                                                            queues, true);
    } else if (virtio_net_started(n, vdev->status)) {
        /* After using the device, we need to reset the network backend to
         * the default (guest native endianness), otherwise the guest may
         * lose network connectivity if it is rebooted into a different
         * endianness.
         */
        virtio_net_set_vnet_endian(vdev, n->nic->ncs, queues, false);
    }
}


static char *failover_find_primary_device_id(VirtIONet *n)
{
    Error *err = NULL;
    FailoverId fid;

    fid.n = n;
    if (!qemu_opts_foreach(qemu_find_opts("device"),
                           failover_set_primary, &fid, &err)) {
        return NULL;
    }
    return fid.id;
}


static uint16_t virtio_net_handle_rss(VirtIONet *n,
                                      struct iovec *iov,
                                      unsigned int iov_cnt,
                                      bool do_rss)
{
    VirtIODevice *vdev = VIRTIO_DEVICE(n);
    struct virtio_net_rss_config cfg;
    size_t s, offset = 0, size_get;
    uint16_t queues, i;
    struct {
        uint16_t us;
        uint8_t b;
    } QEMU_PACKED temp;
    const char *err_msg = "";
    uint32_t err_value = 0;

    if (do_rss && !virtio_vdev_has_feature(vdev, VIRTIO_NET_F_RSS)) {
        err_msg = "RSS is not negotiated";
        goto error;
    }
    if (!do_rss && !virtio_vdev_has_feature(vdev, VIRTIO_NET_F_HASH_REPORT)) {
        err_msg = "Hash report is not negotiated";
        goto error;
    }
    size_get = offsetof(struct virtio_net_rss_config, indirection_table);
    s = iov_to_buf(iov, iov_cnt, offset, &cfg, size_get);
    if (s != size_get) {
        err_msg = "Short command buffer";
        err_value = (uint32_t)s;
        goto error;
    }
    n->rss_data.hash_types = virtio_ldl_p(vdev, &cfg.hash_types);
    n->rss_data.indirections_len =
        virtio_lduw_p(vdev, &cfg.indirection_table_mask);
    n->rss_data.indirections_len++;
    if (!do_rss) {
        n->rss_data.indirections_len = 1;
    }
    if (!is_power_of_2(n->rss_data.indirections_len)) {
        err_msg = "Invalid size of indirection table";
        err_value = n->rss_data.indirections_len;
        goto error;
    }
    if (n->rss_data.indirections_len > VIRTIO_NET_RSS_MAX_TABLE_LEN) {
        err_msg = "Too large indirection table";
        err_value = n->rss_data.indirections_len;
        goto error;
    }
    n->rss_data.default_queue = do_rss ?
        virtio_lduw_p(vdev, &cfg.unclassified_queue) : 0;
    if (n->rss_data.default_queue >= n->max_queues) {
        err_msg = "Invalid default queue";
        err_value = n->rss_data.default_queue;
        goto error;
    }
    offset += size_get;
    size_get = sizeof(uint16_t) * n->rss_data.indirections_len;
    g_free(n->rss_data.indirections_table);
    n->rss_data.indirections_table = g_malloc(size_get);
    if (!n->rss_data.indirections_table) {
        err_msg = "Can't allocate indirections table";
        err_value = n->rss_data.indirections_len;
        goto error;
    }
    s = iov_to_buf(iov, iov_cnt, offset,
                   n->rss_data.indirections_table, size_get);
    if (s != size_get) {
        err_msg = "Short indirection table buffer";
        err_value = (uint32_t)s;
        goto error;
    }
    for (i = 0; i < n->rss_data.indirections_len; ++i) {
        uint16_t val = n->rss_data.indirections_table[i];
        n->rss_data.indirections_table[i] = virtio_lduw_p(vdev, &val);
    }
    offset += size_get;
    size_get = sizeof(temp);
    s = iov_to_buf(iov, iov_cnt, offset, &temp, size_get);
    if (s != size_get) {
        err_msg = "Can't get queues";
        err_value = (uint32_t)s;
        goto error;
    }
    queues = do_rss ? virtio_lduw_p(vdev, &temp.us) : n->curr_queues;
    if (queues == 0 || queues > n->max_queues) {
        err_msg = "Invalid number of queues";
        err_value = queues;
        goto error;
    }
    if (temp.b > VIRTIO_NET_RSS_MAX_KEY_SIZE) {
        err_msg = "Invalid key size";
        err_value = temp.b;
        goto error;
    }
    if (!temp.b && n->rss_data.hash_types) {
        err_msg = "No key provided";
        err_value = 0;
        goto error;
    }
    if (!temp.b && !n->rss_data.hash_types) {
        virtio_net_disable_rss(n);
        return queues;
    }
    offset += size_get;
    size_get = temp.b;
    s = iov_to_buf(iov, iov_cnt, offset, n->rss_data.key, size_get);
    if (s != size_get) {
        err_msg = "Can get key buffer";
        err_value = (uint32_t)s;
        goto error;
    }
    n->rss_data.enabled = true;

    if (!n->rss_data.populate_hash) {
        if (!virtio_net_attach_epbf_rss(n)) {
            /* EBPF must be loaded for vhost */
            if (get_vhost_net(qemu_get_queue(n->nic)->peer)) {
                warn_report("Can't load eBPF RSS for vhost");
                goto error;
            }
            /* fallback to software RSS */
            warn_report("Can't load eBPF RSS - fallback to software RSS");
            n->rss_data.enabled_software_rss = true;
        }
    } else {
        /* use software RSS for hash populating */
        /* and detach eBPF if was loaded before */
        virtio_net_detach_epbf_rss(n);
        n->rss_data.enabled_software_rss = true;
    }

    trace_virtio_net_rss_enable(n->rss_data.hash_types,
                                n->rss_data.indirections_len,
                                temp.b);
    return queues;
error:
    trace_virtio_net_rss_error(err_msg, err_value);
    virtio_net_disable_rss(n);
    return 0;
}


static void virtio_net_set_features(VirtIODevice *vdev, uint64_t features)
{
    VirtIONet *n = VIRTIO_NET(vdev);
    Error *err = NULL;
    int i;

    if (n->mtu_bypass_backend &&
            !virtio_has_feature(vdev->backend_features, VIRTIO_NET_F_MTU)) {
        features &= ~(1ULL << VIRTIO_NET_F_MTU);
    }

    virtio_net_set_multiqueue(n,
                              virtio_has_feature(features, VIRTIO_NET_F_RSS) ||
                              virtio_has_feature(features, VIRTIO_NET_F_MQ));

    virtio_net_set_mrg_rx_bufs(n,
                               virtio_has_feature(features,
                                                  VIRTIO_NET_F_MRG_RXBUF),
                               virtio_has_feature(features,
                                                  VIRTIO_F_VERSION_1),
                               virtio_has_feature(features,
                                                  VIRTIO_NET_F_HASH_REPORT));

    n->rsc4_enabled = virtio_has_feature(features, VIRTIO_NET_F_RSC_EXT) &&
        virtio_has_feature(features, VIRTIO_NET_F_GUEST_TSO4);
    n->rsc6_enabled = virtio_has_feature(features, VIRTIO_NET_F_RSC_EXT) &&
        virtio_has_feature(features, VIRTIO_NET_F_GUEST_TSO6);
    n->rss_data.redirect = virtio_has_feature(features, VIRTIO_NET_F_RSS);

    if (n->has_vnet_hdr) {
        n->curr_guest_offloads =
            virtio_net_guest_offloads_by_features(features);
        virtio_net_apply_guest_offloads(n);
    }

    for (i = 0;  i < n->max_queues; i++) {
        NetClientState *nc = qemu_get_subqueue(n->nic, i);

        if (!get_vhost_net(nc->peer)) {
            continue;
        }
        vhost_net_ack_features(get_vhost_net(nc->peer), features);
    }

    if (virtio_has_feature(features, VIRTIO_NET_F_CTRL_VLAN)) {
        memset(n->vlans, 0, MAX_VLAN >> 3);
    } else {
        memset(n->vlans, 0xff, MAX_VLAN >> 3);
    }

    if (virtio_has_feature(features, VIRTIO_NET_F_STANDBY)) {
        qapi_event_send_failover_negotiated(n->netclient_name);
        qatomic_set(&n->failover_primary_hidden, false);
        failover_add_primary(n, &err);
        if (err) {
            warn_report_err(err);
        }
    }
}


static void virtio_net_set_status(struct VirtIODevice *vdev, uint8_t status)
{
    VirtIONet *n = VIRTIO_NET(vdev);
    VirtIONetQueue *q;
    int i;
    uint8_t queue_status;

    virtio_net_vnet_endian_status(n, status);
    virtio_net_vhost_status(n, status);

    for (i = 0; i < n->max_queues; i++) {
        NetClientState *ncs = qemu_get_subqueue(n->nic, i);
        bool queue_started;
        q = &n->vqs[i];

        if ((!n->multiqueue && i != 0) || i >= n->curr_queues) {
            queue_status = 0;
        } else {
            queue_status = status;
        }
        queue_started =
            virtio_net_started(n, queue_status) && !n->vhost_started;

        if (queue_started) {
            qemu_flush_queued_packets(ncs);
        }

        if (!q->tx_waiting) {
            continue;
        }

        if (queue_started) {
            if (q->tx_timer) {
                timer_mod(q->tx_timer,
                               qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + n->tx_timeout);
            } else {
                qemu_bh_schedule(q->tx_bh);
            }
        } else {
            if (q->tx_timer) {
                timer_del(q->tx_timer);
            } else {
                qemu_bh_cancel(q->tx_bh);
            }
            if ((n->status & VIRTIO_NET_S_LINK_UP) == 0 &&
                (queue_status & VIRTIO_CONFIG_S_DRIVER_OK) &&
                vdev->vm_running) {
                /* if tx is waiting we are likely have some packets in tx queue
                 * and disabled notification */
                q->tx_waiting = 0;
                virtio_queue_set_notification(q->tx_vq, 1);
                virtio_net_drop_tx_queue_data(vdev, q->tx_vq);
            }
        }
    }
}


static void virtio_net_set_mrg_rx_bufs(VirtIONet *n, int mergeable_rx_bufs,
                                       int version_1, int hash_report)
{
    int i;
    NetClientState *nc;

    n->mergeable_rx_bufs = mergeable_rx_bufs;

    if (version_1) {
        n->guest_hdr_len = hash_report ?
            sizeof(struct virtio_net_hdr_v1_hash) :
            sizeof(struct virtio_net_hdr_mrg_rxbuf);
        n->rss_data.populate_hash = !!hash_report;
    } else {
        n->guest_hdr_len = n->mergeable_rx_bufs ?
            sizeof(struct virtio_net_hdr_mrg_rxbuf) :
            sizeof(struct virtio_net_hdr);
    }

    for (i = 0; i < n->max_queues; i++) {
        nc = qemu_get_subqueue(n->nic, i);

        if (peer_has_vnet_hdr(n) &&
            qemu_has_vnet_hdr_len(nc->peer, n->guest_hdr_len)) {
            qemu_set_vnet_hdr_len(nc->peer, n->guest_hdr_len);
            n->host_hdr_len = n->guest_hdr_len;
        }
    }
}


static void virtio_net_get_config(VirtIODevice *vdev, uint8_t *config)
{
    VirtIONet *n = VIRTIO_NET(vdev);
    struct virtio_net_config netcfg;
    NetClientState *nc = qemu_get_queue(n->nic);
    static const MACAddr zero = { .a = { 0, 0, 0, 0, 0, 0 } };

    int ret = 0;
    memset(&netcfg, 0 , sizeof(struct virtio_net_config));
    virtio_stw_p(vdev, &netcfg.status, n->status);
    virtio_stw_p(vdev, &netcfg.max_virtqueue_pairs, n->max_queues);
    virtio_stw_p(vdev, &netcfg.mtu, n->net_conf.mtu);
    memcpy(netcfg.mac, n->mac, ETH_ALEN);
    virtio_stl_p(vdev, &netcfg.speed, n->net_conf.speed);
    netcfg.duplex = n->net_conf.duplex;
    netcfg.rss_max_key_size = VIRTIO_NET_RSS_MAX_KEY_SIZE;
    virtio_stw_p(vdev, &netcfg.rss_max_indirection_table_length,
                 virtio_host_has_feature(vdev, VIRTIO_NET_F_RSS) ?
                 VIRTIO_NET_RSS_MAX_TABLE_LEN : 1);
    virtio_stl_p(vdev, &netcfg.supported_hash_types,
                 VIRTIO_NET_RSS_SUPPORTED_HASHES);
    memcpy(config, &netcfg, n->config_size);

    /*
     * Is this VDPA? No peer means not VDPA: there's no way to
     * disconnect/reconnect a VDPA peer.
     */
    if (nc->peer && nc->peer->info->type == NET_CLIENT_DRIVER_VHOST_VDPA) {
        ret = vhost_net_get_config(get_vhost_net(nc->peer), (uint8_t *)&netcfg,
                                   n->config_size);
        if (ret != -1) {
            /*
             * Some NIC/kernel combinations present 0 as the mac address.  As
             * that is not a legal address, try to proceed with the
             * address from the QEMU command line in the hope that the
             * address has been configured correctly elsewhere - just not
             * reported by the device.
             */
            if (memcmp(&netcfg.mac, &zero, sizeof(zero)) == 0) {
                info_report("Zero hardware mac address detected. Ignoring.");
                memcpy(netcfg.mac, n->mac, ETH_ALEN);
            }
            memcpy(config, &netcfg, n->config_size);
        }
    }
}


static void virtio_net_vhost_status(VirtIONet *n, uint8_t status)
{
    VirtIODevice *vdev = VIRTIO_DEVICE(n);
    NetClientState *nc = qemu_get_queue(n->nic);
    int queues = n->multiqueue ? n->max_queues : 1;

    if (!get_vhost_net(nc->peer)) {
        return;
    }

    if ((virtio_net_started(n, status) && !nc->peer->link_down) ==
        !!n->vhost_started) {
        return;
    }
    if (!n->vhost_started) {
        int r, i;

        if (n->needs_vnet_hdr_swap) {
            error_report("backend does not support %s vnet headers; "
                         "falling back on userspace virtio",
                         virtio_is_big_endian(vdev) ? "BE" : "LE");
            return;
        }

        /* Any packets outstanding? Purge them to avoid touching rings
         * when vhost is running.
         */
        for (i = 0;  i < queues; i++) {
            NetClientState *qnc = qemu_get_subqueue(n->nic, i);

            /* Purge both directions: TX and RX. */
            qemu_net_queue_purge(qnc->peer->incoming_queue, qnc);
            qemu_net_queue_purge(qnc->incoming_queue, qnc->peer);
        }

        if (virtio_has_feature(vdev->guest_features, VIRTIO_NET_F_MTU)) {
            r = vhost_net_set_mtu(get_vhost_net(nc->peer), n->net_conf.mtu);
            if (r < 0) {
                error_report("%uBytes MTU not supported by the backend",
                             n->net_conf.mtu);

                return;
            }
        }

        n->vhost_started = 1;
        r = vhost_net_start(vdev, n->nic->ncs, queues);
        if (r < 0) {
            error_report("unable to start vhost net: %d: "
                         "falling back on userspace virtio", -r);
            n->vhost_started = 0;
        }
    } else {
        vhost_net_stop(vdev, n->nic->ncs, queues);
        n->vhost_started = 0;
    }
}


static int virtio_net_tx_waiting_pre_save(void *opaque)
{
    struct VirtIONetMigTmp *tmp = opaque;

    tmp->vqs_1 = tmp->parent->vqs + 1;
    tmp->curr_queues_1 = tmp->parent->curr_queues - 1;
    if (tmp->parent->curr_queues == 0) {
        tmp->curr_queues_1 = 0;
    }

    return 0;
}


static void virtio_net_reset(VirtIODevice *vdev)
{
    VirtIONet *n = VIRTIO_NET(vdev);
    int i;

    /* Reset back to compatibility mode */
    n->promisc = 1;
    n->allmulti = 0;
    n->alluni = 0;
    n->nomulti = 0;
    n->nouni = 0;
    n->nobcast = 0;
    /* multiqueue is disabled by default */
    n->curr_queues = 1;
    timer_del(n->announce_timer.tm);
    n->announce_timer.round = 0;
    n->status &= ~VIRTIO_NET_S_ANNOUNCE;

    /* Flush any MAC and VLAN filter table state */
    n->mac_table.in_use = 0;
    n->mac_table.first_multi = 0;
    n->mac_table.multi_overflow = 0;
    n->mac_table.uni_overflow = 0;
    memset(n->mac_table.macs, 0, MAC_TABLE_ENTRIES * ETH_ALEN);
    memcpy(&n->mac[0], &n->nic->conf->macaddr, sizeof(n->mac));
    qemu_format_nic_info_str(qemu_get_queue(n->nic), n->mac);
    memset(n->vlans, 0, MAX_VLAN >> 3);

    /* Flush any async TX */
    for (i = 0;  i < n->max_queues; i++) {
        NetClientState *nc = qemu_get_subqueue(n->nic, i);

        if (nc->peer) {
            qemu_flush_or_purge_queued_packets(nc->peer, true);
            assert(!virtio_net_get_subqueue(nc)->async_tx.elem);
        }
    }
}


static int failover_set_primary(void *opaque, QemuOpts *opts, Error **errp)
{
    FailoverId *fid = opaque;
    const char *standby_id = qemu_opt_get(opts, "failover_pair_id");

    if (g_strcmp0(standby_id, fid->n->netclient_name) == 0) {
        fid->id = g_strdup(opts->id);
        return 1;
    }

    return 0;
}


static int virtio_net_tx_waiting_pre_load(void *opaque)
{
    struct VirtIONetMigTmp *tmp = opaque;

    /* Reuse the pointer setup from save */
    virtio_net_tx_waiting_pre_save(opaque);

    if (tmp->parent->curr_queues > tmp->parent->max_queues) {
        error_report("virtio-net: curr_queues %x > max_queues %x",
            tmp->parent->curr_queues, tmp->parent->max_queues);

        return -EINVAL;
    }

    return 0; /* all good */
}


static void failover_add_primary(VirtIONet *n, Error **errp)
{
    Error *err = NULL;
    QemuOpts *opts;
    char *id;
    DeviceState *dev = failover_find_primary_device(n);

    if (dev) {
        return;
    }

    id = failover_find_primary_device_id(n);
    if (!id) {
        error_setg(errp, "Primary device not found");
        error_append_hint(errp, "Virtio-net failover will not work. Make "
                          "sure primary device has parameter"
                          " failover_pair_id=%s\n", n->netclient_name);
        return;
    }
    opts = qemu_opts_find(qemu_find_opts("device"), id);
    g_assert(opts); /* cannot be NULL because id was found using opts list */
    dev = qdev_device_add(opts, &err);
    if (err) {
        qemu_opts_del(opts);
    } else {
        object_unref(OBJECT(dev));
    }
    error_propagate(errp, err);
}


static bool virtio_net_set_vnet_endian(VirtIODevice *vdev, NetClientState *ncs,
                                       int queues, bool enable)
{
    int i;

    for (i = 0; i < queues; i++) {
        if (virtio_net_set_vnet_endian_one(vdev, ncs[i].peer, enable) < 0 &&
            enable) {
            while (--i >= 0) {
                virtio_net_set_vnet_endian_one(vdev, ncs[i].peer, false);
            }

            return true;
        }
    }

    return false;
}


static DeviceState *failover_find_primary_device(VirtIONet *n)
{
    char *id = failover_find_primary_device_id(n);

    if (!id) {
        return NULL;
    }

    return qdev_find_recursive(sysbus_get_default(), id);
}


static int virtio_net_post_load_device(void *opaque, int version_id)
{
    VirtIONet *n = opaque;
    VirtIODevice *vdev = VIRTIO_DEVICE(n);
    int i, link_down;

    trace_virtio_net_post_load_device();
    virtio_net_set_mrg_rx_bufs(n, n->mergeable_rx_bufs,
                               virtio_vdev_has_feature(vdev,
                                                       VIRTIO_F_VERSION_1),
                               virtio_vdev_has_feature(vdev,
                                                       VIRTIO_NET_F_HASH_REPORT));

    /* MAC_TABLE_ENTRIES may be different from the saved image */
    if (n->mac_table.in_use > MAC_TABLE_ENTRIES) {
        n->mac_table.in_use = 0;
    }

    if (!virtio_vdev_has_feature(vdev, VIRTIO_NET_F_CTRL_GUEST_OFFLOADS)) {
        n->curr_guest_offloads = virtio_net_supported_guest_offloads(n);
    }

    /*
     * curr_guest_offloads will be later overwritten by the
     * virtio_set_features_nocheck call done from the virtio_load.
     * Here we make sure it is preserved and restored accordingly
     * in the virtio_net_post_load_virtio callback.
     */
    n->saved_guest_offloads = n->curr_guest_offloads;

    virtio_net_set_queues(n);

    /* Find the first multicast entry in the saved MAC filter */
    for (i = 0; i < n->mac_table.in_use; i++) {
        if (n->mac_table.macs[i * ETH_ALEN] & 1) {
            break;
        }
    }
    n->mac_table.first_multi = i;

    /* nc.link_down can't be migrated, so infer link_down according
     * to link status bit in n->status */
    link_down = (n->status & VIRTIO_NET_S_LINK_UP) == 0;
    for (i = 0; i < n->max_queues; i++) {
        qemu_get_subqueue(n->nic, i)->link_down = link_down;
    }

    if (virtio_vdev_has_feature(vdev, VIRTIO_NET_F_GUEST_ANNOUNCE) &&
        virtio_vdev_has_feature(vdev, VIRTIO_NET_F_CTRL_VQ)) {
        qemu_announce_timer_reset(&n->announce_timer, migrate_announce_params(),
                                  QEMU_CLOCK_VIRTUAL,
                                  virtio_net_announce_timer, n);
        if (n->announce_timer.round) {
            timer_mod(n->announce_timer.tm,
                      qemu_clock_get_ms(n->announce_timer.type));
        } else {
            qemu_announce_timer_del(&n->announce_timer, false);
        }
    }

    if (n->rss_data.enabled) {
        n->rss_data.enabled_software_rss = n->rss_data.populate_hash;
        if (!n->rss_data.populate_hash) {
            if (!virtio_net_attach_epbf_rss(n)) {
                if (get_vhost_net(qemu_get_queue(n->nic)->peer)) {
                    warn_report("Can't post-load eBPF RSS for vhost");
                } else {
                    warn_report("Can't post-load eBPF RSS - "
                                "fallback to software RSS");
                    n->rss_data.enabled_software_rss = true;
                }
            }
        }

        trace_virtio_net_rss_enable(n->rss_data.hash_types,
                                    n->rss_data.indirections_len,
                                    sizeof(n->rss_data.key));
    } else {
        trace_virtio_net_rss_disable();
    }
    return 0;
}


static int virtio_net_max_tx_queue_size(VirtIONet *n)
{
    NetClientState *peer = n->nic_conf.peers.ncs[0];

    /*
     * Backends other than vhost-user don't support max queue size.
     */
    if (!peer) {
        return VIRTIO_NET_TX_QUEUE_DEFAULT_SIZE;
    }

    if (peer->info->type != NET_CLIENT_DRIVER_VHOST_USER) {
        return VIRTIO_NET_TX_QUEUE_DEFAULT_SIZE;
    }

    return VIRTQUEUE_MAX_SIZE;
}


static int peer_attach(VirtIONet *n, int index)
{
    NetClientState *nc = qemu_get_subqueue(n->nic, index);

    if (!nc->peer) {
        return 0;
    }

    if (nc->peer->info->type == NET_CLIENT_DRIVER_VHOST_USER) {
        vhost_set_vring_enable(nc->peer, 1);
    }

    if (nc->peer->info->type != NET_CLIENT_DRIVER_TAP) {
        return 0;
    }

    if (n->max_queues == 1) {
        return 0;
    }

    return tap_enable(nc->peer);
}


static int virtio_net_handle_mq(VirtIONet *n, uint8_t cmd,
                                struct iovec *iov, unsigned int iov_cnt)
{
    VirtIODevice *vdev = VIRTIO_DEVICE(n);
    uint16_t queues;

    virtio_net_disable_rss(n);
    if (cmd == VIRTIO_NET_CTRL_MQ_HASH_CONFIG) {
        queues = virtio_net_handle_rss(n, iov, iov_cnt, false);
        return queues ? VIRTIO_NET_OK : VIRTIO_NET_ERR;
    }
    if (cmd == VIRTIO_NET_CTRL_MQ_RSS_CONFIG) {
        queues = virtio_net_handle_rss(n, iov, iov_cnt, true);
    } else if (cmd == VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET) {
        struct virtio_net_ctrl_mq mq;
        size_t s;
        if (!virtio_vdev_has_feature(vdev, VIRTIO_NET_F_MQ)) {
            return VIRTIO_NET_ERR;
        }
        s = iov_to_buf(iov, iov_cnt, 0, &mq, sizeof(mq));
        if (s != sizeof(mq)) {
            return VIRTIO_NET_ERR;
        }
        queues = virtio_lduw_p(vdev, &mq.virtqueue_pairs);

    } else {
        return VIRTIO_NET_ERR;
    }

    if (queues < VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN ||
        queues > VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX ||
        queues > n->max_queues ||
        !n->multiqueue) {
        return VIRTIO_NET_ERR;
    }

    n->curr_queues = queues;
    /* stop the backend before changing the number of queues to avoid handling a
     * disabled queue */
    virtio_net_set_status(vdev, vdev->status);
    virtio_net_set_queues(n);

    return VIRTIO_NET_OK;
}


static void virtio_net_change_num_queues(VirtIONet *n, int new_max_queues)
{
    VirtIODevice *vdev = VIRTIO_DEVICE(n);
    int old_num_queues = virtio_get_num_queues(vdev);
    int new_num_queues = new_max_queues * 2 + 1;
    int i;

    assert(old_num_queues >= 3);
    assert(old_num_queues % 2 == 1);

    if (old_num_queues == new_num_queues) {
        return;
    }

    /*
     * We always need to remove and add ctrl vq if
     * old_num_queues != new_num_queues. Remove ctrl_vq first,
     * and then we only enter one of the following two loops.
     */
    virtio_del_queue(vdev, old_num_queues - 1);

    for (i = new_num_queues - 1; i < old_num_queues - 1; i += 2) {
        /* new_num_queues < old_num_queues */
        virtio_net_del_queue(n, i / 2);
    }

    for (i = old_num_queues - 1; i < new_num_queues - 1; i += 2) {
        /* new_num_queues > old_num_queues */
        virtio_net_add_queue(n, i / 2);
    }

    /* add ctrl_vq last */
    n->ctrl_vq = virtio_add_queue(vdev, 64, virtio_net_handle_ctrl);
}


static void do_home_file(HttpResponse res) {
        char buf[STRLEN];
        boolean_t on = true;
        boolean_t header = true;

        for (Service_T s = servicelist_conf; s; s = s->next_conf) {
                if (s->type != Service_File)
                        continue;
                if (header) {
                        StringBuffer_append(res->outputbuffer,
                                            "
"
                                            "
"
                                            "File	"
                                            "Status	"
                                            "Size	"
                                            "Permission	"
                                            "UID	"
                                            "GID	"
                                            "
");

                        header = false;
                }
                StringBuffer_append(res->outputbuffer,
                                    "
"
                                    "%s	"
                                    "%s	",
                                    on ? "class='stripe'" : "",
                                    s->name, s->name,
                                    get_service_status(HTML, s, buf, sizeof(buf)));
                if (! Util_hasServiceStatus(s) || s->inf.file->size < 0)
                        StringBuffer_append(res->outputbuffer, "-	");
                else
                        StringBuffer_append(res->outputbuffer, "%s	", Fmt_bytes2str(s->inf.file->size, (char[10]){}));
                if (! Util_hasServiceStatus(s) || s->inf.file->mode < 0)
                        StringBuffer_append(res->outputbuffer, "-	");
                else
                        StringBuffer_append(res->outputbuffer, "%04o	", s->inf.file->mode & 07777);
                if (! Util_hasServiceStatus(s) || s->inf.file->uid < 0)
                        StringBuffer_append(res->outputbuffer, "-	");
                else
                        StringBuffer_append(res->outputbuffer, "%d	", s->inf.file->uid);
                if (! Util_hasServiceStatus(s) || s->inf.file->gid < 0)
                        StringBuffer_append(res->outputbuffer, "-	");
                else
                        StringBuffer_append(res->outputbuffer, "%d	", s->inf.file->gid);
                StringBuffer_append(res->outputbuffer, "
");
                on = ! on;
        }
        if (! header)
                StringBuffer_append(res->outputbuffer, "
");
}


static void do_about(HttpResponse res) {
        StringBuffer_append(res->outputbuffer,
                            "<title>about monit</title>"
                            "

"
                            "monit " VERSION "
");
        StringBuffer_append(res->outputbuffer,
                            "
"
                            "
Copyright © 2001-2018 Tildeslash Ltd"
                            ". All Rights Reserved.

");
        StringBuffer_append(res->outputbuffer, "
");
        StringBuffer_append(res->outputbuffer,
                            "
This program is free software; you can redistribute it and/or "
                            "modify it under the terms of the GNU Affero General Public License version 3
"
                            "
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 AFFERO GENERAL PUBLIC LICENSE for more details.
");
        StringBuffer_append(res->outputbuffer,
                            "
[Back to Monit]
");
}


static void do_home_directory(HttpResponse res) {
        char buf[STRLEN];
        boolean_t on = true;
        boolean_t header = true;

        for (Service_T s = servicelist_conf; s; s = s->next_conf) {
                if (s->type != Service_Directory)
                        continue;
                if (header) {
                        StringBuffer_append(res->outputbuffer,
                                            "
"
                                            "
"
                                            "Directory	"
                                            "Status	"
                                            "Permission	"
                                            "UID	"
                                            "GID	"
                                            "
");
                        header = false;
                }
                StringBuffer_append(res->outputbuffer,
                                    "
"
                                    "%s	"
                                    "%s	",
                                    on ? "class='stripe'" : "",
                                    s->name, s->name,
                                    get_service_status(HTML, s, buf, sizeof(buf)));
                if (! Util_hasServiceStatus(s) || s->inf.directory->mode < 0)
                        StringBuffer_append(res->outputbuffer, "-	");
                else
                        StringBuffer_append(res->outputbuffer, "%04o	", s->inf.directory->mode & 07777);
                if (! Util_hasServiceStatus(s) || s->inf.directory->uid < 0)
                        StringBuffer_append(res->outputbuffer, "-	");
                else
                        StringBuffer_append(res->outputbuffer, "%d	", s->inf.directory->uid);
                if (! Util_hasServiceStatus(s) || s->inf.directory->gid < 0)
                        StringBuffer_append(res->outputbuffer, "-	");
                else
                        StringBuffer_append(res->outputbuffer, "%d	", s->inf.directory->gid);
                StringBuffer_append(res->outputbuffer, "
");
                on = ! on;
        }
        if (! header)
                StringBuffer_append(res->outputbuffer, "
");
}


static void do_service(HttpRequest req, HttpResponse res, Service_T s) {
        char buf[STRLEN];

        ASSERT(s);

        do_head(res, s->name, s->name, Run.polltime);
        StringBuffer_append(res->outputbuffer,
                            "
%s status
"
                            "
"
                            "
"
                            "Parameter	"
                            "Value	"
                            "
"
                            "
"
                            "Name	"
                            "%s	"
                            "
",
                            servicetypes[s->type],
                            s->name);
        if (s->type == Service_Process)
                StringBuffer_append(res->outputbuffer, "
%s	%s
", s->matchlist ? "Match" : "Pid file", s->path);
        else if (s->type == Service_Host)
                StringBuffer_append(res->outputbuffer, "
Address	%s
", s->path);
        else if (s->type == Service_Net)
                StringBuffer_append(res->outputbuffer, "
Interface	%s
", s->path);
        else if (s->type != Service_System)
                StringBuffer_append(res->outputbuffer, "
Path	%s
", s->path);
        StringBuffer_append(res->outputbuffer, "
Status	%s
", get_service_status(HTML, s, buf, sizeof(buf)));
        for (ServiceGroup_T sg = servicegrouplist; sg; sg = sg->next)
                for (list_t m = sg->members->head; m; m = m->next)
                        if (m->e == s)
                                StringBuffer_append(res->outputbuffer, "
Group	%s
", sg->name);
        StringBuffer_append(res->outputbuffer,
                            "
Monitoring status	%s
", get_monitoring_status(HTML, s, buf, sizeof(buf)));
        StringBuffer_append(res->outputbuffer,
                            "
Monitoring mode	%s
", modenames[s->mode]);
        StringBuffer_append(res->outputbuffer,
                            "
On reboot	%s
", onrebootnames[s->onreboot]);
        for (Dependant_T d = s->dependantlist; d; d = d->next) {
                if (d->dependant != NULL) {
                        StringBuffer_append(res->outputbuffer,
                                            "
Depends on service 	  %s
",
                                            d->dependant, d->dependant);
                }
        }
        if (s->start) {
                StringBuffer_append(res->outputbuffer, "
Start program	'%s'", Util_commandDescription(s->start, (char[STRLEN]){}));
                if (s->start->has_uid)
                        StringBuffer_append(res->outputbuffer, " as uid %d", s->start->uid);
                if (s->start->has_gid)
                        StringBuffer_append(res->outputbuffer, " as gid %d", s->start->gid);
                StringBuffer_append(res->outputbuffer, " timeout %s", Fmt_time2str(s->start->timeout, (char[11]){}));
                StringBuffer_append(res->outputbuffer, "
");
        }
        if (s->stop) {
                StringBuffer_append(res->outputbuffer, "
Stop program	'%s'", Util_commandDescription(s->stop, (char[STRLEN]){}));
                if (s->stop->has_uid)
                        StringBuffer_append(res->outputbuffer, " as uid %d", s->stop->uid);
                if (s->stop->has_gid)
                        StringBuffer_append(res->outputbuffer, " as gid %d", s->stop->gid);
                StringBuffer_append(res->outputbuffer, " timeout %s", Fmt_time2str(s->stop->timeout, (char[11]){}));
                StringBuffer_append(res->outputbuffer, "
");
        }
        if (s->restart) {
                StringBuffer_append(res->outputbuffer, "
Restart program	'%s'", Util_commandDescription(s->restart, (char[STRLEN]){}));
                if (s->restart->has_uid)
                        StringBuffer_append(res->outputbuffer, " as uid %d", s->restart->uid);
                if (s->restart->has_gid)
                        StringBuffer_append(res->outputbuffer, " as gid %d", s->restart->gid);
                StringBuffer_append(res->outputbuffer, " timeout %s", Fmt_time2str(s->restart->timeout, (char[11]){}));
                StringBuffer_append(res->outputbuffer, "
");
        }
        if (s->every.type != Every_Cycle) {
                StringBuffer_append(res->outputbuffer, "
Check service	");
                if (s->every.type == Every_SkipCycles)
                        StringBuffer_append(res->outputbuffer, "every %d cycle", s->every.spec.cycle.number);
                else if (s->every.type == Every_Cron)
                        StringBuffer_append(res->outputbuffer, "every \"%s\"", s->every.spec.cron);
                else if (s->every.type == Every_NotInCron)
                        StringBuffer_append(res->outputbuffer, "not every \"%s\"", s->every.spec.cron);
                StringBuffer_append(res->outputbuffer, "
");
        }
        _printStatus(HTML, res, s);
        // Rules
        print_service_rules_timeout(res, s);
        print_service_rules_nonexistence(res, s);
        print_service_rules_existence(res, s);
        print_service_rules_icmp(res, s);
        print_service_rules_port(res, s);
        print_service_rules_socket(res, s);
        print_service_rules_perm(res, s);
        print_service_rules_uid(res, s);
        print_service_rules_euid(res, s);
        print_service_rules_secattr(res, s);
        print_service_rules_gid(res, s);
        print_service_rules_timestamp(res, s);
        print_service_rules_fsflags(res, s);
        print_service_rules_filesystem(res, s);
        print_service_rules_size(res, s);
        print_service_rules_linkstatus(res, s);
        print_service_rules_linkspeed(res, s);
        print_service_rules_linksaturation(res, s);
        print_service_rules_uploadbytes(res, s);
        print_service_rules_uploadpackets(res, s);
        print_service_rules_downloadbytes(res, s);
        print_service_rules_downloadpackets(res, s);
        print_service_rules_uptime(res, s);
        print_service_rules_content(res, s);
        print_service_rules_checksum(res, s);
        print_service_rules_pid(res, s);
        print_service_rules_ppid(res, s);
        print_service_rules_program(res, s);
        print_service_rules_resource(res, s);
        print_alerts(res, s->maillist);
        StringBuffer_append(res->outputbuffer, "
");
        print_buttons(req, res, s);
        do_foot(res);
}


static void print_service_rules_timestamp(HttpResponse res, Service_T s) {
        for (Timestamp_T t = s->timestamplist; t; t = t->next) {
                StringBuffer_append(res->outputbuffer, "
%c%s	", toupper(timestampnames[t->type][0]), timestampnames[t->type] + 1);
                if (t->test_changes)
                        Util_printRule(res->outputbuffer, t->action, "If changed");
                else
                        Util_printRule(res->outputbuffer, t->action, "If %s %s", operatornames[t->operator], Fmt_time2str(t->time * 1000., (char[11]){}));
                StringBuffer_append(res->outputbuffer, "
");
        }
}


static void print_service_rules_uploadbytes(HttpResponse res, Service_T s) {
        for (Bandwidth_T bl = s->uploadbyteslist; bl; bl = bl->next) {
                if (bl->range == Time_Second) {
                        StringBuffer_append(res->outputbuffer, "
Upload bytes	");
                        Util_printRule(res->outputbuffer, bl->action, "If %s %s/s", operatornames[bl->operator], Fmt_bytes2str(bl->limit, (char[10]){}));
                } else {
                        StringBuffer_append(res->outputbuffer, "
Total upload bytes	");
                        Util_printRule(res->outputbuffer, bl->action, "If %s %s in last %d %s(s)", operatornames[bl->operator], Fmt_bytes2str(bl->limit, (char[10]){}), bl->rangecount, Util_timestr(bl->range));
                }
                StringBuffer_append(res->outputbuffer, "
");
        }
}


static void doGet(HttpRequest req, HttpResponse res) {
        set_content_type(res, "text/html");
        if (ACTION(HOME)) {
                LOCK(Run.mutex)
                do_home(res);
                END_LOCK;
        } else if (ACTION(RUNTIME)) {
                handle_runtime(req, res);
        } else if (ACTION(TEST)) {
                is_monit_running(res);
        } else if (ACTION(ABOUT)) {
                do_about(res);
        } else if (ACTION(FAVICON)) {
                printFavicon(res);
        } else if (ACTION(PING)) {
                do_ping(res);
        } else if (ACTION(GETID)) {
                do_getid(res);
        } else if (ACTION(STATUS)) {
                print_status(req, res, 1);
        } else if (ACTION(STATUS2)) {
                print_status(req, res, 2);
        } else if (ACTION(SUMMARY)) {
                print_summary(req, res);
        } else if (ACTION(REPORT)) {
                _printReport(req, res);
        } else {
                handle_service(req, res);
        }
}


static void print_service_rules_socket(HttpResponse res, Service_T s) {
        for (Port_T p = s->socketlist; p; p = p->next) {
                StringBuffer_append(res->outputbuffer, "
Unix Socket	");
                if (p->retry > 1)
                        Util_printRule(res->outputbuffer, p->action, "If failed %s type %s protocol %s with timeout %s and retry %d time(s)", p->target.unix.pathname, Util_portTypeDescription(p), p->protocol->name, Fmt_time2str(p->timeout, (char[11]){}), p->retry);
                else
                        Util_printRule(res->outputbuffer, p->action, "If failed %s type %s protocol %s with timeout %s", p->target.unix.pathname, Util_portTypeDescription(p), p->protocol->name, Fmt_time2str(p->timeout, (char[11]){}));
                StringBuffer_append(res->outputbuffer, "
");
        }
}


static void do_home_system(HttpResponse res) {
        Service_T s = Run.system;
        char buf[STRLEN];

        StringBuffer_append(res->outputbuffer,
                            "
"
                            "
"
                            "System	"
                            "Status	"
                            "Load	"
                            "CPU	"
                            "Memory	"
                            "Swap	"
                            "
"
                            "
"
                            "%s	"
                            "%s	"
                            "[%.2f] [%.2f] [%.2f]	"
                            ""
                            "%.1f%%us, %.1f%%sy"
#ifdef HAVE_CPU_WAIT
                            ", %.1f%%wa"
#endif
                            "	",
                            s->name, s->name,
                            get_service_status(HTML, s, buf, sizeof(buf)),
                            systeminfo.loadavg[0], systeminfo.loadavg[1], systeminfo.loadavg[2],
                            systeminfo.cpu.usage.user > 0. ? systeminfo.cpu.usage.user : 0.,
                            systeminfo.cpu.usage.system > 0. ? systeminfo.cpu.usage.system : 0.
#ifdef HAVE_CPU_WAIT
                            , systeminfo.cpu.usage.wait > 0. ? systeminfo.cpu.usage.wait : 0.
#endif
                            );
        StringBuffer_append(res->outputbuffer,
                            "%.1f%% [%s]	",
                            systeminfo.memory.usage.percent, Fmt_bytes2str(systeminfo.memory.usage.bytes, buf));
        StringBuffer_append(res->outputbuffer,
                            "%.1f%% [%s]	",
                            systeminfo.swap.usage.percent, Fmt_bytes2str(systeminfo.swap.usage.bytes, buf));
        StringBuffer_append(res->outputbuffer,
                            "
"
                            "
");
}


static void printFavicon(HttpResponse res) {
        static size_t l;
        Socket_T S = res->S;
        static unsigned char *favicon = NULL;

        if (! favicon) {
                favicon = CALLOC(sizeof(unsigned char), strlen(FAVICON_ICO));
                l = decode_base64(favicon, FAVICON_ICO);
        }
        if (l) {
                res->is_committed = true;
                Socket_print(S, "HTTP/1.0 200 OK\r\n");
                Socket_print(S, "Content-length: %lu\r\n", (unsigned long)l);
                Socket_print(S, "Content-Type: image/x-icon\r\n");
                Socket_print(S, "Connection: close\r\n\r\n");
                if (Socket_write(S, favicon, l) < 0) {
                        LogError("Error sending favicon data -- %s\n", STRERROR);
                }
        }
}


static void print_alerts(HttpResponse res, Mail_T s) {
        for (Mail_T r = s; r; r = r->next) {
                StringBuffer_append(res->outputbuffer,
                                    "
Alert mail to	"
                                    "%s
", r->to ? r->to : "");
                StringBuffer_append(res->outputbuffer, "
Alert on	");
                if (r->events == Event_Null) {
                        StringBuffer_append(res->outputbuffer, "No events");
                } else if (r->events == Event_All) {
                        StringBuffer_append(res->outputbuffer, "All events");
                } else {
                        if (IS_EVENT_SET(r->events, Event_Action))
                                StringBuffer_append(res->outputbuffer, "Action ");
                        if (IS_EVENT_SET(r->events, Event_ByteIn))
                                StringBuffer_append(res->outputbuffer, "ByteIn ");
                        if (IS_EVENT_SET(r->events, Event_ByteOut))
                                StringBuffer_append(res->outputbuffer, "ByteOut ");
                        if (IS_EVENT_SET(r->events, Event_Checksum))
                                StringBuffer_append(res->outputbuffer, "Checksum ");
                        if (IS_EVENT_SET(r->events, Event_Connection))
                                StringBuffer_append(res->outputbuffer, "Connection ");
                        if (IS_EVENT_SET(r->events, Event_Content))
                                StringBuffer_append(res->outputbuffer, "Content ");
                        if (IS_EVENT_SET(r->events, Event_Data))
                                StringBuffer_append(res->outputbuffer, "Data ");
                        if (IS_EVENT_SET(r->events, Event_Exec))
                                StringBuffer_append(res->outputbuffer, "Exec ");
                        if (IS_EVENT_SET(r->events, Event_Exist))
                                StringBuffer_append(res->outputbuffer, "Exist ");
                        if (IS_EVENT_SET(r->events, Event_FsFlag))
                                StringBuffer_append(res->outputbuffer, "Fsflags ");
                        if (IS_EVENT_SET(r->events, Event_Gid))
                                StringBuffer_append(res->outputbuffer, "Gid ");
                        if (IS_EVENT_SET(r->events, Event_Instance))
                                StringBuffer_append(res->outputbuffer, "Instance ");
                        if (IS_EVENT_SET(r->events, Event_Invalid))
                                StringBuffer_append(res->outputbuffer, "Invalid ");
                        if (IS_EVENT_SET(r->events, Event_Link))
                                StringBuffer_append(res->outputbuffer, "Link ");
                        if (IS_EVENT_SET(r->events, Event_NonExist))
                                StringBuffer_append(res->outputbuffer, "Nonexist ");
                        if (IS_EVENT_SET(r->events, Event_Permission))
                                StringBuffer_append(res->outputbuffer, "Permission ");
                        if (IS_EVENT_SET(r->events, Event_PacketIn))
                                StringBuffer_append(res->outputbuffer, "PacketIn ");
                        if (IS_EVENT_SET(r->events, Event_PacketOut))
                                StringBuffer_append(res->outputbuffer, "PacketOut ");
                        if (IS_EVENT_SET(r->events, Event_Pid))
                                StringBuffer_append(res->outputbuffer, "PID ");
                        if (IS_EVENT_SET(r->events, Event_Icmp))
                                StringBuffer_append(res->outputbuffer, "Ping ");
                        if (IS_EVENT_SET(r->events, Event_PPid))
                                StringBuffer_append(res->outputbuffer, "PPID ");
                        if (IS_EVENT_SET(r->events, Event_Resource))
                                StringBuffer_append(res->outputbuffer, "Resource ");
                        if (IS_EVENT_SET(r->events, Event_Saturation))
                                StringBuffer_append(res->outputbuffer, "Saturation ");
                        if (IS_EVENT_SET(r->events, Event_Size))
                                StringBuffer_append(res->outputbuffer, "Size ");
                        if (IS_EVENT_SET(r->events, Event_Speed))
                                StringBuffer_append(res->outputbuffer, "Speed ");
                        if (IS_EVENT_SET(r->events, Event_Status))
                                StringBuffer_append(res->outputbuffer, "Status ");
                        if (IS_EVENT_SET(r->events, Event_Timeout))
                                StringBuffer_append(res->outputbuffer, "Timeout ");
                        if (IS_EVENT_SET(r->events, Event_Timestamp))
                                StringBuffer_append(res->outputbuffer, "Timestamp ");
                        if (IS_EVENT_SET(r->events, Event_Uid))
                                StringBuffer_append(res->outputbuffer, "Uid ");
                        if (IS_EVENT_SET(r->events, Event_Uptime))
                                StringBuffer_append(res->outputbuffer, "Uptime ");
                }
                StringBuffer_append(res->outputbuffer, "
");
                if (r->reminder) {
                        StringBuffer_append(res->outputbuffer,
                                            "
Alert reminder	%u cycles
",
                                            r->reminder);
                }
        }
}


static char *get_service_status(Output_Type type, Service_T s, char *buf, int buflen) {
        ASSERT(s);
        ASSERT(buf);
        if (s->monitor == Monitor_Not || s->monitor & Monitor_Init) {
                get_monitoring_status(type, s, buf, buflen);
        } else if (s->error == 0) {
                snprintf(buf, buflen, type == HTML ? "OK" : Color_lightGreen("OK"));
        } else {
                // In the case that the service has actualy some failure, the error bitmap will be non zero
                char *p = buf;
                EventTable_T *et = Event_Table;
                while ((*et).id) {
                        if (s->error & (*et).id) {
                                if (p > buf)
                                        p += snprintf(p, buflen - (p - buf), " | ");
                                if (s->error_hint & (*et).id) {
                                        if (type == HTML)
                                                p += snprintf(p, buflen - (p - buf), "%s", (*et).description_changed);
                                        else
                                                p += snprintf(p, buflen - (p - buf), Color_lightYellow("%s", (*et).description_changed));
                                } else {
                                        if (type == HTML)
                                                p += snprintf(p, buflen - (p - buf), "%s", (*et).description_failed);
                                        else
                                                p += snprintf(p, buflen - (p - buf), Color_lightRed("%s", (*et).description_failed));
                                }
                        }
                        et++;
                }
        }
        if (s->doaction)
                snprintf(buf + strlen(buf), buflen - strlen(buf) - 1, " - %s pending", actionnames[s->doaction]);
        return buf;
}


static void do_home_fifo(HttpResponse res) {
        char buf[STRLEN];
        boolean_t on = true;
        boolean_t header = true;

        for (Service_T s = servicelist_conf; s; s = s->next_conf) {
                if (s->type != Service_Fifo)
                        continue;
                if (header) {
                        StringBuffer_append(res->outputbuffer,
                                            "
"
                                            "
"
                                            "Fifo	"
                                            "Status	"
                                            "Permission	"
                                            "UID	"
                                            "GID	"
                                            "
");
                        header = false;
                }
                StringBuffer_append(res->outputbuffer,
                                    "
"
                                    "%s	"
                                    "%s	",
                                    on ? "class='stripe'" : "",
                                    s->name, s->name,
                                    get_service_status(HTML, s, buf, sizeof(buf)));
                if (! Util_hasServiceStatus(s) || s->inf.fifo->mode < 0)
                        StringBuffer_append(res->outputbuffer, "-	");
                else
                        StringBuffer_append(res->outputbuffer, "%04o	", s->inf.fifo->mode & 07777);
                if (! Util_hasServiceStatus(s) || s->inf.fifo->uid < 0)
                        StringBuffer_append(res->outputbuffer, "-	");
                else
                        StringBuffer_append(res->outputbuffer, "%d	", s->inf.fifo->uid);
                if (! Util_hasServiceStatus(s) || s->inf.fifo->gid < 0)
                        StringBuffer_append(res->outputbuffer, "-	");
                else
                        StringBuffer_append(res->outputbuffer, "%d	", s->inf.fifo->gid);
                StringBuffer_append(res->outputbuffer, "
");
                on = ! on;
        }
        if (! header)
                StringBuffer_append(res->outputbuffer, "
");
}


static void handle_service_action(HttpRequest req, HttpResponse res) {
        char *name = req->url;
        if (! name) {
                send_error(req, res, SC_NOT_FOUND, "Service name required");
                return;
        }
        Service_T s = Util_getService(++name);
        if (! s) {
                send_error(req, res, SC_NOT_FOUND, "There is no service named \"%s\"", name);
                return;
        }
        const char *action = get_parameter(req, "action");
        if (action) {
                if (is_readonly(req)) {
                        send_error(req, res, SC_FORBIDDEN, "You do not have sufficient privileges to access this page");
                        return;
                }
                Action_Type doaction = Util_getAction(action);
                if (doaction == Action_Ignored) {
                        send_error(req, res, SC_BAD_REQUEST, "Invalid action \"%s\"", action);
                        return;
                }
                s->doaction = doaction;
                const char *token = get_parameter(req, "token");
                if (token) {
                        FREE(s->token);
                        s->token = Str_dup(token);
                }
                LogInfo("'%s' %s on user request\n", s->name, action);
                Run.flags |= Run_ActionPending; /* set the global flag */
                do_wakeupcall();
        }
        do_service(req, res, s);
}


static void print_service_rules_port(HttpResponse res, Service_T s) {
        for (Port_T p = s->portlist; p; p = p->next) {
                StringBuffer_append(res->outputbuffer, "
Port	");
                StringBuffer_T buf = StringBuffer_create(64);
                StringBuffer_append(buf, "If failed [%s]:%d%s",
                        p->hostname, p->target.net.port, Util_portRequestDescription(p));
                if (p->outgoing.ip)
                        StringBuffer_append(buf, " via address %s", p->outgoing.ip);
                StringBuffer_append(buf, " type %s/%s protocol %s with timeout %s",
                        Util_portTypeDescription(p), Util_portIpDescription(p), p->protocol->name, Fmt_time2str(p->timeout, (char[11]){}));
                if (p->retry > 1)
                        StringBuffer_append(buf, " and retry %d times", p->retry);
#ifdef HAVE_OPENSSL
                if (p->target.net.ssl.options.flags) {
                        StringBuffer_append(buf, " using TLS");
                        const char *options = Ssl_printOptions(&p->target.net.ssl.options, (char[STRLEN]){}, STRLEN);
                        if (options && *options)
                                StringBuffer_append(buf, " with options {%s}", options);
                        if (p->target.net.ssl.certificate.minimumDays > 0)
                                StringBuffer_append(buf, " and certificate valid for at least %d days", p->target.net.ssl.certificate.minimumDays);
                        if (p->target.net.ssl.options.checksum)
                                StringBuffer_append(buf, " and certificate checksum %s equal to '%s'", checksumnames[p->target.net.ssl.options.checksumType], p->target.net.ssl.options.checksum);
                }
#endif
                Util_printRule(res->outputbuffer, p->action, "%s", StringBuffer_toString(buf));
                StringBuffer_free(&buf);
                StringBuffer_append(res->outputbuffer, "
");
        }
}


static void do_viewlog(HttpRequest req, HttpResponse res) {
        if (is_readonly(req)) {
                send_error(req, res, SC_FORBIDDEN, "You do not have sufficient privileges to access this page");
                return;
        }
        do_head(res, "_viewlog", "View log", 100);
        if ((Run.flags & Run_Log) && ! (Run.flags & Run_UseSyslog)) {
                FILE *f = fopen(Run.files.log, "r");
                if (f) {
                        size_t n;
                        char buf[512];
                        StringBuffer_append(res->outputbuffer, "

<form><textarea cols=120 rows=30 readonly>");
                        while ((n = fread(buf, sizeof(char), sizeof(buf) - 1, f)) > 0) {
                                buf[n] = 0;
                                escapeHTML(res->outputbuffer, buf);
                        }
                        fclose(f);
                        StringBuffer_append(res->outputbuffer, "</textarea></form>");
                } else {
                        StringBuffer_append(res->outputbuffer, "Error opening logfile: %s", STRERROR);
                }
        } else {
                StringBuffer_append(res->outputbuffer,
                                    "Cannot view logfile:
");
                if (! (Run.flags & Run_Log))
                        StringBuffer_append(res->outputbuffer, "Monit was started without logging");
                else
                        StringBuffer_append(res->outputbuffer, "Monit uses syslog");
        }
        do_foot(res);
}


static void print_service_rules_program(HttpResponse res, Service_T s) {
        if (s->type == Service_Program) {
                StringBuffer_append(res->outputbuffer, "
Program timeout	Terminate the program if not finished within %s
", Fmt_time2str(s->program->timeout, (char[11]){}));
                for (Status_T status = s->statuslist; status; status = status->next) {
                        StringBuffer_append(res->outputbuffer, "
Test Exit value	");
                        if (status->operator == Operator_Changed)
                                Util_printRule(res->outputbuffer, status->action, "If exit value changed");
                        else
                                Util_printRule(res->outputbuffer, status->action, "If exit value %s %d", operatorshortnames[status->operator], status->return_value);
                        StringBuffer_append(res->outputbuffer, "
");
                }
        }
}


static void _printIOStatistics(Output_Type type, HttpResponse res, Service_T s, IOStatistics_T io, const char *header, const char *name) {
        boolean_t hasOps = Statistics_initialized(&(io->operations));
        boolean_t hasBytes = Statistics_initialized(&(io->bytes));
        if (hasOps && hasBytes) {
                double deltaBytesPerSec = Statistics_deltaNormalize(&(io->bytes));
                double deltaOpsPerSec = Statistics_deltaNormalize(&(io->operations));
                _formatStatus(header, Event_Resource, type, res, s, true, "%s/s [%s total], %.1f %ss/s [%"PRIu64" %ss total]", Fmt_bytes2str(deltaBytesPerSec, (char[10]){}), Fmt_bytes2str(Statistics_raw(&(io->bytes)), (char[10]){}), deltaOpsPerSec, name, Statistics_raw(&(io->operations)), name);
        } else if (hasOps) {
                double deltaOpsPerSec = Statistics_deltaNormalize(&(io->operations));
                _formatStatus(header, Event_Resource, type, res, s, true, "%.1f %ss/s [%"PRIu64" %ss total]", deltaOpsPerSec, name, Statistics_raw(&(io->operations)), name);
        } else if (hasBytes) {
                double deltaBytesPerSec = Statistics_deltaNormalize(&(io->bytes));
                _formatStatus(header, Event_Resource, type, res, s, true, "%s/s [%s total]", Fmt_bytes2str(deltaBytesPerSec, (char[10]){}), Fmt_bytes2str(Statistics_raw(&(io->bytes)), (char[10]){}));
        }
}


static void handle_service(HttpRequest req, HttpResponse res) {
        char *name = req->url;
        if (! name) {
                send_error(req, res, SC_NOT_FOUND, "Service name required");
                return;
        }
        Service_T s = Util_getService(++name);
        if (! s) {
                send_error(req, res, SC_NOT_FOUND, "There is no service named \"%s\"", name);
                return;
        }
        do_service(req, res, s);
}


static void do_home_host(HttpResponse res) {
        char buf[STRLEN];
        boolean_t on = true;
        boolean_t header = true;

        for (Service_T s = servicelist_conf; s; s = s->next_conf) {
                if (s->type != Service_Host)
                        continue;
                if (header) {
                        StringBuffer_append(res->outputbuffer,
                                            "
"
                                            "
"
                                            "Host	"
                                            "Status	"
                                            "Protocol(s)	"
                                            "
");
                        header = false;
                }
                StringBuffer_append(res->outputbuffer,
                                    "
"
                                    "%s	"
                                    "%s	",
                                    on ? "class='stripe'" : "",
                                    s->name, s->name,
                                    get_service_status(HTML, s, buf, sizeof(buf)));
                if (! Util_hasServiceStatus(s)) {
                        StringBuffer_append(res->outputbuffer,
                                            "-	");
                } else {
                        StringBuffer_append(res->outputbuffer,
                                            "");
                        for (Icmp_T icmp = s->icmplist; icmp; icmp = icmp->next) {
                                if (icmp != s->icmplist)
                                        StringBuffer_append(res->outputbuffer, "  |  ");
                                switch (icmp->is_available) {
                                        case Connection_Init:
                                                StringBuffer_append(res->outputbuffer, "[Ping]");
                                                break;
                                        case Connection_Failed:
                                                StringBuffer_append(res->outputbuffer, "[Ping]");
                                                break;
                                        default:
                                                StringBuffer_append(res->outputbuffer, "[Ping]");
                                                break;
                                }
                        }
                        if (s->icmplist && s->portlist)
                                StringBuffer_append(res->outputbuffer, "  |  ");
                        for (Port_T port = s->portlist; port; port = port->next) {
                                if (port != s->portlist)
                                        StringBuffer_append(res->outputbuffer, "  |  ");
                                switch (port->is_available) {
                                        case Connection_Init:
                                                StringBuffer_append(res->outputbuffer, "[%s] at port %d", port->protocol->name, port->target.net.port);
                                                break;
                                        case Connection_Failed:
                                                StringBuffer_append(res->outputbuffer, "[%s] at port %d", port->protocol->name, port->target.net.port);
                                                break;
                                        default:
                                                if (port->target.net.ssl.options.flags && port->target.net.ssl.certificate.validDays < port->target.net.ssl.certificate.minimumDays)
                                                        StringBuffer_append(res->outputbuffer, "[%s] at port %d", port->protocol->name, port->target.net.port);
                                                else
                                                        StringBuffer_append(res->outputbuffer, "[%s] at port %d", port->protocol->name, port->target.net.port);
                                                break;
                                }
                        }
                        StringBuffer_append(res->outputbuffer, "	");
                }
                StringBuffer_append(res->outputbuffer, "
");
                on = ! on;
        }
        if (! header)
                StringBuffer_append(res->outputbuffer, "
");
}


static void print_service_rules_secattr(HttpResponse res, Service_T s) {
        for (SecurityAttribute_T a = s->secattrlist; a; a = a->next) {
                StringBuffer_append(res->outputbuffer, "
Security attribute	");
                Util_printRule(res->outputbuffer, a->action, "If failed %s", a->attribute);
                StringBuffer_append(res->outputbuffer, "
");
         }
}


static void do_home_process(HttpResponse res) {
        char      buf[STRLEN];
        boolean_t on = true;
        boolean_t header = true;

        for (Service_T s = servicelist_conf; s; s = s->next_conf) {
                if (s->type != Service_Process)
                        continue;
                if (header) {
                        StringBuffer_append(res->outputbuffer,
                                            "
"
                                            "
"
                                            "Process	"
                                            "Status	"
                                            "Uptime	"
                                            "CPU Total	"
                                            "Memory Total	"
                                            "Read	"
                                            "Write	"
                                            "
");
                        header = false;
                }
                StringBuffer_append(res->outputbuffer,
                                    "
"
                                    "%s	"
                                    "%s	",
                                    on ? " class='stripe'" : "",
                                    s->name, s->name,
                                    get_service_status(HTML, s, buf, sizeof(buf)));
                if (! (Run.flags & Run_ProcessEngineEnabled) || ! Util_hasServiceStatus(s) || s->inf.process->uptime < 0) {
                        StringBuffer_append(res->outputbuffer, "-	");
                } else {
                        StringBuffer_append(res->outputbuffer, "%s	", _getUptime(s->inf.process->uptime, (char[256]){}));
                }
                if (! (Run.flags & Run_ProcessEngineEnabled) || ! Util_hasServiceStatus(s) || s->inf.process->total_cpu_percent < 0) {
                                StringBuffer_append(res->outputbuffer, "-	");
                } else {
                        StringBuffer_append(res->outputbuffer, "%.1f%%	", (s->error & Event_Resource) ? " red-text" : "", s->inf.process->total_cpu_percent);
                }
                if (! (Run.flags & Run_ProcessEngineEnabled) || ! Util_hasServiceStatus(s) || s->inf.process->total_mem_percent < 0) {
                        StringBuffer_append(res->outputbuffer, "-	");
                } else {
                        StringBuffer_append(res->outputbuffer, "%.1f%% [%s]	", (s->error & Event_Resource) ? " red-text" : "", s->inf.process->total_mem_percent, Fmt_bytes2str(s->inf.process->total_mem, buf));
                }
                boolean_t hasReadBytes = Statistics_initialized(&(s->inf.process->read.bytes));
                boolean_t hasReadOperations = Statistics_initialized(&(s->inf.process->read.operations));
                if (! (Run.flags & Run_ProcessEngineEnabled) || ! Util_hasServiceStatus(s) || (! hasReadBytes && ! hasReadOperations)) {
                        StringBuffer_append(res->outputbuffer, "-	");
                } else if (hasReadBytes) {
                        StringBuffer_append(res->outputbuffer, "%s/s	", (s->error & Event_Resource) ? " red-text" : "", Fmt_bytes2str(Statistics_deltaNormalize(&(s->inf.process->read.bytes)), (char[10]){}));
                } else if (hasReadOperations) {
                        StringBuffer_append(res->outputbuffer, "%.1f/s	", (s->error & Event_Resource) ? " red-text" : "", Statistics_deltaNormalize(&(s->inf.process->read.operations)));
                }
                boolean_t hasWriteBytes = Statistics_initialized(&(s->inf.process->write.bytes));
                boolean_t hasWriteOperations = Statistics_initialized(&(s->inf.process->write.operations));
                if (! (Run.flags & Run_ProcessEngineEnabled) || ! Util_hasServiceStatus(s) || (! hasWriteBytes && ! hasWriteOperations)) {
                        StringBuffer_append(res->outputbuffer, "-	");
                } else if (hasWriteBytes) {
                        StringBuffer_append(res->outputbuffer, "%s/s	", (s->error & Event_Resource) ? " red-text" : "", Fmt_bytes2str(Statistics_deltaNormalize(&(s->inf.process->write.bytes)), (char[10]){}));
                } else if (hasWriteOperations) {
                        StringBuffer_append(res->outputbuffer, "%.1f/s	", (s->error & Event_Resource) ? " red-text" : "", Statistics_deltaNormalize(&(s->inf.process->write.operations)));
                }
                StringBuffer_append(res->outputbuffer, "
");
                on = ! on;
        }
        if (! header)
                StringBuffer_append(res->outputbuffer, "
");
}


static void do_home_filesystem(HttpResponse res) {
        char buf[STRLEN];
        boolean_t on = true;
        boolean_t header = true;

        for (Service_T s = servicelist_conf; s; s = s->next_conf) {
                if (s->type != Service_Filesystem)
                        continue;
                if (header) {
                        StringBuffer_append(res->outputbuffer,
                                            "
"
                                            "
"
                                            "Filesystem	"
                                            "Status	"
                                            "Space usage	"
                                            "Inodes usage	"
                                            "Read	"
                                            "Write	"
                                            "
");
                        header = false;
                }
                StringBuffer_append(res->outputbuffer,
                                    "
"
                                    "%s	"
                                    "%s	",
                                    on ? "class='stripe'" : "",
                                    s->name, s->name,
                                    get_service_status(HTML, s, buf, sizeof(buf)));
                if (! Util_hasServiceStatus(s)) {
                        StringBuffer_append(res->outputbuffer,
                                            "- [-]	"
                                            "- [-]	"
                                            "- [-]	"
                                            "- [-]	");
                } else {
                        StringBuffer_append(res->outputbuffer,
                                            "%.1f%% [%s]	",
                                            (s->error & Event_Resource) ? " red-text" : "",
                                            s->inf.filesystem->space_percent,
                                            s->inf.filesystem->f_bsize > 0 ? Fmt_bytes2str(s->inf.filesystem->f_blocksused * s->inf.filesystem->f_bsize, buf) : "0 MB");
                        if (s->inf.filesystem->f_files > 0) {
                                StringBuffer_append(res->outputbuffer,
                                                    "%.1f%% [%lld objects]	",
                                                    (s->error & Event_Resource) ? " red-text" : "",
                                                    s->inf.filesystem->inode_percent,
                                                    s->inf.filesystem->f_filesused);
                        } else {
                                StringBuffer_append(res->outputbuffer,
                                                    "not supported by filesystem	");
                        }
                        StringBuffer_append(res->outputbuffer,
                                            "%s/s	"
                                            "%s/s	",
                                            (s->error & Event_Resource) ? " red-text" : "",
                                            Fmt_bytes2str(Statistics_deltaNormalize(&(s->inf.filesystem->read.bytes)), (char[10]){}),
                                            (s->error & Event_Resource) ? " red-text" : "",
                                            Fmt_bytes2str(Statistics_deltaNormalize(&(s->inf.filesystem->write.bytes)), (char[10]){}));
                }
                StringBuffer_append(res->outputbuffer, "
");
                on = ! on;
        }
        if (! header)
                StringBuffer_append(res->outputbuffer, "
");
}