[ovs-dev] [PATCH] ovs-ofctl: Ability to sort ovs-ofctl dump-flows by priority/other flow fields.

Arun Sharma arun.sharma at calsoftinc.com
Mon Jun 25 18:13:34 UTC 2012


ovs-ofctl will have --sort[=field]|--rsort[=field] option for 'dump-flows'.
Sort by priority if no field argument is specified or sort by field.
"--sort" to go in ascending order and "--rsort" to go in descending
order for flow fields other then priority field. In case of priority field,
output should be reversed with "--sort" going in descending order and
"--rsort" going in ascending order.

Feature# 8754
Signed-off-by: Arun Sharma <arun.sharma at calsoftinc.com>
---
 lib/ofp-print.c          |   83 ++++++++------
 lib/ofp-print.h          |    7 +
 tests/ovs-ofctl.at       |   54 +++++++++
 utilities/ovs-ofctl.8.in |   19 +++-
 utilities/ovs-ofctl.c    |  291 +++++++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 416 insertions(+), 38 deletions(-)

diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 9f77c5a..d6d9d35 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -1216,6 +1216,38 @@ ofp_print_flow_stats_request(struct ds *string,
     cls_rule_format(&fsr.match, string);
 }
 
+void
+ofp_flow_stats_reply(struct ds *string, struct ofputil_flow_stats *fs) {
+    ds_put_char(string, '\n');
+
+    ds_put_format(string, " cookie=0x%"PRIx64", duration=",
+                  ntohll(fs->cookie));
+
+    ofp_print_duration(string, fs->duration_sec, fs->duration_nsec);
+    ds_put_format(string, ", table=%"PRIu8", ", fs->table_id);
+    ds_put_format(string, "n_packets=%"PRIu64", ", fs->packet_count);
+    ds_put_format(string, "n_bytes=%"PRIu64", ", fs->byte_count);
+    if (fs->idle_timeout != OFP_FLOW_PERMANENT) {
+        ds_put_format(string, "idle_timeout=%"PRIu16", ", fs->idle_timeout);
+    }
+    if (fs->hard_timeout != OFP_FLOW_PERMANENT) {
+        ds_put_format(string, "hard_timeout=%"PRIu16", ", fs->hard_timeout);
+    }
+    if (fs->idle_age >= 0) {
+        ds_put_format(string, "idle_age=%d, ", fs->idle_age);
+    }
+    if (fs->hard_age >= 0 && fs->hard_age != fs->duration_sec) {
+        ds_put_format(string, "hard_age=%d, ", fs->hard_age);
+    }
+
+    cls_rule_format(&fs->rule, string);
+    if (string->string[string->length - 1] != ' ') {
+        ds_put_char(string, ' ');
+    }
+
+    ofp_print_actions(string, fs->actions, fs->n_actions);
+}
+
 static void
 ofp_print_flow_stats_reply(struct ds *string, const struct ofp_header *oh)
 {
@@ -1233,33 +1265,7 @@ ofp_print_flow_stats_reply(struct ds *string, const struct ofp_header *oh)
             }
             break;
         }
-
-        ds_put_char(string, '\n');
-
-        ds_put_format(string, " cookie=0x%"PRIx64", duration=",
-                      ntohll(fs.cookie));
-        ofp_print_duration(string, fs.duration_sec, fs.duration_nsec);
-        ds_put_format(string, ", table=%"PRIu8", ", fs.table_id);
-        ds_put_format(string, "n_packets=%"PRIu64", ", fs.packet_count);
-        ds_put_format(string, "n_bytes=%"PRIu64", ", fs.byte_count);
-        if (fs.idle_timeout != OFP_FLOW_PERMANENT) {
-            ds_put_format(string, "idle_timeout=%"PRIu16", ", fs.idle_timeout);
-        }
-        if (fs.hard_timeout != OFP_FLOW_PERMANENT) {
-            ds_put_format(string, "hard_timeout=%"PRIu16", ", fs.hard_timeout);
-        }
-        if (fs.idle_age >= 0) {
-            ds_put_format(string, "idle_age=%d, ", fs.idle_age);
-        }
-        if (fs.hard_age >= 0 && fs.hard_age != fs.duration_sec) {
-            ds_put_format(string, "hard_age=%d, ", fs.hard_age);
-        }
-
-        cls_rule_format(&fs.rule, string);
-        if (string->string[string->length - 1] != ' ') {
-            ds_put_char(string, ' ');
-        }
-        ofp_print_actions(string, fs.actions, fs.n_actions);
+        ofp_flow_stats_reply(string, &fs);
      }
 }
 
@@ -1595,15 +1601,10 @@ ofp_print_nxt_set_controller_id(struct ds *string,
     ds_put_format(string, " id=%"PRIu16, ntohs(nci->controller_id));
 }
 
-static void
-ofp_to_string__(const struct ofp_header *oh,
-                const struct ofputil_msg_type *type, struct ds *string,
-                int verbosity)
+void ofp_print_version(const struct ofp_header *oh,
+                       const struct ofputil_msg_type *type,
+                       struct ds *string)
 {
-    enum ofputil_msg_code code;
-    const void *msg = oh;
-
-    ds_put_cstr(string, ofputil_msg_type_name(type));
     switch (oh->version) {
     case OFP10_VERSION:
         break;
@@ -1615,7 +1616,19 @@ ofp_to_string__(const struct ofp_header *oh,
         break;
     }
     ds_put_format(string, " (xid=0x%"PRIx32"):", ntohl(oh->xid));
+}
+
+static void
+ofp_to_string__(const struct ofp_header *oh,
+                const struct ofputil_msg_type *type, struct ds *string,
+                int verbosity)
+{
+    enum ofputil_msg_code code;
+    const void *msg = oh;
+
+    ds_put_cstr(string, ofputil_msg_type_name(type));
 
+    ofp_print_version(oh, type, string);
     code = ofputil_msg_type_code(type);
     switch (code) {
     case OFPUTIL_MSG_INVALID:
diff --git a/lib/ofp-print.h b/lib/ofp-print.h
index ae868a4..3045c15 100644
--- a/lib/ofp-print.h
+++ b/lib/ofp-print.h
@@ -26,6 +26,9 @@ struct ofp_flow_mod;
 struct ofp10_match;
 struct ds;
 union ofp_action;
+struct ofputil_flow_stats;
+struct ofp_header;
+struct ofputil_msg_type;
 
 #ifdef  __cplusplus
 extern "C" {
@@ -41,6 +44,10 @@ char *ofp_to_string(const void *, size_t, int verbosity);
 char *ofp10_match_to_string(const struct ofp10_match *, int verbosity);
 char *ofp_packet_to_string(const void *data, size_t len);
 
+void ofp_flow_stats_reply(struct ds *, struct ofputil_flow_stats *);
+void ofp_print_version(const struct ofp_header *,
+                       const struct ofputil_msg_type *, struct ds *);
+
 #ifdef  __cplusplus
 }
 #endif
diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at
index a4fa7e4..b4e18fb 100644
--- a/tests/ovs-ofctl.at
+++ b/tests/ovs-ofctl.at
@@ -1300,3 +1300,57 @@ ofp_util|INFO|post: @&t@
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
+dnl Check that --sort and --rsort works with dump-flows
+dnl Default field is 'priority'. Flow entries are displayed based
+dnl on field to sort.
+AT_SETUP([ovs-ofctl [--sort|--rsort] dump-flows])
+OVS_VSWITCHD_START
+AT_KEYWORDS([dump-flows-sort])
+AT_DATA([allflows.txt], [[
+cookie=0x0, table=0, n_packets=0, n_bytes=0, priority=4,in_port=23213 actions=output:42
+cookie=0x0, table=0, n_packets=0, n_bytes=0, priority=5,in_port=1029 actions=output:43
+cookie=0x0, table=0, n_packets=0, n_bytes=0, priority=3,in_port=1028 actions=output:44
+cookie=0x0, table=0, n_packets=0, n_bytes=0, priority=1,in_port=1026 actions=output:45
+cookie=0x0, table=0, n_packets=0, n_bytes=0, priority=6,in_port=1027 actions=output:64
+cookie=0x0, table=0, n_packets=0, n_bytes=0, priority=2,in_port=1025 actions=output:47
+]])
+
+AT_CHECK([ovs-ofctl add-flows br0 allflows.txt
+], [0], [ignore])
+AT_CHECK([ovs-ofctl --sort dump-flows br0 |tr ' ' "," | awk -F, '{ if (FNR > 1) print $14}'], [0],
+[priority=6
+priority=5
+priority=4
+priority=3
+priority=2
+priority=1
+])
+
+AT_CHECK([ovs-ofctl --rsort dump-flows br0 |tr ' ' "," | awk -F, '{ if (FNR > 1) print $14}'], [0],
+[priority=1
+priority=2
+priority=3
+priority=4
+priority=5
+priority=6
+])
+
+AT_CHECK([ovs-ofctl --sort=in_port dump-flows br0 |tr ' ' "," | awk -F, '{ if (FNR > 1) print $15}'], [0],
+[in_port=1025
+in_port=1026
+in_port=1027
+in_port=1028
+in_port=1029
+in_port=23213
+])
+
+AT_CHECK([ovs-ofctl --rsort=in_port dump-flows br0 |tr ' ' "," | awk -F, '{ if (FNR > 1) print $15}'], [0],
+[in_port=23213
+in_port=1029
+in_port=1028
+in_port=1027
+in_port=1026
+in_port=1025
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index 9c4ea0c..6ddcb69 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -157,13 +157,18 @@ See the description of \fBip_frag\fR, below, for a way to match on
 whether a packet is a fragment and on its fragment offset.
 .
 .TP
-\fBdump\-flows \fIswitch \fR[\fIflows\fR]
+[\fB\-\-sort\fR[=field]\fR] [\fB\-\-rsort\fR[=field]\fR]\fB dump\-flows \fIswitch \fR[\fIflows\fR]
 Prints to the console all flow entries in \fIswitch\fR's
 tables that match \fIflows\fR.  If \fIflows\fR is omitted, all flows
 in the switch are retrieved.  See \fBFlow Syntax\fR, below, for the
 syntax of \fIflows\fR.  The output format is described in 
 \fBTable Entry Output\fR.
 .
+.IP
+Flow entries can be sorted by flow field in ascending or descending order.
+If no field is specified then by default it will sort by \fBpriority\fR field.
+See \fBOPTIONS\fR below for more details.
+.
 .TP
 \fBdump\-aggregate \fIswitch \fR[\fIflows\fR]
 Prints to the console aggregate statistics for flows in 
@@ -1317,6 +1322,18 @@ Increases the verbosity of OpenFlow messages printed and logged by
 \fBovs\-ofctl\fR commands.  Specify this option more than once to
 increase verbosity further.
 .
+.IP "\fB\-\-sort\fR"
+.IQ \fB\-\-sort\fR[\fB=\fIfield\fR]
+Display output sorted by flow \fBfield\fR.
+It will display output in ascending order. If sorted by \fBpriority\fR then
+it will display output in descending order.
+.
+.IP "\fB\-\-rsort\fR"
+.IQ \fB\-\-rsort\fR[\fB=\fIfield\fR]
+Display output sorted by flow \fBfield\fR.
+It will display output in descending order. If sorted by \fBpriority\fR then
+it will display output in ascending order.
+.
 .ds DD \
 \fBovs\-ofctl\fR detaches only when executing the \fBmonitor\fR or \
 \fBsnoop\fR commands.
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index 7413455..0f2a6d0 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -55,6 +55,8 @@
 #include "util.h"
 #include "vconn.h"
 #include "vlog.h"
+#include "meta-flow.h"
+#include "sort.h"
 
 VLOG_DEFINE_THIS_MODULE(ofctl);
 
@@ -83,6 +85,22 @@ static int verbosity;
  * "snoop" command? */
 static bool timestamp;
 
+/* Different sorting orders. */
+enum sort_order
+{
+    ASC_SORT = 1,
+    DES_SORT
+};
+
+/* --sort|--rsort, store sorting order. */
+static enum sort_order selected_sort;
+
+/* --sort[=field]|--rsort[=field], flow field on which sorting is required. */
+static char *field_to_sort;
+
+/* Default sorting field if no argument to --sort|--rsort */
+static char *default_sort_field = "priority";
+
 static const struct command all_commands[];
 
 static void usage(void) NO_RETURN;
@@ -116,6 +134,8 @@ parse_options(int argc, char *argv[])
         {"packet-in-format", required_argument, NULL, 'P'},
         {"more", no_argument, NULL, 'm'},
         {"timestamp", no_argument, NULL, OPT_TIMESTAMP},
+        {"sort", optional_argument, NULL, 's'},
+        {"rsort", optional_argument, NULL, 'r'},
         {"help", no_argument, NULL, 'h'},
         {"version", no_argument, NULL, 'V'},
         DAEMON_LONG_OPTIONS,
@@ -183,6 +203,26 @@ parse_options(int argc, char *argv[])
             timestamp = true;
             break;
 
+        case 's':
+           selected_sort = ASC_SORT;
+           if (optarg) {
+               field_to_sort = (char *) xmalloc(sizeof(optarg));
+               strncpy(field_to_sort, optarg, strlen(optarg));
+           } else {
+               field_to_sort = default_sort_field;
+           }
+           break;
+
+        case 'r':
+           selected_sort = DES_SORT;
+           if (optarg) {
+               field_to_sort = (char *) xmalloc(sizeof(optarg));
+               strncpy(field_to_sort, optarg, strlen(optarg));
+           } else {
+               field_to_sort = default_sort_field;
+           }
+           break;
+
         DAEMON_OPTION_HANDLERS
         VLOG_OPTION_HANDLERS
         STREAM_SSL_OPTION_HANDLERS
@@ -194,6 +234,29 @@ parse_options(int argc, char *argv[])
             abort();
         }
     }
+
+    if (selected_sort) {
+        if (strcmp("dump-flows", argv[optind]) != 0) {
+            ovs_fatal(0, "--sort or --rsort is not supported by %s",
+                      argv[optind]);
+        }
+
+        /* 'priority' field should be sorted in reverse order.
+         * Higher priority should be displayed first for ascending
+         * or vice versa.
+         */
+        if (strcmp(field_to_sort, default_sort_field) == 0) {
+            selected_sort = (selected_sort == ASC_SORT) ? DES_SORT : ASC_SORT;
+        }
+
+        if (strcmp(field_to_sort, default_sort_field) != 0) {
+            const struct mf_field *mf = mf_from_name(field_to_sort);
+            if (mf == NULL) {
+                ovs_fatal(0, "unknown sort field '%s'", field_to_sort);
+            }
+        }
+    }
+
     free(short_options);
 }
 
@@ -244,6 +307,8 @@ usage(void)
            "  -m, --more                  be more verbose printing OpenFlow\n"
            "  --timestamp                 (monitor, snoop) print timestamps\n"
            "  -t, --timeout=SECS          give up after SECS seconds\n"
+           "  --sort                      sort in ascending order\n"
+           "  --rsort                     sort in descending order\n"
            "  -h, --help                  display this help message\n"
            "  -V, --version               display version information\n");
     exit(EXIT_SUCCESS);
@@ -376,12 +441,145 @@ dump_trivial_transaction(const char *vconn_name, uint8_t request_type)
     dump_transaction(vconn_name, request);
 }
 
+struct dump_flows_stats
+{
+    struct ofputil_flow_stats **all_fs;    /* Flow entries to sort */
+    const char *field;                     /* Sorting field */
+};
+
+static int
+compare_flow_stats_by_priority(const void *a_, const void *b_) {
+    const struct ofputil_flow_stats *const *a = a_;
+    const struct ofputil_flow_stats *const *b = b_;
+    if (selected_sort == ASC_SORT) {
+        return ((*a)->rule.priority - (*b)->rule.priority);
+    } else {
+        return ((*b)->rule.priority - (*a)->rule.priority);
+    }
+}
+
+static int
+compare_flow_stats_by_field(size_t a, size_t b, void *fs_dump_) {
+    const struct dump_flows_stats *fs_dump = (struct dump_flows_stats *)fs_dump_;
+    const struct ofputil_flow_stats *fs_a = fs_dump->all_fs[a];
+    const struct ofputil_flow_stats *fs_b = fs_dump->all_fs[b];
+    const struct mf_field *mf = mf_from_name(fs_dump->field);
+    int res;
+    union mf_value value_a;
+    union mf_value value_b;
+
+    /* Flow entries which does meets mf prerequisites or
+     * not present in flow entry should be at the bottom
+     * of ascending sorted list.
+     */
+    if (selected_sort == ASC_SORT) {
+        char *cs_rule_str_a;
+        char *cs_rule_str_b;
+        char f[strlen(fs_dump->field) + 1];
+
+        /* If a's mf prereqs is not ok and b's prereqs is ok
+         * then treat a is bigger then b. This is required to
+         * put unrelated entries at bottom of ascending sorted list.
+         */
+        if (!mf_are_prereqs_ok(mf, &((fs_a)->rule.flow))
+            && mf_are_prereqs_ok(mf, &((fs_b)->rule.flow))) {
+            return 1;
+        }
+
+        /* If a's mf prereqs is ok and b's prereqs is not ok
+         * then treat a is smaller then b. This is required to
+         * put unreleted entries at bottom of ascending sorted list.
+         */
+        if (mf_are_prereqs_ok(mf, &((fs_a)->rule.flow))
+            && !mf_are_prereqs_ok(mf, &((fs_b)->rule.flow))) {
+            return -1;
+        }
+
+        sprintf(f, "%s=", fs_dump->field);
+        cs_rule_str_a = cls_rule_to_string(&(fs_a)->rule);
+        cs_rule_str_b = cls_rule_to_string(&(fs_b)->rule);
+
+        /* if sorting field is not present in cs_rule_str_a but
+         * present in cs_rule_str_b then treat cs_rule_str_a as
+         * bigger then cs_rule_str_b. This is required to
+         * put unrelated entries at bottom of ascending sorted list.
+         */
+        if ((strstr(cs_rule_str_a, f) == NULL)
+            && strstr(cs_rule_str_b, f) != NULL) {
+            return 1;
+        }
+
+        /* if sorting field is present in cs_rule_str_a but
+         * not present in cs_rule_str_b then treat cs_rule_str_a
+         * as smaller then cs_rule_str_b. This is required to
+         * put unrelated entries at bottom of ascending sorted list.
+         */
+        if ((strstr(cs_rule_str_a, f) != NULL)
+            && strstr(cs_rule_str_b, f) == NULL) {
+            return -1;
+        }
+
+        free(cs_rule_str_a);
+        free(cs_rule_str_b);
+    }
+
+    /* compare field values present in both entries */
+    memset(&value_a, '\0', sizeof(union mf_value));
+    memset(&value_b, '\0', sizeof(union mf_value));
+
+    mf_get_value(mf, &((fs_a)->rule.flow), &value_a);
+    mf_get_value(mf, &((fs_b)->rule.flow), &value_b);
+
+    if (mf_is_value_valid(mf, &value_a)
+        && mf_is_value_valid(mf, &value_b)) {
+        if (selected_sort == ASC_SORT) {
+            res = memcmp(&value_a, &value_b, mf->n_bytes);
+        } else {
+            res = memcmp(&value_b, &value_a, mf->n_bytes);
+        }
+    } else {
+        /* Sort by priority field in desending order if field
+         * values are invalid
+         */
+        return ((fs_b)->rule.priority - (fs_a)->rule.priority);
+    }
+
+    /* If field values are same in both entries then it should
+     * be sorted by priority in desending order
+     */
+    if (res == 0) {
+        return ((fs_b)->rule.priority - (fs_a)->rule.priority);
+    }
+    return res;
+}
+
+static void
+swap_flow_stats(size_t a, size_t b, void *fs_dump_) {
+    struct dump_flows_stats *fs_dump = fs_dump_;
+    struct ofputil_flow_stats *tmp;
+    if (selected_sort == ASC_SORT) {
+        tmp = fs_dump->all_fs[a];
+        fs_dump->all_fs[a] = fs_dump->all_fs[b];
+        fs_dump->all_fs[b] = tmp;
+    } else {
+        tmp = fs_dump->all_fs[b];
+        fs_dump->all_fs[b] = fs_dump->all_fs[a];
+        fs_dump->all_fs[a] = tmp;
+    }
+}
+
 static void
 dump_stats_transaction(const char *vconn_name, struct ofpbuf *request)
 {
     ovs_be32 send_xid = ((struct ofp_header *) request->data)->xid;
     struct vconn *vconn;
     bool done = false;
+    size_t i;
+    size_t no_of_fs = 0;
+    size_t no_of_reply = 0;
+    struct ds stats_str = DS_EMPTY_INITIALIZER;
+    struct ofputil_flow_stats **all_fs = NULL;
+    struct ofpbuf **all_reply = NULL;
 
     open_vconn(vconn_name, &vconn);
     send_openflow_buffer(vconn, request);
@@ -394,15 +592,104 @@ dump_stats_transaction(const char *vconn_name, struct ofpbuf *request)
         if (send_xid == recv_xid) {
             struct ofp_stats_msg *osm;
 
-            ofp_print(stdout, reply->data, reply->size, verbosity + 1);
+            if (selected_sort && (!stats_str.string)) {
+                const struct ofputil_msg_type *type;
+                enum ofperr error;
+                error = ofputil_decode_msg_type(reply->data, &type);
+                if (!error) {
+                    ds_put_cstr(&stats_str, ofputil_msg_type_name(type));
+                    ofp_print_version(reply->data, type, &stats_str);
+                }
+            }
+
+            if (!selected_sort) {
+                ofp_print(stdout, reply->data, reply->size, verbosity + 1);
+            }
 
             osm = ofpbuf_at(reply, 0, sizeof *osm);
             done = !osm || !(ntohs(osm->flags) & OFPSF_REPLY_MORE);
+
+            if (selected_sort) {
+                struct ofpbuf b;
+                ofpbuf_use_const(&b, reply->data,
+                                 ntohs(((struct ofp_header *)reply->data)->length));
+
+                all_reply = (struct ofpbuf **)realloc(all_reply,
+                                                      (no_of_reply + 1) *
+                                                      sizeof (struct ofpbuf *));
+
+                all_reply[no_of_reply] = reply;
+
+                for (;;) {
+                    int retval;
+                    all_fs = (struct ofputil_flow_stats **)
+                              realloc (all_fs,
+                                       (no_of_fs + 1) *
+                                       sizeof (struct ofputil_flow_stats *));
+
+                    all_fs[no_of_fs] = (struct ofputil_flow_stats *)
+                                        xmalloc (sizeof(struct ofputil_flow_stats));
+
+                    retval = ofputil_decode_flow_stats_reply(all_fs[no_of_fs],
+                                                             &b,
+                                                             true);
+                    if (retval) {
+                        if (retval != EOF) {
+                            ovs_fatal(0,
+                                      "parse error while decoding flow stats");
+                        }
+                        free (all_fs[no_of_fs]);
+                        break;
+                    }
+                    no_of_fs++;
+                }
+            }
         } else {
             VLOG_DBG("received reply with xid %08"PRIx32" "
                      "!= expected %08"PRIx32, recv_xid, send_xid);
         }
-        ofpbuf_delete(reply);
+
+        if (!selected_sort) {
+            ofpbuf_delete(reply);
+        } else {
+            no_of_reply++;
+        }
+    }
+
+    if (selected_sort) {
+        struct dump_flows_stats fs_dump;
+        fs_dump.all_fs = all_fs;
+        fs_dump.field = field_to_sort;
+
+        if (strcmp(field_to_sort, default_sort_field) == 0) {
+            qsort(all_fs, no_of_fs, sizeof(*all_fs),
+                  compare_flow_stats_by_priority);
+        } else {
+            sort(no_of_fs, compare_flow_stats_by_field,
+                 swap_flow_stats, &fs_dump);
+        }
+
+        fputs(ds_steal_cstr(&stats_str), stdout);
+        for (i=0; i<no_of_fs; i++) {
+            char *formatted_entry;
+            struct ds string = DS_EMPTY_INITIALIZER;
+
+            ofp_flow_stats_reply(&string, all_fs[i]);
+            formatted_entry = ds_steal_cstr(&string);
+            fputs(formatted_entry, stdout);
+            free(formatted_entry);
+        }
+        fputs("\n", stdout);
+
+        for (i=0; i<no_of_fs; i++) {
+            free(all_fs[i]);
+        }
+        free(all_fs);
+
+        for (i=0; i < no_of_reply; i++) {
+            ofpbuf_delete(all_reply[i]);
+        }
+        free(all_reply);
     }
     vconn_close(vconn);
 }
-- 
1.7.2.5




More information about the dev mailing list