X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=ovsdb%2Ffile.c;h=377ca280f5bb57a8966421ba9d9b8fdd8ed35571;hb=f0f54cb4dd1c4d890b6b4b9cd459c11fe880e88f;hp=4883d76f1b2144f78acc40ef17cad814c656f728;hpb=f85f8ebbfac946c19b3c6eb0f4170f579d0a4d25;p=openvswitch diff --git a/ovsdb/file.c b/ovsdb/file.c index 4883d76f..377ca280 100644 --- a/ovsdb/file.c +++ b/ovsdb/file.c @@ -18,343 +18,324 @@ #include "file.h" #include -#include #include -#include -#include -#include +#include "column.h" +#include "log.h" #include "json.h" -#include "lockfile.h" +#include "ovsdb.h" #include "ovsdb-error.h" -#include "sha1.h" +#include "row.h" +#include "table.h" +#include "transaction.h" +#include "uuid.h" #include "util.h" #define THIS_MODULE VLM_ovsdb_file #include "vlog.h" -enum ovsdb_file_mode { - OVSDB_FILE_READ, - OVSDB_FILE_WRITE -}; - -struct ovsdb_file { - off_t offset; - char *name; - struct lockfile *lockfile; - FILE *stream; - struct ovsdb_error *read_error; - struct ovsdb_error *write_error; - enum ovsdb_file_mode mode; -}; +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 *); struct ovsdb_error * -ovsdb_file_open(const char *name, int flags, struct ovsdb_file **filep) +ovsdb_file_open(const char *file_name, bool read_only, struct ovsdb **dbp) { - struct lockfile *lockfile; + struct ovsdb_schema *schema; struct ovsdb_error *error; - struct ovsdb_file *file; - struct stat s; - FILE *stream; - int accmode; - int fd; - - *filep = NULL; - - accmode = flags & O_ACCMODE; - if (accmode == O_RDWR || accmode == O_WRONLY) { - int retval = lockfile_lock(name, 0, &lockfile); - if (retval) { - error = ovsdb_io_error(retval, "%s: failed to lock lockfile", - name); - goto error; - } - } else { - lockfile = NULL; + 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; } - fd = open(name, flags, 0666); - if (fd < 0) { - const char *op = flags & O_CREAT && flags & O_EXCL ? "create" : "open"; - error = ovsdb_io_error(errno, "%s: %s failed", op, name); - goto error_unlock; + 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 (!fstat(fd, &s) && s.st_size == 0) { - /* It's (probably) a new file so fsync() its parent directory to ensure - * that its directory entry is committed to disk. */ - char *dir = dir_name(name); - int dirfd = open(dir, O_RDONLY); - if (dirfd >= 0) { - if (fsync(dirfd) && errno != EINVAL) { - VLOG_ERR("%s: fsync failed (%s)", dir, strerror(errno)); - } - close(dirfd); - } else { - VLOG_ERR("%s: open failed (%s)", dir, strerror(errno)); + 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); + + db = ovsdb_create(schema); + while ((error = ovsdb_log_read(log, &json)) == NULL && json) { + struct ovsdb_txn *txn; + + error = ovsdb_file_txn_from_json(db, json, &txn); + json_destroy(json); + if (error) { + break; } - free(dir); + + ovsdb_txn_commit(txn, false); } + if (error) { + char *msg = ovsdb_error_to_string(error); + VLOG_WARN("%s", msg); + free(msg); - stream = fdopen(fd, (accmode == O_RDONLY ? "rb" - : accmode == O_WRONLY ? "wb" - : "w+b")); - if (!stream) { - error = ovsdb_io_error(errno, "%s: fdopen failed", name); - goto error_close; + ovsdb_error_destroy(error); } - file = xmalloc(sizeof *file); - file->name = xstrdup(name); - file->lockfile = lockfile; - file->stream = stream; - file->offset = 0; - file->read_error = NULL; - file->write_error = NULL; - file->mode = OVSDB_FILE_READ; - *filep = file; - return NULL; + if (!read_only) { + ovsdb_file_replica_create(db, log); + } else { + ovsdb_log_close(log); + } -error_close: - close(fd); -error_unlock: - lockfile_unlock(lockfile); -error: - return error; + *dbp = db; + return NULL; } -void -ovsdb_file_close(struct ovsdb_file *file) +static struct ovsdb_error * +ovsdb_file_txn_row_from_json(struct ovsdb_txn *txn, struct ovsdb_table *table, + const struct uuid *row_uuid, struct json *json) { - if (file) { - free(file->name); - fclose(file->stream); - lockfile_unlock(file->lockfile); - ovsdb_error_destroy(file->read_error); - ovsdb_error_destroy(file->write_error); - free(file); + const struct ovsdb_row *row = ovsdb_table_get_row(table, row_uuid); + if (json->type == JSON_NULL) { + if (!row) { + return ovsdb_syntax_error(NULL, NULL, "transaction deletes " + "row "UUID_FMT" that does not exist", + UUID_ARGS(row_uuid)); + } + 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); + } 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); + if (error) { + ovsdb_row_destroy(new); + } + + ovsdb_txn_row_insert(txn, new); + + return error; } } -static const char magic[] = "OVSDB JSON "; - -static bool -parse_header(char *header, unsigned long int *length, - uint8_t sha1[SHA1_DIGEST_SIZE]) +static struct ovsdb_error * +ovsdb_file_txn_table_from_json(struct ovsdb_txn *txn, + struct ovsdb_table *table, struct json *json) { - char *p; + struct shash_node *node; - /* 'header' must consist of a magic string... */ - if (strncmp(header, magic, strlen(magic))) { - return false; + if (json->type != JSON_OBJECT) { + return ovsdb_syntax_error(json, NULL, "object expected"); } - /* ...followed by a length in bytes... */ - *length = strtoul(header + strlen(magic), &p, 10); - if (!*length || *length == ULONG_MAX || *p != ' ') { - return false; + SHASH_FOR_EACH (node, json->u.object) { + const char *uuid_string = node->name; + struct json *txn_row_json = node->data; + struct ovsdb_error *error; + struct uuid row_uuid; + + if (!uuid_from_string(&row_uuid, uuid_string)) { + return ovsdb_syntax_error(json, NULL, "\"%s\" is not a valid UUID", + uuid_string); + } + + error = ovsdb_file_txn_row_from_json(txn, table, &row_uuid, + txn_row_json); + if (error) { + return error; + } } - p++; - /* ...followed by a SHA-1 hash... */ - if (!sha1_from_hex(sha1, p)) { - return false; + return NULL; +} + +static struct ovsdb_error * +ovsdb_file_txn_from_json(struct ovsdb *db, const struct json *json, + 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"); } - p += SHA1_HEX_DIGEST_LEN; - /* ...and ended by a new-line. */ - if (*p != '\n') { - return false; + txn = ovsdb_txn_create(db); + SHASH_FOR_EACH (node, json->u.object) { + const char *table_name = node->name; + struct json *txn_table_json = node->data; + struct ovsdb_table *table; + + table = shash_find_data(&db->tables, table_name); + if (!table) { + error = ovsdb_syntax_error(json, "unknown table", + "No table named %s.", table_name); + goto error; + } + + error = ovsdb_file_txn_table_from_json(txn, table, txn_table_json); + if (error) { + goto error; + } } + *txnp = txn; + return NULL; - return true; +error: + ovsdb_txn_abort(txn); + return error; } + +/* Replica implementation. */ -struct ovsdb_file_read_cbdata { - char input[4096]; - struct ovsdb_file *file; - int error; - unsigned long length; +struct ovsdb_file_replica { + struct ovsdb_replica replica; + struct ovsdb_log *log; }; -static struct ovsdb_error * -parse_body(struct ovsdb_file *file, off_t offset, unsigned long int length, - uint8_t sha1[SHA1_DIGEST_SIZE], struct json **jsonp) +static const struct ovsdb_replica_class ovsdb_file_replica_class; + +static void +ovsdb_file_replica_create(struct ovsdb *db, struct ovsdb_log *log) { - unsigned long int bytes_left; - struct json_parser *parser; - struct sha1_ctx ctx; - - sha1_init(&ctx); - parser = json_parser_create(JSPF_TRAILER); - - bytes_left = length; - while (length > 0) { - char input[BUFSIZ]; - int chunk; - - chunk = MIN(length, sizeof input); - if (fread(input, 1, chunk, file->stream) != chunk) { - json_parser_abort(parser); - return ovsdb_io_error(ferror(file->stream) ? errno : EOF, - "%s: error reading %lu bytes " - "starting at offset %lld", file->name, - length, (long long int) offset); - } - sha1_update(&ctx, input, chunk); - json_parser_feed(parser, input, chunk); - length -= chunk; - } + 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); - sha1_final(&ctx, sha1); - *jsonp = json_parser_finish(parser); - return NULL; } -struct ovsdb_error * -ovsdb_file_read(struct ovsdb_file *file, struct json **jsonp) +static struct ovsdb_file_replica * +ovsdb_file_replica_cast(struct ovsdb_replica *replica) { - uint8_t expected_sha1[SHA1_DIGEST_SIZE]; - uint8_t actual_sha1[SHA1_DIGEST_SIZE]; - struct ovsdb_error *error; - off_t data_offset; - unsigned long data_length; - struct json *json; - char header[128]; + assert(replica->class == &ovsdb_file_replica_class); + return CONTAINER_OF(replica, struct ovsdb_file_replica, replica); +} - *jsonp = json = NULL; +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'. */ +}; - if (file->read_error) { - return ovsdb_error_clone(file->read_error); - } else if (file->mode == OVSDB_FILE_WRITE) { - return OVSDB_BUG("reading file in write mode"); - } +static bool +ovsdb_file_replica_change_cb(const struct ovsdb_row *old, + const struct ovsdb_row *new, + void *aux_) +{ + struct ovsdb_file_replica_aux *aux = aux_; + struct json *row; - if (!fgets(header, sizeof header, file->stream)) { - if (feof(file->stream)) { - error = NULL; - } else { - error = ovsdb_io_error(errno, "%s: read failed", file->name); + if (!new) { + row = json_null_create(); + } else { + struct shash_node *node; + + row = NULL; + 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))) + { + if (!row) { + row = json_object_create(); + } + json_object_put(row, column->name, + ovsdb_datum_to_json(&new->fields[idx], type)); + } } - goto error; } - if (!parse_header(header, &data_length, expected_sha1)) { - error = ovsdb_syntax_error(NULL, NULL, "%s: parse error at offset " - "%lld in header line \"%.*s\"", - file->name, (long long int) file->offset, - (int) strcspn(header, "\n"), header); - goto error; - } + if (row) { + struct ovsdb_table *table = new ? new->table : old->table; + char uuid[UUID_LEN + 1]; - data_offset = file->offset + strlen(header); - error = parse_body(file, data_offset, data_length, actual_sha1, &json); - if (error) { - goto error; - } + if (table != aux->table) { + /* Create JSON object for transaction overall. */ + if (!aux->json) { + aux->json = json_object_create(); + } - if (memcmp(expected_sha1, actual_sha1, SHA1_DIGEST_SIZE)) { - error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at " - "offset %lld have SHA-1 hash "SHA1_FMT" " - "but should have hash "SHA1_FMT, - file->name, data_length, - (long long int) data_offset, - SHA1_ARGS(actual_sha1), - SHA1_ARGS(expected_sha1)); - goto error; - } + /* 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); + } - if (json->type == JSON_STRING) { - error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at " - "offset %lld are not valid JSON (%s)", - file->name, data_length, - (long long int) data_offset, - json->u.string); - goto error; + /* 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); } - file->offset = data_offset + data_length; - *jsonp = json; - return 0; - -error: - file->read_error = ovsdb_error_clone(error); - json_destroy(json); - return error; + return true; } -struct ovsdb_error * -ovsdb_file_write(struct ovsdb_file *file, struct json *json) +static struct ovsdb_error * +ovsdb_file_replica_commit(struct ovsdb_replica *r_, + const struct ovsdb_txn *txn, bool durable) { - uint8_t sha1[SHA1_DIGEST_SIZE]; + struct ovsdb_file_replica *r = ovsdb_file_replica_cast(r_); + struct ovsdb_file_replica_aux aux; struct ovsdb_error *error; - char *json_string; - char header[128]; - size_t length; - - json_string = NULL; - - if (file->write_error) { - return ovsdb_error_clone(file->write_error); - } else if (file->mode == OVSDB_FILE_READ) { - file->mode = OVSDB_FILE_WRITE; - if (fseeko(file->stream, file->offset, SEEK_SET)) { - error = ovsdb_io_error(errno, "%s: cannot seek to offset %lld", - file->name, (long long int) file->offset); - goto error; - } - if (ftruncate(fileno(file->stream), file->offset)) { - error = ovsdb_io_error(errno, "%s: cannot truncate to length %lld", - file->name, (long long int) file->offset); - goto error; - } - } - if (json->type != JSON_OBJECT && json->type != JSON_ARRAY) { - error = OVSDB_BUG("bad JSON type"); - goto error; + 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; } - /* Compose content. Add a new-line (replacing the null terminator) to make - * the file easier to read, even though it has no semantic value. */ - json_string = json_to_string(json, 0); - length = strlen(json_string) + 1; - json_string[length - 1] = '\n'; - - /* Compose header. */ - sha1_bytes(json_string, length, sha1); - snprintf(header, sizeof header, "%s%zu "SHA1_FMT"\n", - magic, length, SHA1_ARGS(sha1)); - - /* Write. */ - if (fwrite(header, strlen(header), 1, file->stream) != 1 - || fwrite(json_string, length, 1, file->stream) != 1 - || fflush(file->stream)) - { - error = ovsdb_io_error(errno, "%s: write failed", file->name); - - /* Remove any partially written data, ignoring errors since there is - * nothing further we can do. */ - ftruncate(fileno(file->stream), file->offset); - - goto error; + error = ovsdb_log_write(r->log, aux.json); + json_destroy(aux.json); + if (error) { + return ovsdb_wrap_error(error, "writing transaction failed"); } - file->offset += strlen(header) + length; - free(json_string); - return 0; + if (durable) { + error = ovsdb_log_commit(r->log); + if (error) { + return ovsdb_wrap_error(error, "committing transaction failed"); + } + } -error: - file->write_error = ovsdb_error_clone(error); - free(json_string); - return error; + return NULL; } -struct ovsdb_error * -ovsdb_file_commit(struct ovsdb_file *file) +static void +ovsdb_file_replica_destroy(struct ovsdb_replica *r_) { - if (fsync(fileno(file->stream))) { - return ovsdb_io_error(errno, "%s: fsync failed", file->name); - } - return 0; + struct ovsdb_file_replica *r = ovsdb_file_replica_cast(r_); + + ovsdb_log_close(r->log); + free(r); } + +static const struct ovsdb_replica_class ovsdb_file_replica_class = { + ovsdb_file_replica_commit, + ovsdb_file_replica_destroy +};