summaryrefslogtreecommitdiff
path: root/ovsdb/file.c
diff options
context:
space:
mode:
authorBen Pfaff <blp@nicira.com>2009-11-04 15:11:44 -0800
committerBen Pfaff <blp@nicira.com>2009-11-04 17:12:10 -0800
commitf85f8ebbfac946c19b3c6eb0f4170f579d0a4d25 (patch)
tree2111aee77751f2143773907f81c6adb9101afb6c /ovsdb/file.c
parentf212909325be9bc7e296e1a32e2fc89694a0049f (diff)
downloadopenvswitch-f85f8ebbfac946c19b3c6eb0f4170f579d0a4d25.tar.gz
Initial implementation of OVSDB.
Diffstat (limited to 'ovsdb/file.c')
-rw-r--r--ovsdb/file.c360
1 files changed, 360 insertions, 0 deletions
diff --git a/ovsdb/file.c b/ovsdb/file.c
new file mode 100644
index 000000000..4883d76f1
--- /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 <config.h>
+
+#include "file.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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;
+}