/* Copyright (c) 2009, 2010 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 #include "ovsdb-data.h" #include #include #include "hash.h" #include "ovsdb-error.h" #include "json.h" #include "shash.h" #include "sort.h" static struct json * wrap_json(const char *name, struct json *wrapped) { return json_array_create_2(json_string_create(name), wrapped); } void ovsdb_atom_init_default(union ovsdb_atom *atom, enum ovsdb_atomic_type type) { switch (type) { case OVSDB_TYPE_VOID: NOT_REACHED(); case OVSDB_TYPE_INTEGER: atom->integer = 0; break; case OVSDB_TYPE_REAL: atom->real = 0.0; break; case OVSDB_TYPE_BOOLEAN: atom->boolean = false; break; case OVSDB_TYPE_STRING: atom->string = xmemdup("", 1); break; case OVSDB_TYPE_UUID: uuid_zero(&atom->uuid); break; case OVSDB_N_TYPES: default: NOT_REACHED(); } } bool ovsdb_atom_is_default(const union ovsdb_atom *atom, enum ovsdb_atomic_type type) { switch (type) { case OVSDB_TYPE_VOID: NOT_REACHED(); case OVSDB_TYPE_INTEGER: return atom->integer == 0; case OVSDB_TYPE_REAL: return atom->real == 0.0; case OVSDB_TYPE_BOOLEAN: return atom->boolean == false; case OVSDB_TYPE_STRING: return atom->string[0] == '\0'; case OVSDB_TYPE_UUID: return uuid_is_zero(&atom->uuid); case OVSDB_N_TYPES: default: NOT_REACHED(); } } void ovsdb_atom_clone(union ovsdb_atom *new, const union ovsdb_atom *old, enum ovsdb_atomic_type type) { switch (type) { case OVSDB_TYPE_VOID: NOT_REACHED(); case OVSDB_TYPE_INTEGER: new->integer = old->integer; break; case OVSDB_TYPE_REAL: new->real = old->real; break; case OVSDB_TYPE_BOOLEAN: new->boolean = old->boolean; break; case OVSDB_TYPE_STRING: new->string = xstrdup(old->string); break; case OVSDB_TYPE_UUID: new->uuid = old->uuid; break; case OVSDB_N_TYPES: default: NOT_REACHED(); } } void ovsdb_atom_swap(union ovsdb_atom *a, union ovsdb_atom *b) { union ovsdb_atom tmp = *a; *a = *b; *b = tmp; } uint32_t ovsdb_atom_hash(const union ovsdb_atom *atom, enum ovsdb_atomic_type type, uint32_t basis) { switch (type) { case OVSDB_TYPE_VOID: NOT_REACHED(); case OVSDB_TYPE_INTEGER: return hash_int(atom->integer, basis); case OVSDB_TYPE_REAL: return hash_double(atom->real, basis); case OVSDB_TYPE_BOOLEAN: return hash_boolean(atom->boolean, basis); case OVSDB_TYPE_STRING: return hash_string(atom->string, basis); case OVSDB_TYPE_UUID: return hash_int(uuid_hash(&atom->uuid), basis); case OVSDB_N_TYPES: default: NOT_REACHED(); } } int ovsdb_atom_compare_3way(const union ovsdb_atom *a, const union ovsdb_atom *b, enum ovsdb_atomic_type type) { switch (type) { case OVSDB_TYPE_VOID: NOT_REACHED(); case OVSDB_TYPE_INTEGER: return a->integer < b->integer ? -1 : a->integer > b->integer; case OVSDB_TYPE_REAL: return a->real < b->real ? -1 : a->real > b->real; case OVSDB_TYPE_BOOLEAN: return a->boolean - b->boolean; case OVSDB_TYPE_STRING: return strcmp(a->string, b->string); case OVSDB_TYPE_UUID: return uuid_compare_3way(&a->uuid, &b->uuid); case OVSDB_N_TYPES: default: NOT_REACHED(); } } static struct ovsdb_error * unwrap_json(const struct json *json, const char *name, enum json_type value_type, const struct json **value) { if (json->type != JSON_ARRAY || json->u.array.n != 2 || json->u.array.elems[0]->type != JSON_STRING || (name && strcmp(json->u.array.elems[0]->u.string, name)) || json->u.array.elems[1]->type != value_type) { return ovsdb_syntax_error(json, NULL, "expected [\"%s\", <%s>]", name, json_type_to_string(value_type)); } *value = json->u.array.elems[1]; return NULL; } static struct ovsdb_error * parse_json_pair(const struct json *json, const struct json **elem0, const struct json **elem1) { if (json->type != JSON_ARRAY || json->u.array.n != 2) { return ovsdb_syntax_error(json, NULL, "expected 2-element array"); } *elem0 = json->u.array.elems[0]; *elem1 = json->u.array.elems[1]; return NULL; } static struct ovsdb_error * ovsdb_atom_parse_uuid(struct uuid *uuid, const struct json *json, const struct ovsdb_symbol_table *symtab) WARN_UNUSED_RESULT; static struct ovsdb_error * ovsdb_atom_parse_uuid(struct uuid *uuid, const struct json *json, const struct ovsdb_symbol_table *symtab) { struct ovsdb_error *error0; const struct json *value; error0 = unwrap_json(json, "uuid", JSON_STRING, &value); if (!error0) { const char *uuid_string = json_string(value); if (!uuid_from_string(uuid, uuid_string)) { return ovsdb_syntax_error(json, NULL, "\"%s\" is not a valid UUID", uuid_string); } } else if (symtab) { struct ovsdb_error *error1; error1 = unwrap_json(json, "named-uuid", JSON_STRING, &value); if (!error1) { const char *name = json_string(value); const struct ovsdb_symbol *symbol; ovsdb_error_destroy(error0); symbol = ovsdb_symbol_table_get(symtab, name); if (symbol) { *uuid = symbol->uuid; return NULL; } else { return ovsdb_syntax_error(json, NULL, "unknown named-uuid \"%s\"", name); } } ovsdb_error_destroy(error1); } return error0; } struct ovsdb_error * ovsdb_atom_from_json(union ovsdb_atom *atom, enum ovsdb_atomic_type type, const struct json *json, const struct ovsdb_symbol_table *symtab) { switch (type) { case OVSDB_TYPE_VOID: NOT_REACHED(); case OVSDB_TYPE_INTEGER: if (json->type == JSON_INTEGER) { atom->integer = json->u.integer; return NULL; } break; case OVSDB_TYPE_REAL: if (json->type == JSON_INTEGER) { atom->real = json->u.integer; return NULL; } else if (json->type == JSON_REAL) { atom->real = json->u.real; return NULL; } break; case OVSDB_TYPE_BOOLEAN: if (json->type == JSON_TRUE) { atom->boolean = true; return NULL; } else if (json->type == JSON_FALSE) { atom->boolean = false; return NULL; } break; case OVSDB_TYPE_STRING: if (json->type == JSON_STRING) { atom->string = xstrdup(json->u.string); return NULL; } break; case OVSDB_TYPE_UUID: return ovsdb_atom_parse_uuid(&atom->uuid, json, symtab); case OVSDB_N_TYPES: default: NOT_REACHED(); } return ovsdb_syntax_error(json, NULL, "expected %s", ovsdb_atomic_type_to_string(type)); } struct json * ovsdb_atom_to_json(const union ovsdb_atom *atom, enum ovsdb_atomic_type type) { switch (type) { case OVSDB_TYPE_VOID: NOT_REACHED(); case OVSDB_TYPE_INTEGER: return json_integer_create(atom->integer); case OVSDB_TYPE_REAL: return json_real_create(atom->real); case OVSDB_TYPE_BOOLEAN: return json_boolean_create(atom->boolean); case OVSDB_TYPE_STRING: return json_string_create(atom->string); case OVSDB_TYPE_UUID: return wrap_json("uuid", json_string_create_nocopy( xasprintf(UUID_FMT, UUID_ARGS(&atom->uuid)))); case OVSDB_N_TYPES: default: NOT_REACHED(); } } static union ovsdb_atom * alloc_default_atoms(enum ovsdb_atomic_type type, size_t n) { if (type != OVSDB_TYPE_VOID && n) { union ovsdb_atom *atoms; unsigned int i; atoms = xmalloc(n * sizeof *atoms); for (i = 0; i < n; i++) { ovsdb_atom_init_default(&atoms[i], type); } return atoms; } else { /* Avoid wasting memory in the n == 0 case, because xmalloc(0) is * treated as xmalloc(1). */ return NULL; } } void ovsdb_datum_init_default(struct ovsdb_datum *datum, const struct ovsdb_type *type) { datum->n = type->n_min; datum->keys = alloc_default_atoms(type->key_type, datum->n); datum->values = alloc_default_atoms(type->value_type, datum->n); } bool ovsdb_datum_is_default(const struct ovsdb_datum *datum, const struct ovsdb_type *type) { size_t i; if (datum->n != type->n_min) { return false; } for (i = 0; i < datum->n; i++) { if (!ovsdb_atom_is_default(&datum->keys[i], type->key_type)) { return false; } if (type->value_type != OVSDB_TYPE_VOID && !ovsdb_atom_is_default(&datum->values[i], type->value_type)) { return false; } } return true; } static union ovsdb_atom * clone_atoms(const union ovsdb_atom *old, enum ovsdb_atomic_type type, size_t n) { if (type != OVSDB_TYPE_VOID && n) { union ovsdb_atom *new; unsigned int i; new = xmalloc(n * sizeof *new); for (i = 0; i < n; i++) { ovsdb_atom_clone(&new[i], &old[i], type); } return new; } else { /* Avoid wasting memory in the n == 0 case, because xmalloc(0) is * treated as xmalloc(1). */ return NULL; } } void ovsdb_datum_clone(struct ovsdb_datum *new, const struct ovsdb_datum *old, const struct ovsdb_type *type) { unsigned int n = old->n; new->n = n; new->keys = clone_atoms(old->keys, type->key_type, n); new->values = clone_atoms(old->values, type->value_type, n); } static void free_data(enum ovsdb_atomic_type type, union ovsdb_atom *atoms, size_t n_atoms) { if (ovsdb_atom_needs_destruction(type)) { unsigned int i; for (i = 0; i < n_atoms; i++) { ovsdb_atom_destroy(&atoms[i], type); } } free(atoms); } void ovsdb_datum_destroy(struct ovsdb_datum *datum, const struct ovsdb_type *type) { free_data(type->key_type, datum->keys, datum->n); free_data(type->value_type, datum->values, datum->n); } void ovsdb_datum_swap(struct ovsdb_datum *a, struct ovsdb_datum *b) { struct ovsdb_datum tmp = *a; *a = *b; *b = tmp; } struct ovsdb_datum_sort_cbdata { const struct ovsdb_type *type; struct ovsdb_datum *datum; }; static int ovsdb_datum_sort_compare_cb(size_t a, size_t b, void *cbdata_) { struct ovsdb_datum_sort_cbdata *cbdata = cbdata_; return ovsdb_atom_compare_3way(&cbdata->datum->keys[a], &cbdata->datum->keys[b], cbdata->type->key_type); } static void ovsdb_datum_sort_swap_cb(size_t a, size_t b, void *cbdata_) { struct ovsdb_datum_sort_cbdata *cbdata = cbdata_; ovsdb_atom_swap(&cbdata->datum->keys[a], &cbdata->datum->keys[b]); if (cbdata->type->value_type != OVSDB_TYPE_VOID) { ovsdb_atom_swap(&cbdata->datum->values[a], &cbdata->datum->values[b]); } } struct ovsdb_error * ovsdb_datum_sort(struct ovsdb_datum *datum, const struct ovsdb_type *type) { if (datum->n < 2) { return NULL; } else { struct ovsdb_datum_sort_cbdata cbdata; size_t i; cbdata.type = type; cbdata.datum = datum; sort(datum->n, ovsdb_datum_sort_compare_cb, ovsdb_datum_sort_swap_cb, &cbdata); for (i = 0; i < datum->n - 1; i++) { if (ovsdb_atom_equals(&datum->keys[i], &datum->keys[i + 1], type->key_type)) { if (ovsdb_type_is_map(type)) { return ovsdb_error(NULL, "map contains duplicate key"); } else { return ovsdb_error(NULL, "set contains duplicate"); } } } return NULL; } } struct ovsdb_error * ovsdb_datum_from_json(struct ovsdb_datum *datum, const struct ovsdb_type *type, const struct json *json, const struct ovsdb_symbol_table *symtab) { struct ovsdb_error *error; if (ovsdb_type_is_scalar(type)) { datum->n = 1; datum->keys = xmalloc(sizeof *datum->keys); datum->values = NULL; error = ovsdb_atom_from_json(&datum->keys[0], type->key_type, json, symtab); if (error) { free(datum->keys); } return error; } else { bool is_map = ovsdb_type_is_map(type); const char *class = is_map ? "map" : "set"; const struct json *inner; unsigned int i; size_t n; assert(is_map || ovsdb_type_is_set(type)); error = unwrap_json(json, class, JSON_ARRAY, &inner); if (error) { return error; } n = inner->u.array.n; if (n < type->n_min || n > type->n_max) { return ovsdb_syntax_error(json, NULL, "%s must have %u to " "%u members but %zu are present", class, type->n_min, type->n_max, n); } datum->n = 0; datum->keys = xmalloc(n * sizeof *datum->keys); datum->values = is_map ? xmalloc(n * sizeof *datum->values) : NULL; for (i = 0; i < n; i++) { const struct json *element = inner->u.array.elems[i]; const struct json *key = NULL; const struct json *value = NULL; if (!is_map) { key = element; } else { error = parse_json_pair(element, &key, &value); if (error) { goto error; } } error = ovsdb_atom_from_json(&datum->keys[i], type->key_type, key, symtab); if (error) { goto error; } if (is_map) { error = ovsdb_atom_from_json(&datum->values[i], type->value_type, value, symtab); if (error) { ovsdb_atom_destroy(&datum->keys[i], type->key_type); goto error; } } datum->n++; } error = ovsdb_datum_sort(datum, type); if (error) { goto error; } return NULL; error: ovsdb_datum_destroy(datum, type); return error; } } struct json * ovsdb_datum_to_json(const struct ovsdb_datum *datum, const struct ovsdb_type *type) { /* These tests somewhat tolerate a 'datum' that does not exactly match * 'type', in particular a datum with 'n' not in the allowed range. */ if (datum->n == 1 && ovsdb_type_is_scalar(type)) { return ovsdb_atom_to_json(&datum->keys[0], type->key_type); } else if (type->value_type == OVSDB_TYPE_VOID) { struct json **elems; size_t i; elems = xmalloc(datum->n * sizeof *elems); for (i = 0; i < datum->n; i++) { elems[i] = ovsdb_atom_to_json(&datum->keys[i], type->key_type); } return wrap_json("set", json_array_create(elems, datum->n)); } else { struct json **elems; size_t i; elems = xmalloc(datum->n * sizeof *elems); for (i = 0; i < datum->n; i++) { elems[i] = json_array_create_2( ovsdb_atom_to_json(&datum->keys[i], type->key_type), ovsdb_atom_to_json(&datum->values[i], type->value_type)); } return wrap_json("map", json_array_create(elems, datum->n)); } } static uint32_t hash_atoms(enum ovsdb_atomic_type type, const union ovsdb_atom *atoms, unsigned int n, uint32_t basis) { if (type != OVSDB_TYPE_VOID) { unsigned int i; for (i = 0; i < n; i++) { basis = ovsdb_atom_hash(&atoms[i], type, basis); } } return basis; } uint32_t ovsdb_datum_hash(const struct ovsdb_datum *datum, const struct ovsdb_type *type, uint32_t basis) { basis = hash_atoms(type->key_type, datum->keys, datum->n, basis); basis ^= (type->key_type << 24) | (type->value_type << 16) | datum->n; basis = hash_atoms(type->value_type, datum->values, datum->n, basis); return basis; } static int atom_arrays_compare_3way(const union ovsdb_atom *a, const union ovsdb_atom *b, enum ovsdb_atomic_type type, size_t n) { unsigned int i; for (i = 0; i < n; i++) { int cmp = ovsdb_atom_compare_3way(&a[i], &b[i], type); if (cmp) { return cmp; } } return 0; } bool ovsdb_datum_equals(const struct ovsdb_datum *a, const struct ovsdb_datum *b, const struct ovsdb_type *type) { return !ovsdb_datum_compare_3way(a, b, type); } int ovsdb_datum_compare_3way(const struct ovsdb_datum *a, const struct ovsdb_datum *b, const struct ovsdb_type *type) { int cmp; if (a->n != b->n) { return a->n < b->n ? -1 : 1; } cmp = atom_arrays_compare_3way(a->keys, b->keys, type->key_type, a->n); if (cmp) { return cmp; } return (type->value_type == OVSDB_TYPE_VOID ? 0 : atom_arrays_compare_3way(a->values, b->values, type->value_type, a->n)); } /* If atom 'i' in 'a' is also in 'b', returns its index in 'b', otherwise * UINT_MAX. 'type' must be the type of 'a' and 'b', except that * type->value_type may be set to OVSDB_TYPE_VOID to compare keys but not * values. */ static unsigned int ovsdb_datum_find(const struct ovsdb_datum *a, int i, const struct ovsdb_datum *b, const struct ovsdb_type *type) { int low = 0; int high = b->n; while (low < high) { int j = (low + high) / 2; int cmp = ovsdb_atom_compare_3way(&a->keys[i], &b->keys[j], type->key_type); if (cmp < 0) { high = j; } else if (cmp > 0) { low = j + 1; } else { bool eq_value = (type->value_type == OVSDB_TYPE_VOID || ovsdb_atom_equals(&a->values[i], &b->values[j], type->value_type)); return eq_value ? j : UINT_MAX; } } return UINT_MAX; } /* Returns true if every element in 'a' is also in 'b', false otherwise. */ bool ovsdb_datum_includes_all(const struct ovsdb_datum *a, const struct ovsdb_datum *b, const struct ovsdb_type *type) { size_t i; for (i = 0; i < a->n; i++) { if (ovsdb_datum_find(a, i, b, type) == UINT_MAX) { return false; } } return true; } /* Returns true if no element in 'a' is also in 'b', false otherwise. */ bool ovsdb_datum_excludes_all(const struct ovsdb_datum *a, const struct ovsdb_datum *b, const struct ovsdb_type *type) { size_t i; for (i = 0; i < a->n; i++) { if (ovsdb_datum_find(a, i, b, type) != UINT_MAX) { return false; } } return true; } static void ovsdb_datum_reallocate(struct ovsdb_datum *a, const struct ovsdb_type *type, unsigned int capacity) { a->keys = xrealloc(a->keys, capacity * sizeof *a->keys); if (type->value_type != OVSDB_TYPE_VOID) { a->values = xrealloc(a->values, capacity * sizeof *a->values); } } static void ovsdb_datum_remove(struct ovsdb_datum *a, size_t i, const struct ovsdb_type *type) { ovsdb_atom_destroy(&a->keys[i], type->key_type); a->keys[i] = a->keys[a->n - 1]; if (type->value_type != OVSDB_TYPE_VOID) { ovsdb_atom_destroy(&a->values[i], type->value_type); a->values[i] = a->values[a->n - 1]; } a->n--; } void ovsdb_datum_union(struct ovsdb_datum *a, const struct ovsdb_datum *b, const struct ovsdb_type *type) { struct ovsdb_type type_without_value; unsigned int n; size_t i; type_without_value = *type; type_without_value.value_type = OVSDB_TYPE_VOID; n = a->n; for (i = 0; i < b->n; i++) { if (ovsdb_datum_find(b, i, a, &type_without_value) == UINT_MAX) { if (n == a->n) { ovsdb_datum_reallocate(a, type, a->n + (b->n - i)); } ovsdb_atom_clone(&a->keys[n], &b->keys[i], type->key_type); if (type->value_type != OVSDB_TYPE_VOID) { ovsdb_atom_clone(&a->values[n], &b->values[i], type->value_type); } n++; } } if (n != a->n) { struct ovsdb_error *error; a->n = n; error = ovsdb_datum_sort(a, type); assert(!error); } } void ovsdb_datum_subtract(struct ovsdb_datum *a, const struct ovsdb_type *a_type, const struct ovsdb_datum *b, const struct ovsdb_type *b_type) { bool changed = false; size_t i; assert(a_type->key_type == b_type->key_type); assert(a_type->value_type == b_type->value_type || b_type->value_type == OVSDB_TYPE_VOID); /* XXX The big-O of this could easily be improved. */ for (i = 0; i < a->n; ) { unsigned int idx = ovsdb_datum_find(a, i, b, b_type); if (idx != UINT_MAX) { changed = true; ovsdb_datum_remove(a, i, a_type); } else { i++; } } if (changed) { struct ovsdb_error *error = ovsdb_datum_sort(a, a_type); assert(!error); } } struct ovsdb_symbol_table { struct shash sh; }; struct ovsdb_symbol_table * ovsdb_symbol_table_create(void) { struct ovsdb_symbol_table *symtab = xmalloc(sizeof *symtab); shash_init(&symtab->sh); return symtab; } void ovsdb_symbol_table_destroy(struct ovsdb_symbol_table *symtab) { if (symtab) { struct shash_node *node, *next; SHASH_FOR_EACH_SAFE (node, next, &symtab->sh) { struct ovsdb_symbol *symbol = node->data; free(symbol); shash_delete(&symtab->sh, node); } shash_destroy(&symtab->sh); free(symtab); } } struct ovsdb_symbol * ovsdb_symbol_table_get(const struct ovsdb_symbol_table *symtab, const char *name) { return shash_find_data(&symtab->sh, name); } void ovsdb_symbol_table_put(struct ovsdb_symbol_table *symtab, const char *name, const struct uuid *uuid, bool used) { struct ovsdb_symbol *symbol; assert(!ovsdb_symbol_table_get(symtab, name)); symbol = xmalloc(sizeof *symbol); symbol->uuid = *uuid; symbol->used = used; shash_add(&symtab->sh, name, symbol); }