This will be used in later commits of this series to support multiple
files for a single log.

Signed-off-by: Felix Huettner <felix.huettner@stackit.cloud>
---
 ovsdb/log.c           | 566 ++++++++++++++++++++++++++----------------
 tests/ovsdb-log.at    |  20 +-
 tests/ovsdb-server.at |   4 +-
 3 files changed, 364 insertions(+), 226 deletions(-)

diff --git a/ovsdb/log.c b/ovsdb/log.c
index 11d52f950..ac1ea0d58 100644
--- a/ovsdb/log.c
+++ b/ovsdb/log.c
@@ -74,19 +74,29 @@ enum ovsdb_log_state {
     OVSDB_LOG_BROKEN,           /* Disk on fire, see 'error' for details. */
 };
 
-struct ovsdb_log {
-    enum ovsdb_log_state state;
-    struct ovsdb_error *error;
 
-    off_t prev_offset;
-    off_t offset;
+struct ovsdb_log_file {
+    off_t prev_offset;          /* While reading the offset before the latest
+                                 * read call. */
+    off_t offset;               /* The current offset in the file. */
     char *name;                 /* Absolute name of file. */
-    char *display_name;         /* For use in log messages, etc. */
-    char *magic;
     struct lockfile *lockfile;
     FILE *stream;
     off_t base;
     struct afsync *afsync;
+    bool read_fully;
+};
+
+struct ovsdb_log {
+    enum ovsdb_log_state state;
+    struct ovsdb_error *error;
+
+    enum ovsdb_log_open_mode open_mode;
+    bool may_lock;
+    char *display_name;         /* For use in log messages, etc. */
+    char *magic;
+
+    struct ovsdb_log_file *curr;
 };
 
 /* Whether the OS supports renaming open files.
@@ -107,28 +117,12 @@ static bool is_magic_ok(const char *needle, const char 
*haystack);
 static struct afsync *afsync_create(int fd, uint64_t initial_ticket);
 static uint64_t afsync_destroy(struct afsync *);
 
-/* 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 the OVSDB_MAGIC macro.  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 'may_lock':
- * use true to lock if 'open_mode' allows writing, false not to never lock it.
- *
- * A log consists of a series of records.  After opening or creating a log with
- * this function, the client may use ovsdb_log_read() to read any existing
- * records, one by one.  The client may also use ovsdb_log_write() to write new
- * records (if some records have not yet been read at this point, then the
- * first write truncates them).
- */
-struct ovsdb_error *
-ovsdb_log_open(const char *name, const char *magic,
-               enum ovsdb_log_open_mode open_mode,
-               bool may_lock, struct ovsdb_log **filep)
+static struct ovsdb_error *
+ovsdb_log_file_open(const char *name,
+                    const char *magic,
+                    enum ovsdb_log_open_mode open_mode,
+                    bool may_lock, struct ovsdb_log_file **filep,
+                    char **out_actual_magic)
 {
     struct lockfile *lockfile;
     struct ovsdb_error *error;
@@ -137,27 +131,6 @@ ovsdb_log_open(const char *name, const char *magic,
     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;
-
-    /* Get the absolute name of the file because we might need to access it by
-     * name again later after the process has changed directory (e.g. because
-     * daemonize() chdirs to "/").
-     *
-     * We save the user-provided name of the file for use in log messages, to
-     * reduce user confusion. */
-    char *abs_name = abs_file_name(NULL, name);
-    if (!abs_name) {
-        error = ovsdb_io_error(0, "could not determine current "
-                              "working directory");
-        goto error;
-    }
-
     if (may_lock && open_mode != OVSDB_LOG_READ_ONLY) {
         int retval = lockfile_lock(name, &lockfile);
         if (retval) {
@@ -265,19 +238,18 @@ ovsdb_log_open(const char *name, const char *magic,
         goto error_fclose;
     }
 
-    struct ovsdb_log *file = xmalloc(sizeof *file);
-    file->state = OVSDB_LOG_READ;
-    file->error = NULL;
-    file->name = abs_name;
-    file->display_name = xstrdup(name);
-    file->magic = xstrdup(actual_magic);
-    file->lockfile = lockfile;
-    file->stream = stream;
-    file->prev_offset = 0;
-    file->offset = 0;
-    file->base = 0;
-    file->afsync = NULL;
-    *filep = file;
+    if (out_actual_magic) {
+        *out_actual_magic = xstrdup(actual_magic);
+    }
+
+    *filep = xmalloc(sizeof **filep);
+    (*filep)->name = xstrdup(name);
+    (*filep)->lockfile = lockfile;
+    (*filep)->stream = stream;
+    (*filep)->prev_offset = 0;
+    (*filep)->offset = 0;
+    (*filep)->base = 0;
+    (*filep)->afsync = NULL;
     return NULL;
 
 error_fclose:
@@ -285,10 +257,79 @@ error_fclose:
 error_unlock:
     lockfile_unlock(lockfile);
 error:
-    free(abs_name);
     return error;
 }
 
+/* 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 the OVSDB_MAGIC macro.  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 'may_lock':
+ * use true to lock if 'open_mode' allows writing, false not to never lock it.
+ *
+ * A log consists of a series of records.  After opening or creating a log with
+ * this function, the client may use ovsdb_log_read() to read any existing
+ * records, one by one.  The client may also use ovsdb_log_write() to write new
+ * records (if some records have not yet been read at this point, then the
+ * first write truncates them).
+ */
+struct ovsdb_error *
+ovsdb_log_open(const char *name, const char *magic,
+               enum ovsdb_log_open_mode open_mode,
+               bool may_lock, struct ovsdb_log **filep)
+{
+    struct ovsdb_log_file *file;
+    struct ovsdb_error *error;
+    char *actual_magic;
+
+    /* 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;
+
+    /* Get the absolute name of the file because we might need to access it by
+     * name again later after the process has changed directory (e.g. because
+     * daemonize() chdirs to "/").
+     *
+     * We save the user-provided name of the file for use in log messages, to
+     * reduce user confusion. */
+    char *abs_name = abs_file_name(NULL, name);
+    if (!abs_name) {
+        error = ovsdb_io_error(0, "could not determine current "
+                              "working directory");
+        return error;
+    }
+
+    error = ovsdb_log_file_open(abs_name, magic, open_mode, may_lock,
+                                &file, &actual_magic);
+    free(abs_name);
+    if (error) {
+        return error;
+    }
+
+
+    struct ovsdb_log *log = xzalloc(sizeof *log);
+    log->state = OVSDB_LOG_READ;
+    log->error = NULL;
+    log->display_name = xstrdup(name);
+    log->magic = actual_magic;
+    log->open_mode = open_mode;
+    log->may_lock = may_lock;
+
+    log->curr = file;
+    *filep = log;
+    return NULL;
+}
+
 /* Returns true if 'needle' is one of the |-delimited words in 'haystack'. */
 static bool
 is_magic_ok(const char *needle, const char *haystack)
@@ -311,19 +352,28 @@ is_magic_ok(const char *needle, const char *haystack)
     }
 }
 
+static void
+ovsdb_log_file_close(struct ovsdb_log_file *file)
+{
+    if (file) {
+        afsync_destroy(file->afsync);
+        free(file->name);
+        if (file->stream) {
+            fclose(file->stream);
+        }
+        lockfile_unlock(file->lockfile);
+        free(file);
+    }
+}
+
 void
 ovsdb_log_close(struct ovsdb_log *file)
 {
     if (file) {
         ovsdb_error_destroy(file->error);
-        afsync_destroy(file->afsync);
-        free(file->name);
         free(file->display_name);
         free(file->magic);
-        if (file->stream) {
-            fclose(file->stream);
-        }
-        lockfile_unlock(file->lockfile);
+        ovsdb_log_file_close(file->curr);
         free(file);
     }
 }
@@ -397,13 +447,13 @@ parse_body(struct ovsdb_log *file, off_t offset, unsigned 
long int length,
         int chunk;
 
         chunk = MIN(length, sizeof input);
-        if (fread(input, 1, chunk, file->stream) != chunk) {
+        if (fread(input, 1, chunk, file->curr->stream) != chunk) {
             sha1_final(&ctx, sha1);
             json_parser_abort(parser);
-            return ovsdb_io_error(ferror(file->stream) ? errno : EOF,
+            return ovsdb_io_error(ferror(file->curr->stream) ? errno : EOF,
                                   "%s: error reading %lu bytes "
                                   "starting at offset %lld",
-                                  file->display_name, length,
+                                  file->curr->name, length,
                                   (long long int) offset);
         }
         sha1_update(&ctx, input, chunk);
@@ -426,8 +476,8 @@ parse_body(struct ovsdb_log *file, off_t offset, unsigned 
long int length,
  *
  * If the read reaches end of file, returns NULL and stores NULL in
  * '*jsonp'. */
-struct ovsdb_error *
-ovsdb_log_read(struct ovsdb_log *file, struct json **jsonp)
+static struct ovsdb_error *
+ovsdb_log_read_(struct ovsdb_log *file, struct json **jsonp)
 {
     *jsonp = NULL;
     switch (file->state) {
@@ -452,22 +502,22 @@ ovsdb_log_read(struct ovsdb_log *file, struct json 
**jsonp)
 
     json = NULL;
 
-    if (!fgets(header, sizeof header, file->stream)) {
-        if (feof(file->stream)) {
+    if (!fgets(header, sizeof header, file->curr->stream)) {
+        if (feof(file->curr->stream)) {
             return NULL;
         }
-        error = ovsdb_io_error(errno, "%s: read failed", file->display_name);
+        error = ovsdb_io_error(errno, "%s: read failed", file->curr->name);
         goto error;
     }
-    off_t data_offset = file->offset + strlen(header);
+    off_t data_offset = file->curr->offset + strlen(header);
 
     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->display_name,
-                                   (long long int) file->offset,
+                                   file->curr->name,
+                                   (long long int) file->curr->offset,
                                    (int) strcspn(header, "\n"), header);
         goto error;
     }
@@ -481,7 +531,7 @@ ovsdb_log_read(struct ovsdb_log *file, struct json **jsonp)
         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->display_name, data_length,
+                                   file->curr->name, data_length,
                                    (long long int) data_offset,
                                    SHA1_ARGS(actual_sha1),
                                    SHA1_ARGS(expected_sha1));
@@ -491,7 +541,7 @@ ovsdb_log_read(struct ovsdb_log *file, struct json **jsonp)
     if (json->type == JSON_STRING) {
         error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at "
                                    "offset %lld are not valid JSON (%s)",
-                                   file->display_name, data_length,
+                                   file->curr->name, data_length,
                                    (long long int) data_offset,
                                    json_string(json));
         goto error;
@@ -499,22 +549,14 @@ ovsdb_log_read(struct ovsdb_log *file, struct json 
**jsonp)
     if (json->type != JSON_OBJECT) {
         error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at "
                                    "offset %lld are not a JSON object",
-                                   file->display_name, data_length,
+                                   file->curr->name, data_length,
                                    (long long int) data_offset);
         goto error;
     }
 
-    struct json *data = shash_find_and_delete(json_object(json),
-                                              OVSDB_LOG_DATA_KEY);
-    if (data) {
-        *jsonp = data;
-        json_destroy(json);
-    } else {
-        *jsonp = json;
-    }
-
-    file->prev_offset = file->offset;
-    file->offset = data_offset + data_length;
+    file->curr->prev_offset = file->curr->offset;
+    file->curr->offset = data_offset + data_length;
+    *jsonp = json;
     return NULL;
 
 error:
@@ -524,6 +566,53 @@ error:
     return error;
 }
 
+/* Attempts to read a log record from 'file'.
+ *
+ * If successful, returns NULL and stores in '*jsonp' the JSON object that the
+ * record contains.  The caller owns the data and must eventually free it (with
+ * json_destroy()).
+ *
+ * If a read error occurs, returns the error and stores NULL in '*jsonp'.
+ *
+ * If the read reaches end of file, returns NULL and stores NULL in
+ * '*jsonp'. */
+struct ovsdb_error *
+ovsdb_log_read(struct ovsdb_log *log, struct json **jsonp)
+{
+    ovs_assert(log->curr);
+    *jsonp = NULL;
+    while (!*jsonp) {
+        struct ovsdb_error *error = ovsdb_log_read_(log, jsonp);
+        if (error) {
+            return error;
+        }
+        if (!*jsonp) {
+            /* EOF */
+            break;
+        }
+
+        struct json *metadata = shash_find_data(json_object(*jsonp),
+                                                OVSDB_LOG_META_KEY);
+        struct json *data = shash_find_and_delete(json_object(*jsonp),
+                                                  OVSDB_LOG_DATA_KEY);
+
+        if (!metadata && !data) {
+            /* Record that does not yet have the "logdata" key. */
+            break;
+        }
+
+        if (data) {
+            json_destroy(*jsonp);
+            *jsonp = data;
+            break;
+        } else {
+            OVS_NOT_REACHED();
+        }
+    }
+
+    return NULL;
+}
+
 /* Causes the log record read by the previous call to ovsdb_log_read() to be
  * effectively discarded.  The next call to ovsdb_log_write() will overwrite
  * that previously read record.
@@ -537,25 +626,31 @@ void
 ovsdb_log_unread(struct ovsdb_log *file)
 {
     ovs_assert(file->state == OVSDB_LOG_READ);
-    file->offset = file->prev_offset;
+    file->curr->offset = file->curr->prev_offset;
+}
+
+static struct ovsdb_error *
+ovsdb_log_file_truncate(struct ovsdb_log_file *file)
+{
+    struct ovsdb_error *error = NULL;
+    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);
+    } else 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);
+    }
+
+    return error;
 }
 
 static struct ovsdb_error *
 ovsdb_log_truncate(struct ovsdb_log *file)
 {
     file->state = OVSDB_LOG_WRITE;
-
-    struct ovsdb_error *error = NULL;
-    if (fseeko(file->stream, file->offset, SEEK_SET)) {
-        error = ovsdb_io_error(errno, "%s: cannot seek to offset %lld",
-                               file->display_name,
-                               (long long int) file->offset);
-    } else if (ftruncate(fileno(file->stream), file->offset)) {
-        error = ovsdb_io_error(errno, "%s: cannot truncate to length %lld",
-                               file->display_name,
-                               (long long int) file->offset);
-    }
-    return error;
+    return ovsdb_log_file_truncate(file->curr);
 }
 
 /* Removes all the data from the log by moving current offset to zero and
@@ -565,7 +660,7 @@ struct ovsdb_error * OVS_WARN_UNUSED_RESULT
 ovsdb_log_reset(struct ovsdb_log *file)
 {
     ovsdb_error_destroy(file->error);
-    file->offset = file->prev_offset = 0;
+    file->curr->offset = file->curr->prev_offset = 0;
     file->error = ovsdb_log_truncate(file);
     if (file->error) {
         file->state = OVSDB_LOG_WRITE_ERROR;
@@ -602,35 +697,12 @@ ovsdb_log_compose_record(const struct json *json,
 }
 
 static struct ovsdb_error *
-ovsdb_log_write_(struct ovsdb_log *file, const struct json *json)
+ovsdb_log_write_file(struct ovsdb_log *log, struct ovsdb_log_file *file,
+                     const struct json *json)
 {
-    switch (file->state) {
-    case OVSDB_LOG_WRITE:
-        break;
-
-    case OVSDB_LOG_READ:
-    case OVSDB_LOG_READ_ERROR:
-    case OVSDB_LOG_WRITE_ERROR:
-        ovsdb_error_destroy(file->error);
-        file->error = ovsdb_log_truncate(file);
-        if (file->error) {
-            file->state = OVSDB_LOG_WRITE_ERROR;
-            return ovsdb_error_clone(file->error);
-        }
-        file->state = OVSDB_LOG_WRITE;
-        break;
-
-    case OVSDB_LOG_BROKEN:
-        return ovsdb_error_clone(file->error);
-    }
-
-    if (json->type != JSON_OBJECT && json->type != JSON_ARRAY) {
-        return OVSDB_BUG("bad JSON type");
-    }
-
     struct ds header = DS_EMPTY_INITIALIZER;
     struct ds data = DS_EMPTY_INITIALIZER;
-    ovsdb_log_compose_record(json, file->magic, &header, &data);
+    ovsdb_log_compose_record(json, log->magic, &header, &data);
     size_t total_length = header.length + data.length;
 
     /* Write. */
@@ -650,16 +722,55 @@ ovsdb_log_write_(struct ovsdb_log *file, const struct 
json *json)
          * nothing further we can do. */
         ignore(ftruncate(fileno(file->stream), file->offset));
 
-        file->error = ovsdb_io_error(error, "%s: write failed",
-                                     file->display_name);
-        file->state = OVSDB_LOG_WRITE_ERROR;
-        return ovsdb_error_clone(file->error);
+        log->error = ovsdb_io_error(error, "%s: write failed",
+                                     file->name);
+        log->state = OVSDB_LOG_WRITE_ERROR;
+        return ovsdb_error_clone(log->error);
     }
 
     file->offset += total_length;
     return NULL;
 }
 
+static struct ovsdb_error *
+ovsdb_log_write_(struct ovsdb_log *log, const struct json *json)
+{
+
+    if (log->open_mode == OVSDB_LOG_READ_ONLY) {
+        return ovsdb_error(NULL, "can not write to a readonly log");
+    }
+
+    switch (log->state) {
+    case OVSDB_LOG_WRITE:
+        break;
+
+    case OVSDB_LOG_READ:
+    case OVSDB_LOG_READ_ERROR:
+    case OVSDB_LOG_WRITE_ERROR:
+        ovsdb_error_destroy(log->error);
+        log->error = ovsdb_log_truncate(log);
+        if (log->error) {
+            log->state = OVSDB_LOG_WRITE_ERROR;
+            return ovsdb_error_clone(log->error);
+        }
+        log->state = OVSDB_LOG_WRITE;
+        break;
+
+    case OVSDB_LOG_BROKEN:
+        return ovsdb_error_clone(log->error);
+    }
+
+    if (!json) {
+        return NULL;
+    }
+
+    if (json->type != JSON_OBJECT && json->type != JSON_ARRAY) {
+        return OVSDB_BUG("bad JSON type");
+    }
+
+    return ovsdb_log_write_file(log, log->curr, json);
+}
+
 /* Writes log record 'json' to 'file'.  Returns NULL if successful or an error
  * (which the caller must eventually destroy) on failure.
  *
@@ -672,6 +783,7 @@ ovsdb_log_write_(struct ovsdb_log *file, const struct json 
*json)
 struct ovsdb_error *
 ovsdb_log_write(struct ovsdb_log *file, const struct json *json)
 {
+    ovs_assert(file->curr);
     struct json *outer = json_object_create();
     json_object_put(outer, OVSDB_LOG_DATA_KEY, (struct json *) json);
     struct ovsdb_error *error = ovsdb_log_write_(file, outer);
@@ -688,10 +800,8 @@ ovsdb_log_write_and_free(struct ovsdb_log *log, struct 
json *json)
     return error;
 }
 
-/* Attempts to commit 'file' to disk.  Waits for the commit to succeed or fail.
- * Returns NULL if successful, otherwise the error that occurred. */
-struct ovsdb_error *
-ovsdb_log_commit_block(struct ovsdb_log *file)
+static struct ovsdb_error *
+ovsdb_log_file_commit_block(struct ovsdb_log_file *file)
 {
 #if (_POSIX_C_SOURCE >= 199309L || _XOPEN_SOURCE >= 500)
     /* we do not check metadata - mtime, atime, anywhere, so we
@@ -703,18 +813,27 @@ ovsdb_log_commit_block(struct ovsdb_log *file)
 #else
     if (file->stream && fsync(fileno(file->stream))) {
 #endif
-        return ovsdb_io_error(errno, "%s: fsync failed", file->display_name);
+        return ovsdb_io_error(errno, "%s: fsync failed", file->name);
     }
     return NULL;
 }
 
+/* Attempts to commit 'file' to disk.  Waits for the commit to succeed or fail.
+ * Returns NULL if successful, otherwise the error that occurred. */
+struct ovsdb_error *
+ovsdb_log_commit_block(struct ovsdb_log *file)
+{
+    return ovsdb_log_file_commit_block(file->curr);
+}
+
 /* Sets the current position in 'log' as the "base", that is, the initial size
  * of the log that ovsdb_log_grew_lots() uses to determine whether the log has
  * grown enough to make compacting worthwhile. */
 void
 ovsdb_log_mark_base(struct ovsdb_log *log)
 {
-    log->base = log->offset;
+    /* We only need to care about curr, since the others will never grow. */
+    log->curr->base = log->curr->offset;
 }
 
 /* Returns true if 'log' has grown enough above the base that it's worthwhile
@@ -722,7 +841,9 @@ ovsdb_log_mark_base(struct ovsdb_log *log)
 bool
 ovsdb_log_grew_lots(const struct ovsdb_log *log)
 {
-    return log->offset > 10 * 1024 * 1024 && log->offset / 2 > log->base;
+    /* We only need to care about curr, since the others will never grow. */
+    return log->curr->offset > 10 * 1024 * 1024 &&
+        log->curr->offset / 2 > log->curr->base;
 }
 
 /* Attempts to atomically replace the contents of 'log', on disk, by the 'n'
@@ -753,35 +874,6 @@ ovsdb_log_replace(struct ovsdb_log *log, struct json 
**entries, size_t n)
     return ovsdb_log_replace_commit(log, new);
 }
 
-struct ovsdb_error * OVS_WARN_UNUSED_RESULT
-ovsdb_log_replace_start(struct ovsdb_log *old,
-                        struct ovsdb_log **newp)
-{
-    /* If old->name is a symlink, then we want the new file to be in the same
-     * directory as the symlink's referent. */
-    char *deref_name = follow_symlinks(old->name);
-    char *tmp_name = xasprintf("%s.tmp", deref_name);
-    free(deref_name);
-
-    struct ovsdb_error *error;
-
-    ovs_assert(old->lockfile);
-
-    /* Remove temporary file.  (It might not exist.) */
-    if (unlink(tmp_name) < 0 && errno != ENOENT) {
-        error = ovsdb_io_error(errno, "failed to remove %s", tmp_name);
-        free(tmp_name);
-        *newp = NULL;
-        return error;
-    }
-
-    /* Create temporary file. */
-    error = ovsdb_log_open(tmp_name, old->magic, OVSDB_LOG_CREATE_EXCL,
-                           false, newp);
-    free(tmp_name);
-    return error;
-}
-
 /* Rename 'old' to 'new', replacing 'new' if it exists.  Returns NULL if
  * successful, otherwise an ovsdb_error that the caller must destroy. */
 static struct ovsdb_error * OVS_WARN_UNUSED_RESULT
@@ -802,6 +894,35 @@ ovsdb_rename(const char *old, const char *new)
             : NULL);
 }
 
+struct ovsdb_error * OVS_WARN_UNUSED_RESULT
+ovsdb_log_replace_start(struct ovsdb_log *old,
+                        struct ovsdb_log **newp)
+{
+    ovs_assert(old->curr->lockfile);
+
+    /* If old->name is a symlink, then we want the new file to be in the same
+     * directory as the symlink's referent. */
+    char *deref_name = follow_symlinks(old->curr->name);
+    char *tmp_name = xasprintf("%s.tmp", deref_name);
+    free(deref_name);
+
+    struct ovsdb_error *error;
+
+    /* Remove temporary file.  (It might not exist.) */
+    if (unlink(tmp_name) < 0 && errno != ENOENT) {
+        error = ovsdb_io_error(errno, "failed to remove %s", tmp_name);
+        free(tmp_name);
+        *newp = NULL;
+        return error;
+    }
+
+    /* Create temporary file. */
+    error = ovsdb_log_open(tmp_name, old->magic, OVSDB_LOG_CREATE_EXCL,
+                           false, newp);
+    free(tmp_name);
+    return error;
+}
+
 struct ovsdb_error * OVS_WARN_UNUSED_RESULT
 ovsdb_log_replace_commit(struct ovsdb_log *old, struct ovsdb_log *new)
 {
@@ -830,40 +951,39 @@ ovsdb_log_replace_commit(struct ovsdb_log *old, struct 
ovsdb_log *new)
      * to test both strategies on Unix-like systems, and to make the code
      * easier to read. */
     if (!rename_open_files) {
-        fclose(old->stream);
-        old->stream = NULL;
+        fclose(old->curr->stream);
+        old->curr->stream = NULL;
 
-        fclose(new->stream);
-        new->stream = NULL;
+        fclose(new->curr->stream);
+        new->curr->stream = NULL;
     }
 
     /* Rename 'old' to 'new'.  We dereference the old name because, if it is a
      * symlink, we want to replace the referent of the symlink instead of the
      * symlink itself. */
-    char *deref_name = follow_symlinks(old->name);
-    error = ovsdb_rename(new->name, deref_name);
+    char *deref_name = follow_symlinks(old->curr->name);
+    error = ovsdb_rename(new->curr->name, deref_name);
     free(deref_name);
-
     if (error) {
         ovsdb_log_replace_abort(new);
         return error;
     }
     if (rename_open_files) {
-        fsync_parent_dir(old->name);
-        fclose(old->stream);
-        old->stream = new->stream;
-        new->stream = NULL;
+        fsync_parent_dir(old->curr->name);
+        old->curr->stream = new->curr->stream;
+        new->curr->stream = NULL;
     } else {
-        old->stream = fopen(old->name, "r+b");
-        if (!old->stream) {
+        old->curr->stream = fopen(old->curr->name, "r+b");
+        if (!old->curr->stream) {
             old->error = ovsdb_io_error(errno, "%s: could not reopen log",
-                                        old->name);
+                                        old->curr->name);
             old->state = OVSDB_LOG_BROKEN;
             return ovsdb_error_clone(old->error);
         }
 
-        if (fseek(old->stream, new->offset, SEEK_SET)) {
-            old->error = ovsdb_io_error(errno, "%s: seek failed", old->name);
+        if (fseek(old->curr->stream, new->curr->offset, SEEK_SET)) {
+            old->error = ovsdb_io_error(errno, "%s: seek failed",
+                                        old->curr->name);
             old->state = OVSDB_LOG_BROKEN;
             return ovsdb_error_clone(old->error);
         }
@@ -877,17 +997,18 @@ ovsdb_log_replace_commit(struct ovsdb_log *old, struct 
ovsdb_log *new)
     ovsdb_error_destroy(old->error);
     old->error = NULL;
     /* prev_offset only matters for OVSDB_LOG_READ. */
-    if (old->afsync) {
-        uint64_t ticket = afsync_destroy(old->afsync);
-        old->afsync = afsync_create(fileno(old->stream), ticket + 1);
+    if (old->curr->afsync) {
+        uint64_t ticket = afsync_destroy(old->curr->afsync);
+        old->curr->afsync = afsync_create(fileno(old->curr->stream),
+                                          ticket + 1);
     }
-    old->offset = new->offset;
+    old->curr->offset = new->curr->offset;
+    old->curr->base = new->curr->base;
     /* Keep old->name. */
     free(old->magic);
     old->magic = new->magic;
     new->magic = NULL;
     /* Keep old->lockfile. */
-    old->base = new->base;
 
     /* Free 'new'. */
     ovsdb_log_close(new);
@@ -901,7 +1022,8 @@ ovsdb_log_replace_abort(struct ovsdb_log *new)
     if (new) {
         /* Unlink the new file, but only after we close it (because Windows
          * does not allow removing an open file). */
-        char *name = xstrdup(new->name);
+        char *name = new->curr->name;
+        new->curr->name = NULL;
         ovsdb_log_close(new);
         unlink(name);
         free(name);
@@ -989,7 +1111,7 @@ afsync_destroy(struct afsync *afsync)
 }
 
 static struct afsync *
-ovsdb_log_get_afsync(struct ovsdb_log *log)
+ovsdb_log_get_afsync(struct ovsdb_log_file *log)
 {
     if (!log->afsync) {
         log->afsync = afsync_create(log->stream ? fileno(log->stream) : -1, 0);
@@ -997,11 +1119,8 @@ ovsdb_log_get_afsync(struct ovsdb_log *log)
     return log->afsync;
 }
 
-/* Starts committing 'log' to disk.  Returns a ticket that can be passed to
- * ovsdb_log_commit_wait() or compared against the return value of
- * ovsdb_log_commit_progress() later. */
-uint64_t
-ovsdb_log_commit_start(struct ovsdb_log *log)
+static uint64_t
+ovsdb_log_file_commit_start(struct ovsdb_log_file *log)
 {
     struct afsync *afsync = ovsdb_log_get_afsync(log);
 
@@ -1013,12 +1132,17 @@ ovsdb_log_commit_start(struct ovsdb_log *log)
     return orig + 1;
 }
 
-/* Returns a ticket value that represents the current progress of commits to
- * 'log'.  Suppose that some call to ovsdb_log_commit_start() returns X and any
- * call ovsdb_log_commit_progress() returns Y, for the same 'log'.  Then commit
- * X is complete if and only if X <= Y. */
+/* Starts committing 'log' to disk.  Returns a ticket that can be passed to
+ * ovsdb_log_commit_wait() or compared against the return value of
+ * ovsdb_log_commit_progress() later. */
 uint64_t
-ovsdb_log_commit_progress(struct ovsdb_log *log)
+ovsdb_log_commit_start(struct ovsdb_log *log)
+{
+    return ovsdb_log_file_commit_start(log->curr);
+}
+
+static uint64_t
+ovsdb_log_file_commit_progress(struct ovsdb_log_file *log)
 {
     struct afsync *afsync = ovsdb_log_get_afsync(log);
     uint64_t cur;
@@ -1026,17 +1150,33 @@ ovsdb_log_commit_progress(struct ovsdb_log *log)
     return cur;
 }
 
-/* Causes poll_block() to wake up if and when ovsdb_log_commit_progress(log)
- * would return at least 'goal'. */
-void
-ovsdb_log_commit_wait(struct ovsdb_log *log, uint64_t goal)
+/* Returns a ticket value that represents the current progress of commits to
+ * 'log'.  Suppose that some call to ovsdb_log_commit_start() returns X and any
+ * call ovsdb_log_commit_progress() returns Y, for the same 'log'.  Then commit
+ * X is complete if and only if X <= Y. */
+uint64_t
+ovsdb_log_commit_progress(struct ovsdb_log *log)
+{
+    return ovsdb_log_file_commit_progress(log->curr);
+}
+
+static void
+ovsdb_log_file_commit_wait(struct ovsdb_log_file *log, uint64_t goal)
 {
     struct afsync *afsync = ovsdb_log_get_afsync(log);
     uint64_t complete = seq_read(afsync->complete);
-    uint64_t cur = ovsdb_log_commit_progress(log);
+    uint64_t cur = ovsdb_log_file_commit_progress(log);
     if (cur < goal) {
         seq_wait(afsync->complete, complete);
     } else {
         poll_immediate_wake();
     }
 }
+
+/* Causes poll_block() to wake up if and when ovsdb_log_commit_progress(log)
+ * would return at least 'goal'. */
+void
+ovsdb_log_commit_wait(struct ovsdb_log *log, uint64_t goal)
+{
+    ovsdb_log_file_commit_wait(log->curr, goal);
+}
diff --git a/tests/ovsdb-log.at b/tests/ovsdb-log.at
index d00a64cb2..a73990a15 100644
--- a/tests/ovsdb-log.at
+++ b/tests/ovsdb-log.at
@@ -46,9 +46,8 @@ AT_CHECK(
 file: read: {"x":1}
 ]], [ignore])
 AT_CHECK(
-  [test-ovsdb log-io file create-excl read], [1],
-  [], [test-ovsdb: I/O error: file: create failed (File exists)
-])
+  [test-ovsdb log-io file create-excl read 2>&1 | grep -q "create failed"], 
[0],
+  [], [])
 AT_CHECK(
   [test-ovsdb log-io file create read], [0],
   [file: open successful
@@ -173,9 +172,8 @@ file: read: {"x":2}
 file: read: end of log
 ]], [ignore])
 AT_CHECK(
-  [test-ovsdb log-io file read-only], [1], [],
-  [test-ovsdb: ovsdb error: file: cannot identify file type
-])
+  [test-ovsdb log-io file read-only 2>&1 | grep -q "cannot identify file 
type"], [0], [],
+  [])
 AT_CHECK([test -f .file.~lock~])
 AT_CLEANUP
 
@@ -247,7 +245,7 @@ file: write:{"x":2} successful
 ]], [ignore])
 AT_CHECK([echo 'xxx' >> file])
 AT_CHECK(
-  [test-ovsdb log-io file read-only read read read read], [0], 
+  [test-ovsdb log-io file read-only read read read read | sed -e 's|error: 
.*file[[.0-9]]*:|error: file:|'], [0],
   [[file: open successful
 file: read: {"x":0}
 file: read: {"x":1}
@@ -269,7 +267,7 @@ file: write:{"x":2} successful
 ]], [ignore])
 AT_CHECK([echo 'xxx' >> file])
 AT_CHECK(
-  [[test-ovsdb log-io file read/write read read read read 'write:{"x":3}']], 
[0],
+  [[test-ovsdb log-io file read/write read read read read 'write:{"x":3}' | 
sed -e 's|error: .*file[.0-9]*:|error: file:|']], [0],
   [[file: open successful
 file: read: {"x":0}
 file: read: {"x":1}
@@ -304,7 +302,7 @@ AT_CHECK([mv file.tmp file])
 AT_CHECK([[grep -c '{"x":3}' file]], [0], [1
 ])
 AT_CHECK(
-  [[test-ovsdb log-io file read/write read read read 'write:{"longer 
data":0}']], [0],
+  [[test-ovsdb log-io file read/write read read read 'write:{"longer data":0}' 
| sed -e 's|error: .*file[.0-9]*:|error: file:|']], [0],
   [[file: open successful
 file: read: {"x":0}
 file: read: {"x":1}
@@ -337,7 +335,7 @@ AT_CHECK([mv file.tmp file])
 AT_CHECK([[grep -c '^{"logdata":2}$' file]], [0], [1
 ])
 AT_CHECK(
-  [[test-ovsdb log-io file read/write read read read 'write:{"longer 
data":0}']], [0],
+  [[test-ovsdb log-io file read/write read read read 'write:{"longer data":0}' 
| sed -e 's|error: .*file[.0-9]*:|error: file:|']], [0],
   [[file: open successful
 file: read: {"x":0}
 file: read: {"x":1}
@@ -367,7 +365,7 @@ file: write:{"x":2} successful
 ]], [ignore])
 AT_CHECK([[printf '%s\n%s\n' 'OVSDB JSON 5 
d910b02871075d3156ec8675dfc95b7d5d640aa6' 'null' >> file]])
 AT_CHECK(
-  [[test-ovsdb log-io file read/write read read read read 'write:{"replacement 
data":0}']], [0],
+  [[test-ovsdb log-io file read/write read read read read 'write:{"replacement 
data":0}' | sed -e 's|error: .*file[.0-9]*:|error: file:|']], [0],
   [[file: open successful
 file: read: {"x":0}
 file: read: {"x":1}
diff --git a/tests/ovsdb-server.at b/tests/ovsdb-server.at
index bf9d25fa2..ef27d298e 100644
--- a/tests/ovsdb-server.at
+++ b/tests/ovsdb-server.at
@@ -83,7 +83,7 @@ AT_DATA([txnfile], [[ovsdb-client transact unix:socket \
    "row": {"number": 1, "name": "one"}}]'
 ]])
 AT_CHECK([ovsdb-server --remote=punix:socket db --run="sh txnfile"], [0], 
[stdout], [stderr])
-AT_CHECK([grep 'syntax error: db: parse error.* in header line "xxx"' stderr],
+AT_CHECK([grep 'syntax error: .*db.*: parse error.* in header line "xxx"' 
stderr],
   [0], [ignore])
 cat stdout >> output
 dnl Run a final transaction to verify that both transactions succeeeded.
@@ -263,7 +263,7 @@ fi
 
 # Add a non-existing database.
 AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/add-db db3], 2, [], [stderr])
-AT_CHECK([sed 's/(.*)/(...)/' stderr], [0],
+AT_CHECK([sed -e 's/(.*)/(...)/' -e 's|error: .*db3:|error: db3:|' stderr], 
[0],
   [I/O error: db3: open failed (...)
 ovs-appctl: ovsdb-server: server returned an error
 ])
-- 
2.43.0


_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to