From c182393feeefa622567490b6ebf104cb0a2a3996 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Tue, 12 Dec 2017 15:52:47 +0000 Subject: [PATCH] Split database into a writer and reader This allows us to build both parts more to their own purpose instead of making code overly complicated to archive flexibility we don't need. Signed-off-by: Michael Tremer --- Makefile.am | 6 +- src/database.c | 385 ++++++++++++++------------------------------ src/database.h | 7 +- src/libloc.c | 2 +- src/libloc.sym | 16 +- src/loc/writer.h | 42 +++++ src/stringpool.c | 2 - src/test-as.c | 22 ++- src/test-database.c | 28 ++-- src/writer.c | 252 +++++++++++++++++++++++++++++ 10 files changed, 460 insertions(+), 302 deletions(-) create mode 100644 src/loc/writer.h create mode 100644 src/writer.c diff --git a/Makefile.am b/Makefile.am index 6f4875d..7d48831 100644 --- a/Makefile.am +++ b/Makefile.am @@ -37,7 +37,8 @@ pkgconfigdir = $(libdir)/pkgconfig pkginclude_HEADERS = \ src/loc/format.h \ - src/loc/libloc.h + src/loc/libloc.h \ + src/loc/writer.h lib_LTLIBRARIES = \ src/libloc.la @@ -50,7 +51,8 @@ src_libloc_la_SOURCES =\ src/database.c \ src/database.h \ src/stringpool.c \ - src/stringpool.h + src/stringpool.h \ + src/writer.c EXTRA_DIST += src/libloc.sym diff --git a/src/database.c b/src/database.c index 9db875b..41b5ffd 100644 --- a/src/database.c +++ b/src/database.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -44,169 +45,12 @@ struct loc_database { off_t description; // ASes in the database - struct loc_as** as; + struct loc_database_as_v0* as_v0; size_t as_count; struct loc_stringpool* pool; }; -LOC_EXPORT int loc_database_new(struct loc_ctx* ctx, struct loc_database** database, size_t pool_size) { - struct loc_database* db = calloc(1, sizeof(*db)); - if (!db) - return -ENOMEM; - - // Reference context - db->ctx = loc_ref(ctx); - db->refcount = 1; - - // Save creation time - db->created_at = time(NULL); - - DEBUG(db->ctx, "Database allocated at %p\n", db); - - // Create string pool - int r = loc_stringpool_new(db->ctx, &db->pool, pool_size); - if (r) { - loc_database_unref(db); - return r; - } - - *database = db; - - return 0; -} - -LOC_EXPORT int loc_database_open(struct loc_ctx* ctx, struct loc_database** database, FILE* f) { - int r = loc_database_new(ctx, database, 0); - if (r) - return r; - - return loc_database_read(*database, f); -} - -LOC_EXPORT struct loc_database* loc_database_ref(struct loc_database* db) { - db->refcount++; - - return db; -} - -static void loc_database_free(struct loc_database* db) { - DEBUG(db->ctx, "Releasing database %p\n", db); - - // Remove references to all ASes - if (db->as) { - for (unsigned int i = 0; i < db->as_count; i++) { - loc_as_unref(db->as[i]); - } - free(db->as); - } - - loc_stringpool_unref(db->pool); - - // Close file - if (db->file) - fclose(db->file); - - loc_unref(db->ctx); - free(db); -} - -LOC_EXPORT struct loc_database* loc_database_unref(struct loc_database* db) { - if (--db->refcount > 0) - return NULL; - - loc_database_free(db); - return NULL; -} - -LOC_EXPORT time_t loc_database_created_at(struct loc_database* db) { - return db->created_at; -} - -LOC_EXPORT const char* loc_database_get_vendor(struct loc_database* db) { - return loc_stringpool_get(db->pool, db->vendor); -} - -LOC_EXPORT int loc_database_set_vendor(struct loc_database* db, const char* vendor) { - // Add the string to the string pool - off_t offset = loc_stringpool_add(db->pool, vendor); - if (offset < 0) - return offset; - - db->vendor = offset; - return 0; -} - -LOC_EXPORT const char* loc_database_get_description(struct loc_database* db) { - return loc_stringpool_get(db->pool, db->description); -} - -LOC_EXPORT int loc_database_set_description(struct loc_database* db, const char* description) { - // Add the string to the string pool - off_t offset = loc_stringpool_add(db->pool, description); - if (offset < 0) - return offset; - - db->description = offset; - return 0; -} - -LOC_EXPORT size_t loc_database_count_as(struct loc_database* db) { - return db->as_count; -} - -static int loc_database_has_as(struct loc_database* db, struct loc_as* as) { - for (unsigned int i = 0; i < db->as_count; i++) { - if (loc_as_cmp(as, db->as[i]) == 0) - return i; - } - - return -1; -} - -static int __loc_as_cmp(const void* as1, const void* as2) { - return loc_as_cmp(*(struct loc_as**)as1, *(struct loc_as**)as2); -} - -static void loc_database_sort_ases(struct loc_database* db) { - qsort(db->as, db->as_count, sizeof(*db->as), __loc_as_cmp); -} - -static struct loc_as* __loc_database_add_as(struct loc_database* db, struct loc_as* as) { - // Check if AS exists already - int i = loc_database_has_as(db, as); - if (i >= 0) { - loc_as_unref(as); - - // Select already existing AS - as = db->as[i]; - - return loc_as_ref(as); - } - - db->as_count++; - - // Make space for the new entry - db->as = realloc(db->as, sizeof(*db->as) * db->as_count); - - // Add the new entry at the end - db->as[db->as_count - 1] = loc_as_ref(as); - - // Sort everything - loc_database_sort_ases(db); - - return as; -} - -LOC_EXPORT struct loc_as* loc_database_add_as(struct loc_database* db, uint32_t number) { - struct loc_as* as; - int r = loc_as_new(db->ctx, db->pool, &as, number); - if (r) - return NULL; - - return __loc_database_add_as(db, as); -} - static int loc_database_read_magic(struct loc_database* db) { struct loc_database_magic magic; @@ -239,33 +83,18 @@ static int loc_database_read_magic(struct loc_database* db) { static int loc_database_read_as_section_v0(struct loc_database* db, off_t as_offset, size_t as_length) { - struct loc_database_as_v0 dbobj; + DEBUG(db->ctx, "Reading AS section from %jd (%zu bytes)\n", as_offset, as_length); - // Read from the start of the section - int r = fseek(db->file, as_offset, SEEK_SET); - if (r) - return r; - - // Read all ASes - size_t as_count = as_length / sizeof(dbobj); - for (unsigned int i = 0; i < as_count; i++) { - size_t bytes_read = fread(&dbobj, 1, sizeof(dbobj), db->file); - if (bytes_read < sizeof(dbobj)) { - ERROR(db->ctx, "Could not read an AS object\n"); - return -ENOMSG; - } - - // Allocate a new AS - struct loc_as* as; - r = loc_as_new_from_database_v0(db->ctx, db->pool, &as, &dbobj); - if (r) - return r; + if (as_length > 0) { + db->as_v0 = mmap(NULL, as_length, PROT_READ, + MAP_SHARED, fileno(db->file), as_offset); - // Attach it to the database - as = __loc_database_add_as(db, as); - loc_as_unref(as); + if (db->as_v0 == MAP_FAILED) + return -errno; } + db->as_count = as_length / sizeof(*db->as_v0); + INFO(db->ctx, "Read %zu ASes from the database\n", db->as_count); return 0; @@ -291,7 +120,11 @@ static int loc_database_read_header_v0(struct loc_database* db) { off_t pool_offset = ntohl(header.pool_offset); size_t pool_length = ntohl(header.pool_length); - int r = loc_stringpool_read(db->pool, db->file, pool_offset, pool_length); + int r = loc_stringpool_new(db->ctx, &db->pool, 0); + if (r) + return r; + + r = loc_stringpool_read(db->pool, db->file, pool_offset, pool_length); if (r) return r; @@ -317,25 +150,34 @@ static int loc_database_read_header(struct loc_database* db) { } } -LOC_EXPORT int loc_database_read(struct loc_database* db, FILE* f) { - // Copy the file pointer and work on that so we don't care if - // the calling function closes the file +static FILE* copy_file_pointer(FILE* f) { int fd = fileno(f); // Make a copy fd = dup(fd); - // Retrieve a file pointer - db->file = fdopen(fd, "r"); - if (!db->file) - return -errno; + return fdopen(fd, "r"); +} - int r = fseek(db->file, 0, SEEK_SET); - if (r) - return r; +LOC_EXPORT int loc_database_new(struct loc_ctx* ctx, struct loc_database** database, FILE* f) { + struct loc_database* db = calloc(1, sizeof(*db)); + if (!db) + return -ENOMEM; + + // Reference context + db->ctx = loc_ref(ctx); + db->refcount = 1; + + DEBUG(db->ctx, "Database object allocated at %p\n", db); + + // Copy the file pointer and work on that so we don't care if + // the calling function closes the file + db->file = copy_file_pointer(f); + if (!db->file) + goto FAIL; // Read magic bytes - r = loc_database_read_magic(db); + int r = loc_database_read_magic(db); if (r) return r; @@ -344,111 +186,120 @@ LOC_EXPORT int loc_database_read(struct loc_database* db, FILE* f) { if (r) return r; + *database = db; + return 0; -} -static void loc_database_make_magic(struct loc_database* db, struct loc_database_magic* magic) { - // Copy magic bytes - for (unsigned int i = 0; i < strlen(LOC_DATABASE_MAGIC); i++) - magic->magic[i] = LOC_DATABASE_MAGIC[i]; +FAIL: + loc_database_unref(db); - // Set version - magic->version = htons(LOC_DATABASE_VERSION); + return -errno; } -static void loc_database_align_page_boundary(off_t* offset, FILE* f) { - // Move to next page boundary - while (*offset % LOC_DATABASE_PAGE_SIZE > 0) - *offset += fwrite("", 1, 1, f); +LOC_EXPORT struct loc_database* loc_database_ref(struct loc_database* db) { + db->refcount++; + + return db; } -static int loc_database_write_pool(struct loc_database* db, struct loc_database_header_v0* header, off_t* offset, FILE* f) { - // Save the offset of the pool section - DEBUG(db->ctx, "Pool starts at %jd bytes\n", *offset); - header->pool_offset = htonl(*offset); +static void loc_database_free(struct loc_database* db) { + DEBUG(db->ctx, "Releasing database %p\n", db); - // Write the pool - size_t pool_length = loc_stringpool_write(db->pool, f); - *offset += pool_length; + // Removing all ASes + if (db->as_v0) { + int r = munmap(db->as_v0, db->as_count * sizeof(*db->as_v0)); + if (r) + ERROR(db->ctx, "Could not unmap AS section: %s\n", strerror(errno)); + } - DEBUG(db->ctx, "Pool has a length of %zu bytes\n", pool_length); - header->pool_length = htonl(pool_length); + loc_stringpool_unref(db->pool); - return 0; + // Close file + if (db->file) + fclose(db->file); + + loc_unref(db->ctx); + free(db); } -static int loc_database_write_as_section(struct loc_database* db, - struct loc_database_header_v0* header, off_t* offset, FILE* f) { - DEBUG(db->ctx, "AS section starts at %jd bytes\n", *offset); - header->as_offset = htonl(*offset); +LOC_EXPORT struct loc_database* loc_database_unref(struct loc_database* db) { + if (--db->refcount > 0) + return NULL; - size_t as_length = 0; + loc_database_free(db); + return NULL; +} - struct loc_database_as_v0 dbas; - for (unsigned int i = 0; i < db->as_count; i++) { - // Convert AS into database format - loc_as_to_database_v0(db->as[i], &dbas); +LOC_EXPORT time_t loc_database_created_at(struct loc_database* db) { + return db->created_at; +} - // Write to disk - offset += fwrite(&dbas, 1, sizeof(dbas), f); - as_length += sizeof(dbas); - } +LOC_EXPORT const char* loc_database_get_vendor(struct loc_database* db) { + return loc_stringpool_get(db->pool, db->vendor); +} - DEBUG(db->ctx, "AS section has a length of %zu bytes\n", as_length); - header->as_length = htonl(as_length); +LOC_EXPORT const char* loc_database_get_description(struct loc_database* db) { + return loc_stringpool_get(db->pool, db->description); +} - return 0; +LOC_EXPORT size_t loc_database_count_as(struct loc_database* db) { + return db->as_count; } -LOC_EXPORT int loc_database_write(struct loc_database* db, FILE* f) { - struct loc_database_magic magic; - loc_database_make_magic(db, &magic); +// Returns the AS at position pos +static int loc_database_fetch_as(struct loc_database* db, struct loc_as** as, off_t pos) { + if ((size_t)pos >= db->as_count) + return -EINVAL; - // Make the header - struct loc_database_header_v0 header; - header.created_at = htobe64(db->created_at); - header.vendor = htonl(db->vendor); - header.description = htonl(db->description); + DEBUG(db->ctx, "Fetching AS at position %jd\n", pos); int r; - off_t offset = 0; - - // Start writing at the beginning of the file - r = fseek(f, 0, SEEK_SET); - if (r) - return r; + switch (db->version) { + case 0: + r = loc_as_new_from_database_v0(db->ctx, db->pool, as, db->as_v0 + pos); + break; - // Write the magic - offset += fwrite(&magic, 1, sizeof(magic), f); + default: + return -1; + } - // Skip the space we need to write the header later - r = fseek(f, sizeof(header), SEEK_CUR); - if (r) { - DEBUG(db->ctx, "Could not seek to position after header\n"); - return r; + if (r == 0) { + DEBUG(db->ctx, "Got AS%u\n", loc_as_get_number(*as)); } - offset += sizeof(header); - loc_database_align_page_boundary(&offset, f); + return r; +} - // Write all ASes - r = loc_database_write_as_section(db, &header, &offset, f); - if (r) - return r; +// Performs a binary search to find the AS in the list +LOC_EXPORT int loc_database_get_as(struct loc_database* db, struct loc_as** as, uint32_t number) { + off_t lo = 0; + off_t hi = db->as_count - 1; - loc_database_align_page_boundary(&offset, f); + while (lo <= hi) { + off_t i = (lo + hi) / 2; - // Write pool - r = loc_database_write_pool(db, &header, &offset, f); - if (r) - return r; + // Fetch AS in the middle between lo and hi + int r = loc_database_fetch_as(db, as, i); + if (r) + return r; - // Write the header - r = fseek(f, sizeof(magic), SEEK_SET); - if (r) - return r; + // Check if this is a match + uint32_t as_number = loc_as_get_number(*as); + if (as_number == number) + return 0; + + // If it wasn't, we release the AS and + // adjust our search pointers + loc_as_unref(*as); + + if (as_number < number) { + lo = i + 1; + } else + hi = i - 1; + } - offset += fwrite(&header, 1, sizeof(header), f); + // Nothing found + *as = NULL; return 0; } diff --git a/src/database.h b/src/database.h index 407abcb..bec2f41 100644 --- a/src/database.h +++ b/src/database.h @@ -25,19 +25,16 @@ #include "as.h" struct loc_database; -int loc_database_new(struct loc_ctx* ctx, struct loc_database** db, size_t pool_size); -int loc_database_open(struct loc_ctx* ctx, struct loc_database** database, FILE* f); +int loc_database_new(struct loc_ctx* ctx, struct loc_database** database, FILE* f); struct loc_database* loc_database_ref(struct loc_database* db); struct loc_database* loc_database_unref(struct loc_database* db); time_t loc_database_created_at(struct loc_database* db); const char* loc_database_get_vendor(struct loc_database* db); -int loc_database_set_vendor(struct loc_database* db, const char* vendor); const char* loc_database_get_description(struct loc_database* db); -int loc_database_set_description(struct loc_database* db, const char* description); +int loc_database_get_as(struct loc_database* db, struct loc_as** as, uint32_t number); size_t loc_database_count_as(struct loc_database* db); -struct loc_as* loc_database_add_as(struct loc_database* db, uint32_t number); int loc_database_read(struct loc_database* db, FILE* f); int loc_database_write(struct loc_database* db, FILE* f); diff --git a/src/libloc.c b/src/libloc.c index 58e13cc..667fd7c 100644 --- a/src/libloc.c +++ b/src/libloc.c @@ -147,7 +147,7 @@ LOC_EXPORT int loc_load(struct loc_ctx* ctx, const char* path) { loc_database_unref(ctx->db); // Open the new database - int r = loc_database_open(ctx, &ctx->db, f); + int r = loc_database_new(ctx, &ctx->db, f); if (r) return r; diff --git a/src/libloc.sym b/src/libloc.sym index 3469290..6934102 100644 --- a/src/libloc.sym +++ b/src/libloc.sym @@ -12,15 +12,12 @@ global: loc_database_add_as; loc_database_count_as; loc_database_created_at; + loc_database_get_as; loc_database_get_description; loc_database_get_vendor; loc_database_new; - loc_database_open; loc_database_ref; - loc_database_set_description; - loc_database_set_vendor; loc_database_unref; - loc_database_write; # String Pool loc_stringpool_add; @@ -30,6 +27,17 @@ global: loc_stringpool_new; loc_stringpool_ref; loc_stringpool_unref; + + # Writer + loc_writer_add_as; + loc_writer_get_description; + loc_writer_get_vendor; + loc_writer_new; + loc_writer_ref; + loc_writer_set_description; + loc_writer_set_vendor; + loc_writer_unref; + loc_writer_write; }; LIBLOC_1 { diff --git a/src/loc/writer.h b/src/loc/writer.h new file mode 100644 index 0000000..f433ac8 --- /dev/null +++ b/src/loc/writer.h @@ -0,0 +1,42 @@ +/* + libloc - A library to determine the location of someone on the Internet + + Copyright (C) 2017 IPFire Development Team + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. +*/ + +#ifndef LIBLOC_WRITER_H +#define LIBLOC_WRITER_H + +#include + +#include + +#include "as.h" + +struct loc_writer; + +int loc_writer_new(struct loc_ctx* ctx, struct loc_writer** writer); + +struct loc_writer* loc_writer_ref(struct loc_writer* writer); +struct loc_writer* loc_writer_unref(struct loc_writer* writer); + +const char* loc_writer_get_vendor(struct loc_writer* writer); +int loc_writer_set_vendor(struct loc_writer* writer, const char* vendor); +const char* loc_writer_get_description(struct loc_writer* writer); +int loc_writer_set_description(struct loc_writer* writer, const char* description); + +int loc_writer_add_as(struct loc_writer* writer, struct loc_as** as, uint32_t number); + +int loc_writer_write(struct loc_writer* writer, FILE* f); + +#endif diff --git a/src/stringpool.c b/src/stringpool.c index 27388cc..4a0a46b 100644 --- a/src/stringpool.c +++ b/src/stringpool.c @@ -234,8 +234,6 @@ LOC_EXPORT void loc_stringpool_dump(struct loc_stringpool* pool) { } } -#include - LOC_EXPORT int loc_stringpool_read(struct loc_stringpool* pool, FILE* f, off_t offset, size_t length) { DEBUG(pool->ctx, "Reading string pool from %zu (%zu bytes)\n", offset, length); diff --git a/src/test-as.c b/src/test-as.c index 1e1c91e..e0e1b3f 100644 --- a/src/test-as.c +++ b/src/test-as.c @@ -19,6 +19,7 @@ #include #include +#include #include "database.h" #define TEST_AS_COUNT 100 @@ -32,14 +33,15 @@ int main(int argc, char** argv) { exit(EXIT_FAILURE); // Create a database - struct loc_database* db; - err = loc_database_new(ctx, &db, 1024); + struct loc_writer* writer; + err = loc_writer_new(ctx, &writer); if (err < 0) exit(EXIT_FAILURE); char name[256]; for (unsigned int i = 1; i <= TEST_AS_COUNT; i++) { - struct loc_as* as = loc_database_add_as(db, i); + struct loc_as* as; + int r = loc_writer_add_as(writer, &as, i); sprintf(name, "Test AS%u", i); loc_as_set_name(as, name); @@ -53,14 +55,14 @@ int main(int argc, char** argv) { exit(EXIT_FAILURE); } - err = loc_database_write(db, f); + err = loc_writer_write(writer, f); if (err) { fprintf(stderr, "Could not write database: %s\n", strerror(-err)); exit(EXIT_FAILURE); } fclose(f); - loc_database_unref(db); + loc_writer_unref(writer); // And open it again from disk f = fopen("test.db", "r"); @@ -69,7 +71,8 @@ int main(int argc, char** argv) { exit(EXIT_FAILURE); } - err = loc_database_open(ctx, &db, f); + struct loc_database* db; + err = loc_database_new(ctx, &db, f); if (err) { fprintf(stderr, "Could not open database: %s\n", strerror(-err)); exit(EXIT_FAILURE); @@ -81,6 +84,13 @@ int main(int argc, char** argv) { exit(EXIT_FAILURE); } + struct loc_as* as; + err = loc_database_get_as(db, &as, 99); + if (err) { + fprintf(stderr, "Could not find AS99\n"); + exit(EXIT_FAILURE); + } + loc_database_unref(db); loc_unref(ctx); diff --git a/src/test-database.c b/src/test-database.c index 1137326..2dd0528 100644 --- a/src/test-database.c +++ b/src/test-database.c @@ -24,6 +24,7 @@ #include #include +#include #include "database.h" const char* DESCRIPTION = @@ -43,20 +44,20 @@ int main(int argc, char** argv) { exit(EXIT_FAILURE); // Create a database - struct loc_database* db; - err = loc_database_new(ctx, &db, 1024); + struct loc_writer* writer; + err = loc_writer_new(ctx, &writer); if (err < 0) exit(EXIT_FAILURE); // Set the vendor - err = loc_database_set_vendor(db, "Test Vendor"); + err = loc_writer_set_vendor(writer, "Test Vendor"); if (err) { fprintf(stderr, "Could not set vendor\n"); exit(EXIT_FAILURE); } // Retrieve vendor - const char* vendor1 = loc_database_get_vendor(db); + const char* vendor1 = loc_writer_get_vendor(writer); if (vendor1) { printf("Vendor is: %s\n", vendor1); } else { @@ -65,14 +66,14 @@ int main(int argc, char** argv) { } // Set a description - err = loc_database_set_description(db, DESCRIPTION); + err = loc_writer_set_description(writer, DESCRIPTION); if (err) { fprintf(stderr, "Could not set description\n"); exit(EXIT_FAILURE); } // Retrieve description - const char* description = loc_database_get_description(db); + const char* description = loc_writer_get_description(writer); if (description) { printf("Description is: %s\n", description); } else { @@ -86,7 +87,7 @@ int main(int argc, char** argv) { exit(EXIT_FAILURE); } - err = loc_database_write(db, f); + err = loc_writer_write(writer, f); if (err) { fprintf(stderr, "Could not write database: %s\n", strerror(err)); exit(EXIT_FAILURE); @@ -95,9 +96,6 @@ int main(int argc, char** argv) { // Close the file fclose(f); - // Close the database - //loc_database_unref(db); - // And open it again from disk f = fopen("test.db", "r"); if (!f) { @@ -105,14 +103,14 @@ int main(int argc, char** argv) { exit(EXIT_FAILURE); } - struct loc_database* db2; - err = loc_database_open(ctx, &db2, f); + struct loc_database* db; + err = loc_database_new(ctx, &db, f); if (err) { fprintf(stderr, "Could not open database: %s\n", strerror(-err)); exit(EXIT_FAILURE); } - const char* vendor2 = loc_database_get_vendor(db2); + const char* vendor2 = loc_database_get_vendor(db); if (!vendor2) { fprintf(stderr, "Could not retrieve vendor\n"); exit(EXIT_FAILURE); @@ -122,9 +120,9 @@ int main(int argc, char** argv) { } // Close the database - loc_database_unref(db2); - fclose(f); + loc_database_unref(db); + loc_writer_unref(writer); loc_unref(ctx); return EXIT_SUCCESS; diff --git a/src/writer.c b/src/writer.c new file mode 100644 index 0000000..a76c3f1 --- /dev/null +++ b/src/writer.c @@ -0,0 +1,252 @@ +/* + libloc - A library to determine the location of someone on the Internet + + Copyright (C) 2017 IPFire Development Team + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "libloc-private.h" +#include "as.h" + +struct loc_writer { + struct loc_ctx* ctx; + int refcount; + + struct loc_stringpool* pool; + off_t vendor; + off_t description; + + struct loc_as** as; + size_t as_count; +}; + +LOC_EXPORT int loc_writer_new(struct loc_ctx* ctx, struct loc_writer** writer) { + struct loc_writer* w = calloc(1, sizeof(*w)); + if (!w) + return -ENOMEM; + + w->ctx = loc_ref(ctx); + w->refcount = 1; + + int r = loc_stringpool_new(ctx, &w->pool, 1024 * 1024); + if (r) { + loc_writer_unref(w); + return r; + } + + *writer = w; + return 0; +} + +LOC_EXPORT struct loc_writer* loc_writer_ref(struct loc_writer* writer) { + writer->refcount++; + + return writer; +} + +static void loc_writer_free(struct loc_writer* writer) { + DEBUG(writer->ctx, "Releasing writer at %p\n", writer); + + // Unref all AS + for (unsigned int i = 0; i < writer->as_count; i++) { + loc_as_unref(writer->as[i]); + } + + // Unref the string pool + loc_stringpool_unref(writer->pool); + + loc_unref(writer->ctx); + free(writer); +} + +LOC_EXPORT struct loc_writer* loc_writer_unref(struct loc_writer* writer) { + if (--writer->refcount > 0) + return writer; + + loc_writer_free(writer); + + return NULL; +} + +LOC_EXPORT const char* loc_writer_get_vendor(struct loc_writer* writer) { + return loc_stringpool_get(writer->pool, writer->vendor); +} + +LOC_EXPORT int loc_writer_set_vendor(struct loc_writer* writer, const char* vendor) { + // Add the string to the string pool + off_t offset = loc_stringpool_add(writer->pool, vendor); + if (offset < 0) + return offset; + + writer->vendor = offset; + return 0; +} + +LOC_EXPORT const char* loc_writer_get_description(struct loc_writer* writer) { + return loc_stringpool_get(writer->pool, writer->description); +} + +LOC_EXPORT int loc_writer_set_description(struct loc_writer* writer, const char* description) { + // Add the string to the string pool + off_t offset = loc_stringpool_add(writer->pool, description); + if (offset < 0) + return offset; + + writer->description = offset; + return 0; +} + +static int __loc_as_cmp(const void* as1, const void* as2) { + return loc_as_cmp(*(struct loc_as**)as1, *(struct loc_as**)as2); +} + +LOC_EXPORT int loc_writer_add_as(struct loc_writer* writer, struct loc_as** as, uint32_t number) { + int r = loc_as_new(writer->ctx, writer->pool, as, number); + if (r) + return r; + + // We have a new AS to add + writer->as_count++; + + // Make space + writer->as = realloc(writer->as, sizeof(*writer->as) * writer->as_count); + if (!writer->as) + return -ENOMEM; + + // Add as last element + writer->as[writer->as_count - 1] = loc_as_ref(*as); + + // Sort everything + qsort(writer->as, writer->as_count, sizeof(*writer->as), __loc_as_cmp); + + return 0; +} + +static void make_magic(struct loc_writer* writer, struct loc_database_magic* magic) { + // Copy magic bytes + for (unsigned int i = 0; i < strlen(LOC_DATABASE_MAGIC); i++) + magic->magic[i] = LOC_DATABASE_MAGIC[i]; + + // Set version + magic->version = htons(LOC_DATABASE_VERSION); +} + +static void align_page_boundary(off_t* offset, FILE* f) { + // Move to next page boundary + while (*offset % LOC_DATABASE_PAGE_SIZE > 0) + *offset += fwrite("", 1, 1, f); +} + +static int loc_database_write_pool(struct loc_writer* writer, + struct loc_database_header_v0* header, off_t* offset, FILE* f) { + // Save the offset of the pool section + DEBUG(writer->ctx, "Pool starts at %jd bytes\n", *offset); + header->pool_offset = htonl(*offset); + + // Write the pool + size_t pool_length = loc_stringpool_write(writer->pool, f); + *offset += pool_length; + + DEBUG(writer->ctx, "Pool has a length of %zu bytes\n", pool_length); + header->pool_length = htonl(pool_length); + + return 0; +} + +static int loc_database_write_as_section(struct loc_writer* writer, + struct loc_database_header_v0* header, off_t* offset, FILE* f) { + DEBUG(writer->ctx, "AS section starts at %jd bytes\n", *offset); + header->as_offset = htonl(*offset); + + size_t as_length = 0; + + struct loc_database_as_v0 as; + for (unsigned int i = 0; i < writer->as_count; i++) { + // Convert AS into database format + loc_as_to_database_v0(writer->as[i], &as); + + // Write to disk + offset += fwrite(&as, 1, sizeof(as), f); + as_length += sizeof(as); + } + + DEBUG(writer->ctx, "AS section has a length of %zu bytes\n", as_length); + header->as_length = htonl(as_length); + + return 0; +} + +LOC_EXPORT int loc_writer_write(struct loc_writer* writer, FILE* f) { + struct loc_database_magic magic; + make_magic(writer, &magic); + + // Make the header + struct loc_database_header_v0 header; + header.vendor = htonl(writer->vendor); + header.description = htonl(writer->description); + + time_t now = time(NULL); + header.created_at = htobe64(now); + + int r; + off_t offset = 0; + + // Start writing at the beginning of the file + r = fseek(f, 0, SEEK_SET); + if (r) + return r; + + // Write the magic + offset += fwrite(&magic, 1, sizeof(magic), f); + + // Skip the space we need to write the header later + r = fseek(f, sizeof(header), SEEK_CUR); + if (r) { + DEBUG(writer->ctx, "Could not seek to position after header\n"); + return r; + } + offset += sizeof(header); + + align_page_boundary(&offset, f); + + // Write all ASes + r = loc_database_write_as_section(writer, &header, &offset, f); + if (r) + return r; + + align_page_boundary(&offset, f); + + // Write pool + r = loc_database_write_pool(writer, &header, &offset, f); + if (r) + return r; + + // Write the header + r = fseek(f, sizeof(magic), SEEK_SET); + if (r) + return r; + + offset += fwrite(&header, 1, sizeof(header), f); + + return 0; +} -- 2.39.5