/* * Copyright 2012 Canonical Ltd. * * This file is part of u1db. * * u1db is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 * as published by the Free Software Foundation. * * u1db 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. * * You should have received a copy of the GNU Lesser General Public License * along with u1db. If not, see . */ #include #include #include "u1db/u1db.h" #include "u1db/compat.h" #include "u1db/u1db_vectorclock.h" #ifdef _MSC_VER // Windows doesn't have strndup, so we fake one static char * _win32_strndup(const char *s, size_t n) { char *out; out = (char*)calloc(1, n+1); if (out == NULL) { return NULL; } memcpy(out, s, n); out[n] = '\0'; return out; } #endif //_MSC_VER struct inserts_needed { struct inserts_needed *next; int other_offset; int clock_offset; }; static void free_inserts(struct inserts_needed **chain) { struct inserts_needed *cur, *next; if (chain == NULL || *chain == NULL) { return; } cur = *chain; while (cur != NULL) { next = cur->next; free(cur); cur = next; } *chain = NULL; } void u1db__free_vectorclock(u1db_vectorclock **clock) { int i; char *replica_uid; if (clock == NULL || *clock == NULL) { return; } if ((*clock)->items != NULL) { for (i = 0; i < (*clock)->num_items; i++) { replica_uid = (*clock)->items[i].replica_uid; if (replica_uid != NULL) { free(replica_uid); } } } free((*clock)->items); free(*clock); *clock = NULL; } // Add this replica_uid, generation to the vectorclock entries. // offset is the current location in vectorclock which is empty. // Since vectorclocks are generally sorted, it will usually be the location // where we want to insert the new value. // This is roughly insertion sort, though we don't bisect existing values. We // could, but it adds a fair amount of complexity, and 99% of the time the // clocks are already sorted. // If the vector is reverse sorted, this becomes quadratic, though. static int insert_replica_and_generation(u1db_vectorclock *vc, int offset, char *replica_uid, int generation) { int cmp; for (; offset >= 0; offset--) { if (offset == 0) { // We know that the item comes here cmp = -1; } else { cmp = strcmp(vc->items[offset-1].replica_uid, replica_uid); } if (cmp < 0) { // replica_uid comes after the stored value, this is where we want // to insert it vc->items[offset].replica_uid = replica_uid; vc->items[offset].generation = generation; return 1; // Found the spot } else if (cmp == 0) { // This is invalid, the vector has the same value twice... return 0; } else { // replica_uid comes before the stored value, so move the stored // value and try again. vc->items[offset].replica_uid = vc->items[offset-1].replica_uid; vc->items[offset].generation = vc->items[offset-1].generation; vc->items[offset-1].replica_uid = NULL; } } return 0; } u1db_vectorclock * u1db__vectorclock_from_str(const char *s) { u1db_vectorclock *res = NULL; int i, generation; const char *cur, *colon, *pipe, *end; char *replica_uid; char *last_digit; if (s == NULL) { s = ""; } end = s + strlen(s); res = (u1db_vectorclock *)calloc(1, sizeof(u1db_vectorclock)); if (res == NULL) { return NULL; } if ((end - s) == 0) { // Empty string, no items res->items = NULL; res->num_items = 0; return res; } // Count the number of '|' symbols, and allocate buffers for it res->num_items = 1; for (cur = s; cur < end; cur++) { if (*cur == '|') { res->num_items += 1; } } res->items = (u1db_vectorclock_item*)calloc(res->num_items, sizeof(u1db_vectorclock_item)); // Now walk through it again, looking for the machine:count pairs cur = s; for (i = 0; i < res->num_items; i++) { if (cur >= end) { // Ran off the end. Most likely indicates a trailing | that isn't // followed by content. u1db__free_vectorclock(&res); return NULL; } pipe = memchr(cur, '|', end-cur); if (pipe == NULL) { // We assume the rest of the string is what we want pipe = end; } colon = memchr(cur, ':', pipe-cur); if (colon == NULL || (colon - cur) == 0 || (pipe - colon) == 1) { // Either, no colon, no replica_uid, or no digits u1db__free_vectorclock(&res); return NULL; } replica_uid = strndup(cur, colon-cur); if (replica_uid == NULL) { u1db__free_vectorclock(&res); return NULL; } generation = strtol(colon+1, &last_digit, 10); if (last_digit != pipe) { free(replica_uid); u1db__free_vectorclock(&res); return NULL; } if (!insert_replica_and_generation(res, i, replica_uid, generation)) { free(replica_uid); u1db__free_vectorclock(&res); return NULL; } cur = pipe + 1; } return res; } int u1db__vectorclock_increment(u1db_vectorclock *clock, const char *replica_uid) { int i, cmp; u1db_vectorclock_item *new_buf; if (clock == NULL || replica_uid == NULL) { return U1DB_INVALID_PARAMETER; } for (i = 0; i < clock->num_items; ++i) { cmp = strcmp(replica_uid, clock->items[i].replica_uid); if (cmp == 0) { // We found the entry clock->items[i].generation++; return U1DB_OK; } else if (cmp < 0) { // replica_uid would come right before items[i] if it was present. // So we break, and insert it here break; } } // If we got here, then 'i' points at the location where we want to insert // a new entry. new_buf = (u1db_vectorclock_item*)realloc(clock->items, sizeof(u1db_vectorclock_item) * (clock->num_items + 1)); if (new_buf == NULL) { return U1DB_NOMEM; } clock->items = new_buf; clock->num_items++; memmove(&clock->items[i + 1], &clock->items[i], sizeof(u1db_vectorclock_item) * (clock->num_items - i - 1)); clock->items[i].replica_uid = strdup(replica_uid); clock->items[i].generation = 1; return U1DB_OK; } int u1db__vectorclock_maximize(u1db_vectorclock *clock, u1db_vectorclock *other) { int ci, oi, cmp; int num_inserts, move_to_end, num_to_move, item_size; struct inserts_needed *needed = NULL, *next = NULL; u1db_vectorclock_item *new_buf; if (clock == NULL || other == NULL) { return U1DB_INVALID_PARAMETER; } num_inserts = ci = oi = 0; // First pass, walk both lists, determining what items need to be inserted while (oi < other->num_items) { if (ci >= clock->num_items) { // We have already walked all of clock, so everything in other // gets appended next = (struct inserts_needed *)calloc(1, sizeof(struct inserts_needed)); next->next = needed; needed = next; // We need the final offset, after everything has been moved. next->clock_offset = ci + num_inserts; next->other_offset = oi; num_inserts++; oi++; continue; } cmp = strcmp(clock->items[ci].replica_uid, other->items[oi].replica_uid); if (cmp == 0) { // These machines are the same, take the 'max' value: if (clock->items[ci].generation < other->items[oi].generation) { clock->items[ci].generation = other->items[oi].generation; } ci++; oi++; continue; } else if (cmp < 0) { // clock[ci] comes before other[oi], so step clock ci++; } else { // oi comes before ci, so it needs to be inserted next = (struct inserts_needed *)calloc(1, sizeof(struct inserts_needed)); next->next = needed; needed = next; next->clock_offset = ci + num_inserts; next->other_offset = oi; num_inserts++; oi++; } } if (num_inserts == 0) { // Nothing more to do return U1DB_OK; } // Now we need to expand the clock array, and start shuffling the data // around item_size = sizeof(u1db_vectorclock_item); new_buf = (u1db_vectorclock_item *)realloc(clock->items, item_size * (clock->num_items + num_inserts)); if (new_buf == NULL) { free_inserts(&needed); return U1DB_NOMEM; } clock->items = new_buf; clock->num_items += num_inserts; next = needed; move_to_end = clock->num_items - 1; // Imagine we have 3 inserts, into an initial list 5-wide. // a c e g h, inserting b f i // Final length is 8, // i should have ci=7, num_inserts = 3 // f should have ci=4, num_inserts = 2 // b should have ci=1, num_inserts = 1 // First step, we want to move 0 items, and just insert i at the end (7) // Second step, we want to move g & h from 3 4, to be at 5 6, and then // insert f into 4 // Third step, we move c & e from 1 2 to 2 3 and insert b at 1 while (next != NULL) { num_to_move = move_to_end - next->clock_offset; if (num_to_move > 0) { memmove(&clock->items[next->clock_offset + 1], &clock->items[next->clock_offset - num_inserts + 1], item_size * num_to_move); } clock->items[next->clock_offset].replica_uid = strdup( other->items[next->other_offset].replica_uid); clock->items[next->clock_offset].generation = other->items[next->other_offset].generation; num_inserts--; move_to_end = next->clock_offset - 1; next = next->next; } free_inserts(&needed); return U1DB_OK; } int u1db__vectorclock_as_str(u1db_vectorclock *clock, char **result) { int buf_size, i, val, count; char *cur, *fmt; // Quick pass, to determine the buffer size: buf_size = 1; // Trailing null if (result == NULL) { return U1DB_INVALID_PARAMETER; } if (clock == NULL) { // Allocate space for the empty string cur = (char *)calloc(1, 1); *result = cur; return U1DB_OK; } for (i = 0; i < clock->num_items; i++) { buf_size += strlen(clock->items[i].replica_uid); buf_size += 2; // ':' and possible '|' val = clock->items[i].generation; do { // divide by 8 is close to divide by 10, to get the number of // binary digits we will need to represent the decimal form val >>= 3; buf_size++; } while (val > 0); } cur = (char *)calloc(buf_size, 1); *result = cur; for (i = 0; i < clock->num_items; i++) { if (i == 0) { fmt = "%s:%d"; } else { fmt = "|%s:%d"; } count = snprintf(cur, buf_size, fmt, clock->items[i].replica_uid, clock->items[i].generation); cur += count; buf_size -= count; } return U1DB_OK; } int u1db__vectorclock_is_newer(u1db_vectorclock *maybe_newer, u1db_vectorclock *other) { int ci, oi, cmp, is_newer, n_generation, o_generation; if (maybe_newer == NULL || maybe_newer->num_items == 0) { // NULL is never newer return 0; } if (other == NULL || other->num_items == 0) { // This is not NULL, so it should be newer, we may need to check if // self is the empty string, though. return 1; } ci = oi = 0; is_newer = 0; // First pass, walk both lists, determining what items need to be inserted while (oi < other->num_items && ci < maybe_newer->num_items) { cmp = strcmp(maybe_newer->items[ci].replica_uid, other->items[oi].replica_uid); if (cmp == 0) { // Both clocks have the same machine, see if one is newer n_generation = maybe_newer->items[ci].generation; o_generation = other->items[oi].generation; if (n_generation < o_generation) { // At least one entry in other is newer than this return 0; } else if (n_generation > o_generation) { // If we have no conflicts, this is strictly newer is_newer = 1; } ci++; oi++; continue; } else if (cmp < 0) { // maybe_newer has an entry that other doesn't have, which would // make it newer is_newer = 1; ci++; } else { // other has an entry that maybe_newer doesn't have, so we must // not be strictly newer return 0; } } if (oi == other->num_items && ci < maybe_newer->num_items) { // ci has an entry that other doesn't have, it is newer is_newer = 1; } if (oi < other->num_items) { // We didn't walk all of other, which means it has an entry which ci // doesn't have, and thus maybe_newer is not strictly newer return 0; } return is_newer; }