From f85f8ebbfac946c19b3c6eb0f4170f579d0a4d25 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Wed, 4 Nov 2009 15:11:44 -0800 Subject: Initial implementation of OVSDB. --- ovsdb/file.c | 360 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 360 insertions(+) create mode 100644 ovsdb/file.c (limited to 'ovsdb/file.c') diff --git a/ovsdb/file.c b/ovsdb/file.c new file mode 100644 index 00000000..4883d76f --- /dev/null +++ b/ovsdb/file.c @@ -0,0 +1,360 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "file.h" + +#include +#include +#include +#include +#include +#include + +#include "json.h" +#include "lockfile.h" +#include "ovsdb-error.h" +#include "sha1.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; +}; + +struct ovsdb_error * +ovsdb_file_open(const char *name, int flags, struct ovsdb_file **filep) +{ + struct lockfile *lockfile; + 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; + } + + 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; + } + + 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)); + } + free(dir); + } + + 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; + } + + 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; + +error_close: + close(fd); +error_unlock: + lockfile_unlock(lockfile); +error: + return error; +} + +void +ovsdb_file_close(struct ovsdb_file *file) +{ + 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); + } +} + +static const char magic[] = "OVSDB JSON "; + +static bool +parse_header(char *header, unsigned long int *length, + uint8_t sha1[SHA1_DIGEST_SIZE]) +{ + char *p; + + /* 'header' must consist of a magic string... */ + if (strncmp(header, magic, strlen(magic))) { + return false; + } + + /* ...followed by a length in bytes... */ + *length = strtoul(header + strlen(magic), &p, 10); + if (!*length || *length == ULONG_MAX || *p != ' ') { + return false; + } + p++; + + /* ...followed by a SHA-1 hash... */ + if (!sha1_from_hex(sha1, p)) { + return false; + } + p += SHA1_HEX_DIGEST_LEN; + + /* ...and ended by a new-line. */ + if (*p != '\n') { + return false; + } + + return true; +} + +struct ovsdb_file_read_cbdata { + char input[4096]; + struct ovsdb_file *file; + int error; + unsigned long length; +}; + +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) +{ + 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; + } + + sha1_final(&ctx, sha1); + *jsonp = json_parser_finish(parser); + return NULL; +} + +struct ovsdb_error * +ovsdb_file_read(struct ovsdb_file *file, struct json **jsonp) +{ + 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]; + + *jsonp = json = NULL; + + 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"); + } + + if (!fgets(header, sizeof header, file->stream)) { + if (feof(file->stream)) { + error = NULL; + } else { + error = ovsdb_io_error(errno, "%s: read failed", file->name); + } + 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; + } + + data_offset = file->offset + strlen(header); + error = parse_body(file, data_offset, data_length, actual_sha1, &json); + if (error) { + goto error; + } + + 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; + } + + 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; + } + + file->offset = data_offset + data_length; + *jsonp = json; + return 0; + +error: + file->read_error = ovsdb_error_clone(error); + json_destroy(json); + return error; +} + +struct ovsdb_error * +ovsdb_file_write(struct ovsdb_file *file, struct json *json) +{ + uint8_t sha1[SHA1_DIGEST_SIZE]; + 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; + } + + /* 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; + } + + file->offset += strlen(header) + length; + free(json_string); + return 0; + +error: + file->write_error = ovsdb_error_clone(error); + free(json_string); + return error; +} + +struct ovsdb_error * +ovsdb_file_commit(struct ovsdb_file *file) +{ + if (fsync(fileno(file->stream))) { + return ovsdb_io_error(errno, "%s: fsync failed", file->name); + } + return 0; +} -- cgit v1.2.3