--- /dev/null
+/* PSPP - a program for statistical analysis.
+ Copyright (C) 2010, 2013 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include "libpspp/pxd.h"
+
+#include <gdbm.h>
+#include <stdlib.h>
+
+#include "data/value.h"
+#include "libpspp/cast.h"
+#include "libpspp/compiler.h"
+#include "libpspp/float-format.h"
+#include "libpspp/integer-format.h"
+#include "libpspp/intern.h"
+
+#include "gl/error.h"
+#include "gl/minmax.h"
+#include "gl/sha1.h"
+#include "gl/xalloc.h"
+#include "gl/xmemdup0.h"
+
+struct pxd
+ {
+ GDBM_FILE dbf;
+ };
+
+static struct pxd_object *pxd_object_lookup__ (const struct pxd_id *id);
+static struct pxd_object *pxd_object_create_raw__ (char *raw, size_t n_links,
+ size_t size,
+ const struct pxd_id *);
+
+static datum
+make_datum (const void *data, size_t size)
+{
+ datum d;
+
+ d.dptr = CONST_CAST (void *, data);
+ d.dsize = size;
+
+ return d;
+}
+
+static datum
+string_datum (const char *s)
+{
+ return make_datum (s, strlen (s));
+}
+
+static datum
+id_datum (const struct pxd_id *id)
+{
+ return make_datum (id, sizeof *id);
+}
+
+static datum
+root_key (void)
+{
+ return string_datum ("root");
+}
+
+void
+pxd_get_root (const struct pxd *pxd, struct pxd_id *id)
+{
+ datum content;
+
+ content = gdbm_fetch (pxd->dbf, root_key ());
+ if (content.dptr != NULL && content.dsize == sizeof *id)
+ memcpy (id, content.dptr, sizeof *id);
+ else
+ memset (id, 0, sizeof *id);
+ free (content.dptr);
+}
+
+struct pxd *
+pxd_open (const char *name, bool may_create)
+{
+ struct pxd *pxd;
+ GDBM_FILE dbf;
+
+ dbf = gdbm_open (CONST_CAST (char *, name), 4096,
+ may_create ? GDBM_WRCREAT : GDBM_WRITER, 0777, NULL);
+ if (dbf == NULL)
+ {
+ error (0, 0, "%s: open failed (%s)", name, gdbm_strerror (gdbm_errno));
+ return NULL;
+ }
+
+ pxd = xmalloc (sizeof *pxd);
+ pxd->dbf = dbf;
+ return pxd;
+}
+
+void
+pxd_close (struct pxd *pxd)
+{
+ if (pxd != NULL)
+ {
+ gdbm_close (pxd->dbf);
+ free (pxd);
+ }
+}
+
+struct pxd_object *
+pxd_fetch (const struct pxd *pxd, const struct pxd_id *id)
+{
+ struct pxd_object *obj;
+ datum content;
+ uint32_t n_links;
+ size_t header_size;
+
+ obj = pxd_object_lookup__ (id);
+ if (obj != NULL)
+ return obj;
+
+ content = gdbm_fetch (pxd->dbf, id_datum (id));
+ if (content.dptr == NULL)
+ error (1, 0, PXD_ID_FMT": not found in database", PXD_ID_ARGS (id));
+
+ if (content.dsize < sizeof n_links)
+ error (1, 0, PXD_ID_FMT": size %d is less than minimum size 4",
+ PXD_ID_ARGS(id), content.dsize);
+
+ n_links = le32_to_cpu (*(uint32_t *) content.dptr);
+ header_size = 4 + n_links * sizeof (struct pxd_id);
+ if (header_size > content.dsize)
+ error (1, 0, PXD_ID_FMT": size %d is less than minimum size %zu for "
+ "object with %"PRIu32" links",
+ PXD_ID_ARGS (id), content.dsize, header_size, n_links);
+
+ return pxd_object_create_raw__ (content.dptr, n_links,
+ content.dsize - header_size, id);
+}
+
+void
+pxd_store (struct pxd *pxd, const struct pxd_object *object)
+{
+ datum content;
+
+ content = make_datum (pxd_object_raw_data__ (object),
+ pxd_object_raw_size__ (object));
+ if (gdbm_store (pxd->dbf, id_datum (&object->id), content, GDBM_INSERT) < 0)
+ error (1, 0, PXD_ID_FMT": storing object failed",
+ PXD_ID_ARGS (&object->id));
+}
+
+bool
+pxd_swap_root (struct pxd *pxd, const struct pxd_id *old_root,
+ struct pxd_object *new_root)
+{
+ struct pxd_id cur_root;
+
+ pxd_get_root (pxd, &cur_root);
+ if (pxd_id_equals (&cur_root, old_root))
+ {
+ if (gdbm_store (pxd->dbf, root_key (), id_datum (&new_root->id),
+ GDBM_INSERT) < 0)
+ error (1, 0, "storing root object failed");
+ pxd_object_unref (new_root);
+ return true;
+ }
+ else
+ return false;
+}
+\f
+static struct hmap object_table = HMAP_INITIALIZER (object_table);
+
+static struct pxd_object *
+pxd_object_lookup__ (const struct pxd_id *id)
+{
+ const struct pxd_object *obj;
+
+ HMAP_FOR_EACH_IN_BUCKET (obj, struct pxd_object, hmap_node,
+ pxd_id_hash (id), &object_table)
+ if (pxd_id_equals (id, &obj->id))
+ return pxd_object_ref (obj);
+
+ return NULL;
+}
+
+static struct pxd_object *
+pxd_object_create_raw__ (char *raw, size_t n_links, size_t size,
+ const struct pxd_id *id)
+{
+ struct pxd_object *obj;
+
+ obj = xmalloc (sizeof *obj);
+ hmap_insert (&object_table, &obj->hmap_node, pxd_id_hash (id));
+ obj->n_refs = 1;
+ obj->id = *id;
+ obj->n_links = n_links;
+ obj->size = size;
+ obj->links = (struct pxd_id *) (raw + sizeof (uint32_t));
+ obj->data = (uint8_t *) (obj->links + obj->n_links);
+
+ return obj;
+}
+
+struct pxd_object *
+pxd_object_create (const struct pxd_id links[], size_t n_links,
+ const void *data, size_t size)
+{
+ struct pxd_object *obj;
+ struct sha1_ctx sha;
+ uint32_t le_n_links;
+ struct pxd_id id;
+ char *raw;
+
+ /* Hash the raw form of the object. */
+ sha1_init_ctx (&sha);
+ le_n_links = cpu_to_le32 (n_links);
+ sha1_process_bytes (&le_n_links, sizeof le_n_links, &sha);
+ sha1_process_bytes (links, n_links * sizeof *links, &sha);
+ sha1_process_bytes (data, size, &sha);
+ sha1_finish_ctx (&sha, &id);
+
+ /* Reuse an existing copy if there is one. */
+ obj = pxd_object_lookup__ (&id);
+ if (obj != NULL)
+ return obj;
+
+ /* Build a new object. */
+ raw = xmalloc (sizeof le_n_links + n_links * sizeof *links +
+ size);
+ memcpy (raw, &le_n_links, sizeof le_n_links);
+ memcpy (raw + sizeof le_n_links, links, n_links * sizeof *links);
+ memcpy (raw + sizeof le_n_links + n_links * sizeof *links, data, size);
+
+ return pxd_object_create_raw__ (raw, n_links, size, &id);
+}
+
+struct pxd_object *
+pxd_object_ref (const struct pxd_object *const_obj)
+{
+ struct pxd_object *obj = CONST_CAST (struct pxd_object *, const_obj);
+ obj->n_refs++;
+ return obj;
+}
+
+void
+pxd_object_unref (struct pxd_object *obj)
+{
+ if (obj != NULL && --obj->n_refs == 0)
+ {
+ hmap_delete (&object_table, &obj->hmap_node);
+ free (pxd_object_raw_data__ (obj));
+ free (obj);
+ }
+}
+
+void *
+pxd_object_raw_data__ (const struct pxd_object *obj)
+{
+ return ((uint32_t *) obj->links) - 1;
+}
+
+size_t
+pxd_object_raw_size__ (const struct pxd_object *obj)
+{
+ return sizeof (uint32_t *) + obj->n_links * sizeof *obj->links + obj->size;
+}
+\f
+void
+pxd_builder_init (struct pxd_builder *b, struct pxd *pxd)
+{
+ b->pxd = pxd;
+
+ b->links = NULL;
+ b->n_links = b->links_allocated = 0;
+
+ b->data = NULL;
+ b->size = b->data_allocated = 0;
+}
+
+void
+pxd_builder_destroy (struct pxd_builder *b)
+{
+ if (b != NULL)
+ {
+ free (b->links);
+ b->links = NULL;
+
+ free (b->data);
+ b->data = NULL;
+ }
+}
+
+struct pxd_object *
+pxd_builder_commit (struct pxd_builder *b)
+{
+ struct pxd_object *obj;
+
+ obj = pxd_object_create (b->links, b->n_links, b->data, b->size);
+ pxd_store (b->pxd, obj);
+ pxd_builder_destroy (b);
+
+ return obj;
+}
+
+void *
+pxd_builder_put_uninit (struct pxd_builder *b, size_t n)
+{
+ void *p;
+
+ if (b->size + n > b->data_allocated)
+ {
+ b->data_allocated = MAX (b->size + n + 64, b->data_allocated * 2);
+ b->data = xrealloc (b->data, b->data_allocated);
+ }
+
+ p = &b->data[b->size];
+ b->size += n;
+ return p;
+}
+
+void
+pxd_builder_put (struct pxd_builder *b, const void *p, size_t n)
+{
+ memcpy (pxd_builder_put_uninit (b, n), p, n);
+}
+
+void
+pxd_builder_put_value (struct pxd_builder *b,
+ const union value *value, int width)
+{
+ if (width == 0)
+ pxd_builder_put_double (b, value->f);
+ else
+ {
+ const uint8_t *s = value_str (value, width);
+ if (width < sizeof (struct pxd_id))
+ pxd_builder_put (b, s, width);
+ else
+ pxd_builder_put_link (b, pxd_object_create (NULL, 0, s, width));
+ }
+}
+
+static void
+pxd_builder_put_string__ (struct pxd_builder *b, const char *s, size_t length)
+{
+ pxd_builder_put_u32 (b, length);
+ if (length < sizeof (struct pxd_id))
+ pxd_builder_put (b, s, length);
+ else
+ pxd_builder_put_link (b, pxd_object_create (NULL, 0, s, length));
+}
+
+void
+pxd_builder_put_string (struct pxd_builder *b, const char *s)
+{
+ return pxd_builder_put_string__ (b, s, strlen (s));
+}
+
+void
+pxd_builder_put_interned_string (struct pxd_builder *b, const char *s)
+{
+ return pxd_builder_put_string__ (b, s, intern_strlen (s));
+}
+
+void
+pxd_builder_put_bool (struct pxd_builder *b, bool x)
+{
+ pxd_builder_put_u8 (b, x != 0);
+}
+
+void
+pxd_builder_put_u8 (struct pxd_builder *b, unsigned char x)
+{
+ pxd_builder_put (b, &x, 1);
+}
+
+void
+pxd_builder_put_u16 (struct pxd_builder *b, unsigned short int x)
+{
+ uint16_t le_x = cpu_to_le16 (x);
+ pxd_builder_put (b, &le_x, sizeof (le_x));
+}
+
+void pxd_builder_put_u32 (struct pxd_builder *b, unsigned int x)
+{
+ uint32_t le_x = cpu_to_le32 (x);
+ pxd_builder_put (b, &le_x, sizeof (le_x));
+}
+
+void
+pxd_builder_put_u64 (struct pxd_builder *b, unsigned long long int x)
+{
+ uint64_t le_x = cpu_to_le64 (x);
+ pxd_builder_put (b, &le_x, sizeof (le_x));
+}
+
+void
+pxd_builder_put_s8 (struct pxd_builder *b, signed char x)
+{
+ pxd_builder_put (b, &x, 1);
+}
+
+void
+pxd_builder_put_s16 (struct pxd_builder *b, short int x)
+{
+ uint16_t le_x = cpu_to_le16 (x);
+ pxd_builder_put (b, &le_x, sizeof (le_x));
+}
+
+void pxd_builder_put_s32 (struct pxd_builder *b, int x)
+{
+ uint32_t le_x = cpu_to_le32 (x);
+ pxd_builder_put (b, &le_x, sizeof (le_x));
+}
+
+void
+pxd_builder_put_s64 (struct pxd_builder *b, long long int x)
+{
+ uint64_t le_x = cpu_to_le64 (x);
+ pxd_builder_put (b, &le_x, sizeof (le_x));
+}
+
+void
+pxd_builder_put_size_t (struct pxd_builder *b, size_t x)
+{
+ pxd_builder_put_u64 (b, x);
+}
+
+void
+pxd_builder_put_casenumber (struct pxd_builder *b, casenumber x)
+{
+ pxd_builder_put_u64 (b, x);
+}
+
+void
+pxd_builder_put_double (struct pxd_builder *b, double x)
+{
+ pxd_builder_put_u64 (b, double_to_ieee64le (x));
+}
+
+void
+pxd_builder_put_link (struct pxd_builder *b, struct pxd_object *obj)
+{
+ if (b->n_links >= b->links_allocated)
+ b->links = x2nrealloc (b->links, &b->links_allocated, sizeof *b->links);
+
+ b->links[b->n_links++] = obj->id;
+}
+\f
+void
+pxd_parser_init (struct pxd_parser *p,
+ struct pxd_object *obj, const struct pxd *pxd)
+{
+ p->obj = obj;
+ p->pxd = pxd;
+ p->offset = 0;
+ p->link = 0;
+}
+
+void
+pxd_parser_destroy (struct pxd_parser *p)
+{
+ if (p != NULL)
+ pxd_object_unref (p->obj);
+}
+
+
+const void *
+pxd_parser_get (struct pxd_parser *p, size_t n)
+{
+ const void *data;
+
+ assert (p->offset + n <= p->obj->size);
+
+ data = &p->obj->data[p->offset];
+ p->offset += n;
+ return data;
+}
+
+void
+pxd_parser_get_copy (struct pxd_parser *p, void *dst, size_t n)
+{
+ memcpy (dst, pxd_parser_get (p, n), n);
+}
+
+void
+pxd_parser_get_value (struct pxd_parser *p, union value *value, int width)
+{
+ value_init (value, width);
+ if (width == 0)
+ value->f = ieee64le_to_double (pxd_parser_get_u64 (p));
+ else
+ {
+ uint8_t *s = value_str_rw (value, width);
+ if (width < sizeof (struct pxd_id))
+ pxd_parser_get_copy (p, s, width);
+ else
+ {
+ struct pxd_object *link = pxd_parser_get_link (p);
+ assert (link->size == width);
+ memcpy (s, link->data, width);
+ pxd_object_unref (link);
+ }
+ }
+}
+
+char *
+pxd_parser_get_string (struct pxd_parser *p)
+{
+ uint32_t length = pxd_parser_get_u32 (p);
+ if (length < sizeof (struct pxd_id))
+ return xmemdup0 (pxd_parser_get (p, length), length);
+ else
+ {
+ struct pxd_object *link;
+ char *s;
+
+ link = pxd_parser_get_link (p);
+ assert (link->size == length);
+ s = xmemdup0 (link->data, link->size);
+ pxd_object_unref (link);
+
+ return s;
+ }
+}
+
+const char *
+pxd_parser_get_interned_string (struct pxd_parser *p)
+{
+ uint32_t length = pxd_parser_get_u32 (p);
+ if (length < sizeof (struct pxd_id))
+ return intern_buffer (pxd_parser_get (p, length), length);
+ else
+ {
+ struct pxd_object *link;
+ const char *s;
+
+ link = pxd_parser_get_link (p);
+ assert (link->size == length);
+ s = intern_buffer ((const void *) link->data, link->size);
+ pxd_object_unref (link);
+
+ return s;
+ }
+}
+
+bool
+pxd_parser_get_bool (struct pxd_parser *p)
+{
+ return pxd_parser_get_u8 (p) != 0;
+}
+
+unsigned char
+pxd_parser_get_u8 (struct pxd_parser *p)
+{
+ uint8_t x;
+ pxd_parser_get_copy (p, &x, 1);
+ return x;
+}
+
+unsigned short int
+pxd_parser_get_u16 (struct pxd_parser *p)
+{
+ uint16_t x;
+ pxd_parser_get_copy (p, &x, sizeof x);
+ return le16_to_cpu (x);
+}
+
+unsigned int
+pxd_parser_get_u32 (struct pxd_parser *p)
+{
+ uint32_t x;
+ pxd_parser_get_copy (p, &x, sizeof x);
+ return le32_to_cpu (x);
+}
+
+unsigned long long int
+pxd_parser_get_u64 (struct pxd_parser *p)
+{
+ uint64_t x;
+ pxd_parser_get_copy (p, &x, sizeof x);
+ return le64_to_cpu (x);
+}
+
+signed char
+pxd_parser_get_s8 (struct pxd_parser *p)
+{
+ int8_t x;
+ pxd_parser_get_copy (p, &x, 1);
+ return x;
+}
+
+short int
+pxd_parser_get_s16 (struct pxd_parser *p)
+{
+ uint16_t x;
+ pxd_parser_get_copy (p, &x, sizeof x);
+ return le16_to_cpu (x);
+}
+
+int
+pxd_parser_get_s32 (struct pxd_parser *p)
+{
+ uint32_t x;
+ pxd_parser_get_copy (p, &x, sizeof x);
+ return le32_to_cpu (x);
+}
+
+long long int
+pxd_parser_get_s64 (struct pxd_parser *p)
+{
+ uint64_t x;
+ pxd_parser_get_copy (p, &x, sizeof x);
+ return le64_to_cpu (x);
+}
+
+size_t
+pxd_parser_get_size_t (struct pxd_parser *p)
+{
+ uint64_t x = pxd_parser_get_u64 (p);
+ assert (x <= SIZE_MAX);
+ return x;
+}
+
+casenumber
+pxd_parser_get_casenumber (struct pxd_parser *p)
+{
+ uint64_t x = pxd_parser_get_u64 (p);
+ assert (x <= CASENUMBER_MAX);
+ return x;
+}
+
+double
+pxd_parser_get_double (struct pxd_parser *p)
+{
+ return ieee64le_to_double (pxd_parser_get_u64 (p));
+}
+
+struct pxd_object *
+pxd_parser_get_link (struct pxd_parser *p)
+{
+ assert (p->link < p->obj->n_links);
+
+ return pxd_fetch (p->pxd, &p->obj->links[p->link++]);
+}
+\f
+void
+pxd_array_builder_init (struct pxd_array_builder *ab, struct pxd *pxd)
+{
+ pxd_builder_init (&ab->b, pxd);
+}
+
+void
+pxd_array_builder_destroy (struct pxd_array_builder *ab)
+{
+ pxd_builder_destroy (&ab->b);
+}
+
+struct pxd_object *
+pxd_array_builder_commit (struct pxd_array_builder *ab)
+{
+ return pxd_builder_commit (&ab->b);
+}
+
+void
+pxd_array_builder_add (struct pxd_array_builder *ab, struct pxd_object *obj)
+{
+ pxd_builder_put_link (&ab->b, obj);
+}
+\f
+void
+pxd_array_init (struct pxd_array *array, struct pxd_object *obj,
+ const struct pxd *pxd)
+{
+ array->pxd = pxd;
+ array->obj = obj;
+}
+
+void
+pxd_array_destroy (struct pxd_array *array)
+{
+ pxd_object_unref (array->obj);
+}