/*
- * Copyright (c) 2009, 2010 Nicira Networks.
+ * Copyright (c) 2009, 2010, 2011 Nicira Networks.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include "ovsdb-idl.h"
#include "poll-loop.h"
#include "process.h"
+#include "stream.h"
#include "stream-ssl.h"
#include "svec.h"
#include "vswitchd/vswitch-idl.h"
+#include "table.h"
#include "timeval.h"
#include "util.h"
#include "vlog.h"
void (*prerequisites)(struct vsctl_context *ctx);
/* Does the actual work of the command and puts the command's output, if
- * any, in ctx->output.
+ * any, in ctx->output or ctx->table.
*
* Alternatively, if some prerequisite of the command is not met and the
* caller should wait for something to change and then retry, it may set
/* Data modified by commands. */
struct ds output;
+ struct table *table;
};
/* --db: The database server to contact. */
/* --timeout: Time to wait for a connection to 'db'. */
static int timeout;
+/* Format for table output. */
+static struct table_style table_style = TABLE_STYLE_DEFAULT;
+
/* All supported commands. */
static const struct vsctl_command_syntax all_commands[];
const struct ovsdb_idl_row *, const char *arg,
struct ovsdb_symbol_table *);
+static bool is_condition_satisfied(const struct vsctl_table_class *,
+ const struct ovsdb_idl_row *,
+ const char *arg,
+ struct ovsdb_symbol_table *);
+
int
main(int argc, char *argv[])
{
OPT_NO_WAIT,
OPT_DRY_RUN,
OPT_PEER_CA_CERT,
- VLOG_OPTION_ENUMS
+ VLOG_OPTION_ENUMS,
+ TABLE_OPTION_ENUMS
};
static struct option long_options[] = {
{"db", required_argument, 0, OPT_DB},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'V'},
VLOG_LONG_OPTIONS,
+ TABLE_LONG_OPTIONS,
#ifdef HAVE_OPENSSL
STREAM_SSL_LONG_OPTIONS
{"peer-ca-cert", required_argument, 0, OPT_PEER_CA_CERT},
short_options = xasprintf("+%s", tmp);
free(tmp);
+ table_style.format = TF_LIST;
+
for (;;) {
int c;
break;
VLOG_OPTION_HANDLERS
+ TABLE_OPTION_HANDLERS(&table_style)
#ifdef HAVE_OPENSSL
STREAM_SSL_OPTION_HANDLERS
%s: ovs-vswitchd management utility\n\
usage: %s [OPTIONS] COMMAND [ARG...]\n\
\n\
+Open vSwitch commands:\n\
+ init initialize database, if not yet initialized\n\
+ emer-reset reset configuration to clean state\n\
+\n\
Bridge commands:\n\
add-br BRIDGE create a new bridge named BRIDGE\n\
add-br BRIDGE PARENT VLAN create new fake BRIDGE in PARENT on VLAN\n\
br-get-external-id BRIDGE KEY print value of KEY on BRIDGE\n\
br-get-external-id BRIDGE list key-value pairs on BRIDGE\n\
\n\
-Port commands:\n\
+Port commands (a bond is considered to be a single port):\n\
list-ports BRIDGE print the names of all the ports on BRIDGE\n\
add-port BRIDGE PORT add network device PORT to BRIDGE\n\
add-bond BRIDGE PORT IFACE... add bonded port PORT in BRIDGE from IFACES\n\
del-port [BRIDGE] PORT delete PORT (which may be bonded) from BRIDGE\n\
port-to-br PORT print name of bridge that contains PORT\n\
-A bond is considered to be a single port.\n\
\n\
Interface commands (a bond consists of multiple interfaces):\n\
list-ifaces BRIDGE print the names of all interfaces on BRIDGE\n\
del-fail-mode BRIDGE delete the fail-mode for BRIDGE\n\
set-fail-mode BRIDGE MODE set the fail-mode for BRIDGE to MODE\n\
\n\
+Manager commands:\n\
+ get-manager print all manager(s)\n\
+ del-manager delete all manager(s)\n\
+ set-manager TARGET... set the list of manager(s) to TARGET(s)\n\
+\n\
SSL commands:\n\
get-ssl print the SSL configuration\n\
del-ssl delete the SSL configuration\n\
\n\
Database commands:\n\
list TBL [REC] list RECord (or all records) in TBL\n\
+ find TBL CONDITION... list records satisfying CONDITION in TBL\n\
get TBL REC COL[:KEY] print values of COLumns in RECord in TBL\n\
set TBL REC COL[:KEY]=VALUE set COLumn values in RECord in TBL\n\
add TBL REC COL [KEY=]VALUE add (KEY=)VALUE to COLumn in RECord in TBL\n\
Options:\n\
--db=DATABASE connect to DATABASE\n\
(default: %s)\n\
+ --no-wait do not wait for ovs-vswitchd to reconfigure\n\
+ -t, --timeout=SECS wait at most SECS seconds for ovs-vswitchd\n\
+ --dry-run do not commit changes to database\n\
--oneline print exactly one line of output per command\n",
program_name, program_name, default_db());
vlog_usage();
+ printf("\
+ --no-syslog equivalent to --verbose=vsctl:syslog:warn\n");
+ stream_usage("database", true, true, false);
printf("\n\
Other options:\n\
-h, --help display this help message\n\
{
static char *def;
if (!def) {
- def = xasprintf("unix:%s/db.sock", ovs_rundir);
+ def = xasprintf("unix:%s/db.sock", ovs_rundir());
}
return def;
}
/* Modifiable state. */
struct ds output;
+ struct table *table;
struct ovsdb_idl *idl;
struct ovsdb_idl_txn *txn;
struct ovsdb_symbol_table *symtab;
static void
pre_cmd_emer_reset(struct vsctl_context *ctx)
{
- ovsdb_idl_add_column(ctx->idl, &ovsrec_open_vswitch_col_managers);
+ ovsdb_idl_add_column(ctx->idl, &ovsrec_open_vswitch_col_manager_options);
ovsdb_idl_add_column(ctx->idl, &ovsrec_open_vswitch_col_ssl);
ovsdb_idl_add_column(ctx->idl, &ovsrec_bridge_col_controller);
+ ovsdb_idl_add_column(ctx->idl, &ovsrec_bridge_col_fail_mode);
ovsdb_idl_add_column(ctx->idl, &ovsrec_bridge_col_mirrors);
ovsdb_idl_add_column(ctx->idl, &ovsrec_bridge_col_netflow);
ovsdb_idl_add_column(ctx->idl, &ovsrec_bridge_col_sflow);
const struct ovsrec_interface *iface;
const struct ovsrec_mirror *mirror, *next_mirror;
const struct ovsrec_controller *ctrl, *next_ctrl;
+ const struct ovsrec_manager *mgr, *next_mgr;
const struct ovsrec_netflow *nf, *next_nf;
const struct ovsrec_ssl *ssl, *next_ssl;
const struct ovsrec_sflow *sflow, *next_sflow;
/* Reset the Open_vSwitch table. */
- ovsrec_open_vswitch_set_managers(ctx->ovs, NULL, 0);
+ ovsrec_open_vswitch_set_manager_options(ctx->ovs, NULL, 0);
ovsrec_open_vswitch_set_ssl(ctx->ovs, NULL);
OVSREC_BRIDGE_FOR_EACH (br, idl) {
char *hw_val = NULL;
ovsrec_bridge_set_controller(br, NULL, 0);
+ ovsrec_bridge_set_fail_mode(br, NULL);
ovsrec_bridge_set_mirrors(br, NULL, 0);
ovsrec_bridge_set_netflow(br, NULL);
ovsrec_bridge_set_sflow(br, NULL);
ovsrec_controller_delete(ctrl);
}
+ OVSREC_MANAGER_FOR_EACH_SAFE (mgr, next_mgr, idl) {
+ ovsrec_manager_delete(mgr);
+ }
+
OVSREC_NETFLOW_FOR_EACH_SAFE (nf, next_nf, idl) {
ovsrec_netflow_delete(nf);
}
}
}
+static void
+pre_controller(struct vsctl_context *ctx)
+{
+ pre_get_info(ctx);
+
+ ovsdb_idl_add_column(ctx->idl, &ovsrec_controller_col_target);
+}
+
static void
cmd_get_controller(struct vsctl_context *ctx)
{
free_info(&info);
}
+static void
+verify_managers(const struct ovsrec_open_vswitch *ovs)
+{
+ size_t i;
+
+ ovsrec_open_vswitch_verify_manager_options(ovs);
+
+ for (i = 0; i < ovs->n_manager_options; ++i) {
+ const struct ovsrec_manager *mgr = ovs->manager_options[i];
+
+ ovsrec_manager_verify_target(mgr);
+ }
+}
+
+static void
+pre_manager(struct vsctl_context *ctx)
+{
+ ovsdb_idl_add_column(ctx->idl, &ovsrec_open_vswitch_col_manager_options);
+ ovsdb_idl_add_column(ctx->idl, &ovsrec_manager_col_target);
+}
+
+static void
+cmd_get_manager(struct vsctl_context *ctx)
+{
+ const struct ovsrec_open_vswitch *ovs = ctx->ovs;
+ struct svec targets;
+ size_t i;
+
+ verify_managers(ovs);
+
+ /* Print the targets in sorted order for reproducibility. */
+ svec_init(&targets);
+
+ for (i = 0; i < ovs->n_manager_options; i++) {
+ svec_add(&targets, ovs->manager_options[i]->target);
+ }
+
+ svec_sort_unique(&targets);
+ for (i = 0; i < targets.n; i++) {
+ ds_put_format(&ctx->output, "%s\n", targets.names[i]);
+ }
+ svec_destroy(&targets);
+}
+
+static void
+delete_managers(const struct vsctl_context *ctx)
+{
+ const struct ovsrec_open_vswitch *ovs = ctx->ovs;
+ size_t i;
+
+ /* Delete Manager rows pointed to by 'manager_options' column. */
+ for (i = 0; i < ovs->n_manager_options; i++) {
+ ovsrec_manager_delete(ovs->manager_options[i]);
+ }
+
+ /* Delete 'Manager' row refs in 'manager_options' column. */
+ ovsrec_open_vswitch_set_manager_options(ovs, NULL, 0);
+}
+
+static void
+cmd_del_manager(struct vsctl_context *ctx)
+{
+ const struct ovsrec_open_vswitch *ovs = ctx->ovs;
+
+ verify_managers(ovs);
+ delete_managers(ctx);
+}
+
+static void
+insert_managers(struct vsctl_context *ctx, char *targets[], size_t n)
+{
+ struct ovsrec_manager **managers;
+ size_t i;
+
+ /* Insert each manager in a new row in Manager table. */
+ managers = xmalloc(n * sizeof *managers);
+ for (i = 0; i < n; i++) {
+ managers[i] = ovsrec_manager_insert(ctx->txn);
+ ovsrec_manager_set_target(managers[i], targets[i]);
+ }
+
+ /* Store uuids of new Manager rows in 'manager_options' column. */
+ ovsrec_open_vswitch_set_manager_options(ctx->ovs, managers, n);
+ free(managers);
+}
+
+static void
+cmd_set_manager(struct vsctl_context *ctx)
+{
+ const size_t n = ctx->argc - 1;
+
+ verify_managers(ctx->ovs);
+ delete_managers(ctx);
+ insert_managers(ctx, &ctx->argv[1], n);
+}
+
static void
pre_cmd_get_ssl(struct vsctl_context *ctx)
{
{{&ovsrec_table_port, &ovsrec_port_col_name, &ovsrec_port_col_qos},
{NULL, NULL, NULL}}},
+ {&ovsrec_table_monitor,
+ {{&ovsrec_table_interface,
+ &ovsrec_interface_col_name,
+ &ovsrec_interface_col_monitor},
+ {NULL, NULL, NULL}}},
+
+ {&ovsrec_table_maintenance_point,
+ {{NULL, NULL, NULL},
+ {NULL, NULL, NULL}}},
+
{&ovsrec_table_queue,
{{NULL, NULL, NULL},
{NULL, NULL, NULL}}},
}
}
+static void
+parse_column_names(const char *column_names,
+ const struct vsctl_table_class *table,
+ const struct ovsdb_idl_column ***columnsp,
+ size_t *n_columnsp)
+{
+ const struct ovsdb_idl_column **columns;
+ size_t n_columns;
+
+ if (!column_names) {
+ size_t i;
+
+ n_columns = table->class->n_columns + 1;
+ columns = xmalloc(n_columns * sizeof *columns);
+ columns[0] = NULL;
+ for (i = 0; i < table->class->n_columns; i++) {
+ columns[i + 1] = &table->class->columns[i];
+ }
+ } else {
+ char *s = xstrdup(column_names);
+ size_t allocated_columns;
+ char *save_ptr = NULL;
+ char *column_name;
+
+ columns = NULL;
+ allocated_columns = n_columns = 0;
+ for (column_name = strtok_r(s, ", ", &save_ptr); column_name;
+ column_name = strtok_r(NULL, ", ", &save_ptr)) {
+ const struct ovsdb_idl_column *column;
+
+ if (!strcasecmp(column_name, "_uuid")) {
+ column = NULL;
+ } else {
+ die_if_error(get_column(table, column_name, &column));
+ }
+ if (n_columns >= allocated_columns) {
+ columns = x2nrealloc(columns, &allocated_columns,
+ sizeof *columns);
+ }
+ columns[n_columns++] = column;
+ }
+ free(s);
+
+ if (!n_columns) {
+ vsctl_fatal("must specify at least one column name");
+ }
+ }
+ *columnsp = columns;
+ *n_columnsp = n_columns;
+}
+
+
+static void
+pre_list_columns(struct vsctl_context *ctx,
+ const struct vsctl_table_class *table,
+ const char *column_names)
+{
+ const struct ovsdb_idl_column **columns;
+ size_t n_columns;
+ size_t i;
+
+ parse_column_names(column_names, table, &columns, &n_columns);
+ for (i = 0; i < n_columns; i++) {
+ if (columns[i]) {
+ ovsdb_idl_add_column(ctx->idl, columns[i]);
+ }
+ }
+ free(columns);
+}
+
static void
pre_cmd_list(struct vsctl_context *ctx)
{
+ const char *column_names = shash_find_data(&ctx->options, "--columns");
const char *table_name = ctx->argv[1];
const struct vsctl_table_class *table;
- size_t i;
table = pre_get_table(ctx, table_name);
- for (i = 0; i < table->class->n_columns; i++) {
- ovsdb_idl_add_column(ctx->idl, &table->class->columns[i]);
+ pre_list_columns(ctx, table, column_names);
+}
+
+static struct table *
+list_make_table(const struct ovsdb_idl_column **columns, size_t n_columns)
+{
+ struct table *out;
+ size_t i;
+
+ out = xmalloc(sizeof *out);
+ table_init(out);
+
+ for (i = 0; i < n_columns; i++) {
+ const struct ovsdb_idl_column *column = columns[i];
+ const char *column_name = column ? column->name : "_uuid";
+
+ table_add_column(out, "%s", column_name);
}
+
+ return out;
}
static void
-list_record(const struct vsctl_table_class *table,
- const struct ovsdb_idl_row *row, struct ds *out)
+list_record(const struct ovsdb_idl_row *row,
+ const struct ovsdb_idl_column **columns, size_t n_columns,
+ struct table *out)
{
size_t i;
- ds_put_format(out, "%-20s: "UUID_FMT"\n", "_uuid",
- UUID_ARGS(&row->uuid));
- for (i = 0; i < table->class->n_columns; i++) {
- const struct ovsdb_idl_column *column = &table->class->columns[i];
- const struct ovsdb_datum *datum;
+ table_add_row(out);
+ for (i = 0; i < n_columns; i++) {
+ const struct ovsdb_idl_column *column = columns[i];
+ struct cell *cell = table_add_cell(out);
- datum = ovsdb_idl_read(row, column);
+ if (!column) {
+ struct ovsdb_datum datum;
+ union ovsdb_atom atom;
- ds_put_format(out, "%-20s: ", column->name);
- ovsdb_datum_to_string(datum, &column->type, out);
- ds_put_char(out, '\n');
+ atom.uuid = row->uuid;
+
+ datum.keys = &atom;
+ datum.values = NULL;
+ datum.n = 1;
+
+ cell->json = ovsdb_datum_to_json(&datum, &ovsdb_type_uuid);
+ cell->type = &ovsdb_type_uuid;
+ } else {
+ const struct ovsdb_datum *datum = ovsdb_idl_read(row, column);
+
+ cell->json = ovsdb_datum_to_json(datum, &column->type);
+ cell->type = &column->type;
+ }
}
}
static void
cmd_list(struct vsctl_context *ctx)
{
+ const char *column_names = shash_find_data(&ctx->options, "--columns");
+ const struct ovsdb_idl_column **columns;
const char *table_name = ctx->argv[1];
const struct vsctl_table_class *table;
- struct ds *out = &ctx->output;
+ struct table *out;
+ size_t n_columns;
int i;
table = get_table(table_name);
+ parse_column_names(column_names, table, &columns, &n_columns);
+ out = ctx->table = list_make_table(columns, n_columns);
if (ctx->argc > 2) {
for (i = 2; i < ctx->argc; i++) {
- if (i > 2) {
- ds_put_char(out, '\n');
- }
- list_record(table, must_get_row(ctx, table, ctx->argv[i]), out);
+ list_record(must_get_row(ctx, table, ctx->argv[i]),
+ columns, n_columns, out);
}
} else {
const struct ovsdb_idl_row *row;
for (row = ovsdb_idl_first_row(ctx->idl, table->class), first = true;
row != NULL;
row = ovsdb_idl_next_row(row), first = false) {
- if (!first) {
- ds_put_char(out, '\n');
+ list_record(row, columns, n_columns, out);
+ }
+ }
+ free(columns);
+}
+
+static void
+pre_cmd_find(struct vsctl_context *ctx)
+{
+ const char *column_names = shash_find_data(&ctx->options, "--columns");
+ const char *table_name = ctx->argv[1];
+ const struct vsctl_table_class *table;
+ int i;
+
+ table = pre_get_table(ctx, table_name);
+ pre_list_columns(ctx, table, column_names);
+ for (i = 2; i < ctx->argc; i++) {
+ pre_parse_column_key_value(ctx, ctx->argv[i], table);
+ }
+}
+
+static void
+cmd_find(struct vsctl_context *ctx)
+{
+ const char *column_names = shash_find_data(&ctx->options, "--columns");
+ const struct ovsdb_idl_column **columns;
+ const char *table_name = ctx->argv[1];
+ const struct vsctl_table_class *table;
+ const struct ovsdb_idl_row *row;
+ struct table *out;
+ size_t n_columns;
+
+ table = get_table(table_name);
+ parse_column_names(column_names, table, &columns, &n_columns);
+ out = ctx->table = list_make_table(columns, n_columns);
+ for (row = ovsdb_idl_first_row(ctx->idl, table->class); row;
+ row = ovsdb_idl_next_row(row)) {
+ int i;
+
+ for (i = 2; i < ctx->argc; i++) {
+ if (!is_condition_satisfied(table, row, ctx->argv[i],
+ ctx->symtab)) {
+ goto next_row;
}
- list_record(table, row, out);
}
+ list_record(row, columns, n_columns, out);
+
+ next_row: ;
}
}
}
static bool
-is_condition_satified(const struct vsctl_table_class *table,
- const struct ovsdb_idl_row *row, const char *arg,
- struct ovsdb_symbol_table *symtab)
+is_condition_satisfied(const struct vsctl_table_class *table,
+ const struct ovsdb_idl_row *row, const char *arg,
+ struct ovsdb_symbol_table *symtab)
{
static const char *operators[] = {
"=", "!=", "<", ">", "<=", ">="
}
for (i = 3; i < ctx->argc; i++) {
- if (!is_condition_satified(table, row, ctx->argv[i], ctx->symtab)) {
+ if (!is_condition_satisfied(table, row, ctx->argv[i], ctx->symtab)) {
ctx->try_again = true;
return;
}
ctx->options = command->options;
ds_swap(&ctx->output, &command->output);
+ ctx->table = command->table;
ctx->idl = idl;
ctx->txn = txn;
ctx->ovs = ovs;
vsctl_context_done(struct vsctl_context *ctx, struct vsctl_command *command)
{
ds_swap(&ctx->output, &command->output);
+ command->table = ctx->table;
}
static void
struct vsctl_context ctx;
ds_init(&c->output);
+ c->table = NULL;
vsctl_context_init(&ctx, c, idl, NULL, NULL, NULL);
(c->syntax->prerequisites)(&ctx);
vsctl_context_done(&ctx, c);
assert(!c->output.string);
+ assert(!c->table);
}
}
}
symtab = ovsdb_symbol_table_create();
for (c = commands; c < &commands[n_commands]; c++) {
ds_init(&c->output);
+ c->table = NULL;
}
for (c = commands; c < &commands[n_commands]; c++) {
struct vsctl_context ctx;
for (c = commands; c < &commands[n_commands]; c++) {
struct ds *ds = &c->output;
- struct shash_node *node;
- if (oneline) {
+ if (c->table) {
+ table_print(c->table, &table_style);
+ } else if (oneline) {
size_t j;
ds_chomp(ds, '\n');
fputs(ds_cstr(ds), stdout);
}
ds_destroy(&c->output);
+ table_destroy(c->table);
+ free(c->table);
- SHASH_FOR_EACH (node, &c->options) {
- free(node->data);
- }
- shash_destroy(&c->options);
+ smap_destroy(&c->options);
}
free(commands);
ovsdb_symbol_table_destroy(symtab);
for (c = commands; c < &commands[n_commands]; c++) {
ds_destroy(&c->output);
+ table_destroy(c->table);
+ free(c->table);
}
free(error);
}
{"br-exists", 1, 1, pre_get_info, cmd_br_exists, NULL, "", RO},
{"br-to-vlan", 1, 1, pre_get_info, cmd_br_to_vlan, NULL, "", RO},
{"br-to-parent", 1, 1, pre_get_info, cmd_br_to_parent, NULL, "", RO},
- {"br-set-external-id", 2, 3, pre_get_info, cmd_br_set_external_id, NULL,
- "", RW},
+ {"br-set-external-id", 2, 3, pre_cmd_br_set_external_id,
+ cmd_br_set_external_id, NULL, "", RW},
{"br-get-external-id", 1, 2, pre_cmd_br_get_external_id,
cmd_br_get_external_id, NULL, "", RO},
{"iface-to-br", 1, 1, pre_get_info, cmd_iface_to_br, NULL, "", RO},
/* Controller commands. */
- {"get-controller", 1, 1, pre_get_info, cmd_get_controller, NULL, "", RO},
- {"del-controller", 1, 1, pre_get_info, cmd_del_controller, NULL, "", RW},
- {"set-controller", 1, INT_MAX, pre_get_info, cmd_set_controller, NULL, "",
- RW},
+ {"get-controller", 1, 1, pre_controller, cmd_get_controller, NULL, "", RO},
+ {"del-controller", 1, 1, pre_controller, cmd_del_controller, NULL, "", RW},
+ {"set-controller", 1, INT_MAX, pre_controller, cmd_set_controller, NULL,
+ "", RW},
{"get-fail-mode", 1, 1, pre_get_info, cmd_get_fail_mode, NULL, "", RO},
{"del-fail-mode", 1, 1, pre_get_info, cmd_del_fail_mode, NULL, "", RW},
{"set-fail-mode", 2, 2, pre_get_info, cmd_set_fail_mode, NULL, "", RW},
+ /* Manager commands. */
+ {"get-manager", 0, 0, pre_manager, cmd_get_manager, NULL, "", RO},
+ {"del-manager", 0, INT_MAX, pre_manager, cmd_del_manager, NULL, "", RW},
+ {"set-manager", 1, INT_MAX, pre_manager, cmd_set_manager, NULL, "", RW},
+
/* SSL commands. */
{"get-ssl", 0, 0, pre_cmd_get_ssl, cmd_get_ssl, NULL, "", RO},
{"del-ssl", 0, 0, pre_cmd_del_ssl, cmd_del_ssl, NULL, "", RW},
/* Parameter commands. */
{"get", 2, INT_MAX, pre_cmd_get, cmd_get, NULL, "--if-exists,--id=", RO},
- {"list", 1, INT_MAX, pre_cmd_list, cmd_list, NULL, "", RO},
+ {"list", 1, INT_MAX, pre_cmd_list, cmd_list, NULL, "--columns=", RO},
+ {"find", 1, INT_MAX, pre_cmd_find, cmd_find, NULL, "--columns=", RO},
{"set", 3, INT_MAX, pre_cmd_set, cmd_set, NULL, "", RW},
{"add", 4, INT_MAX, pre_cmd_add, cmd_add, NULL, "", RW},
{"remove", 4, INT_MAX, pre_cmd_remove, cmd_remove, NULL, "", RW},