+ OVS_NOT_REACHED();
+ }
+}
+
+static char *
+ovsdb_atom_from_string__(union ovsdb_atom *atom,
+ const struct ovsdb_base_type *base, const char *s,
+ struct ovsdb_symbol_table *symtab)
+{
+ enum ovsdb_atomic_type type = base->type;
+
+ switch (type) {
+ case OVSDB_TYPE_VOID:
+ OVS_NOT_REACHED();
+
+ case OVSDB_TYPE_INTEGER: {
+ long long int integer;
+ if (!str_to_llong(s, 10, &integer)) {
+ return xasprintf("\"%s\" is not a valid integer", s);
+ }
+ atom->integer = integer;
+ }
+ break;
+
+ case OVSDB_TYPE_REAL:
+ if (!str_to_double(s, &atom->real)) {
+ return xasprintf("\"%s\" is not a valid real number", s);
+ }
+ /* Our JSON input routines map negative zero to zero, so do that here
+ * too for consistency. */
+ if (atom->real == 0.0) {
+ atom->real = 0.0;
+ }
+ break;
+
+ case OVSDB_TYPE_BOOLEAN:
+ if (!strcmp(s, "true") || !strcmp(s, "yes") || !strcmp(s, "on")
+ || !strcmp(s, "1")) {
+ atom->boolean = true;
+ } else if (!strcmp(s, "false") || !strcmp(s, "no") || !strcmp(s, "off")
+ || !strcmp(s, "0")) {
+ atom->boolean = false;
+ } else {
+ return xasprintf("\"%s\" is not a valid boolean "
+ "(use \"true\" or \"false\")", s);
+ }
+ break;
+
+ case OVSDB_TYPE_STRING:
+ if (*s == '\0') {
+ return xstrdup("An empty string is not valid as input; "
+ "use \"\" to represent the empty string");
+ } else if (*s == '"') {
+ size_t s_len = strlen(s);
+
+ if (s_len < 2 || s[s_len - 1] != '"') {
+ return xasprintf("%s: missing quote at end of "
+ "quoted string", s);
+ } else if (!json_string_unescape(s + 1, s_len - 2,
+ &atom->string)) {
+ char *error = xasprintf("%s: %s", s, atom->string);
+ free(atom->string);
+ return error;
+ }
+ } else {
+ atom->string = xstrdup(s);
+ }
+ break;
+
+ case OVSDB_TYPE_UUID:
+ if (*s == '@') {
+ struct ovsdb_symbol *symbol = ovsdb_symbol_table_insert(symtab, s);
+ atom->uuid = symbol->uuid;
+ ovsdb_symbol_referenced(symbol, base);
+ } else if (!uuid_from_string(&atom->uuid, s)) {
+ return xasprintf("\"%s\" is not a valid UUID", s);
+ }
+ break;
+
+ case OVSDB_N_TYPES:
+ default:
+ OVS_NOT_REACHED();
+ }
+
+ return NULL;
+}
+
+/* Initializes 'atom' to a value of type 'base' parsed from 's', which takes
+ * one of the following forms:
+ *
+ * - OVSDB_TYPE_INTEGER: A decimal integer optionally preceded by a sign.
+ *
+ * - OVSDB_TYPE_REAL: A floating-point number in the format accepted by
+ * strtod().
+ *
+ * - OVSDB_TYPE_BOOLEAN: "true", "yes", "on", "1" for true, or "false",
+ * "no", "off", or "0" for false.
+ *
+ * - OVSDB_TYPE_STRING: A JSON string if it begins with a quote, otherwise
+ * an arbitrary string.
+ *
+ * - OVSDB_TYPE_UUID: A UUID in RFC 4122 format. If 'symtab' is nonnull,
+ * then an identifier beginning with '@' is also acceptable. If the
+ * named identifier is already in 'symtab', then the associated UUID is
+ * used; otherwise, a new, random UUID is used and added to the symbol
+ * table. If 'base' is a reference and a symbol is parsed, then the
+ * symbol's 'strong_ref' or 'weak_ref' member is set to true, as
+ * appropriate.
+ *
+ * Returns a null pointer if successful, otherwise an error message describing
+ * the problem. On failure, the contents of 'atom' are indeterminate. The
+ * caller is responsible for freeing the atom or the error.
+ */
+char *
+ovsdb_atom_from_string(union ovsdb_atom *atom,
+ const struct ovsdb_base_type *base, const char *s,
+ struct ovsdb_symbol_table *symtab)
+{
+ struct ovsdb_error *error;
+ char *msg;
+
+ msg = ovsdb_atom_from_string__(atom, base, s, symtab);
+ if (msg) {
+ return msg;
+ }
+
+ error = ovsdb_atom_check_constraints(atom, base);
+ if (error) {
+ ovsdb_atom_destroy(atom, base->type);
+ msg = ovsdb_error_to_string(error);
+ ovsdb_error_destroy(error);
+ }
+ return msg;
+}
+
+static bool
+string_needs_quotes(const char *s)
+{
+ const char *p = s;
+ unsigned char c;
+
+ c = *p++;
+ if (!isalpha(c) && c != '_') {
+ return true;
+ }
+
+ while ((c = *p++) != '\0') {
+ if (!isalpha(c) && c != '_' && c != '-' && c != '.') {
+ return true;
+ }
+ }
+
+ if (!strcmp(s, "true") || !strcmp(s, "false")) {
+ return true;
+ }
+
+ return false;
+}
+
+/* Appends 'atom' (which has the given 'type') to 'out', in a format acceptable
+ * to ovsdb_atom_from_string(). */
+void
+ovsdb_atom_to_string(const union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+ struct ds *out)
+{
+ switch (type) {
+ case OVSDB_TYPE_VOID:
+ OVS_NOT_REACHED();
+
+ case OVSDB_TYPE_INTEGER:
+ ds_put_format(out, "%"PRId64, atom->integer);
+ break;
+
+ case OVSDB_TYPE_REAL:
+ ds_put_format(out, "%.*g", DBL_DIG, atom->real);
+ break;
+
+ case OVSDB_TYPE_BOOLEAN:
+ ds_put_cstr(out, atom->boolean ? "true" : "false");
+ break;
+
+ case OVSDB_TYPE_STRING:
+ if (string_needs_quotes(atom->string)) {
+ struct json json;
+
+ json.type = JSON_STRING;
+ json.u.string = atom->string;
+ json_to_ds(&json, 0, out);
+ } else {
+ ds_put_cstr(out, atom->string);
+ }
+ break;
+
+ case OVSDB_TYPE_UUID:
+ ds_put_format(out, UUID_FMT, UUID_ARGS(&atom->uuid));
+ break;
+
+ case OVSDB_N_TYPES:
+ default:
+ OVS_NOT_REACHED();
+ }
+}
+
+/* Appends 'atom' (which has the given 'type') to 'out', in a bare string
+ * format that cannot be parsed uniformly back into a datum but is easier for
+ * shell scripts, etc., to deal with. */
+void
+ovsdb_atom_to_bare(const union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+ struct ds *out)
+{
+ if (type == OVSDB_TYPE_STRING) {
+ ds_put_cstr(out, atom->string);
+ } else {
+ ovsdb_atom_to_string(atom, type, out);
+ }
+}
+
+static struct ovsdb_error *
+check_string_constraints(const char *s,
+ const struct ovsdb_string_constraints *c)
+{
+ size_t n_chars;
+ char *msg;
+
+ msg = utf8_validate(s, &n_chars);
+ if (msg) {
+ struct ovsdb_error *error;
+
+ error = ovsdb_error("constraint violation",
+ "not a valid UTF-8 string: %s", msg);
+ free(msg);
+ return error;
+ }
+
+ if (n_chars < c->minLen) {
+ return ovsdb_error(
+ "constraint violation",
+ "\"%s\" length %"PRIuSIZE" is less than minimum allowed "
+ "length %u", s, n_chars, c->minLen);
+ } else if (n_chars > c->maxLen) {
+ return ovsdb_error(
+ "constraint violation",
+ "\"%s\" length %"PRIuSIZE" is greater than maximum allowed "
+ "length %u", s, n_chars, c->maxLen);
+ }
+
+ return NULL;
+}
+
+/* Checks whether 'atom' meets the constraints (if any) defined in 'base'.
+ * (base->type must specify 'atom''s type.) Returns a null pointer if the
+ * constraints are met, otherwise an error that explains the violation.
+ *
+ * Checking UUID constraints is deferred to transaction commit time, so this
+ * function does nothing for UUID constraints. */
+struct ovsdb_error *
+ovsdb_atom_check_constraints(const union ovsdb_atom *atom,
+ const struct ovsdb_base_type *base)
+{
+ if (base->enum_
+ && ovsdb_datum_find_key(base->enum_, atom, base->type) == UINT_MAX) {
+ struct ovsdb_error *error;
+ struct ds actual = DS_EMPTY_INITIALIZER;
+ struct ds valid = DS_EMPTY_INITIALIZER;
+
+ ovsdb_atom_to_string(atom, base->type, &actual);
+ ovsdb_datum_to_string(base->enum_,
+ ovsdb_base_type_get_enum_type(base->type),
+ &valid);
+ error = ovsdb_error("constraint violation",
+ "%s is not one of the allowed values (%s)",
+ ds_cstr(&actual), ds_cstr(&valid));
+ ds_destroy(&actual);
+ ds_destroy(&valid);
+
+ return error;
+ }
+
+ switch (base->type) {
+ case OVSDB_TYPE_VOID:
+ OVS_NOT_REACHED();
+
+ case OVSDB_TYPE_INTEGER:
+ if (atom->integer >= base->u.integer.min
+ && atom->integer <= base->u.integer.max) {
+ return NULL;
+ } else if (base->u.integer.min != INT64_MIN) {
+ if (base->u.integer.max != INT64_MAX) {
+ return ovsdb_error("constraint violation",
+ "%"PRId64" is not in the valid range "
+ "%"PRId64" to %"PRId64" (inclusive)",
+ atom->integer,
+ base->u.integer.min, base->u.integer.max);
+ } else {
+ return ovsdb_error("constraint violation",
+ "%"PRId64" is less than minimum allowed "
+ "value %"PRId64,
+ atom->integer, base->u.integer.min);
+ }
+ } else {
+ return ovsdb_error("constraint violation",
+ "%"PRId64" is greater than maximum allowed "
+ "value %"PRId64,
+ atom->integer, base->u.integer.max);
+ }
+ OVS_NOT_REACHED();
+
+ case OVSDB_TYPE_REAL:
+ if (atom->real >= base->u.real.min && atom->real <= base->u.real.max) {
+ return NULL;
+ } else if (base->u.real.min != -DBL_MAX) {
+ if (base->u.real.max != DBL_MAX) {
+ return ovsdb_error("constraint violation",
+ "%.*g is not in the valid range "
+ "%.*g to %.*g (inclusive)",
+ DBL_DIG, atom->real,
+ DBL_DIG, base->u.real.min,
+ DBL_DIG, base->u.real.max);
+ } else {
+ return ovsdb_error("constraint violation",
+ "%.*g is less than minimum allowed "
+ "value %.*g",
+ DBL_DIG, atom->real,
+ DBL_DIG, base->u.real.min);
+ }
+ } else {
+ return ovsdb_error("constraint violation",
+ "%.*g is greater than maximum allowed "
+ "value %.*g",
+ DBL_DIG, atom->real,
+ DBL_DIG, base->u.real.max);
+ }
+ OVS_NOT_REACHED();
+
+ case OVSDB_TYPE_BOOLEAN:
+ return NULL;
+
+ case OVSDB_TYPE_STRING:
+ return check_string_constraints(atom->string, &base->u.string);
+
+ case OVSDB_TYPE_UUID:
+ return NULL;
+
+ case OVSDB_N_TYPES:
+ default:
+ OVS_NOT_REACHED();