]> git.proxmox.com Git - mirror_ovs.git/blobdiff - ovsdb/file.c
trigger: Free leaked ovsdb_schema
[mirror_ovs.git] / ovsdb / file.c
index 97359d0359b34c7dd05028a8214d22aab7f4c37d..8d16b097b7ea22c209907f67a8915a3f1d8bae6d 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2009 Nicira Networks
+/* Copyright (c) 2009, 2010, 2011, 2012, 2013, 2016, 2017 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #include "file.h"
 
-#include <assert.h>
+#include <errno.h>
 #include <fcntl.h>
+#include <unistd.h>
 
+#include "bitmap.h"
 #include "column.h"
 #include "log.h"
-#include "json.h"
+#include "openvswitch/json.h"
+#include "lockfile.h"
 #include "ovsdb.h"
 #include "ovsdb-error.h"
 #include "row.h"
+#include "socket-util.h"
+#include "storage.h"
 #include "table.h"
 #include "timeval.h"
 #include "transaction.h"
 #include "uuid.h"
 #include "util.h"
+#include "openvswitch/vlog.h"
 
-#define THIS_MODULE VLM_ovsdb_file
-#include "vlog.h"
+VLOG_DEFINE_THIS_MODULE(ovsdb_file);
 
-static struct ovsdb_error *ovsdb_file_txn_from_json(struct ovsdb *,
-                                                    const struct json *,
-                                                    struct ovsdb_txn **);
-static void ovsdb_file_replica_create(struct ovsdb *, struct ovsdb_log *);
+/* A transaction being converted to JSON for writing to a file. */
+struct ovsdb_file_txn {
+    struct json *json;          /* JSON for the whole transaction. */
+    struct json *table_json;    /* JSON for 'table''s transaction. */
+    struct ovsdb_table *table;  /* Table described in 'table_json'.  */
+};
 
-struct ovsdb_error *
-ovsdb_file_open(const char *file_name, bool read_only, struct ovsdb **dbp)
+static void ovsdb_file_txn_init(struct ovsdb_file_txn *);
+static void ovsdb_file_txn_add_row(struct ovsdb_file_txn *,
+                                   const struct ovsdb_row *old,
+                                   const struct ovsdb_row *new,
+                                   const unsigned long int *changed);
+
+static struct ovsdb_error *
+ovsdb_file_update_row_from_json(struct ovsdb_row *row, bool converting,
+                                const struct json *json)
 {
-    struct ovsdb_schema *schema;
+    struct ovsdb_table_schema *schema = row->table->schema;
     struct ovsdb_error *error;
-    struct ovsdb_log *log;
-    struct json *json;
-    struct ovsdb *db;
-
-    error = ovsdb_log_open(file_name, read_only ? O_RDONLY : O_RDWR, &log);
-    if (error) {
-        return error;
-    }
+    struct shash_node *node;
 
-    error = ovsdb_log_read(log, &json);
-    if (error) {
-        return error;
-    } else if (!json) {
-        return ovsdb_io_error(EOF, "%s: database file contains no schema",
-                              file_name);
+    if (json->type != JSON_OBJECT) {
+        return ovsdb_syntax_error(json, NULL, "row must be JSON object");
     }
 
-    error = ovsdb_schema_from_json(json, &schema);
-    if (error) {
-        json_destroy(json);
-        return ovsdb_wrap_error(error,
-                                "failed to parse \"%s\" as ovsdb schema",
-                                file_name);
-    }
-    json_destroy(json);
+    SHASH_FOR_EACH (node, json_object(json)) {
+        const char *column_name = node->name;
+        const struct ovsdb_column *column;
+        struct ovsdb_datum datum;
 
-    db = ovsdb_create(schema);
-    while ((error = ovsdb_log_read(log, &json)) == NULL && json) {
-        struct ovsdb_txn *txn;
+        column = ovsdb_table_schema_get_column(schema, column_name);
+        if (!column) {
+            if (converting) {
+                continue;
+            }
+            return ovsdb_syntax_error(json, "unknown column",
+                                      "No column %s in table %s.",
+                                      column_name, schema->name);
+        }
 
-        error = ovsdb_file_txn_from_json(db, json, &txn);
-        json_destroy(json);
+        error = ovsdb_datum_from_json(&datum, &column->type, node->data, NULL);
         if (error) {
-            break;
+            return error;
         }
-
-        ovsdb_txn_commit(txn, false);
+        ovsdb_datum_swap(&row->fields[column->index], &datum);
+        ovsdb_datum_destroy(&datum, &column->type);
     }
-    if (error) {
-        char *msg = ovsdb_error_to_string(error);
-        VLOG_WARN("%s", msg);
-        free(msg);
 
-        ovsdb_error_destroy(error);
-    }
-
-    if (!read_only) {
-        ovsdb_file_replica_create(db, log);
-    } else {
-        ovsdb_log_close(log);
-    }
-
-    *dbp = db;
     return NULL;
 }
 
 static struct ovsdb_error *
 ovsdb_file_txn_row_from_json(struct ovsdb_txn *txn, struct ovsdb_table *table,
+                             bool converting,
                              const struct uuid *row_uuid, struct json *json)
 {
     const struct ovsdb_row *row = ovsdb_table_get_row(table, row_uuid);
@@ -115,28 +106,28 @@ ovsdb_file_txn_row_from_json(struct ovsdb_txn *txn, struct ovsdb_table *table,
         ovsdb_txn_row_delete(txn, row);
         return NULL;
     } else if (row) {
-        return ovsdb_row_from_json(ovsdb_txn_row_modify(txn, row),
-                                   json, NULL, NULL);
+        return ovsdb_file_update_row_from_json(ovsdb_txn_row_modify(txn, row),
+                                               converting, json);
     } else {
         struct ovsdb_error *error;
         struct ovsdb_row *new;
 
         new = ovsdb_row_create(table);
         *ovsdb_row_get_uuid_rw(new) = *row_uuid;
-        error = ovsdb_row_from_json(new, json, NULL, NULL);
+        error = ovsdb_file_update_row_from_json(new, converting, json);
         if (error) {
             ovsdb_row_destroy(new);
+        } else {
+            ovsdb_txn_row_insert(txn, new);
         }
-
-        ovsdb_txn_row_insert(txn, new);
-
         return error;
     }
 }
 
 static struct ovsdb_error *
 ovsdb_file_txn_table_from_json(struct ovsdb_txn *txn,
-                               struct ovsdb_table *table, struct json *json)
+                               struct ovsdb_table *table,
+                               bool converting, struct json *json)
 {
     struct shash_node *node;
 
@@ -144,7 +135,7 @@ ovsdb_file_txn_table_from_json(struct ovsdb_txn *txn,
         return ovsdb_syntax_error(json, NULL, "object expected");
     }
 
-    SHASH_FOR_EACH (node, json->u.object) {
+    SHASH_FOR_EACH (node, json->object) {
         const char *uuid_string = node->name;
         struct json *txn_row_json = node->data;
         struct ovsdb_error *error;
@@ -155,8 +146,8 @@ ovsdb_file_txn_table_from_json(struct ovsdb_txn *txn,
                                       uuid_string);
         }
 
-        error = ovsdb_file_txn_row_from_json(txn, table, &row_uuid,
-                                             txn_row_json);
+        error = ovsdb_file_txn_row_from_json(txn, table, converting,
+                                             &row_uuid, txn_row_json);
         if (error) {
             return error;
         }
@@ -165,29 +156,38 @@ ovsdb_file_txn_table_from_json(struct ovsdb_txn *txn,
     return NULL;
 }
 
-static struct ovsdb_error *
+/* Converts 'json' to an ovsdb_txn for 'db', storing the new transaction in
+ * '*txnp'.  Returns NULL if successful, otherwise an error.
+ *
+ * If 'converting' is true, then unknown table and column names are ignored
+ * (which can ease upgrading and downgrading schemas); otherwise, they are
+ * treated as errors. */
+struct ovsdb_error *
 ovsdb_file_txn_from_json(struct ovsdb *db, const struct json *json,
-                         struct ovsdb_txn **txnp)
+                         bool converting, struct ovsdb_txn **txnp)
 {
     struct ovsdb_error *error;
     struct shash_node *node;
     struct ovsdb_txn *txn;
 
     *txnp = NULL;
+
     if (json->type != JSON_OBJECT) {
         return ovsdb_syntax_error(json, NULL, "object expected");
     }
 
     txn = ovsdb_txn_create(db);
-    SHASH_FOR_EACH (node, json->u.object) {
+    SHASH_FOR_EACH (node, json->object) {
         const char *table_name = node->name;
-        struct json *txn_table_json = node->data;
+        struct json *node_json = node->data;
         struct ovsdb_table *table;
 
         table = shash_find_data(&db->tables, table_name);
         if (!table) {
             if (!strcmp(table_name, "_date")
-                || !strcmp(table_name, "_comment")) {
+                && node_json->type == JSON_INTEGER) {
+                continue;
+            } else if (!strcmp(table_name, "_comment") || converting) {
                 continue;
             }
 
@@ -196,7 +196,8 @@ ovsdb_file_txn_from_json(struct ovsdb *db, const struct json *json,
             goto error;
         }
 
-        error = ovsdb_file_txn_table_from_json(txn, table, txn_table_json);
+        error = ovsdb_file_txn_table_from_json(txn, table, converting,
+                                               node_json);
         if (error) {
             goto error;
         }
@@ -208,45 +209,164 @@ error:
     ovsdb_txn_abort(txn);
     return error;
 }
-\f
-/* Replica implementation. */
 
-struct ovsdb_file_replica {
-    struct ovsdb_replica replica;
-    struct ovsdb_log *log;
-};
+static struct ovsdb_error * OVS_WARN_UNUSED_RESULT
+ovsdb_convert_table(struct ovsdb_txn *txn,
+                    const struct ovsdb_table *src_table,
+                    struct ovsdb_table *dst_table)
+{
+    const struct ovsdb_row *src_row;
+    HMAP_FOR_EACH (src_row, hmap_node, &src_table->rows) {
+        struct ovsdb_row *dst_row = ovsdb_row_create(dst_table);
+        *ovsdb_row_get_uuid_rw(dst_row) = *ovsdb_row_get_uuid(src_row);
 
-static const struct ovsdb_replica_class ovsdb_file_replica_class;
+        struct shash_node *node;
+        SHASH_FOR_EACH (node, &src_table->schema->columns) {
+            const struct ovsdb_column *src_column = node->data;
+            if (src_column->index == OVSDB_COL_UUID ||
+                src_column->index == OVSDB_COL_VERSION) {
+                continue;
+            }
 
-static void
-ovsdb_file_replica_create(struct ovsdb *db, struct ovsdb_log *log)
+            const struct ovsdb_column *dst_column
+                = shash_find_data(&dst_table->schema->columns,
+                                  src_column->name);
+            if (!dst_column) {
+                continue;
+            }
+
+            struct ovsdb_error *error = ovsdb_datum_convert(
+                &dst_row->fields[dst_column->index], &dst_column->type,
+                &src_row->fields[src_column->index], &src_column->type);
+            if (error) {
+                ovsdb_row_destroy(dst_row);
+                return error;
+            }
+        }
+
+        ovsdb_txn_row_insert(txn, dst_row);
+    }
+    return NULL;
+}
+
+/* Copies the data in 'src', converts it into the schema specified in
+ * 'new_schema', and puts it into a newly created, unbacked database, and
+ * stores a pointer to the new database in '*dstp'.  Returns null if
+ * successful, otherwise an error; on error, stores NULL in '*dstp'. */
+struct ovsdb_error * OVS_WARN_UNUSED_RESULT
+ovsdb_convert(const struct ovsdb *src, const struct ovsdb_schema *new_schema,
+              struct ovsdb **dstp)
 {
-    struct ovsdb_file_replica *r = xmalloc(sizeof *r);
-    ovsdb_replica_init(&r->replica, &ovsdb_file_replica_class);
-    r->log = log;
-    ovsdb_add_replica(db, &r->replica);
+    struct ovsdb *dst = ovsdb_create(ovsdb_schema_clone(new_schema),
+                                     ovsdb_storage_create_unbacked());
+    struct ovsdb_txn *txn = ovsdb_txn_create(dst);
+    struct ovsdb_error *error = NULL;
+
+    struct shash_node *node;
+    SHASH_FOR_EACH (node, &src->tables) {
+        const char *table_name = node->name;
+        struct ovsdb_table *src_table = node->data;
+        struct ovsdb_table *dst_table = shash_find_data(&dst->tables,
+                                                        table_name);
+        if (!dst_table) {
+            continue;
+        }
+
+        error = ovsdb_convert_table(txn, src_table, dst_table);
+        if (error) {
+            goto error;
+        }
+    }
+
+    error = ovsdb_txn_replay_commit(txn);
+    if (error) {
+        txn = NULL;            /* ovsdb_txn_replay_commit() already aborted. */
+        goto error;
+    }
+
+    *dstp = dst;
+    return NULL;
 
+error:
+    ovsdb_destroy(dst);
+    if (txn) {
+        ovsdb_txn_abort(txn);
+    }
+    *dstp = NULL;
+    return error;
+}
+\f
+static bool
+ovsdb_file_change_cb(const struct ovsdb_row *old,
+                     const struct ovsdb_row *new,
+                     const unsigned long int *changed,
+                     void *ftxn_)
+{
+    struct ovsdb_file_txn *ftxn = ftxn_;
+    ovsdb_file_txn_add_row(ftxn, old, new, changed);
+    return true;
 }
 
-static struct ovsdb_file_replica *
-ovsdb_file_replica_cast(struct ovsdb_replica *replica)
+struct json *
+ovsdb_to_txn_json(const struct ovsdb *db, const char *comment)
 {
-    assert(replica->class == &ovsdb_file_replica_class);
-    return CONTAINER_OF(replica, struct ovsdb_file_replica, replica);
+    struct ovsdb_file_txn ftxn;
+
+    ovsdb_file_txn_init(&ftxn);
+
+    struct shash_node *node;
+    SHASH_FOR_EACH (node, &db->tables) {
+        const struct ovsdb_table *table = node->data;
+        const struct ovsdb_row *row;
+
+        HMAP_FOR_EACH (row, hmap_node, &table->rows) {
+            ovsdb_file_txn_add_row(&ftxn, NULL, row, NULL);
+        }
+    }
+
+    return ovsdb_file_txn_annotate(ftxn.json, comment);
 }
 
-struct ovsdb_file_replica_aux {
-    struct json *json;          /* JSON for the whole transaction. */
-    struct json *table_json;    /* JSON for 'table''s transaction. */
-    struct ovsdb_table *table;  /* Table described in 'table_json'.  */
-};
+/* Returns 'txn' transformed into the JSON format that is used in OVSDB files.
+ * (But the caller must use ovsdb_file_txn_annotate() to add the _comment and
+ * _date members.)  If 'txn' doesn't actually change anything, returns NULL */
+struct json *
+ovsdb_file_txn_to_json(const struct ovsdb_txn *txn)
+{
+    struct ovsdb_file_txn ftxn;
 
-static bool
-ovsdb_file_replica_change_cb(const struct ovsdb_row *old,
-                             const struct ovsdb_row *new,
-                             void *aux_)
+    ovsdb_file_txn_init(&ftxn);
+    ovsdb_txn_for_each_change(txn, ovsdb_file_change_cb, &ftxn);
+    return ftxn.json;
+}
+
+struct json *
+ovsdb_file_txn_annotate(struct json *json, const char *comment)
+{
+    if (!json) {
+        json = json_object_create();
+    }
+    if (comment) {
+        json_object_put_string(json, "_comment", comment);
+    }
+    json_object_put(json, "_date", json_integer_create(time_wall_msec()));
+    return json;
+}
+\f
+static void
+ovsdb_file_txn_init(struct ovsdb_file_txn *ftxn)
+{
+    ftxn->json = NULL;
+    ftxn->table_json = NULL;
+    ftxn->table = NULL;
+}
+
+static void
+ovsdb_file_txn_add_row(struct ovsdb_file_txn *ftxn,
+                       const struct ovsdb_row *old,
+                       const struct ovsdb_row *new,
+                       const unsigned long int *changed)
 {
-    struct ovsdb_file_replica_aux *aux = aux_;
     struct json *row;
 
     if (!new) {
@@ -254,15 +374,16 @@ ovsdb_file_replica_change_cb(const struct ovsdb_row *old,
     } else {
         struct shash_node *node;
 
-        row = NULL;
+        row = old ? NULL : json_object_create();
         SHASH_FOR_EACH (node, &new->table->schema->columns) {
             const struct ovsdb_column *column = node->data;
             const struct ovsdb_type *type = &column->type;
             unsigned int idx = column->index;
 
             if (idx != OVSDB_COL_UUID && column->persistent
-                && (!old || !ovsdb_datum_equals(&old->fields[idx],
-                                                &new->fields[idx], type)))
+                && (old
+                    ? bitmap_is_set(changed, idx)
+                    : !ovsdb_datum_is_default(&new->fields[idx], type)))
             {
                 if (!row) {
                     row = json_object_create();
@@ -277,79 +398,89 @@ ovsdb_file_replica_change_cb(const struct ovsdb_row *old,
         struct ovsdb_table *table = new ? new->table : old->table;
         char uuid[UUID_LEN + 1];
 
-        if (table != aux->table) {
+        if (table != ftxn->table) {
             /* Create JSON object for transaction overall. */
-            if (!aux->json) {
-                aux->json = json_object_create();
+            if (!ftxn->json) {
+                ftxn->json = json_object_create();
             }
 
             /* Create JSON object for transaction on this table. */
-            aux->table_json = json_object_create();
-            aux->table = table;
-            json_object_put(aux->json, table->schema->name, aux->table_json);
+            ftxn->table_json = json_object_create();
+            ftxn->table = table;
+            json_object_put(ftxn->json, table->schema->name, ftxn->table_json);
         }
 
         /* Add row to transaction for this table. */
         snprintf(uuid, sizeof uuid,
                  UUID_FMT, UUID_ARGS(ovsdb_row_get_uuid(new ? new : old)));
-        json_object_put(aux->table_json, uuid, row);
+        json_object_put(ftxn->table_json, uuid, row);
     }
-
-    return true;
 }
-
-static struct ovsdb_error *
-ovsdb_file_replica_commit(struct ovsdb_replica *r_,
-                          const struct ovsdb_txn *txn, bool durable)
+\f
+static struct ovsdb *
+ovsdb_file_read__(const char *filename, bool rw,
+                  struct ovsdb_schema *new_schema)
 {
-    struct ovsdb_file_replica *r = ovsdb_file_replica_cast(r_);
-    struct ovsdb_file_replica_aux aux;
-    struct ovsdb_error *error;
-    const char *comment;
-
-    aux.json = NULL;
-    aux.table_json = NULL;
-    aux.table = NULL;
-    ovsdb_txn_for_each_change(txn, ovsdb_file_replica_change_cb, &aux);
-
-    if (!aux.json) {
-        /* Nothing to commit. */
-        return NULL;
+    struct ovsdb_storage *storage = ovsdb_storage_open_standalone(filename,
+                                                                  rw);
+    struct ovsdb_schema *schema = ovsdb_storage_read_schema(storage);
+    if (new_schema) {
+        ovsdb_schema_destroy(schema);
+        schema = new_schema;
     }
+    struct ovsdb *ovsdb = ovsdb_create(schema, storage);
+    for (;;) {
+        /* Read a transaction.  Bail if end-of-file. */
+        struct json *txn_json;
+        struct ovsdb_schema *schema2;
+        struct ovsdb_error *error = ovsdb_storage_read(storage, &schema2,
+                                                       &txn_json, NULL);
+        if (error) {
+            ovs_fatal(0, "%s", ovsdb_error_to_string_free(error));
+        }
+        ovs_assert(!schema2);
+        if (!txn_json) {
+            break;
+        }
 
-    comment = ovsdb_txn_get_comment(txn);
-    if (comment) {
-        json_object_put_string(aux.json, "_comment", comment);
-    }
-
-    json_object_put(aux.json, "_date", json_integer_create(time_now()));
-
-    error = ovsdb_log_write(r->log, aux.json);
-    json_destroy(aux.json);
-    if (error) {
-        return ovsdb_wrap_error(error, "writing transaction failed");
-    }
+        /* Apply transaction to database. */
+        struct ovsdb_txn *txn;
+        error = ovsdb_file_txn_from_json(ovsdb, txn_json, new_schema != NULL,
+                                         &txn);
+        if (error) {
+            ovs_fatal(0, "%s", ovsdb_error_to_string_free(error));
+        }
+        json_destroy(txn_json);
 
-    if (durable) {
-        error = ovsdb_log_commit(r->log);
+        error = ovsdb_txn_replay_commit(txn);
         if (error) {
-            return ovsdb_wrap_error(error, "committing transaction failed");
+            ovsdb_storage_unread(storage);
+            break;
         }
     }
-
-    return NULL;
+    return ovsdb;
 }
 
-static void
-ovsdb_file_replica_destroy(struct ovsdb_replica *r_)
+/* Reads 'filename' as a standalone database.  Returns the new database.  On
+ * error, prints a message on stderr and terminates the process.
+ *
+ * If 'rw' is true, opens the database for read/write access, otherwise
+ * read-only.
+ *
+ * Consumes 'schema'. */
+struct ovsdb *
+ovsdb_file_read(const char *filename, bool rw)
 {
-    struct ovsdb_file_replica *r = ovsdb_file_replica_cast(r_);
-
-    ovsdb_log_close(r->log);
-    free(r);
+    return ovsdb_file_read__(filename, rw, NULL);
 }
 
-static const struct ovsdb_replica_class ovsdb_file_replica_class = {
-    ovsdb_file_replica_commit,
-    ovsdb_file_replica_destroy
-};
+/* Reads 'filename' as a standalone database, using 'schema' in place of the
+ * schema embedded in the file.  Returns the new database.  On error,
+ * prints a message on stderr and terminates the process.
+ *
+ * Consumes 'schema'. */
+struct ovsdb *
+ovsdb_file_read_as_schema(const char *filename, struct ovsdb_schema *schema)
+{
+    return ovsdb_file_read__(filename, false, schema);
+}