diff mbox

[ovs-dev,v5] Add read-only option to ovs-dpctl and ovs-ofctl commands.

Message ID 1471286849-28619-1-git-send-email-rmoats@us.ibm.com
State Accepted
Headers show

Commit Message

Ryan Moats Aug. 15, 2016, 6:47 p.m. UTC
ovs-dpctl and ovs-ofctl lack a read-only option to prevent
running of commands that perform read-write operations.  Add
it and the necessary scaffolding to each.

Signed-off-by: Ryan Moats <rmoats@us.ibm.com>
---
 lib/command-line.c             |  51 +++++++++----
 lib/command-line.h             |   6 +-
 lib/db-ctl-base.h              |   2 +-
 lib/dpctl.c                    |  47 ++++++------
 lib/dpctl.h                    |   3 +
 ovsdb/ovsdb-tool.c             |  28 ++++----
 tests/ovstest.c                |   4 +-
 tests/test-bitmap.c            |   6 +-
 tests/test-ccmap.c             |   6 +-
 tests/test-classifier.c        |  28 ++++----
 tests/test-cmap.c              |   6 +-
 tests/test-conntrack.c         |   6 +-
 tests/test-heap.c              |  14 ++--
 tests/test-jsonrpc.c           |  10 +--
 tests/test-netlink-conntrack.c |   8 +--
 tests/test-ovn.c               |  26 +++----
 tests/test-ovsdb.c             |  80 ++++++++++-----------
 tests/test-reconnect.c         |  32 ++++-----
 tests/test-util.c              |  34 ++++-----
 tests/test-vconn.c             |  18 ++---
 utilities/ovs-dpctl.c          |   7 ++
 utilities/ovs-ofctl.8.in       |   3 +
 utilities/ovs-ofctl.c          | 160 ++++++++++++++++++++++-------------------
 23 files changed, 323 insertions(+), 262 deletions(-)

Comments

Ryan Moats Aug. 16, 2016, 1:35 p.m. UTC | #1
Ben Pfaff <blp@ovn.org> wrote on 08/15/2016 07:28:18 PM:

> From: Ben Pfaff <blp@ovn.org>
> To: Ryan Moats/Omaha/IBM@IBMUS
> Cc: dev@openvswitch.org
> Date: 08/16/2016 01:54 AM
> Subject: Re: [ovs-dev, v5] Add read-only option to ovs-dpctl and
> ovs-ofctl commands.
>
> On Mon, Aug 15, 2016 at 06:47:29PM +0000, Ryan Moats wrote:
> > ovs-dpctl and ovs-ofctl lack a read-only option to prevent
> > running of commands that perform read-write operations.  Add
> > it and the necessary scaffolding to each.
> >
> > Signed-off-by: Ryan Moats <rmoats@us.ibm.com>
>
> I folded in a few changes, see below, and applied this to master.
>
> --8<--------------------------cut here-------------------------->8--
>
> diff --git a/lib/command-line.c b/lib/command-line.c
> index 92ce888..8128331 100644
> --- a/lib/command-line.c
> +++ b/lib/command-line.c
> @@ -87,11 +87,10 @@ ovs_cmdl_print_options(const struct option options[])
>      ds_destroy(&ds);
>  }
>
> -/* Whether to commit changes or not. */
> -static bool read_only;
> -
>  static void
> -_ovs_cmdl_run_command(struct ovs_cmdl_context *ctx, const struct
> ovs_cmdl_command commands[])
> +ovs_cmdl_run_command__(struct ovs_cmdl_context *ctx,
> +                       const struct ovs_cmdl_command commands[],
> +                       bool read_only)
>  {
>      const struct ovs_cmdl_command *p;
>
> @@ -144,16 +143,14 @@ void
>  ovs_cmdl_run_command(struct ovs_cmdl_context *ctx,
>                       const struct ovs_cmdl_command commands[])
>  {
> -    read_only = false;
> -    _ovs_cmdl_run_command(ctx, commands);
> +    ovs_cmdl_run_command__(ctx, commands, false);
>  }
>
>  void
>  ovs_cmdl_run_command_read_only(struct ovs_cmdl_context *ctx,
>                                 const struct ovs_cmdl_command commands[])
>  {
> -    read_only = true;
> -    _ovs_cmdl_run_command(ctx, commands);
> +    ovs_cmdl_run_command__(ctx, commands, true);
>  }
>
>  /* Process title. */
> diff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c
> index c123e43..35b70e9 100644
> --- a/tests/test-ovsdb.c
> +++ b/tests/test-ovsdb.c
> @@ -2720,7 +2720,7 @@ static struct ovs_cmdl_command all_commands[] = {
>      { "log-io", NULL, 2, INT_MAX, do_log_io, OVS_RO },
>      { "default-atoms", NULL, 0, 0, do_default_atoms, OVS_RO },
>      { "default-data", NULL, 0, 0, do_default_data, OVS_RO },
> -    { "diff-data", NULL, 3, INT_MAX, do_diff_data},
> +    { "diff-data", NULL, 3, INT_MAX, do_diff_data, OVS_RO },
>      { "parse-atomic-type", NULL, 1, 1, do_parse_atomic_type, OVS_RO },
>      { "parse-base-type", NULL, 1, 1, do_parse_base_type, OVS_RO },
>      { "parse-type", NULL, 1, 1, do_parse_type, OVS_RO },
>

The first two changes work for me -

The last one annoys me because I *know* I fixed that as compiling fails
without it.  So, now I'm wondering how the heck it reverted in the patch
set...

It's all good and I'll send out the more controversial patch later
today...
diff mbox

Patch

diff --git a/lib/command-line.c b/lib/command-line.c
index bda5ed6..92ce888 100644
--- a/lib/command-line.c
+++ b/lib/command-line.c
@@ -87,20 +87,11 @@  ovs_cmdl_print_options(const struct option options[])
     ds_destroy(&ds);
 }
 
-/* Runs the command designated by argv[0] within the command table specified by
- * 'commands', which must be terminated by a command whose 'name' member is a
- * null pointer.
- *
- * Command-line options should be stripped off, so that a typical invocation
- * looks like:
- *    struct ovs_cmdl_context ctx = {
- *        .argc = argc - optind,
- *        .argv = argv + optind,
- *    };
- *    ovs_cmdl_run_command(&ctx, my_commands);
- * */
-void
-ovs_cmdl_run_command(struct ovs_cmdl_context *ctx, const struct ovs_cmdl_command commands[])
+/* Whether to commit changes or not. */
+static bool read_only;
+
+static void
+_ovs_cmdl_run_command(struct ovs_cmdl_context *ctx, const struct ovs_cmdl_command commands[])
 {
     const struct ovs_cmdl_command *p;
 
@@ -118,6 +109,10 @@  ovs_cmdl_run_command(struct ovs_cmdl_context *ctx, const struct ovs_cmdl_command
                 VLOG_FATAL("'%s' command takes at most %d arguments",
                            p->name, p->max_args);
             } else {
+                if (p->mode == OVS_RW && read_only) {
+                    VLOG_FATAL("'%s' command does not work in read only mode",
+                               p->name);
+                }
                 p->handler(ctx);
                 if (ferror(stdout)) {
                     VLOG_FATAL("write to stdout failed");
@@ -132,6 +127,34 @@  ovs_cmdl_run_command(struct ovs_cmdl_context *ctx, const struct ovs_cmdl_command
 
     VLOG_FATAL("unknown command '%s'; use --help for help", ctx->argv[0]);
 }
+
+/* Runs the command designated by argv[0] within the command table specified by
+ * 'commands', which must be terminated by a command whose 'name' member is a
+ * null pointer.
+ *
+ * Command-line options should be stripped off, so that a typical invocation
+ * looks like:
+ *    struct ovs_cmdl_context ctx = {
+ *        .argc = argc - optind,
+ *        .argv = argv + optind,
+ *    };
+ *    ovs_cmdl_run_command(&ctx, my_commands);
+ * */
+void
+ovs_cmdl_run_command(struct ovs_cmdl_context *ctx,
+                     const struct ovs_cmdl_command commands[])
+{
+    read_only = false;
+    _ovs_cmdl_run_command(ctx, commands);
+}
+
+void
+ovs_cmdl_run_command_read_only(struct ovs_cmdl_context *ctx,
+                               const struct ovs_cmdl_command commands[])
+{
+    read_only = true;
+    _ovs_cmdl_run_command(ctx, commands);
+}
 
 /* Process title. */
 
diff --git a/lib/command-line.h b/lib/command-line.h
index e9e3b7b..00ace94 100644
--- a/lib/command-line.h
+++ b/lib/command-line.h
@@ -41,12 +41,16 @@  struct ovs_cmdl_command {
     int min_args;
     int max_args;
     ovs_cmdl_handler handler;
+    enum { OVS_RO, OVS_RW } mode;    /* Does this command modify things? */
 };
 
 char *ovs_cmdl_long_options_to_short_options(const struct option *options);
 void ovs_cmdl_print_options(const struct option *options);
 void ovs_cmdl_print_commands(const struct ovs_cmdl_command *commands);
-void ovs_cmdl_run_command(struct ovs_cmdl_context *, const struct ovs_cmdl_command[]);
+void ovs_cmdl_run_command(struct ovs_cmdl_context *,
+                          const struct ovs_cmdl_command[]);
+void ovs_cmdl_run_command_read_only(struct ovs_cmdl_context *,
+                                    const struct ovs_cmdl_command[]);
 
 void ovs_cmdl_proctitle_init(int argc, char **argv);
 #if defined(__FreeBSD__) || defined(__NetBSD__)
diff --git a/lib/db-ctl-base.h b/lib/db-ctl-base.h
index 0f4658e..e5a354d 100644
--- a/lib/db-ctl-base.h
+++ b/lib/db-ctl-base.h
@@ -120,7 +120,7 @@  struct ctl_command_syntax {
      * empty string if the command does not support any options. */
     const char *options;
 
-    enum { RO, RW } mode;       /* Does this command modify the database? */
+    enum { RO, RW } mode;   /* Does this command modify the database? */
 };
 
 /* A command extracted from command-line input plus the structs for
diff --git a/lib/dpctl.c b/lib/dpctl.c
index b470ab0..28f2f83 100644
--- a/lib/dpctl.c
+++ b/lib/dpctl.c
@@ -59,6 +59,7 @@  struct dpctl_command {
     int min_args;
     int max_args;
     dpctl_command_handler *handler;
+    enum { DP_RO, DP_RW} mode;
 };
 static const struct dpctl_command *get_all_dpctl_commands(void);
 static void dpctl_print(struct dpctl_params *dpctl_p, const char *fmt, ...)
@@ -1615,29 +1616,29 @@  out:
 }
 
 static const struct dpctl_command all_commands[] = {
-    { "add-dp", "add-dp dp [iface...]", 1, INT_MAX, dpctl_add_dp },
-    { "del-dp", "del-dp dp", 1, 1, dpctl_del_dp },
-    { "add-if", "add-if dp iface...", 2, INT_MAX, dpctl_add_if },
-    { "del-if", "del-if dp iface...", 2, INT_MAX, dpctl_del_if },
-    { "set-if", "set-if dp iface...", 2, INT_MAX, dpctl_set_if },
-    { "dump-dps", "", 0, 0, dpctl_dump_dps },
-    { "show", "[dp...]", 0, INT_MAX, dpctl_show },
-    { "dump-flows", "[dp]", 0, 2, dpctl_dump_flows },
-    { "add-flow", "add-flow [dp] flow actions", 2, 3, dpctl_add_flow },
-    { "mod-flow", "mod-flow [dp] flow actions", 2, 3, dpctl_mod_flow },
-    { "get-flow", "get-flow [dp] ufid", 1, 2, dpctl_get_flow },
-    { "del-flow", "del-flow [dp] flow", 1, 2, dpctl_del_flow },
-    { "del-flows", "[dp]", 0, 1, dpctl_del_flows },
-    { "dump-conntrack", "[dp] [zone=N]", 0, 2, dpctl_dump_conntrack },
-    { "flush-conntrack", "[dp] [zone=N]", 0, 2, dpctl_flush_conntrack },
-    { "help", "", 0, INT_MAX, dpctl_help },
-    { "list-commands", "", 0, INT_MAX, dpctl_list_commands },
+    { "add-dp", "add-dp dp [iface...]", 1, INT_MAX, dpctl_add_dp, DP_RW },
+    { "del-dp", "del-dp dp", 1, 1, dpctl_del_dp, DP_RW },
+    { "add-if", "add-if dp iface...", 2, INT_MAX, dpctl_add_if, DP_RW },
+    { "del-if", "del-if dp iface...", 2, INT_MAX, dpctl_del_if, DP_RW },
+    { "set-if", "set-if dp iface...", 2, INT_MAX, dpctl_set_if, DP_RW },
+    { "dump-dps", "", 0, 0, dpctl_dump_dps, DP_RO },
+    { "show", "[dp...]", 0, INT_MAX, dpctl_show, DP_RO },
+    { "dump-flows", "[dp]", 0, 2, dpctl_dump_flows, DP_RO },
+    { "add-flow", "add-flow [dp] flow actions", 2, 3, dpctl_add_flow, DP_RW },
+    { "mod-flow", "mod-flow [dp] flow actions", 2, 3, dpctl_mod_flow, DP_RW },
+    { "get-flow", "get-flow [dp] ufid", 1, 2, dpctl_get_flow, DP_RO },
+    { "del-flow", "del-flow [dp] flow", 1, 2, dpctl_del_flow, DP_RW },
+    { "del-flows", "[dp]", 0, 1, dpctl_del_flows, DP_RW },
+    { "dump-conntrack", "[dp] [zone=N]", 0, 2, dpctl_dump_conntrack, DP_RO },
+    { "flush-conntrack", "[dp] [zone=N]", 0, 2, dpctl_flush_conntrack, DP_RW },
+    { "help", "", 0, INT_MAX, dpctl_help, DP_RO },
+    { "list-commands", "", 0, INT_MAX, dpctl_list_commands, DP_RO },
 
     /* Undocumented commands for testing. */
-    { "parse-actions", "actions", 1, INT_MAX, dpctl_parse_actions },
-    { "normalize-actions", "actions", 2, INT_MAX, dpctl_normalize_actions },
+    { "parse-actions", "actions", 1, INT_MAX, dpctl_parse_actions, DP_RO },
+    { "normalize-actions", "actions", 2, INT_MAX, dpctl_normalize_actions, DP_RO },
 
-    { NULL, NULL, 0, 0, NULL },
+    { NULL, NULL, 0, 0, NULL, DP_RO },
 };
 
 static const struct dpctl_command *get_all_dpctl_commands(void)
@@ -1672,6 +1673,12 @@  dpctl_run_command(int argc, const char *argv[], struct dpctl_params *dpctl_p)
                             p->name, p->max_args);
                 return EINVAL;
             } else {
+                if (p->mode == DP_RW && dpctl_p->read_only) {
+                    dpctl_error(dpctl_p, 0,
+                                "'%s' command does not work in read only mode",
+                                p->name);
+                    return EINVAL;
+                }
                 return p->handler(argc, argv, dpctl_p);
             }
         }
diff --git a/lib/dpctl.h b/lib/dpctl.h
index 11a172d..4ee083f 100644
--- a/lib/dpctl.h
+++ b/lib/dpctl.h
@@ -33,6 +33,9 @@  struct dpctl_params {
     /* --may-create: Allow mod-flows command to create a new flow? */
     bool may_create;
 
+    /* --read-only: Do not run R/W commands? */
+    bool read_only;
+
     /* -m, --more: Increase output verbosity. */
     int verbosity;
 
diff --git a/ovsdb/ovsdb-tool.c b/ovsdb/ovsdb-tool.c
index 06d7aca..8d7e76a 100644
--- a/ovsdb/ovsdb-tool.c
+++ b/ovsdb/ovsdb-tool.c
@@ -573,20 +573,20 @@  do_list_commands(struct ovs_cmdl_context *ctx OVS_UNUSED)
 }
 
 static const struct ovs_cmdl_command all_commands[] = {
-    { "create", "[db [schema]]", 0, 2, do_create },
-    { "compact", "[db [dst]]", 0, 2, do_compact },
-    { "convert", "[db [schema [dst]]]", 0, 3, do_convert },
-    { "needs-conversion", NULL, 0, 2, do_needs_conversion },
-    { "db-version", "[db]",  0, 1, do_db_version },
-    { "db-cksum", "[db]", 0, 1, do_db_cksum },
-    { "schema-version", "[schema]", 0, 1, do_schema_version },
-    { "schema-cksum", "[schema]", 0, 1, do_schema_cksum },
-    { "query", "[db] trns", 1, 2, do_query },
-    { "transact", "[db] trns", 1, 2, do_transact },
-    { "show-log", "[db]", 0, 1, do_show_log },
-    { "help", NULL, 0, INT_MAX, do_help },
-    { "list-commands", NULL, 0, INT_MAX, do_list_commands },
-    { NULL, NULL, 0, 0, NULL },
+    { "create", "[db [schema]]", 0, 2, do_create, OVS_RW },
+    { "compact", "[db [dst]]", 0, 2, do_compact, OVS_RW },
+    { "convert", "[db [schema [dst]]]", 0, 3, do_convert, OVS_RW },
+    { "needs-conversion", NULL, 0, 2, do_needs_conversion, OVS_RO },
+    { "db-version", "[db]",  0, 1, do_db_version, OVS_RO },
+    { "db-cksum", "[db]", 0, 1, do_db_cksum, OVS_RO },
+    { "schema-version", "[schema]", 0, 1, do_schema_version, OVS_RO },
+    { "schema-cksum", "[schema]", 0, 1, do_schema_cksum, OVS_RO },
+    { "query", "[db] trns", 1, 2, do_query, OVS_RO },
+    { "transact", "[db] trns", 1, 2, do_transact, OVS_RO },
+    { "show-log", "[db]", 0, 1, do_show_log, OVS_RO },
+    { "help", NULL, 0, INT_MAX, do_help, OVS_RO },
+    { "list-commands", NULL, 0, INT_MAX, do_list_commands, OVS_RO },
+    { NULL, NULL, 0, 0, NULL, OVS_RO },
 };
 
 static const struct ovs_cmdl_command *get_all_commands(void)
diff --git a/tests/ovstest.c b/tests/ovstest.c
index 2fb7f66..745bd2e 100644
--- a/tests/ovstest.c
+++ b/tests/ovstest.c
@@ -33,7 +33,7 @@  static size_t allocated_commands = 0;
 static void
 add_command(struct ovs_cmdl_command *cmd)
 {
-    const struct ovs_cmdl_command nil = {NULL, NULL, 0, 0, NULL};
+    const struct ovs_cmdl_command nil = {NULL, NULL, 0, 0, NULL, OVS_RO};
 
     while (n_commands + 1 >= allocated_commands) {
         commands = x2nrealloc(commands, &allocated_commands,
@@ -86,7 +86,7 @@  help(struct ovs_cmdl_context *ctx OVS_UNUSED)
 static void
 add_top_level_commands(void)
 {
-    struct ovs_cmdl_command help_cmd = {"--help", NULL, 0, 0, help};
+    struct ovs_cmdl_command help_cmd = {"--help", NULL, 0, 0, help, OVS_RO };
 
     add_command(&help_cmd);
 }
diff --git a/tests/test-bitmap.c b/tests/test-bitmap.c
index 3dbc8df..484407b 100644
--- a/tests/test-bitmap.c
+++ b/tests/test-bitmap.c
@@ -149,9 +149,9 @@  run_benchmarks(struct ovs_cmdl_context *ctx)
 }
 
 static const struct ovs_cmdl_command commands[] = {
-    {"check", NULL, 0, 0, run_tests},
-    {"benchmark", NULL, 1, 1, run_benchmarks},
-    {NULL, NULL, 0, 0, NULL},
+    {"check", NULL, 0, 0, run_tests, OVS_RO},
+    {"benchmark", NULL, 1, 1, run_benchmarks, OVS_RO},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };
 
 static void
diff --git a/tests/test-ccmap.c b/tests/test-ccmap.c
index 4efe1b9..5ebc659 100644
--- a/tests/test-ccmap.c
+++ b/tests/test-ccmap.c
@@ -272,9 +272,9 @@  benchmark_ccmap(void)
 
 
 static const struct ovs_cmdl_command commands[] = {
-    {"check", NULL, 0, 1, run_tests},
-    {"benchmark", NULL, 3, 3, run_benchmarks},
-    {NULL, NULL, 0, 0, NULL},
+    {"check", NULL, 0, 1, run_tests, OVS_RO},
+    {"benchmark", NULL, 3, 3, run_benchmarks, OVS_RO},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };
 
 static void
diff --git a/tests/test-classifier.c b/tests/test-classifier.c
index 3a275b4..f85ea4f 100644
--- a/tests/test-classifier.c
+++ b/tests/test-classifier.c
@@ -1838,23 +1838,23 @@  static void help(struct ovs_cmdl_context *ctx);
 
 static const struct ovs_cmdl_command commands[] = {
     /* Classifier tests. */
-    {"empty", NULL, 0, 0, test_empty},
-    {"destroy-null", NULL, 0, 0, test_destroy_null},
-    {"single-rule", NULL, 0, 0, test_single_rule},
-    {"rule-replacement", NULL, 0, 0, test_rule_replacement},
-    {"many-rules-in-one-list", NULL, 0, 1, test_many_rules_in_one_list},
-    {"many-rules-in-one-table", NULL, 0, 1, test_many_rules_in_one_table},
-    {"many-rules-in-two-tables", NULL, 0, 0, test_many_rules_in_two_tables},
-    {"many-rules-in-five-tables", NULL, 0, 0, test_many_rules_in_five_tables},
-    {"benchmark", NULL, 0, 5, run_benchmarks},
+    {"empty", NULL, 0, 0, test_empty, OVS_RO },
+    {"destroy-null", NULL, 0, 0, test_destroy_null, OVS_RO },
+    {"single-rule", NULL, 0, 0, test_single_rule, OVS_RO },
+    {"rule-replacement", NULL, 0, 0, test_rule_replacement, OVS_RO },
+    {"many-rules-in-one-list", NULL, 0, 1, test_many_rules_in_one_list, OVS_RO },
+    {"many-rules-in-one-table", NULL, 0, 1, test_many_rules_in_one_table, OVS_RO },
+    {"many-rules-in-two-tables", NULL, 0, 0, test_many_rules_in_two_tables, OVS_RO },
+    {"many-rules-in-five-tables", NULL, 0, 0, test_many_rules_in_five_tables, OVS_RO },
+    {"benchmark", NULL, 0, 5, run_benchmarks, OVS_RO },
 
     /* Miniflow and minimask tests. */
-    {"miniflow", NULL, 0, 0, test_miniflow},
-    {"minimask_has_extra", NULL, 0, 0, test_minimask_has_extra},
-    {"minimask_combine", NULL, 0, 0, test_minimask_combine},
+    {"miniflow", NULL, 0, 0, test_miniflow, OVS_RO },
+    {"minimask_has_extra", NULL, 0, 0, test_minimask_has_extra, OVS_RO },
+    {"minimask_combine", NULL, 0, 0, test_minimask_combine, OVS_RO },
 
-    {"--help", NULL, 0, 0, help},
-    {NULL, NULL, 0, 0, NULL},
+    {"--help", NULL, 0, 0, help, OVS_RO },
+    {NULL, NULL, 0, 0, NULL, OVS_RO },
 };
 
 static void
diff --git a/tests/test-cmap.c b/tests/test-cmap.c
index 4cac7de..e159a16 100644
--- a/tests/test-cmap.c
+++ b/tests/test-cmap.c
@@ -636,9 +636,9 @@  benchmark_hmap(void)
 }
 
 static const struct ovs_cmdl_command commands[] = {
-    {"check", NULL, 0, 1, run_tests},
-    {"benchmark", NULL, 3, 4, run_benchmarks},
-    {NULL, NULL, 0, 0, NULL},
+    {"check", NULL, 0, 1, run_tests, OVS_RO},
+    {"benchmark", NULL, 3, 4, run_benchmarks, OVS_RO},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };
 
 static void
diff --git a/tests/test-conntrack.c b/tests/test-conntrack.c
index 6c1b373..803e2b9 100644
--- a/tests/test-conntrack.c
+++ b/tests/test-conntrack.c
@@ -260,13 +260,13 @@  static const struct ovs_cmdl_command commands[] = {
      * is '1', each packet in a batch will have a different source and
      * destination port */
     {"benchmark", "n_threads n_pkts batch_size [change_connection]", 3, 4,
-     test_benchmark},
+     test_benchmark, OVS_RO},
     /* Reads packets from 'file' and sends them to the connection tracker,
      * 'batch_size' (1 by default) per call, with the commit flag set.
      * Prints the ct_state of each packet. */
-    {"pcap", "file [batch_size]", 1, 2, test_pcap},
+    {"pcap", "file [batch_size]", 1, 2, test_pcap, OVS_RO},
 
-    {NULL, NULL, 0, 0, NULL},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };
 
 static void
diff --git a/tests/test-heap.c b/tests/test-heap.c
index 6dab22b..88c9f25 100644
--- a/tests/test-heap.c
+++ b/tests/test-heap.c
@@ -461,16 +461,16 @@  test_heap_raw_delete(struct ovs_cmdl_context *ctx OVS_UNUSED)
 
 static const struct ovs_cmdl_command commands[] = {
     { "insert-delete-same-order", NULL, 0, 0,
-      test_heap_insert_delete_same_order, },
+      test_heap_insert_delete_same_order, OVS_RO },
     { "insert-delete-reverse-order", NULL, 0, 0,
-      test_heap_insert_delete_reverse_order, },
+      test_heap_insert_delete_reverse_order, OVS_RO },
     { "insert-delete-every-order", NULL, 0, 0,
-      test_heap_insert_delete_every_order, },
+      test_heap_insert_delete_every_order, OVS_RO },
     { "insert-delete-same-order-with-dups", NULL, 0, 0,
-      test_heap_insert_delete_same_order_with_dups, },
-    { "raw-insert", NULL, 0, 0, test_heap_raw_insert, },
-    { "raw-delete", NULL, 0, 0, test_heap_raw_delete, },
-    { NULL, NULL, 0, 0, NULL, },
+      test_heap_insert_delete_same_order_with_dups, OVS_RO },
+    { "raw-insert", NULL, 0, 0, test_heap_raw_insert, OVS_RO },
+    { "raw-delete", NULL, 0, 0, test_heap_raw_delete, OVS_RO },
+    { NULL, NULL, 0, 0, NULL, OVS_RO },
 };
 
 static void
diff --git a/tests/test-jsonrpc.c b/tests/test-jsonrpc.c
index be79064..684601a 100644
--- a/tests/test-jsonrpc.c
+++ b/tests/test-jsonrpc.c
@@ -331,11 +331,11 @@  do_help(struct ovs_cmdl_context *ctx OVS_UNUSED)
 }
 
 static struct ovs_cmdl_command all_commands[] = {
-    { "listen", NULL, 1, 1, do_listen },
-    { "request", NULL, 3, 3, do_request },
-    { "notify", NULL, 3, 3, do_notify },
-    { "help", NULL, 0, INT_MAX, do_help },
-    { NULL, NULL, 0, 0, NULL },
+    { "listen", NULL, 1, 1, do_listen, OVS_RO },
+    { "request", NULL, 3, 3, do_request, OVS_RO },
+    { "notify", NULL, 3, 3, do_notify, OVS_RO },
+    { "help", NULL, 0, INT_MAX, do_help, OVS_RO },
+    { NULL, NULL, 0, 0, NULL, OVS_RO },
 };
 
 static struct ovs_cmdl_command *
diff --git a/tests/test-netlink-conntrack.c b/tests/test-netlink-conntrack.c
index f0d48f7..000062d 100644
--- a/tests/test-netlink-conntrack.c
+++ b/tests/test-netlink-conntrack.c
@@ -161,14 +161,14 @@  static const struct ovs_cmdl_command commands[] = {
     /* Linux netlink connection tracker interface test. */
 
     /* Prints all the entries in the connection table and exits. */
-    {"dump", "[zone=zone]", 0, 1, test_nl_ct_dump},
+    {"dump", "[zone=zone]", 0, 1, test_nl_ct_dump, OVS_RO},
     /* Listens to all the connection tracking events and prints them to
      * standard output until killed. */
-    {"monitor", "", 0, 0, test_nl_ct_monitor},
+    {"monitor", "", 0, 0, test_nl_ct_monitor, OVS_RO},
     /* Flushes all the entries from all the tables.. */
-    {"flush", "[zone=zone]", 0, 1, test_nl_ct_flush},
+    {"flush", "[zone=zone]", 0, 1, test_nl_ct_flush, OVS_RO},
 
-    {NULL, NULL, 0, 0, NULL},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };
 
 static void
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index b5fca56..75af537 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -1470,26 +1470,26 @@  test_ovn_main(int argc, char *argv[])
 
     static const struct ovs_cmdl_command commands[] = {
         /* Lexer. */
-        {"lex", NULL, 0, 0, test_lex},
+        {"lex", NULL, 0, 0, test_lex, OVS_RO},
 
         /* Symbol table. */
-        {"dump-symtab", NULL, 0, 0, test_dump_symtab},
+        {"dump-symtab", NULL, 0, 0, test_dump_symtab, OVS_RO},
 
         /* Expressions. */
-        {"parse-expr", NULL, 0, 0, test_parse_expr},
-        {"annotate-expr", NULL, 0, 0, test_annotate_expr},
-        {"simplify-expr", NULL, 0, 0, test_simplify_expr},
-        {"normalize-expr", NULL, 0, 0, test_normalize_expr},
-        {"expr-to-flows", NULL, 0, 0, test_expr_to_flows},
-        {"evaluate-expr", NULL, 1, 1, test_evaluate_expr},
-        {"composition", NULL, 1, 1, test_composition},
-        {"tree-shape", NULL, 1, 1, test_tree_shape},
-        {"exhaustive", NULL, 1, 1, test_exhaustive},
+        {"parse-expr", NULL, 0, 0, test_parse_expr, OVS_RO},
+        {"annotate-expr", NULL, 0, 0, test_annotate_expr, OVS_RO},
+        {"simplify-expr", NULL, 0, 0, test_simplify_expr, OVS_RO},
+        {"normalize-expr", NULL, 0, 0, test_normalize_expr, OVS_RO},
+        {"expr-to-flows", NULL, 0, 0, test_expr_to_flows, OVS_RO},
+        {"evaluate-expr", NULL, 1, 1, test_evaluate_expr, OVS_RO},
+        {"composition", NULL, 1, 1, test_composition, OVS_RO},
+        {"tree-shape", NULL, 1, 1, test_tree_shape, OVS_RO},
+        {"exhaustive", NULL, 1, 1, test_exhaustive, OVS_RO},
 
         /* Actions. */
-        {"parse-actions", NULL, 0, 0, test_parse_actions},
+        {"parse-actions", NULL, 0, 0, test_parse_actions, OVS_RO},
 
-        {NULL, NULL, 0, 0, NULL},
+        {NULL, NULL, 0, 0, NULL, OVS_RO},
     };
     struct ovs_cmdl_context ctx;
     ctx.argc = argc - optind;
diff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c
index 3455fcc..c123e43 100644
--- a/tests/test-ovsdb.c
+++ b/tests/test-ovsdb.c
@@ -1686,13 +1686,13 @@  static void
 do_transact(struct ovs_cmdl_context *ctx)
 {
     static const struct ovs_cmdl_command do_transact_commands[] = {
-        { "commit", NULL, 0, 0, do_transact_commit },
-        { "abort", NULL, 0, 0, do_transact_abort },
-        { "insert", NULL, 2, 3, do_transact_insert },
-        { "delete", NULL, 1, 1, do_transact_delete },
-        { "modify", NULL, 2, 3, do_transact_modify },
-        { "print", NULL, 0, 0, do_transact_print },
-        { NULL, NULL, 0, 0, NULL },
+        { "commit", NULL, 0, 0, do_transact_commit, OVS_RO },
+        { "abort", NULL, 0, 0, do_transact_abort, OVS_RO },
+        { "insert", NULL, 2, 3, do_transact_insert, OVS_RO },
+        { "delete", NULL, 1, 1, do_transact_delete, OVS_RO },
+        { "modify", NULL, 2, 3, do_transact_modify, OVS_RO },
+        { "print", NULL, 0, 0, do_transact_print, OVS_RO },
+        { NULL, NULL, 0, 0, NULL, OVS_RO },
     };
 
     struct ovsdb_schema *schema;
@@ -2717,42 +2717,42 @@  do_idl_partial_update_set_column(struct ovs_cmdl_context *ctx)
 }
 
 static struct ovs_cmdl_command all_commands[] = {
-    { "log-io", NULL, 2, INT_MAX, do_log_io },
-    { "default-atoms", NULL, 0, 0, do_default_atoms },
-    { "default-data", NULL, 0, 0, do_default_data },
+    { "log-io", NULL, 2, INT_MAX, do_log_io, OVS_RO },
+    { "default-atoms", NULL, 0, 0, do_default_atoms, OVS_RO },
+    { "default-data", NULL, 0, 0, do_default_data, OVS_RO },
     { "diff-data", NULL, 3, INT_MAX, do_diff_data},
-    { "parse-atomic-type", NULL, 1, 1, do_parse_atomic_type },
-    { "parse-base-type", NULL, 1, 1, do_parse_base_type },
-    { "parse-type", NULL, 1, 1, do_parse_type },
-    { "parse-atoms", NULL, 2, INT_MAX, do_parse_atoms },
-    { "parse-atom-strings", NULL, 2, INT_MAX, do_parse_atom_strings },
-    { "parse-data", NULL, 2, INT_MAX, do_parse_data },
-    { "parse-data-strings", NULL, 2, INT_MAX, do_parse_data_strings },
-    { "sort-atoms", NULL, 2, 2, do_sort_atoms },
-    { "parse-column", NULL, 2, 2, do_parse_column },
-    { "parse-table", NULL, 2, 3, do_parse_table },
-    { "parse-rows", NULL, 2, INT_MAX, do_parse_rows },
-    { "compare-rows", NULL, 2, INT_MAX, do_compare_rows },
-    { "parse-conditions", NULL, 2, INT_MAX, do_parse_conditions },
-    { "evaluate-conditions", NULL, 3, 3, do_evaluate_conditions },
-    { "evaluate-conditions-any", NULL, 3, 3, do_evaluate_conditions_any },
-    { "compare-conditions", NULL, 2, 2, do_compare_conditions },
-    { "parse-mutations", NULL, 2, INT_MAX, do_parse_mutations },
-    { "execute-mutations", NULL, 3, 3, do_execute_mutations },
-    { "query", NULL, 3, 3, do_query },
-    { "query-distinct", NULL, 4, 4, do_query_distinct },
-    { "transact", NULL, 1, INT_MAX, do_transact },
-    { "parse-schema", NULL, 1, 1, do_parse_schema },
-    { "execute", NULL, 2, INT_MAX, do_execute },
-    { "execute-readonly", NULL, 2, INT_MAX, do_execute_ro },
-    { "trigger", NULL, 2, INT_MAX, do_trigger },
-    { "idl", NULL, 1, INT_MAX, do_idl },
+    { "parse-atomic-type", NULL, 1, 1, do_parse_atomic_type, OVS_RO },
+    { "parse-base-type", NULL, 1, 1, do_parse_base_type, OVS_RO },
+    { "parse-type", NULL, 1, 1, do_parse_type, OVS_RO },
+    { "parse-atoms", NULL, 2, INT_MAX, do_parse_atoms, OVS_RO },
+    { "parse-atom-strings", NULL, 2, INT_MAX, do_parse_atom_strings, OVS_RO },
+    { "parse-data", NULL, 2, INT_MAX, do_parse_data, OVS_RO },
+    { "parse-data-strings", NULL, 2, INT_MAX, do_parse_data_strings, OVS_RO },
+    { "sort-atoms", NULL, 2, 2, do_sort_atoms, OVS_RO },
+    { "parse-column", NULL, 2, 2, do_parse_column, OVS_RO },
+    { "parse-table", NULL, 2, 3, do_parse_table, OVS_RO },
+    { "parse-rows", NULL, 2, INT_MAX, do_parse_rows, OVS_RO },
+    { "compare-rows", NULL, 2, INT_MAX, do_compare_rows, OVS_RO },
+    { "parse-conditions", NULL, 2, INT_MAX, do_parse_conditions, OVS_RO },
+    { "evaluate-conditions", NULL, 3, 3, do_evaluate_conditions, OVS_RO },
+    { "evaluate-conditions-any", NULL, 3, 3, do_evaluate_conditions_any, OVS_RO },
+    { "compare-conditions", NULL, 2, 2, do_compare_conditions, OVS_RO },
+    { "parse-mutations", NULL, 2, INT_MAX, do_parse_mutations, OVS_RO },
+    { "execute-mutations", NULL, 3, 3, do_execute_mutations, OVS_RO },
+    { "query", NULL, 3, 3, do_query, OVS_RO },
+    { "query-distinct", NULL, 4, 4, do_query_distinct, OVS_RO },
+    { "transact", NULL, 1, INT_MAX, do_transact, OVS_RO },
+    { "parse-schema", NULL, 1, 1, do_parse_schema, OVS_RO },
+    { "execute", NULL, 2, INT_MAX, do_execute, OVS_RO },
+    { "execute-readonly", NULL, 2, INT_MAX, do_execute_ro, OVS_RO },
+    { "trigger", NULL, 2, INT_MAX, do_trigger, OVS_RO },
+    { "idl", NULL, 1, INT_MAX, do_idl, OVS_RO },
     { "idl-partial-update-map-column", NULL, 1, INT_MAX,
-                                       do_idl_partial_update_map_column },
+        do_idl_partial_update_map_column, OVS_RO },
     { "idl-partial-update-set-column", NULL, 1, INT_MAX,
-                                       do_idl_partial_update_set_column },
-    { "help", NULL, 0, INT_MAX, do_help },
-    { NULL, NULL, 0, 0, NULL },
+        do_idl_partial_update_set_column, OVS_RO },
+    { "help", NULL, 0, INT_MAX, do_help, OVS_RO },
+    { NULL, NULL, 0, 0, NULL, OVS_RO },
 };
 
 static struct ovs_cmdl_command *
diff --git a/tests/test-reconnect.c b/tests/test-reconnect.c
index 76e43ea..72252b8 100644
--- a/tests/test-reconnect.c
+++ b/tests/test-reconnect.c
@@ -272,22 +272,22 @@  do_listen_error(struct ovs_cmdl_context *ctx)
 }
 
 static const struct ovs_cmdl_command all_commands[] = {
-    { "enable", NULL, 0, 0, do_enable },
-    { "disable", NULL, 0, 0, do_disable },
-    { "force-reconnect", NULL, 0, 0, do_force_reconnect },
-    { "disconnected", NULL, 0, 1, do_disconnected },
-    { "connecting", NULL, 0, 0, do_connecting },
-    { "connect-failed", NULL, 0, 1, do_connect_failed },
-    { "connected", NULL, 0, 0, do_connected },
-    { "activity", NULL, 0, 0, do_activity },
-    { "run", NULL, 0, 1, do_run },
-    { "advance", NULL, 1, 1, do_advance },
-    { "timeout", NULL, 0, 0, do_timeout },
-    { "set-max-tries", NULL, 1, 1, do_set_max_tries },
-    { "passive", NULL, 0, 0, do_set_passive },
-    { "listening", NULL, 0, 0, do_listening },
-    { "listen-error", NULL, 1, 1, do_listen_error },
-    { NULL, NULL, 0, 0, NULL },
+    { "enable", NULL, 0, 0, do_enable, OVS_RO },
+    { "disable", NULL, 0, 0, do_disable, OVS_RO },
+    { "force-reconnect", NULL, 0, 0, do_force_reconnect, OVS_RO },
+    { "disconnected", NULL, 0, 1, do_disconnected, OVS_RO },
+    { "connecting", NULL, 0, 0, do_connecting, OVS_RO },
+    { "connect-failed", NULL, 0, 1, do_connect_failed, OVS_RO },
+    { "connected", NULL, 0, 0, do_connected, OVS_RO },
+    { "activity", NULL, 0, 0, do_activity, OVS_RO },
+    { "run", NULL, 0, 1, do_run, OVS_RO },
+    { "advance", NULL, 1, 1, do_advance, OVS_RO },
+    { "timeout", NULL, 0, 0, do_timeout, OVS_RO },
+    { "set-max-tries", NULL, 1, 1, do_set_max_tries, OVS_RO },
+    { "passive", NULL, 0, 0, do_set_passive, OVS_RO },
+    { "listening", NULL, 0, 0, do_listening, OVS_RO },
+    { "listen-error", NULL, 1, 1, do_listen_error, OVS_RO },
+    { NULL, NULL, 0, 0, NULL, OVS_RO },
 };
 
 static const struct ovs_cmdl_command *
diff --git a/tests/test-util.c b/tests/test-util.c
index ef45903..e37c722 100644
--- a/tests/test-util.c
+++ b/tests/test-util.c
@@ -1149,25 +1149,25 @@  test_file_name(struct ovs_cmdl_context *ctx)
 #endif /* _WIN32 */
 
 static const struct ovs_cmdl_command commands[] = {
-    {"ctz", NULL, 0, 0, test_ctz},
-    {"clz", NULL, 0, 0, test_clz},
-    {"round_up_pow2", NULL, 0, 0, test_round_up_pow2},
-    {"round_down_pow2", NULL, 0, 0, test_round_down_pow2},
-    {"count_1bits", NULL, 0, 0, test_count_1bits},
-    {"log_2_floor", NULL, 0, 0, test_log_2_floor},
-    {"bitwise_copy", NULL, 0, 0, test_bitwise_copy},
-    {"bitwise_zero", NULL, 0, 0, test_bitwise_zero},
-    {"bitwise_one", NULL, 0, 0, test_bitwise_one},
-    {"bitwise_is_all_zeros", NULL, 0, 0, test_bitwise_is_all_zeros},
-    {"bitwise_rscan", NULL, 0, 0, test_bitwise_rscan},
-    {"follow-symlinks", NULL, 1, INT_MAX, test_follow_symlinks},
-    {"assert", NULL, 0, 0, test_assert},
-    {"ovs_scan", NULL, 0, 0, test_ovs_scan},
-    {"snprintf", NULL, 0, 0, test_snprintf},
+    {"ctz", NULL, 0, 0, test_ctz, OVS_RO},
+    {"clz", NULL, 0, 0, test_clz, OVS_RO},
+    {"round_up_pow2", NULL, 0, 0, test_round_up_pow2, OVS_RO},
+    {"round_down_pow2", NULL, 0, 0, test_round_down_pow2, OVS_RO},
+    {"count_1bits", NULL, 0, 0, test_count_1bits, OVS_RO},
+    {"log_2_floor", NULL, 0, 0, test_log_2_floor, OVS_RO},
+    {"bitwise_copy", NULL, 0, 0, test_bitwise_copy, OVS_RO},
+    {"bitwise_zero", NULL, 0, 0, test_bitwise_zero, OVS_RO},
+    {"bitwise_one", NULL, 0, 0, test_bitwise_one, OVS_RO},
+    {"bitwise_is_all_zeros", NULL, 0, 0, test_bitwise_is_all_zeros, OVS_RO},
+    {"bitwise_rscan", NULL, 0, 0, test_bitwise_rscan, OVS_RO},
+    {"follow-symlinks", NULL, 1, INT_MAX, test_follow_symlinks, OVS_RO},
+    {"assert", NULL, 0, 0, test_assert, OVS_RO},
+    {"ovs_scan", NULL, 0, 0, test_ovs_scan, OVS_RO},
+    {"snprintf", NULL, 0, 0, test_snprintf, OVS_RO},
 #ifndef _WIN32
-    {"file_name", NULL, 1, INT_MAX, test_file_name},
+    {"file_name", NULL, 1, INT_MAX, test_file_name, OVS_RO},
 #endif
-    {NULL, NULL, 0, 0, NULL},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };
 
 static void
diff --git a/tests/test-vconn.c b/tests/test-vconn.c
index faa824a..1bf9aa0 100644
--- a/tests/test-vconn.c
+++ b/tests/test-vconn.c
@@ -432,15 +432,15 @@  test_send_invalid_version_hello(struct ovs_cmdl_context *ctx)
 }
 
 static const struct ovs_cmdl_command commands[] = {
-    {"refuse-connection", NULL, 1, 1, test_refuse_connection},
-    {"accept-then-close", NULL, 1, 1, test_accept_then_close},
-    {"read-hello", NULL, 1, 1, test_read_hello},
-    {"send-plain-hello", NULL, 1, 1, test_send_plain_hello},
-    {"send-long-hello", NULL, 1, 1, test_send_long_hello},
-    {"send-echo-hello", NULL, 1, 1, test_send_echo_hello},
-    {"send-short-hello", NULL, 1, 1, test_send_short_hello},
-    {"send-invalid-version-hello", NULL, 1, 1, test_send_invalid_version_hello},
-    {NULL, NULL, 0, 0, NULL},
+    {"refuse-connection", NULL, 1, 1, test_refuse_connection, OVS_RO},
+    {"accept-then-close", NULL, 1, 1, test_accept_then_close, OVS_RO},
+    {"read-hello", NULL, 1, 1, test_read_hello, OVS_RO},
+    {"send-plain-hello", NULL, 1, 1, test_send_plain_hello, OVS_RO},
+    {"send-long-hello", NULL, 1, 1, test_send_long_hello, OVS_RO},
+    {"send-echo-hello", NULL, 1, 1, test_send_echo_hello, OVS_RO},
+    {"send-short-hello", NULL, 1, 1, test_send_short_hello, OVS_RO},
+    {"send-invalid-version-hello", NULL, 1, 1, test_send_invalid_version_hello, OVS_RO},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };
 
 static void
diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c
index 4897ea3..843d305 100644
--- a/utilities/ovs-dpctl.c
+++ b/utilities/ovs-dpctl.c
@@ -77,12 +77,14 @@  parse_options(int argc, char *argv[])
     enum {
         OPT_CLEAR = UCHAR_MAX + 1,
         OPT_MAY_CREATE,
+        OPT_READ_ONLY,
         VLOG_OPTION_ENUMS
     };
     static const struct option long_options[] = {
         {"statistics", no_argument, NULL, 's'},
         {"clear", no_argument, NULL, OPT_CLEAR},
         {"may-create", no_argument, NULL, OPT_MAY_CREATE},
+        {"read-only", no_argument, NULL, OPT_READ_ONLY},
         {"more", no_argument, NULL, 'm'},
         {"timeout", required_argument, NULL, 't'},
         {"help", no_argument, NULL, 'h'},
@@ -115,6 +117,10 @@  parse_options(int argc, char *argv[])
             dpctl_p.may_create = true;
             break;
 
+        case OPT_READ_ONLY:
+            dpctl_p.read_only = true;
+            break;
+
         case 'm':
             dpctl_p.verbosity++;
             break;
@@ -186,6 +192,7 @@  usage(void *userdata OVS_UNUSED)
            "  -m, --more                  increase verbosity of output\n"
            "\nOptions for mod-flow:\n"
            "  --may-create                create flow if it doesn't exist\n"
+           "  --read-only                 do not run read/write commands\n"
            "  --clear                     reset existing stats to zero\n"
            "\nOther options:\n"
            "  -t, --timeout=SECS          give up after SECS seconds\n"
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index cccd265..b56e5b3 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -2975,6 +2975,9 @@  depending on its configuration.
 \fB\-\-strict\fR
 Uses strict matching when running flow modification commands.
 .
+.IP "\fB\-\-read-only\fR"
+Do not execute read/write commands.
+.
 .IP "\fB\-\-bundle\fR"
 Execute flow mods as an OpenFlow 1.4 atomic bundle transaction.
 .RS
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index 42d358f..6fd3818 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -79,6 +79,9 @@  static bool bundle = false;
 /* --color: Use color markers. */
 static bool enable_color;
 
+/* --read-only: Do not execute read only commands. */
+static bool read_only;
+
 /* --strict: Use strict matching for flow mod commands?  Additionally governs
  * use of nx_pull_match() instead of nx_pull_match_loose() in parse-nx-match.
  */
@@ -142,7 +145,11 @@  main(int argc, char *argv[])
     ctx.argv = argv + optind;
 
     daemon_become_new_user(false);
-    ovs_cmdl_run_command(&ctx, get_all_commands());
+    if (read_only) {
+        ovs_cmdl_run_command_read_only(&ctx, get_all_commands());
+    } else {
+        ovs_cmdl_run_command(&ctx, get_all_commands());
+    }
     return 0;
 }
 
@@ -180,6 +187,7 @@  parse_options(int argc, char *argv[])
         OPT_BUNDLE,
         OPT_COLOR,
         OPT_MAY_CREATE,
+        OPT_READ_ONLY,
         DAEMON_OPTION_ENUMS,
         OFP_VERSION_OPTION_ENUMS,
         VLOG_OPTION_ENUMS
@@ -200,6 +208,7 @@  parse_options(int argc, char *argv[])
         {"bundle", no_argument, NULL, OPT_BUNDLE},
         {"color", optional_argument, NULL, OPT_COLOR},
         {"may-create", no_argument, NULL, OPT_MAY_CREATE},
+        {"read-only", no_argument, NULL, OPT_READ_ONLY},
         DAEMON_LONG_OPTIONS,
         OFP_VERSION_LONG_OPTIONS,
         VLOG_LONG_OPTIONS,
@@ -281,6 +290,10 @@  parse_options(int argc, char *argv[])
             strict = true;
             break;
 
+        case OPT_READ_ONLY:
+            read_only = true;
+            break;
+
         case OPT_READD:
             readd = true;
             break;
@@ -452,6 +465,7 @@  usage(void)
     vlog_usage();
     printf("\nOther options:\n"
            "  --strict                    use strict match for flow commands\n"
+           "  --read-only                 do not execute read/write commands\n"
            "  --readd                     replace flows that haven't changed\n"
            "  -F, --flow-format=FORMAT    force particular flow format\n"
            "  -P, --packet-in-format=FRMT force particular packet in format\n"
@@ -4120,136 +4134,136 @@  ofctl_parse_key_value(struct ovs_cmdl_context *ctx)
 
 static const struct ovs_cmdl_command all_commands[] = {
     { "show", "switch",
-      1, 1, ofctl_show },
+      1, 1, ofctl_show, OVS_RO },
     { "monitor", "switch [misslen] [invalid_ttl] [watch:[...]]",
-      1, 3, ofctl_monitor },
+      1, 3, ofctl_monitor, OVS_RO },
     { "snoop", "switch",
-      1, 1, ofctl_snoop },
+      1, 1, ofctl_snoop, OVS_RO },
     { "dump-desc", "switch",
-      1, 1, ofctl_dump_desc },
+      1, 1, ofctl_dump_desc, OVS_RO },
     { "dump-tables", "switch",
-      1, 1, ofctl_dump_tables },
+      1, 1, ofctl_dump_tables, OVS_RO },
     { "dump-table-features", "switch",
-      1, 1, ofctl_dump_table_features },
+      1, 1, ofctl_dump_table_features, OVS_RO },
     { "dump-table-desc", "switch",
-      1, 1, ofctl_dump_table_desc },
+      1, 1, ofctl_dump_table_desc, OVS_RO },
     { "dump-flows", "switch",
-      1, 2, ofctl_dump_flows },
+      1, 2, ofctl_dump_flows, OVS_RO },
     { "dump-aggregate", "switch",
-      1, 2, ofctl_dump_aggregate },
+      1, 2, ofctl_dump_aggregate, OVS_RO },
     { "queue-stats", "switch [port [queue]]",
-      1, 3, ofctl_queue_stats },
+      1, 3, ofctl_queue_stats, OVS_RO },
     { "queue-get-config", "switch [port [queue]]",
-      1, 3, ofctl_queue_get_config },
+      1, 3, ofctl_queue_get_config, OVS_RO },
     { "add-flow", "switch flow",
-      2, 2, ofctl_add_flow },
+      2, 2, ofctl_add_flow, OVS_RW },
     { "add-flows", "switch file",
-      2, 2, ofctl_add_flows },
+      2, 2, ofctl_add_flows, OVS_RW },
     { "mod-flows", "switch flow",
-      2, 2, ofctl_mod_flows },
+      2, 2, ofctl_mod_flows, OVS_RW },
     { "del-flows", "switch [flow]",
-      1, 2, ofctl_del_flows },
+      1, 2, ofctl_del_flows, OVS_RW },
     { "replace-flows", "switch file",
-      2, 2, ofctl_replace_flows },
+      2, 2, ofctl_replace_flows, OVS_RW },
     { "diff-flows", "source1 source2",
-      2, 2, ofctl_diff_flows },
+      2, 2, ofctl_diff_flows, OVS_RW },
     { "add-meter", "switch meter",
-      2, 2, ofctl_add_meter },
+      2, 2, ofctl_add_meter, OVS_RW },
     { "mod-meter", "switch meter",
-      2, 2, ofctl_mod_meter },
+      2, 2, ofctl_mod_meter, OVS_RW },
     { "del-meter", "switch meter",
-      2, 2, ofctl_del_meters },
+      2, 2, ofctl_del_meters, OVS_RW },
     { "del-meters", "switch",
-      1, 1, ofctl_del_meters },
+      1, 1, ofctl_del_meters, OVS_RW },
     { "dump-meter", "switch meter",
-      2, 2, ofctl_dump_meters },
+      2, 2, ofctl_dump_meters, OVS_RO },
     { "dump-meters", "switch",
-      1, 1, ofctl_dump_meters },
+      1, 1, ofctl_dump_meters, OVS_RO },
     { "meter-stats", "switch [meter]",
-      1, 2, ofctl_meter_stats },
+      1, 2, ofctl_meter_stats, OVS_RO },
     { "meter-features", "switch",
-      1, 1, ofctl_meter_features },
+      1, 1, ofctl_meter_features, OVS_RO },
     { "packet-out", "switch in_port actions packet...",
-      4, INT_MAX, ofctl_packet_out },
+      4, INT_MAX, ofctl_packet_out, OVS_RW },
     { "dump-ports", "switch [port]",
-      1, 2, ofctl_dump_ports },
+      1, 2, ofctl_dump_ports, OVS_RO },
     { "dump-ports-desc", "switch [port]",
-      1, 2, ofctl_dump_ports_desc },
+      1, 2, ofctl_dump_ports_desc, OVS_RO },
     { "mod-port", "switch iface act",
-      3, 3, ofctl_mod_port },
+      3, 3, ofctl_mod_port, OVS_RW },
     { "mod-table", "switch mod",
-      3, 3, ofctl_mod_table },
+      3, 3, ofctl_mod_table, OVS_RW },
     { "get-frags", "switch",
-      1, 1, ofctl_get_frags },
+      1, 1, ofctl_get_frags, OVS_RO },
     { "set-frags", "switch frag_mode",
-      2, 2, ofctl_set_frags },
+      2, 2, ofctl_set_frags, OVS_RW },
     { "probe", "target",
-      1, 1, ofctl_probe },
+      1, 1, ofctl_probe, OVS_RO },
     { "ping", "target [n]",
-      1, 2, ofctl_ping },
+      1, 2, ofctl_ping, OVS_RO },
     { "benchmark", "target n count",
-      3, 3, ofctl_benchmark },
+      3, 3, ofctl_benchmark, OVS_RO },
 
     { "dump-ipfix-bridge", "switch",
-      1, 1, ofctl_dump_ipfix_bridge},
+      1, 1, ofctl_dump_ipfix_bridge, OVS_RO },
     { "dump-ipfix-flow", "switch",
-      1, 1, ofctl_dump_ipfix_flow},
+      1, 1, ofctl_dump_ipfix_flow, OVS_RO },
 
     { "ofp-parse", "file",
-      1, 1, ofctl_ofp_parse },
+      1, 1, ofctl_ofp_parse, OVS_RW },
     { "ofp-parse-pcap", "pcap",
-      1, INT_MAX, ofctl_ofp_parse_pcap },
+      1, INT_MAX, ofctl_ofp_parse_pcap, OVS_RW },
 
     { "add-group", "switch group",
-      1, 2, ofctl_add_group },
+      1, 2, ofctl_add_group, OVS_RW },
     { "add-groups", "switch file",
-      1, 2, ofctl_add_groups },
+      1, 2, ofctl_add_groups, OVS_RW },
     { "mod-group", "switch group",
-      1, 2, ofctl_mod_group },
+      1, 2, ofctl_mod_group, OVS_RW },
     { "del-groups", "switch [group]",
-      1, 2, ofctl_del_groups },
+      1, 2, ofctl_del_groups, OVS_RW },
     { "insert-buckets", "switch [group]",
-      1, 2, ofctl_insert_bucket },
+      1, 2, ofctl_insert_bucket, OVS_RW },
     { "remove-buckets", "switch [group]",
-      1, 2, ofctl_remove_bucket },
+      1, 2, ofctl_remove_bucket, OVS_RW },
     { "dump-groups", "switch [group]",
-      1, 2, ofctl_dump_group_desc },
+      1, 2, ofctl_dump_group_desc, OVS_RO },
     { "dump-group-stats", "switch [group]",
-      1, 2, ofctl_dump_group_stats },
+      1, 2, ofctl_dump_group_stats, OVS_RO },
     { "dump-group-features", "switch",
-      1, 1, ofctl_dump_group_features },
+      1, 1, ofctl_dump_group_features, OVS_RO },
 
     { "bundle", "switch file",
-      2, 2, ofctl_bundle },
+      2, 2, ofctl_bundle, OVS_RW },
 
     { "add-tlv-map", "switch map",
-      2, 2, ofctl_add_tlv_map },
+      2, 2, ofctl_add_tlv_map, OVS_RO },
     { "del-tlv-map", "switch [map]",
-      1, 2, ofctl_del_tlv_map },
+      1, 2, ofctl_del_tlv_map, OVS_RO },
     { "dump-tlv-map", "switch",
-      1, 1, ofctl_dump_tlv_map },
-    { "help", NULL, 0, INT_MAX, ofctl_help },
-    { "list-commands", NULL, 0, INT_MAX, ofctl_list_commands },
+      1, 1, ofctl_dump_tlv_map, OVS_RO },
+    { "help", NULL, 0, INT_MAX, ofctl_help, OVS_RO },
+    { "list-commands", NULL, 0, INT_MAX, ofctl_list_commands, OVS_RO },
 
     /* Undocumented commands for testing. */
-    { "parse-flow", NULL, 1, 1, ofctl_parse_flow },
-    { "parse-flows", NULL, 1, 1, ofctl_parse_flows },
-    { "parse-nx-match", NULL, 0, 0, ofctl_parse_nxm },
-    { "parse-nxm", NULL, 0, 0, ofctl_parse_nxm },
-    { "parse-oxm", NULL, 1, 1, ofctl_parse_oxm },
-    { "parse-actions", NULL, 1, 1, ofctl_parse_actions },
-    { "parse-instructions", NULL, 1, 1, ofctl_parse_instructions },
-    { "parse-ofp10-match", NULL, 0, 0, ofctl_parse_ofp10_match },
-    { "parse-ofp11-match", NULL, 0, 0, ofctl_parse_ofp11_match },
-    { "parse-pcap", NULL, 1, INT_MAX, ofctl_parse_pcap },
-    { "check-vlan", NULL, 2, 2, ofctl_check_vlan },
-    { "print-error", NULL, 1, 1, ofctl_print_error },
-    { "encode-error-reply", NULL, 2, 2, ofctl_encode_error_reply },
-    { "ofp-print", NULL, 1, 2, ofctl_ofp_print },
-    { "encode-hello", NULL, 1, 1, ofctl_encode_hello },
-    { "parse-key-value", NULL, 1, INT_MAX, ofctl_parse_key_value },
-
-    { NULL, NULL, 0, 0, NULL },
+    { "parse-flow", NULL, 1, 1, ofctl_parse_flow, OVS_RW },
+    { "parse-flows", NULL, 1, 1, ofctl_parse_flows, OVS_RW },
+    { "parse-nx-match", NULL, 0, 0, ofctl_parse_nxm, OVS_RW },
+    { "parse-nxm", NULL, 0, 0, ofctl_parse_nxm, OVS_RW },
+    { "parse-oxm", NULL, 1, 1, ofctl_parse_oxm, OVS_RW },
+    { "parse-actions", NULL, 1, 1, ofctl_parse_actions, OVS_RW },
+    { "parse-instructions", NULL, 1, 1, ofctl_parse_instructions, OVS_RW },
+    { "parse-ofp10-match", NULL, 0, 0, ofctl_parse_ofp10_match, OVS_RW },
+    { "parse-ofp11-match", NULL, 0, 0, ofctl_parse_ofp11_match, OVS_RW },
+    { "parse-pcap", NULL, 1, INT_MAX, ofctl_parse_pcap, OVS_RW },
+    { "check-vlan", NULL, 2, 2, ofctl_check_vlan, OVS_RW },
+    { "print-error", NULL, 1, 1, ofctl_print_error, OVS_RW },
+    { "encode-error-reply", NULL, 2, 2, ofctl_encode_error_reply, OVS_RW },
+    { "ofp-print", NULL, 1, 2, ofctl_ofp_print, OVS_RW },
+    { "encode-hello", NULL, 1, 1, ofctl_encode_hello, OVS_RW },
+    { "parse-key-value", NULL, 1, INT_MAX, ofctl_parse_key_value, OVS_RW },
+
+    { NULL, NULL, 0, 0, NULL, OVS_RO },
 };
 
 static const struct ovs_cmdl_command *get_all_commands(void)