From patchwork Fri Dec 8 00:12:37 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ben Pfaff X-Patchwork-Id: 845935 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=openvswitch.org (client-ip=140.211.169.12; helo=mail.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3ytCdG1gQQz9s83 for ; Fri, 8 Dec 2017 11:18:58 +1100 (AEDT) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 524F9CF8; Fri, 8 Dec 2017 00:13:05 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTPS id 76002CD6 for ; Fri, 8 Dec 2017 00:13:02 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6 Received: from relay2-d.mail.gandi.net (relay2-d.mail.gandi.net [217.70.183.194]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id CD004456 for ; Fri, 8 Dec 2017 00:13:00 +0000 (UTC) X-Originating-IP: 208.91.3.26 Received: from sigabrt.benpfaff.org (unknown [208.91.3.26]) (Authenticated sender: blp@ovn.org) by relay2-d.mail.gandi.net (Postfix) with ESMTPSA id B6AC0C5A49; Fri, 8 Dec 2017 01:12:58 +0100 (CET) From: Ben Pfaff To: dev@openvswitch.org Date: Thu, 7 Dec 2017 16:12:37 -0800 Message-Id: <20171208001240.25829-11-blp@ovn.org> X-Mailer: git-send-email 2.10.2 In-Reply-To: <20171208001240.25829-1-blp@ovn.org> References: <20171208001240.25829-1-blp@ovn.org> X-Spam-Status: No, score=-2.6 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_LOW autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on smtp1.linux-foundation.org Cc: Ben Pfaff Subject: [ovs-dev] [PATCH 10/13] log: Support multiple magic. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org Some OVSDB tools will want to open files that might be standalone or clustered databases, and so it's better if ovsdb_log_open() can accept more than one valid "magic". This commit makes that possible. Signed-off-by: Ben Pfaff Acked-by: Justin Pettit --- ovsdb/log.c | 147 +++++++++++++++++++++++++++++++++++++---------------- ovsdb/log.h | 6 ++- tests/ovsdb-log.at | 2 +- 3 files changed, 108 insertions(+), 47 deletions(-) diff --git a/ovsdb/log.c b/ovsdb/log.c index d25f766474dc..dd641884a2ee 100644 --- a/ovsdb/log.c +++ b/ovsdb/log.c @@ -88,13 +88,19 @@ static bool rename_open_files = false; static bool rename_open_files = true; #endif +static bool parse_header(char *header, const char **magicp, + unsigned long int *length, + uint8_t sha1[SHA1_DIGEST_SIZE]); +static bool is_magic_ok(const char *needle, const char *haystack); + /* Attempts to open 'name' with the specified 'open_mode'. On success, stores * the new log into '*filep' and returns NULL; otherwise returns NULL and * stores NULL into '*filep'. * * 'magic' is a short text string put at the beginning of every record and used * to distinguish one kind of log file from another. For a conventional OVSDB - * log file, use OVSDB_MAGIC. + * log file, use OVSDB_MAGIC. To accept more than one magic string, separate + * them with "|", e.g. "MAGIC 1|MAGIC 2". * * Whether the file will be locked using lockfile_lock() depends on 'locking': * use true to lock it, false not to lock it, or -1 to lock it only if @@ -107,12 +113,16 @@ ovsdb_log_open(const char *name, const char *magic, { struct lockfile *lockfile; struct ovsdb_error *error; - struct ovsdb_log *file; struct stat s; FILE *stream; int flags; int fd; + /* If we can create a new file, we need to know what kind of magic to + * use, so there must be only one kind. */ + if (open_mode == OVSDB_LOG_CREATE_EXCL || open_mode == OVSDB_LOG_CREATE) { + ovs_assert(!strchr(magic, '|')); + } *filep = NULL; ovs_assert(locking == -1 || locking == false || locking == true); @@ -181,43 +191,54 @@ ovsdb_log_open(const char *name, const char *magic, goto error_unlock; } - if (!fstat(fd, &s)) { - if (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. */ - fsync_parent_dir(name); - } else if (s.st_size >= strlen(magic) && S_ISREG(s.st_mode)) { - /* Try to read the magic from the first log record. If it's not - * the magic we expect, this is the wrong kind of file, so reject - * it immediately. */ - size_t magic_len = strlen(magic); - char *buf = xzalloc(magic_len + 1); - bool err = (read(fd, buf, magic_len) == magic_len - && strcmp(buf, magic)); - free(buf); - if (err) { - error = ovsdb_error(NULL, "%s: bad magic (unexpected " - "kind of file)", name); - goto error_close; - } - if (lseek(fd, 0, SEEK_SET)) { - error = ovsdb_io_error(errno, "%s: seek failed", name); - goto error_close; - } - } - } - stream = fdopen(fd, open_mode == OVSDB_LOG_READ_ONLY ? "rb" : "w+b"); if (!stream) { error = ovsdb_io_error(errno, "%s: fdopen failed", name); - goto error_close; + close(fd); + goto error_unlock; + } + + /* Read the magic from the first log record. */ + char header[128]; + const char *actual_magic; + if (!fgets(header, sizeof header, stream)) { + if (ferror(stream)) { + error = ovsdb_io_error(errno, "%s: read error", name); + goto error_fclose; + } + + /* We need to be able to report what kind of file this is but we can't + * if it's empty and we accept more than one. */ + if (strchr(magic, '|')) { + error = ovsdb_error(NULL, "%s: unexpected end of file", name); + goto error_fclose; + } + actual_magic = magic; + + /* It's an empty file and therefore probably a new file, so fsync() + * its parent directory to ensure that its directory entry is + * committed to disk. */ + fsync_parent_dir(name); + } else { + unsigned long int length; + uint8_t sha1[SHA1_DIGEST_SIZE]; + if (!parse_header(header, &actual_magic, &length, sha1) + || !is_magic_ok(actual_magic, magic)) { + error = ovsdb_error(NULL, "%s: unexpected file format", name); + goto error_fclose; + } + } + + if (fseek(stream, 0, SEEK_SET)) { + error = ovsdb_io_error(errno, "%s: seek failed", name); + goto error_fclose; } - file = xmalloc(sizeof *file); + struct ovsdb_log *file = xmalloc(sizeof *file); file->state = OVSDB_LOG_READ; file->error = NULL; file->name = xstrdup(name); - file->magic = xstrdup(magic); + file->magic = xstrdup(actual_magic); file->lockfile = lockfile; file->stream = stream; file->prev_offset = 0; @@ -225,20 +246,43 @@ ovsdb_log_open(const char *name, const char *magic, *filep = file; return NULL; -error_close: - close(fd); +error_fclose: + fclose(stream); error_unlock: lockfile_unlock(lockfile); error: return error; } +/* Returns true if 'needle' is one of the |-delimited words in 'haystack'. */ +static bool +is_magic_ok(const char *needle, const char *haystack) +{ + /* 'needle' can't be multiple words. */ + if (strchr(needle, '|')) { + return false; + } + + size_t n = strlen(needle); + for (;;) { + if (!strncmp(needle, haystack, n) && strchr("|", haystack[n])) { + return true; + } + haystack = strchr(haystack, '|'); + if (!haystack) { + return false; + } + haystack++; + } +} + void ovsdb_log_close(struct ovsdb_log *file) { if (file) { ovsdb_error_destroy(file->error); free(file->name); + free(file->magic); if (file->stream) { fclose(file->stream); } @@ -247,20 +291,34 @@ ovsdb_log_close(struct ovsdb_log *file) } } +const char * +ovsdb_log_get_magic(const struct ovsdb_log *log) +{ + return log->magic; +} + static bool -parse_header(const char *magic, char *header, unsigned long int *length, - uint8_t sha1[SHA1_DIGEST_SIZE]) +parse_header(char *header, const char **magicp, + unsigned long int *length, uint8_t sha1[SHA1_DIGEST_SIZE]) { - char *p; + /* 'header' must consist of "OVSDB "... */ + const char lead[] = "OVSDB "; + if (strncmp(lead, header, strlen(lead))) { + return false; + } - /* 'header' must consist of a magic string... */ - size_t magic_len = strlen(magic); - if (strncmp(header, magic, magic_len) || header[magic_len] != ' ') { + /* ...followed by a magic string... */ + char *magic = header + strlen(lead); + size_t magic_len = strcspn(magic, " "); + if (magic[magic_len] != ' ') { return false; } + magic[magic_len] = '\0'; + *magicp = magic; /* ...followed by a length in bytes... */ - *length = strtoul(header + magic_len + 1, &p, 10); + char *p; + *length = strtoul(magic + magic_len + 1, &p, 10); if (!*length || *length == ULONG_MAX || *p != ' ') { return false; } @@ -342,7 +400,6 @@ ovsdb_log_read(struct ovsdb_log *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]; @@ -356,8 +413,11 @@ ovsdb_log_read(struct ovsdb_log *file, struct json **jsonp) error = ovsdb_io_error(errno, "%s: read failed", file->name); goto error; } + off_t data_offset = file->offset + strlen(header); - if (!parse_header(file->magic, header, &data_length, expected_sha1)) { + const char *magic; + if (!parse_header(header, &magic, &data_length, expected_sha1) + || strcmp(magic, file->magic)) { error = ovsdb_syntax_error(NULL, NULL, "%s: parse error at offset " "%lld in header line \"%.*s\"", file->name, (long long int) file->offset, @@ -365,7 +425,6 @@ ovsdb_log_read(struct ovsdb_log *file, struct json **jsonp) goto error; } - data_offset = file->offset + strlen(header); error = parse_body(file, data_offset, data_length, actual_sha1, &json); if (error) { goto error; @@ -442,7 +501,7 @@ ovsdb_log_compose_record(const struct json *json, /* Compose header. */ uint8_t sha1[SHA1_DIGEST_SIZE]; sha1_bytes(data->string, data->length, sha1); - ds_put_format(header, "%s %"PRIuSIZE" "SHA1_FMT"\n", + ds_put_format(header, "OVSDB %s %"PRIuSIZE" "SHA1_FMT"\n", magic, data->length, SHA1_ARGS(sha1)); } diff --git a/ovsdb/log.h b/ovsdb/log.h index cc8469c01e57..4b74ca11ce6a 100644 --- a/ovsdb/log.h +++ b/ovsdb/log.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2009, 2010, 2011 Nicira, Inc. +/* Copyright (c) 2009, 2010, 2011, 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. @@ -31,7 +31,7 @@ enum ovsdb_log_open_mode { OVSDB_LOG_CREATE /* Create or open file, read/write. */ }; -#define OVSDB_MAGIC "OVSDB JSON" +#define OVSDB_MAGIC "JSON" struct ovsdb_error *ovsdb_log_open(const char *name, const char *magic, enum ovsdb_log_open_mode, @@ -39,6 +39,8 @@ struct ovsdb_error *ovsdb_log_open(const char *name, const char *magic, OVS_WARN_UNUSED_RESULT; void ovsdb_log_close(struct ovsdb_log *); +const char *ovsdb_log_get_magic(const struct ovsdb_log *); + struct ovsdb_error *ovsdb_log_read(struct ovsdb_log *, struct json **) OVS_WARN_UNUSED_RESULT; void ovsdb_log_unread(struct ovsdb_log *); diff --git a/tests/ovsdb-log.at b/tests/ovsdb-log.at index 125ddd187551..9aa1e870039c 100644 --- a/tests/ovsdb-log.at +++ b/tests/ovsdb-log.at @@ -174,7 +174,7 @@ file: read: end of log ]], [ignore]) AT_CHECK( [test-ovsdb log-io file read-only], [1], [], - [test-ovsdb: ovsdb error: file: bad magic (unexpected kind of file) + [test-ovsdb: ovsdb error: file: unexpected file format ]) AT_CHECK([test -f .file.~lock~]) AT_CLEANUP