[v2,08/19] libctf: creation functions

Message ID 20190517221002.408822-9-nick.alcock@oracle.com
State Superseded
Headers show
Series
  • libctf, and CTF support for objdump and readelf
Related show

Commit Message

Nick Alcock May 17, 2019, 10:09 p.m.
The CTF creation process looks roughly like (error handling elided):

int err;
ctf_file_t *foo = ctf_create (&err);

ctf_id_t type = ctf_add_THING (foo, ...);
ctf_update (foo);
ctf_*write (...);

Some ctf_add_THING functions accept other type IDs as arguments,
depending on the type: cv-quals, pointers, and structure and union
members all take other types as arguments.  So do 'slices', which
let you take an existing integral type and recast it as a type
with a different bitness or offset within a byte, for bitfields.
One class of THING is not a type: "variables", which are mappings
of names (in the internal string table) to types.  These are mostly
useful when encoding variables that do not appear in a symbol table
but which some external user has some other way to figure out the
address of at runtime (dynamic symbol lookup or querying a VM
interpreter or something).

You can snapshot the creation process at any point: rolling back to a
snapshot deletes all types and variables added since that point.

You can make arbitrary type queries on the CTF container during the
creation process, but you must call ctf_update() first, which
translates the growing dynamic container into a static one (this uses
the CTF opening machinery, added in a later commit), which is quite
expensive.  This function must also be called after adding types
and before writing the container out.

Because addition of types involves looking up existing types, we add a
little of the type lookup machinery here, as well: only enough to
look up types in dynamic containers under construction.

Changes from v1:
 - Correct erroneous license (GPLv2+ -> v3+) and reset copyright years.
 - Move ctf_write(), ctf_gzwrite() and ctf_compress_write() to
   ctf-create.c, since ctf-lib.c is gone
 - drop <gelf.h> and direct mmap usage; use libctf_init_debug(); adjust
   to ctf_free() prototype change.

libctf/
	* ctf-create.c: New file.
	* ctf-lookup.c: New file.

include/
	* ctf-api.h (zlib.h): New include.
	(ctf_sect_t): New.
	(ctf_sect_names_t): Likewise.
	(ctf_encoding_t): Likewise.
	(ctf_membinfo_t): Likewise.
	(ctf_arinfo_t): Likewise.
	(ctf_funcinfo_t): Likewise.
	(ctf_lblinfo_t): Likewise.
	(ctf_snapshot_id_t): Likewise.
	(CTF_FUNC_VARARG): Likewise.
	(ctf_simple_open): Likewise.
	(ctf_bufopen): Likewise.
	(ctf_create): Likewise.
	(ctf_add_array): Likewise.
	(ctf_add_const): Likewise.
	(ctf_add_enum_encoded): Likewise.
	(ctf_add_enum): Likewise.
	(ctf_add_float): Likewise.
	(ctf_add_forward): Likewise.
	(ctf_add_function): Likewise.
	(ctf_add_integer): Likewise.
	(ctf_add_slice): Likewise.
	(ctf_add_pointer): Likewise.
	(ctf_add_type): Likewise.
	(ctf_add_typedef): Likewise.
	(ctf_add_restrict): Likewise.
	(ctf_add_struct): Likewise.
	(ctf_add_union): Likewise.
	(ctf_add_struct_sized): Likewise.
	(ctf_add_union_sized): Likewise.
	(ctf_add_volatile): Likewise.
	(ctf_add_enumerator): Likewise.
	(ctf_add_member): Likewise.
	(ctf_add_member_offset): Likewise.
	(ctf_add_member_encoded): Likewise.
	(ctf_add_variable): Likewise.
	(ctf_set_array): Likewise.
	(ctf_update): Likewise.
	(ctf_snapshot): Likewise.
	(ctf_rollback): Likewise.
	(ctf_discard): Likewise.
	(ctf_write): Likewise.
	(ctf_gzwrite): Likewise.
	(ctf_compress_write): Likewise.
---
 include/ctf-api.h   |  129 ++++
 libctf/ctf-create.c | 1547 +++++++++++++++++++++++++++++++++++++++++++
 libctf/ctf-lookup.c |   63 ++
 3 files changed, 1739 insertions(+)
 create mode 100644 libctf/ctf-create.c
 create mode 100644 libctf/ctf-lookup.c

-- 
2.21.0.237.gd0cfaa883d

Comments

Szabolcs Nagy May 29, 2019, 2:09 p.m. | #1
On 17/05/2019 23:09, Nick Alcock wrote:
> --- a/include/ctf-api.h

> +++ b/include/ctf-api.h

> @@ -27,6 +27,7 @@

>  #include <sys/param.h>

>  #include <sys/types.h>

>  #include <ctf.h>

> +#include <zlib.h>


is zlib guaranteed to be available everywhere where
ctf-api.h may be used?

in particular i see

In file included from /S/libctf/ctf-impl.h:25:0,
                 from /S/libctf/ctf-archive.c:20:
/S/libctf/../include/ctf-api.h:30:18: fatal error: zlib.h: No such file or directory
 #include <zlib.h>
                  ^
compilation terminated.
make[2]: *** [ctf-archive.o] Error 1

when the host has no zlib.h
(in that case the binutils internal zlib.h should be used,
ZLIBINC etc should be set up in the Makefile of libctf)
Nick Alcock May 29, 2019, 2:19 p.m. | #2
On 29 May 2019, Szabolcs Nagy spake thusly:

> On 17/05/2019 23:09, Nick Alcock wrote:

>> --- a/include/ctf-api.h

>> +++ b/include/ctf-api.h

>> @@ -27,6 +27,7 @@

>>  #include <sys/param.h>

>>  #include <sys/types.h>

>>  #include <ctf.h>

>> +#include <zlib.h>

>

> is zlib guaranteed to be available everywhere where

> ctf-api.h may be used?


I hoped to be using the internal implementation in that case.

> in particular i see

>

> In file included from /S/libctf/ctf-impl.h:25:0,

>                  from /S/libctf/ctf-archive.c:20:

> /S/libctf/../include/ctf-api.h:30:18: fatal error: zlib.h: No such file or directory

>  #include <zlib.h>

>                   ^

> compilation terminated.

> make[2]: *** [ctf-archive.o] Error 1

>

> when the host has no zlib.h

> (in that case the binutils internal zlib.h should be used,

> ZLIBINC etc should be set up in the Makefile of libctf)


Hm. I thought I'd done everything necessary for that (deps in
Makefile.tpl etc). Clearly I've missed something. I'll do what other
zlib users do...

-- 
NULL && (void)

Patch

diff --git a/include/ctf-api.h b/include/ctf-api.h
index 81ec5c6522..030770649e 100644
--- a/include/ctf-api.h
+++ b/include/ctf-api.h
@@ -27,6 +27,7 @@ 
 #include <sys/param.h>
 #include <sys/types.h>
 #include <ctf.h>
+#include <zlib.h>
 
 #ifdef	__cplusplus
 extern "C"
@@ -44,6 +45,81 @@  typedef struct ctf_file ctf_file_t;
 typedef struct ctf_archive ctf_archive_t;
 typedef long ctf_id_t;
 
+/* If the debugger needs to provide the CTF library with a set of raw buffers
+   for use as the CTF data, symbol table, and string table, it can do so by
+   filling in ctf_sect_t structures and passing them to ctf_bufopen().
+
+   The contents of this structure must always be in native endianness (no
+   byteswapping is performed).  */
+
+typedef struct ctf_sect
+{
+  const char *cts_name;		  /* Section name (if any).  */
+  unsigned long cts_type;	  /* Section type (ELF SHT_... value).  */
+  unsigned long cts_flags;	  /* Section flags (ELF SHF_... value).  */
+  const void *cts_data;		  /* Pointer to section data.  */
+  size_t cts_size;		  /* Size of data in bytes.  */
+  size_t cts_entsize;		  /* Size of each section entry (symtab only).  */
+  off64_t cts_offset;		  /* File offset of this section (if any).  */
+} ctf_sect_t;
+
+/* Symbolic names for CTF sections.  */
+
+typedef enum ctf_sect_names
+  {
+   CTF_SECT_HEADER,
+   CTF_SECT_LABEL,
+   CTF_SECT_OBJT,
+   CTF_SECT_FUNC,
+   CTF_SECT_VAR,
+   CTF_SECT_TYPE,
+   CTF_SECT_STR
+  } ctf_sect_names_t;
+
+/* Encoding information for integers, floating-point values, and certain other
+   intrinsics can be obtained by calling ctf_type_encoding(), below.  The flags
+   field will contain values appropriate for the type defined in <ctf.h>.  */
+
+typedef struct ctf_encoding
+{
+  uint32_t cte_format;		 /* Data format (CTF_INT_* or CTF_FP_* flags).  */
+  uint32_t cte_offset;		 /* Offset of value in bits.  */
+  uint32_t cte_bits;		 /* Size of storage in bits.  */
+} ctf_encoding_t;
+
+typedef struct ctf_membinfo
+{
+  ctf_id_t ctm_type;		/* Type of struct or union member.  */
+  unsigned long ctm_offset;	/* Offset of member in bits.  */
+} ctf_membinfo_t;
+
+typedef struct ctf_arinfo
+{
+  ctf_id_t ctr_contents;	/* Type of array contents.  */
+  ctf_id_t ctr_index;		/* Type of array index.  */
+  uint32_t ctr_nelems;		/* Number of elements.  */
+} ctf_arinfo_t;
+
+typedef struct ctf_funcinfo
+{
+  ctf_id_t ctc_return;		/* Function return type.  */
+  uint32_t ctc_argc;		/* Number of typed arguments to function.  */
+  uint32_t ctc_flags;		/* Function attributes (see below).  */
+} ctf_funcinfo_t;
+
+typedef struct ctf_lblinfo
+{
+  ctf_id_t ctb_type;		/* Last type associated with the label.  */
+} ctf_lblinfo_t;
+
+typedef struct ctf_snapshot_id
+{
+  unsigned long dtd_id;		/* Highest DTD ID at time of snapshot.  */
+  unsigned long snapshot_id;	/* Snapshot id at time of snapshot.  */
+} ctf_snapshot_id_t;
+
+#define	CTF_FUNC_VARARG	0x1	/* Function arguments end with varargs.  */
+
 /* Functions that return integer status or a ctf_id_t use the following value
    to indicate failure.  ctf_errno() can be used to obtain an error code.  */
 #define	CTF_ERR	(-1L)
@@ -122,8 +198,61 @@  enum
 #define	CTF_ADD_NONROOT	0	/* Type only visible in nested scope.  */
 #define	CTF_ADD_ROOT	1	/* Type visible at top-level scope.  */
 
+extern ctf_file_t *ctf_simple_open (const char *, size_t, const char *, size_t,
+				   size_t, const char *, size_t, int *);
+extern ctf_file_t *ctf_bufopen (const ctf_sect_t *, const ctf_sect_t *,
+				const ctf_sect_t *, int *);
+extern ctf_file_t *ctf_create (int *);
+
 extern int ctf_errno (ctf_file_t *);
 extern const char *ctf_errmsg (int);
+extern ctf_id_t ctf_add_array (ctf_file_t *, uint32_t,
+			       const ctf_arinfo_t *);
+extern ctf_id_t ctf_add_const (ctf_file_t *, uint32_t, ctf_id_t);
+extern ctf_id_t ctf_add_enum_encoded (ctf_file_t *, uint32_t, const char *,
+				      const ctf_encoding_t *);
+extern ctf_id_t ctf_add_enum (ctf_file_t *, uint32_t, const char *);
+extern ctf_id_t ctf_add_float (ctf_file_t *, uint32_t,
+			       const char *, const ctf_encoding_t *);
+extern ctf_id_t ctf_add_forward (ctf_file_t *, uint32_t, const char *,
+				 uint32_t);
+extern ctf_id_t ctf_add_function (ctf_file_t *, uint32_t,
+				  const ctf_funcinfo_t *, const ctf_id_t *);
+extern ctf_id_t ctf_add_integer (ctf_file_t *, uint32_t, const char *,
+				 const ctf_encoding_t *);
+extern ctf_id_t ctf_add_slice (ctf_file_t *, uint32_t, ctf_id_t, const ctf_encoding_t *);
+extern ctf_id_t ctf_add_pointer (ctf_file_t *, uint32_t, ctf_id_t);
+extern ctf_id_t ctf_add_type (ctf_file_t *, ctf_file_t *, ctf_id_t);
+extern ctf_id_t ctf_add_typedef (ctf_file_t *, uint32_t, const char *,
+				 ctf_id_t);
+extern ctf_id_t ctf_add_restrict (ctf_file_t *, uint32_t, ctf_id_t);
+extern ctf_id_t ctf_add_struct (ctf_file_t *, uint32_t, const char *);
+extern ctf_id_t ctf_add_union (ctf_file_t *, uint32_t, const char *);
+extern ctf_id_t ctf_add_struct_sized (ctf_file_t *, uint32_t, const char *,
+				      size_t);
+extern ctf_id_t ctf_add_union_sized (ctf_file_t *, uint32_t, const char *,
+				     size_t);
+extern ctf_id_t ctf_add_volatile (ctf_file_t *, uint32_t, ctf_id_t);
+
+extern int ctf_add_enumerator (ctf_file_t *, ctf_id_t, const char *, int);
+extern int ctf_add_member (ctf_file_t *, ctf_id_t, const char *, ctf_id_t);
+extern int ctf_add_member_offset (ctf_file_t *, ctf_id_t, const char *,
+				  ctf_id_t, unsigned long);
+extern int ctf_add_member_encoded (ctf_file_t *, ctf_id_t, const char *,
+				   ctf_id_t, unsigned long,
+				   const ctf_encoding_t);
+
+extern int ctf_add_variable (ctf_file_t *, const char *, ctf_id_t);
+
+extern int ctf_set_array (ctf_file_t *, ctf_id_t, const ctf_arinfo_t *);
+
+extern int ctf_update (ctf_file_t *);
+extern ctf_snapshot_id_t ctf_snapshot (ctf_file_t *);
+extern int ctf_rollback (ctf_file_t *, ctf_snapshot_id_t);
+extern int ctf_discard (ctf_file_t *);
+extern int ctf_write (ctf_file_t *, int);
+extern int ctf_gzwrite (ctf_file_t * fp, gzFile fd);
+extern int ctf_compress_write (ctf_file_t * fp, int fd);
 
 extern void ctf_setdebug (int debug);
 extern int ctf_getdebug (void);
diff --git a/libctf/ctf-create.c b/libctf/ctf-create.c
new file mode 100644
index 0000000000..42012d8342
--- /dev/null
+++ b/libctf/ctf-create.c
@@ -0,0 +1,1547 @@ 
+/* CTF file creation.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+
+   This file is part of libctf.
+
+   libctf 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, 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; see the file COPYING.  If not see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <ctf-impl.h>
+#include <sys/param.h>
+#include <assert.h>
+#include <string.h>
+#include <zlib.h>
+
+/* To create an empty CTF container, we just declare a zeroed header and call
+   ctf_bufopen() on it.  If ctf_bufopen succeeds, we mark the new container r/w
+   and initialize the dynamic members.  We set dtvstrlen to 1 to reserve the
+   first byte of the string table for a \0 byte, and we start assigning type
+   IDs at 1 because type ID 0 is used as a sentinel and a not-found
+   indicator.  */
+
+ctf_file_t *
+ctf_create (int *errp)
+{
+  static const ctf_header_t hdr = { .cth_preamble = { CTF_MAGIC, CTF_VERSION, 0 } };
+
+  ctf_dynhash_t *dthash;
+  ctf_dynhash_t *dvhash;
+  ctf_dynhash_t *dtbyname;
+  ctf_sect_t cts;
+  ctf_file_t *fp;
+
+  libctf_init_debug();
+  dthash = ctf_dynhash_create (ctf_hash_integer, ctf_hash_eq_integer,
+			       NULL, NULL);
+  if (dthash == NULL)
+    {
+      ctf_set_open_errno (errp, EAGAIN);
+      goto err;
+    }
+
+  dvhash = ctf_dynhash_create (ctf_hash_string, ctf_hash_eq_string,
+			       NULL, NULL);
+  if (dvhash == NULL)
+    {
+      ctf_set_open_errno (errp, EAGAIN);
+      goto err_dt;
+    }
+
+  dtbyname = ctf_dynhash_create (ctf_hash_string, ctf_hash_eq_string,
+				 free, NULL);
+  if (dtbyname == NULL)
+    {
+      ctf_set_open_errno (errp, EAGAIN);
+      goto err_dv;
+    }
+
+  cts.cts_name = _CTF_SECTION;
+  cts.cts_type = SHT_PROGBITS;
+  cts.cts_flags = 0;
+  cts.cts_data = &hdr;
+  cts.cts_size = sizeof (hdr);
+  cts.cts_entsize = 1;
+  cts.cts_offset = 0;
+
+  if ((fp = ctf_bufopen (&cts, NULL, NULL, errp)) == NULL)
+      goto err_dtbyname;
+
+  fp->ctf_flags |= LCTF_RDWR;
+  fp->ctf_dtbyname = dtbyname;
+  fp->ctf_dthash = dthash;
+  fp->ctf_dvhash = dvhash;
+  fp->ctf_dtvstrlen = 1;
+  fp->ctf_dtnextid = 1;
+  fp->ctf_dtoldid = 0;
+  fp->ctf_snapshots = 0;
+  fp->ctf_snapshot_lu = 0;
+
+  return fp;
+
+ err_dtbyname:
+  ctf_dynhash_destroy (dtbyname);
+ err_dv:
+  ctf_dynhash_destroy (dvhash);
+ err_dt:
+  ctf_dynhash_destroy (dthash);
+ err:
+  return NULL;
+}
+
+static unsigned char *
+ctf_copy_smembers (ctf_dtdef_t *dtd, uint32_t soff, unsigned char *t)
+{
+  ctf_dmdef_t *dmd = ctf_list_next (&dtd->dtd_u.dtu_members);
+  ctf_member_t ctm;
+
+  for (; dmd != NULL; dmd = ctf_list_next (dmd))
+    {
+      if (dmd->dmd_name)
+	{
+	  ctm.ctm_name = soff;
+	  soff += strlen (dmd->dmd_name) + 1;
+	}
+      else
+	ctm.ctm_name = 0;
+
+      ctm.ctm_type = (uint32_t) dmd->dmd_type;
+      ctm.ctm_offset = (uint32_t) dmd->dmd_offset;
+
+      memcpy (t, &ctm, sizeof (ctm));
+      t += sizeof (ctm);
+    }
+
+  return t;
+}
+
+static unsigned char *
+ctf_copy_lmembers (ctf_dtdef_t *dtd, uint32_t soff, unsigned char *t)
+{
+  ctf_dmdef_t *dmd = ctf_list_next (&dtd->dtd_u.dtu_members);
+  ctf_lmember_t ctlm;
+
+  for (; dmd != NULL; dmd = ctf_list_next (dmd))
+    {
+      if (dmd->dmd_name)
+	{
+	  ctlm.ctlm_name = soff;
+	  soff += strlen (dmd->dmd_name) + 1;
+	}
+      else
+	ctlm.ctlm_name = 0;
+
+      ctlm.ctlm_type = (uint32_t) dmd->dmd_type;
+      ctlm.ctlm_offsethi = CTF_OFFSET_TO_LMEMHI (dmd->dmd_offset);
+      ctlm.ctlm_offsetlo = CTF_OFFSET_TO_LMEMLO (dmd->dmd_offset);
+
+      memcpy (t, &ctlm, sizeof (ctlm));
+      t += sizeof (ctlm);
+    }
+
+  return t;
+}
+
+static unsigned char *
+ctf_copy_emembers (ctf_dtdef_t *dtd, uint32_t soff, unsigned char *t)
+{
+  ctf_dmdef_t *dmd = ctf_list_next (&dtd->dtd_u.dtu_members);
+  ctf_enum_t cte;
+
+  for (; dmd != NULL; dmd = ctf_list_next (dmd))
+    {
+      cte.cte_name = soff;
+      cte.cte_value = dmd->dmd_value;
+      soff += strlen (dmd->dmd_name) + 1;
+      memcpy (t, &cte, sizeof (cte));
+      t += sizeof (cte);
+    }
+
+  return t;
+}
+
+static unsigned char *
+ctf_copy_membnames (ctf_dtdef_t *dtd, unsigned char *s)
+{
+  ctf_dmdef_t *dmd = ctf_list_next (&dtd->dtd_u.dtu_members);
+  size_t len;
+
+  for (; dmd != NULL; dmd = ctf_list_next (dmd))
+    {
+      if (dmd->dmd_name == NULL)
+	continue;			/* Skip anonymous members.  */
+      len = strlen (dmd->dmd_name) + 1;
+      memcpy (s, dmd->dmd_name, len);
+      s += len;
+    }
+
+  return s;
+}
+
+/* Sort a newly-constructed static variable array.  */
+
+static int
+ctf_sort_var (const void *one_, const void *two_, void *strtab_)
+{
+  const ctf_varent_t *one = one_;
+  const ctf_varent_t *two = two_;
+  const char *strtab = strtab_;
+  const char *n1 = strtab + CTF_NAME_OFFSET (one->ctv_name);
+  const char *n2 = strtab + CTF_NAME_OFFSET (two->ctv_name);
+
+  return (strcmp (n1, n2));
+}
+
+/* If the specified CTF container is writable and has been modified, reload this
+   container with the updated type definitions.  In order to make this code and
+   the rest of libctf as simple as possible, we perform updates by taking the
+   dynamic type definitions and creating an in-memory CTF file containing the
+   definitions, and then call ctf_simple_open() on it.  This not only leverages
+   ctf_simple_open(), but also avoids having to bifurcate the rest of the library
+   code with different lookup paths for static and dynamic type definitions.  We
+   are therefore optimizing greatly for lookup over update, which we assume will
+   be an uncommon operation.  We perform one extra trick here for the benefit of
+   callers and to keep our code simple: ctf_simple_open() will return a new
+   ctf_file_t, but we want to keep the fp constant for the caller, so after
+   ctf_simple_open() returns, we use memcpy to swap the interior of the old and
+   new ctf_file_t's, and then free the old.  */
+int
+ctf_update (ctf_file_t *fp)
+{
+  ctf_file_t ofp, *nfp;
+  ctf_header_t hdr;
+  ctf_dtdef_t *dtd;
+  ctf_dvdef_t *dvd;
+  ctf_varent_t *dvarents;
+
+  unsigned char *s, *s0, *t;
+  unsigned long i;
+  size_t buf_size, type_size, nvars;
+  void *buf;
+  int err;
+
+  if (!(fp->ctf_flags & LCTF_RDWR))
+    return (ctf_set_errno (fp, ECTF_RDONLY));
+
+  /* Update required?  */
+  if (!(fp->ctf_flags & LCTF_DIRTY))
+    return 0;
+
+  /* Fill in an initial CTF header.  We will leave the label, object,
+     and function sections empty and only output a header, type section,
+     and string table.  The type section begins at a 4-byte aligned
+     boundary past the CTF header itself (at relative offset zero).  */
+
+  memset (&hdr, 0, sizeof (hdr));
+  hdr.cth_magic = CTF_MAGIC;
+  hdr.cth_version = CTF_VERSION;
+
+  if (fp->ctf_flags & LCTF_CHILD)
+    hdr.cth_parname = 1;		/* parname added just below.  */
+
+  /* Iterate through the dynamic type definition list and compute the
+     size of the CTF type section we will need to generate.  */
+
+  for (type_size = 0, dtd = ctf_list_next (&fp->ctf_dtdefs);
+       dtd != NULL; dtd = ctf_list_next (dtd))
+    {
+      uint32_t kind = LCTF_INFO_KIND (fp, dtd->dtd_data.ctt_info);
+      uint32_t vlen = LCTF_INFO_VLEN (fp, dtd->dtd_data.ctt_info);
+
+      if (dtd->dtd_data.ctt_size != CTF_LSIZE_SENT)
+	type_size += sizeof (ctf_stype_t);
+      else
+	type_size += sizeof (ctf_type_t);
+
+      switch (kind)
+	{
+	case CTF_K_INTEGER:
+	case CTF_K_FLOAT:
+	  type_size += sizeof (uint32_t);
+	  break;
+	case CTF_K_ARRAY:
+	  type_size += sizeof (ctf_array_t);
+	  break;
+	case CTF_K_SLICE:
+	  type_size += sizeof (ctf_slice_t);
+	  break;
+	case CTF_K_FUNCTION:
+	  type_size += sizeof (uint32_t) * (vlen + (vlen & 1));
+	  break;
+	case CTF_K_STRUCT:
+	case CTF_K_UNION:
+	  if (dtd->dtd_data.ctt_size < CTF_LSTRUCT_THRESH)
+	    type_size += sizeof (ctf_member_t) * vlen;
+	  else
+	    type_size += sizeof (ctf_lmember_t) * vlen;
+	  break;
+	case CTF_K_ENUM:
+	  type_size += sizeof (ctf_enum_t) * vlen;
+	  break;
+	}
+    }
+
+  /* Computing the number of entries in the CTF variable section is much
+     simpler.  */
+
+  for (nvars = 0, dvd = ctf_list_next (&fp->ctf_dvdefs);
+       dvd != NULL; dvd = ctf_list_next (dvd), nvars++);
+
+  /* Fill in the string table and type offset and size, compute the size
+     of the entire CTF buffer we need, and then allocate a new buffer and
+     memcpy the finished header to the start of the buffer.  */
+
+  hdr.cth_typeoff = hdr.cth_varoff + (nvars * sizeof (ctf_varent_t));
+  hdr.cth_stroff = hdr.cth_typeoff + type_size;
+  hdr.cth_strlen = fp->ctf_dtvstrlen;
+  if (fp->ctf_parname != NULL)
+    hdr.cth_strlen += strlen (fp->ctf_parname) + 1;
+
+  buf_size = sizeof (ctf_header_t) + hdr.cth_stroff + hdr.cth_strlen;
+
+  if ((buf = ctf_data_alloc (buf_size)) == NULL)
+    return (ctf_set_errno (fp, EAGAIN));
+
+  memcpy (buf, &hdr, sizeof (ctf_header_t));
+  t = (unsigned char *) buf + sizeof (ctf_header_t) + hdr.cth_varoff;
+  s = s0 = (unsigned char *) buf + sizeof (ctf_header_t) + hdr.cth_stroff;
+
+  s[0] = '\0';
+  s++;
+
+  if (fp->ctf_parname != NULL)
+    {
+      memcpy (s, fp->ctf_parname, strlen (fp->ctf_parname) + 1);
+      s += strlen (fp->ctf_parname) + 1;
+    }
+
+  /* Work over the variable list, translating everything into
+     ctf_varent_t's and filling out the string table, then sort the buffer
+     of ctf_varent_t's.  */
+
+  dvarents = (ctf_varent_t *) t;
+  for (i = 0, dvd = ctf_list_next (&fp->ctf_dvdefs); dvd != NULL;
+       dvd = ctf_list_next (dvd), i++)
+    {
+      ctf_varent_t *var = &dvarents[i];
+      size_t len = strlen (dvd->dvd_name) + 1;
+
+      var->ctv_name = (uint32_t) (s - s0);
+      var->ctv_type = dvd->dvd_type;
+      memcpy (s, dvd->dvd_name, len);
+      s += len;
+    }
+  assert (i == nvars);
+
+  qsort_r (dvarents, nvars, sizeof (ctf_varent_t), ctf_sort_var, s0);
+  t += sizeof (ctf_varent_t) * nvars;
+
+  assert (t == (unsigned char *) buf + sizeof (ctf_header_t) + hdr.cth_typeoff);
+
+  /* We now take a final lap through the dynamic type definition list and
+     copy the appropriate type records and strings to the output buffer.  */
+
+  for (dtd = ctf_list_next (&fp->ctf_dtdefs);
+       dtd != NULL; dtd = ctf_list_next (dtd))
+    {
+
+      uint32_t kind = LCTF_INFO_KIND (fp, dtd->dtd_data.ctt_info);
+      uint32_t vlen = LCTF_INFO_VLEN (fp, dtd->dtd_data.ctt_info);
+
+      ctf_array_t cta;
+      uint32_t encoding;
+      size_t len;
+
+      if (dtd->dtd_name != NULL)
+	{
+	  dtd->dtd_data.ctt_name = (uint32_t) (s - s0);
+	  len = strlen (dtd->dtd_name) + 1;
+	  memcpy (s, dtd->dtd_name, len);
+	  s += len;
+	}
+      else
+	dtd->dtd_data.ctt_name = 0;
+
+      if (dtd->dtd_data.ctt_size != CTF_LSIZE_SENT)
+	len = sizeof (ctf_stype_t);
+      else
+	len = sizeof (ctf_type_t);
+
+      memcpy (t, &dtd->dtd_data, len);
+      t += len;
+
+      switch (kind)
+	{
+	case CTF_K_INTEGER:
+	case CTF_K_FLOAT:
+	  if (kind == CTF_K_INTEGER)
+	    {
+	      encoding = CTF_INT_DATA (dtd->dtd_u.dtu_enc.cte_format,
+				       dtd->dtd_u.dtu_enc.cte_offset,
+				       dtd->dtd_u.dtu_enc.cte_bits);
+	    }
+	  else
+	    {
+	      encoding = CTF_FP_DATA (dtd->dtd_u.dtu_enc.cte_format,
+				      dtd->dtd_u.dtu_enc.cte_offset,
+				      dtd->dtd_u.dtu_enc.cte_bits);
+	    }
+	  memcpy (t, &encoding, sizeof (encoding));
+	  t += sizeof (encoding);
+	  break;
+
+	case CTF_K_SLICE:
+	  memcpy (t, &dtd->dtd_u.dtu_slice, sizeof (struct ctf_slice));
+	  t += sizeof (struct ctf_slice);
+	  break;
+
+	case CTF_K_ARRAY:
+	  cta.cta_contents = (uint32_t) dtd->dtd_u.dtu_arr.ctr_contents;
+	  cta.cta_index = (uint32_t) dtd->dtd_u.dtu_arr.ctr_index;
+	  cta.cta_nelems = dtd->dtd_u.dtu_arr.ctr_nelems;
+	  memcpy (t, &cta, sizeof (cta));
+	  t += sizeof (cta);
+	  break;
+
+	case CTF_K_FUNCTION:
+	  {
+	    uint32_t *argv = (uint32_t *) (uintptr_t) t;
+	    uint32_t argc;
+
+	    for (argc = 0; argc < vlen; argc++)
+	      *argv++ = (uint32_t) dtd->dtd_u.dtu_argv[argc];
+
+	    if (vlen & 1)
+	      *argv++ = 0;	/* Pad to 4-byte boundary.  */
+
+	    t = (unsigned char *) argv;
+	    break;
+	  }
+
+	case CTF_K_STRUCT:
+	case CTF_K_UNION:
+	  if (dtd->dtd_data.ctt_size < CTF_LSTRUCT_THRESH)
+	    t = ctf_copy_smembers (dtd, (uint32_t) (s - s0), t);
+	  else
+	    t = ctf_copy_lmembers (dtd, (uint32_t) (s - s0), t);
+	  s = ctf_copy_membnames (dtd, s);
+	  break;
+
+	case CTF_K_ENUM:
+	  t = ctf_copy_emembers (dtd, (uint32_t) (s - s0), t);
+	  s = ctf_copy_membnames (dtd, s);
+	  break;
+	}
+    }
+  assert (t == (unsigned char *) buf + sizeof (ctf_header_t) + hdr.cth_stroff);
+
+  /* Finally, we are ready to ctf_simple_open() the new container.  If this
+     is successful, we then switch nfp and fp and free the old container.  */
+
+  ctf_data_protect (buf, buf_size);
+
+  if ((nfp = ctf_simple_open (buf, buf_size, NULL, 0, 0, NULL, 0, &err)) == NULL)
+    {
+      ctf_data_free (buf, buf_size);
+      return (ctf_set_errno (fp, err));
+    }
+
+  (void) ctf_setmodel (nfp, ctf_getmodel (fp));
+  (void) ctf_import (nfp, fp->ctf_parent);
+
+  nfp->ctf_refcnt = fp->ctf_refcnt;
+  nfp->ctf_flags |= fp->ctf_flags & ~LCTF_DIRTY;
+  nfp->ctf_data.cts_data = NULL;	/* Force ctf_data_free() on close.  */
+  nfp->ctf_dthash = fp->ctf_dthash;
+  nfp->ctf_dtdefs = fp->ctf_dtdefs;
+  nfp->ctf_dtbyname = fp->ctf_dtbyname;
+  nfp->ctf_dvhash = fp->ctf_dvhash;
+  nfp->ctf_dvdefs = fp->ctf_dvdefs;
+  nfp->ctf_dtvstrlen = fp->ctf_dtvstrlen;
+  nfp->ctf_dtnextid = fp->ctf_dtnextid;
+  nfp->ctf_dtoldid = fp->ctf_dtnextid - 1;
+  nfp->ctf_snapshots = fp->ctf_snapshots + 1;
+  nfp->ctf_specific = fp->ctf_specific;
+
+  nfp->ctf_snapshot_lu = fp->ctf_snapshots;
+
+  fp->ctf_dtbyname = NULL;
+  fp->ctf_dthash = NULL;
+  memset (&fp->ctf_dtdefs, 0, sizeof (ctf_list_t));
+
+  fp->ctf_dvhash = NULL;
+  memset (&fp->ctf_dvdefs, 0, sizeof (ctf_list_t));
+
+  memcpy (&ofp, fp, sizeof (ctf_file_t));
+  memcpy (fp, nfp, sizeof (ctf_file_t));
+  memcpy (nfp, &ofp, sizeof (ctf_file_t));
+
+  /* Initialize the ctf_lookup_by_name top-level dictionary.  We keep an
+     array of type name prefixes and the corresponding ctf_dynhash to use.
+     NOTE: This code must be kept in sync with the code in ctf_bufopen().  */
+
+  fp->ctf_lookups[0].ctl_hash = fp->ctf_structs;
+  fp->ctf_lookups[1].ctl_hash = fp->ctf_unions;
+  fp->ctf_lookups[2].ctl_hash = fp->ctf_enums;
+  fp->ctf_lookups[3].ctl_hash = fp->ctf_names;
+
+  nfp->ctf_refcnt = 1;		/* Force nfp to be freed.  */
+  ctf_close (nfp);
+
+  return 0;
+}
+
+static char *
+ctf_prefixed_name (int kind, const char *name)
+{
+  char *prefixed;
+
+  switch (kind)
+    {
+    case CTF_K_STRUCT:
+      prefixed = ctf_strdup ("struct ");
+      break;
+    case CTF_K_UNION:
+      prefixed = ctf_strdup ("union ");
+      break;
+    case CTF_K_ENUM:
+      prefixed = ctf_strdup ("enum ");
+      break;
+    default:
+      prefixed = ctf_strdup ("");
+    }
+
+  prefixed = ctf_str_append (prefixed, name);
+  return prefixed;
+}
+
+void
+ctf_dtd_insert (ctf_file_t *fp, ctf_dtdef_t *dtd)
+{
+  ctf_dynhash_insert (fp->ctf_dthash, (void *) dtd->dtd_type, dtd);
+  ctf_list_append (&fp->ctf_dtdefs, dtd);
+  if (dtd->dtd_name)
+    {
+      int kind = LCTF_INFO_KIND (fp, dtd->dtd_data.ctt_info);
+      ctf_dynhash_insert (fp->ctf_dtbyname, ctf_prefixed_name (kind,
+							       dtd->dtd_name),
+			  dtd);
+    }
+}
+
+void
+ctf_dtd_delete (ctf_file_t *fp, ctf_dtdef_t *dtd)
+{
+  ctf_dmdef_t *dmd, *nmd;
+  int kind = LCTF_INFO_KIND (fp, dtd->dtd_data.ctt_info);
+
+  ctf_dynhash_remove (fp->ctf_dthash, (void *) dtd->dtd_type);
+
+  switch (kind)
+    {
+    case CTF_K_STRUCT:
+    case CTF_K_UNION:
+    case CTF_K_ENUM:
+      for (dmd = ctf_list_next (&dtd->dtd_u.dtu_members);
+	   dmd != NULL; dmd = nmd)
+	{
+	  if (dmd->dmd_name != NULL)
+	    {
+	      ctf_free (dmd->dmd_name);
+	      fp->ctf_dtvstrlen -= strlen (dmd->dmd_name) + 1;
+	    }
+	  nmd = ctf_list_next (dmd);
+	  ctf_free (dmd);
+	}
+      break;
+    case CTF_K_FUNCTION:
+      ctf_free (dtd->dtd_u.dtu_argv);
+      break;
+    }
+
+  if (dtd->dtd_name)
+    {
+      char *name;
+
+      name = ctf_prefixed_name (kind, dtd->dtd_name);
+      ctf_dynhash_remove (fp->ctf_dtbyname, name);
+      free (name);
+
+      ctf_free (dtd->dtd_name);
+      fp->ctf_dtvstrlen -= strlen (dtd->dtd_name) + 1;
+    }
+
+  ctf_list_delete (&fp->ctf_dtdefs, dtd);
+  ctf_free (dtd);
+}
+
+ctf_dtdef_t *
+ctf_dtd_lookup (const ctf_file_t *fp, ctf_id_t type)
+{
+  return (ctf_dtdef_t *) ctf_dynhash_lookup (fp->ctf_dthash, (void *) type);
+}
+
+static ctf_id_t
+ctf_dtd_lookup_type_by_name (ctf_file_t *fp, int kind, const char *name)
+{
+  ctf_dtdef_t *dtd;
+  char *decorated = ctf_prefixed_name (kind, name);
+
+  dtd = (ctf_dtdef_t *) ctf_dynhash_lookup (fp->ctf_dtbyname, decorated);
+  free (decorated);
+
+  if (dtd)
+    return dtd->dtd_type;
+
+  return 0;
+}
+
+ctf_dtdef_t *
+ctf_dynamic_type (const ctf_file_t *fp, ctf_id_t id)
+{
+  ctf_id_t idx;
+
+  if ((fp->ctf_flags & LCTF_CHILD) && LCTF_TYPE_ISPARENT (fp, id))
+    fp = fp->ctf_parent;
+
+  idx = LCTF_TYPE_TO_INDEX(fp, id);
+
+  if (((unsigned long) idx > fp->ctf_typemax) &&
+      ((unsigned long) idx < fp->ctf_dtnextid))
+    return ctf_dtd_lookup (fp, id);
+  return NULL;
+}
+
+void
+ctf_dvd_insert (ctf_file_t *fp, ctf_dvdef_t *dvd)
+{
+  ctf_dynhash_insert (fp->ctf_dvhash, dvd->dvd_name, dvd);
+  ctf_list_append (&fp->ctf_dvdefs, dvd);
+}
+
+void
+ctf_dvd_delete (ctf_file_t *fp, ctf_dvdef_t *dvd)
+{
+  ctf_dynhash_remove (fp->ctf_dvhash, dvd->dvd_name);
+
+  ctf_free (dvd->dvd_name);
+  fp->ctf_dtvstrlen -= strlen (dvd->dvd_name) + 1;
+
+  ctf_list_delete (&fp->ctf_dvdefs, dvd);
+  ctf_free (dvd);
+}
+
+ctf_dvdef_t *
+ctf_dvd_lookup (const ctf_file_t *fp, const char *name)
+{
+  return (ctf_dvdef_t *) ctf_dynhash_lookup (fp->ctf_dvhash, name);
+}
+
+/* Discard all of the dynamic type definitions and variable definitions that
+   have been added to the container since the last call to ctf_update().  We
+   locate such types by scanning the dtd list and deleting elements that have
+   type IDs greater than ctf_dtoldid, which is set by ctf_update(), above, and
+   by scanning the variable list and deleting elements that have update IDs
+   equal to the current value of the last-update snapshot count (indicating that
+   they were added after the most recent call to ctf_update()).  */
+int
+ctf_discard (ctf_file_t *fp)
+{
+  ctf_snapshot_id_t last_update =
+    { fp->ctf_dtoldid,
+      fp->ctf_snapshot_lu + 1 };
+
+  /* Update required?  */
+  if (!(fp->ctf_flags & LCTF_DIRTY))
+    return 0;
+
+  return (ctf_rollback (fp, last_update));
+}
+
+ctf_snapshot_id_t
+ctf_snapshot (ctf_file_t *fp)
+{
+  ctf_snapshot_id_t snapid;
+  snapid.dtd_id = fp->ctf_dtnextid - 1;
+  snapid.snapshot_id = fp->ctf_snapshots++;
+  return snapid;
+}
+
+/* Like ctf_discard(), only discards everything after a particular ID.  */
+int
+ctf_rollback (ctf_file_t *fp, ctf_snapshot_id_t id)
+{
+  ctf_dtdef_t *dtd, *ntd;
+  ctf_dvdef_t *dvd, *nvd;
+
+  if (!(fp->ctf_flags & LCTF_RDWR))
+    return (ctf_set_errno (fp, ECTF_RDONLY));
+
+  if (fp->ctf_dtoldid > id.dtd_id)
+    return (ctf_set_errno (fp, ECTF_OVERROLLBACK));
+
+  if (fp->ctf_snapshot_lu >= id.snapshot_id)
+    return (ctf_set_errno (fp, ECTF_OVERROLLBACK));
+
+  for (dtd = ctf_list_next (&fp->ctf_dtdefs); dtd != NULL; dtd = ntd)
+    {
+      ntd = ctf_list_next (dtd);
+
+      if (LCTF_TYPE_TO_INDEX (fp, dtd->dtd_type) <= id.dtd_id)
+	continue;
+
+      ctf_dtd_delete (fp, dtd);
+    }
+
+  for (dvd = ctf_list_next (&fp->ctf_dvdefs); dvd != NULL; dvd = nvd)
+    {
+      nvd = ctf_list_next (dvd);
+
+      if (dvd->dvd_snapshots <= id.snapshot_id)
+	continue;
+
+      ctf_dvd_delete (fp, dvd);
+    }
+
+  fp->ctf_dtnextid = id.dtd_id + 1;
+  fp->ctf_snapshots = id.snapshot_id;
+
+  if (fp->ctf_snapshots == fp->ctf_snapshot_lu)
+    fp->ctf_flags &= ~LCTF_DIRTY;
+
+  return 0;
+}
+
+static ctf_id_t
+ctf_add_generic (ctf_file_t *fp, uint32_t flag, const char *name,
+		 ctf_dtdef_t **rp)
+{
+  ctf_dtdef_t *dtd;
+  ctf_id_t type;
+  char *s = NULL;
+
+  if (flag != CTF_ADD_NONROOT && flag != CTF_ADD_ROOT)
+    return (ctf_set_errno (fp, EINVAL));
+
+  if (!(fp->ctf_flags & LCTF_RDWR))
+    return (ctf_set_errno (fp, ECTF_RDONLY));
+
+  if (LCTF_INDEX_TO_TYPE (fp, fp->ctf_dtnextid, 1) > CTF_MAX_TYPE)
+    return (ctf_set_errno (fp, ECTF_FULL));
+
+  if (LCTF_INDEX_TO_TYPE (fp, fp->ctf_dtnextid, 1) == CTF_MAX_PTYPE)
+    return (ctf_set_errno (fp, ECTF_FULL));
+
+  if ((dtd = ctf_alloc (sizeof (ctf_dtdef_t))) == NULL)
+    return (ctf_set_errno (fp, EAGAIN));
+
+  if (name != NULL && (s = ctf_strdup (name)) == NULL)
+    {
+      ctf_free (dtd);
+      return (ctf_set_errno (fp, EAGAIN));
+    }
+
+  type = fp->ctf_dtnextid++;
+  type = LCTF_INDEX_TO_TYPE (fp, type, (fp->ctf_flags & LCTF_CHILD));
+
+  memset (dtd, 0, sizeof (ctf_dtdef_t));
+  dtd->dtd_name = s;
+  dtd->dtd_type = type;
+
+  if (s != NULL)
+    fp->ctf_dtvstrlen += strlen (s) + 1;
+
+  ctf_dtd_insert (fp, dtd);
+  fp->ctf_flags |= LCTF_DIRTY;
+
+  *rp = dtd;
+  return type;
+}
+
+/* When encoding integer sizes, we want to convert a byte count in the range
+   1-8 to the closest power of 2 (e.g. 3->4, 5->8, etc).  The clp2() function
+   is a clever implementation from "Hacker's Delight" by Henry Warren, Jr.  */
+static size_t
+clp2 (size_t x)
+{
+  x--;
+
+  x |= (x >> 1);
+  x |= (x >> 2);
+  x |= (x >> 4);
+  x |= (x >> 8);
+  x |= (x >> 16);
+
+  return (x + 1);
+}
+
+static ctf_id_t
+ctf_add_encoded (ctf_file_t *fp, uint32_t flag,
+		 const char *name, const ctf_encoding_t *ep, uint32_t kind)
+{
+  ctf_dtdef_t *dtd;
+  ctf_id_t type;
+
+  if (ep == NULL)
+    return (ctf_set_errno (fp, EINVAL));
+
+  if ((type = ctf_add_generic (fp, flag, name, &dtd)) == CTF_ERR)
+    return CTF_ERR;		/* errno is set for us.  */
+
+  dtd->dtd_data.ctt_info = CTF_TYPE_INFO (kind, flag, 0);
+  dtd->dtd_data.ctt_size = clp2 (P2ROUNDUP (ep->cte_bits, NBBY) / NBBY);
+  dtd->dtd_u.dtu_enc = *ep;
+
+  return type;
+}
+
+static ctf_id_t
+ctf_add_reftype (ctf_file_t *fp, uint32_t flag, ctf_id_t ref, uint32_t kind)
+{
+  ctf_dtdef_t *dtd;
+  ctf_id_t type;
+  ctf_file_t *tmp = fp;
+
+  if (ref == CTF_ERR || ref < 0 || ref > CTF_MAX_TYPE)
+    return (ctf_set_errno (fp, EINVAL));
+
+  if (ctf_lookup_by_id (&tmp, ref) == NULL)
+    return CTF_ERR;		/* errno is set for us.  */
+
+  if ((type = ctf_add_generic (fp, flag, NULL, &dtd)) == CTF_ERR)
+    return CTF_ERR;		/* errno is set for us.  */
+
+  dtd->dtd_data.ctt_info = CTF_TYPE_INFO (kind, flag, 0);
+  dtd->dtd_data.ctt_type = (uint32_t) ref;
+
+  return type;
+}
+
+ctf_id_t
+ctf_add_slice (ctf_file_t *fp, uint32_t flag, ctf_id_t ref,
+	       const ctf_encoding_t *ep)
+{
+  ctf_dtdef_t *dtd;
+  ctf_id_t type;
+  int kind;
+  const ctf_type_t *tp;
+  ctf_file_t *tmp = fp;
+
+  if (ep == NULL)
+    return (ctf_set_errno (fp, EINVAL));
+
+  if ((ep->cte_bits > 255) || (ep->cte_offset > 255))
+    return (ctf_set_errno (fp, ECTF_SLICEOVERFLOW));
+
+  if (ref == CTF_ERR || ref < 0 || ref > CTF_MAX_TYPE)
+    return (ctf_set_errno (fp, EINVAL));
+
+  if ((tp = ctf_lookup_by_id (&tmp, ref)) == NULL)
+    return CTF_ERR;		/* errno is set for us.  */
+
+  kind = ctf_type_kind_unsliced (tmp, ref);
+  if ((kind != CTF_K_INTEGER) && (kind != CTF_K_FLOAT) &&
+      (kind != CTF_K_ENUM))
+    return (ctf_set_errno (fp, ECTF_NOTINTFP));
+
+  if ((type = ctf_add_generic (fp, flag, NULL, &dtd)) == CTF_ERR)
+    return CTF_ERR;		/* errno is set for us.  */
+
+  dtd->dtd_data.ctt_info = CTF_TYPE_INFO (CTF_K_SLICE, flag, 0);
+  dtd->dtd_data.ctt_size = clp2 (P2ROUNDUP (ep->cte_bits, NBBY) / NBBY);
+  dtd->dtd_u.dtu_slice.cts_type = ref;
+  dtd->dtd_u.dtu_slice.cts_bits = ep->cte_bits;
+  dtd->dtd_u.dtu_slice.cts_offset = ep->cte_offset;
+
+  return type;
+}
+
+ctf_id_t
+ctf_add_integer (ctf_file_t *fp, uint32_t flag,
+		 const char *name, const ctf_encoding_t *ep)
+{
+  return (ctf_add_encoded (fp, flag, name, ep, CTF_K_INTEGER));
+}
+
+ctf_id_t
+ctf_add_float (ctf_file_t *fp, uint32_t flag,
+	       const char *name, const ctf_encoding_t *ep)
+{
+  return (ctf_add_encoded (fp, flag, name, ep, CTF_K_FLOAT));
+}
+
+ctf_id_t
+ctf_add_pointer (ctf_file_t *fp, uint32_t flag, ctf_id_t ref)
+{
+  return (ctf_add_reftype (fp, flag, ref, CTF_K_POINTER));
+}
+
+ctf_id_t
+ctf_add_array (ctf_file_t *fp, uint32_t flag, const ctf_arinfo_t *arp)
+{
+  ctf_dtdef_t *dtd;
+  ctf_id_t type;
+  ctf_file_t *tmp = fp;
+
+  if (arp == NULL)
+    return (ctf_set_errno (fp, EINVAL));
+
+  if (ctf_lookup_by_id (&tmp, arp->ctr_contents) == NULL)
+    return CTF_ERR;		/* errno is set for us.  */
+
+  tmp = fp;
+  if (ctf_lookup_by_id (&tmp, arp->ctr_index) == NULL)
+    return CTF_ERR;		/* errno is set for us.  */
+
+  if ((type = ctf_add_generic (fp, flag, NULL, &dtd)) == CTF_ERR)
+    return CTF_ERR;		/* errno is set for us.  */
+
+  dtd->dtd_data.ctt_info = CTF_TYPE_INFO (CTF_K_ARRAY, flag, 0);
+  dtd->dtd_data.ctt_size = 0;
+  dtd->dtd_u.dtu_arr = *arp;
+
+  return type;
+}
+
+int
+ctf_set_array (ctf_file_t *fp, ctf_id_t type, const ctf_arinfo_t *arp)
+{
+  ctf_dtdef_t *dtd = ctf_dtd_lookup (fp, type);
+
+  if (!(fp->ctf_flags & LCTF_RDWR))
+    return (ctf_set_errno (fp, ECTF_RDONLY));
+
+  if (dtd == NULL
+      || LCTF_INFO_KIND (fp, dtd->dtd_data.ctt_info) != CTF_K_ARRAY)
+    return (ctf_set_errno (fp, ECTF_BADID));
+
+  fp->ctf_flags |= LCTF_DIRTY;
+  dtd->dtd_u.dtu_arr = *arp;
+
+  return 0;
+}
+
+ctf_id_t
+ctf_add_function (ctf_file_t *fp, uint32_t flag,
+		  const ctf_funcinfo_t *ctc, const ctf_id_t *argv)
+{
+  ctf_dtdef_t *dtd;
+  ctf_id_t type;
+  uint32_t vlen;
+  ctf_id_t *vdat = NULL;
+  ctf_file_t *tmp = fp;
+  size_t i;
+
+  if (ctc == NULL || (ctc->ctc_flags & ~CTF_FUNC_VARARG) != 0
+      || (ctc->ctc_argc != 0 && argv == NULL))
+    return (ctf_set_errno (fp, EINVAL));
+
+  vlen = ctc->ctc_argc;
+  if (ctc->ctc_flags & CTF_FUNC_VARARG)
+    vlen++;	       /* Add trailing zero to indicate varargs (see below).  */
+
+  if (ctf_lookup_by_id (&tmp, ctc->ctc_return) == NULL)
+    return CTF_ERR;		/* errno is set for us.  */
+
+  for (i = 0; i < ctc->ctc_argc; i++)
+    {
+      tmp = fp;
+      if (ctf_lookup_by_id (&tmp, argv[i]) == NULL)
+	return CTF_ERR;		/* errno is set for us.  */
+    }
+
+  if (vlen > CTF_MAX_VLEN)
+    return (ctf_set_errno (fp, EOVERFLOW));
+
+  if (vlen != 0 && (vdat = ctf_alloc (sizeof (ctf_id_t) * vlen)) == NULL)
+    return (ctf_set_errno (fp, EAGAIN));
+
+  if ((type = ctf_add_generic (fp, flag, NULL, &dtd)) == CTF_ERR)
+    {
+      ctf_free (vdat);
+      return CTF_ERR;		   /* errno is set for us.  */
+    }
+
+  dtd->dtd_data.ctt_info = CTF_TYPE_INFO (CTF_K_FUNCTION, flag, vlen);
+  dtd->dtd_data.ctt_type = (uint32_t) ctc->ctc_return;
+
+  memcpy (vdat, argv, sizeof (ctf_id_t) * ctc->ctc_argc);
+  if (ctc->ctc_flags & CTF_FUNC_VARARG)
+    vdat[vlen - 1] = 0;		   /* Add trailing zero to indicate varargs.  */
+  dtd->dtd_u.dtu_argv = vdat;
+
+  return type;
+}
+
+ctf_id_t
+ctf_add_struct_sized (ctf_file_t *fp, uint32_t flag, const char *name,
+		      size_t size)
+{
+  ctf_hash_t *hp = fp->ctf_structs;
+  ctf_dtdef_t *dtd;
+  ctf_id_t type = 0;
+
+  /* Promote forwards to structs.  */
+
+  if (name != NULL)
+    {
+      type = ctf_hash_lookup_type (hp, fp, name);
+      if (type == 0)
+	type = ctf_dtd_lookup_type_by_name (fp, CTF_K_STRUCT, name);
+    }
+
+  if (type != 0 && ctf_type_kind (fp, type) == CTF_K_FORWARD)
+    dtd = ctf_dtd_lookup (fp, type);
+  else if ((type = ctf_add_generic (fp, flag, name, &dtd)) == CTF_ERR)
+    return CTF_ERR;		/* errno is set for us.  */
+
+  dtd->dtd_data.ctt_info = CTF_TYPE_INFO (CTF_K_STRUCT, flag, 0);
+
+  if (size > CTF_MAX_SIZE)
+    {
+      dtd->dtd_data.ctt_size = CTF_LSIZE_SENT;
+      dtd->dtd_data.ctt_lsizehi = CTF_SIZE_TO_LSIZE_HI (size);
+      dtd->dtd_data.ctt_lsizelo = CTF_SIZE_TO_LSIZE_LO (size);
+    }
+  else
+    dtd->dtd_data.ctt_size = (uint32_t) size;
+
+  return type;
+}
+
+ctf_id_t
+ctf_add_struct (ctf_file_t *fp, uint32_t flag, const char *name)
+{
+  return (ctf_add_struct_sized (fp, flag, name, 0));
+}
+
+ctf_id_t
+ctf_add_union_sized (ctf_file_t *fp, uint32_t flag, const char *name,
+		     size_t size)
+{
+  ctf_hash_t *hp = fp->ctf_unions;
+  ctf_dtdef_t *dtd;
+  ctf_id_t type = 0;
+
+  /* Promote forwards to unions.  */
+  if (name != NULL)
+    {
+      type = ctf_hash_lookup_type (hp, fp, name);
+      if (type == 0)
+	type = ctf_dtd_lookup_type_by_name (fp, CTF_K_UNION, name);
+    }
+
+  if (type != 0 && ctf_type_kind (fp, type) == CTF_K_FORWARD)
+    dtd = ctf_dtd_lookup (fp, type);
+  else if ((type = ctf_add_generic (fp, flag, name, &dtd)) == CTF_ERR)
+    return CTF_ERR;		/* errno is set for us */
+
+  dtd->dtd_data.ctt_info = CTF_TYPE_INFO (CTF_K_UNION, flag, 0);
+
+  if (size > CTF_MAX_SIZE)
+    {
+      dtd->dtd_data.ctt_size = CTF_LSIZE_SENT;
+      dtd->dtd_data.ctt_lsizehi = CTF_SIZE_TO_LSIZE_HI (size);
+      dtd->dtd_data.ctt_lsizelo = CTF_SIZE_TO_LSIZE_LO (size);
+    }
+  else
+    dtd->dtd_data.ctt_size = (uint32_t) size;
+
+  return type;
+}
+
+ctf_id_t
+ctf_add_union (ctf_file_t *fp, uint32_t flag, const char *name)
+{
+  return (ctf_add_union_sized (fp, flag, name, 0));
+}
+
+ctf_id_t
+ctf_add_enum (ctf_file_t *fp, uint32_t flag, const char *name)
+{
+  ctf_hash_t *hp = fp->ctf_enums;
+  ctf_dtdef_t *dtd;
+  ctf_id_t type = 0;
+
+  /* Promote forwards to enums.  */
+  if (name != NULL)
+    {
+      type = ctf_hash_lookup_type (hp, fp, name);
+      if (type == 0)
+	type = ctf_dtd_lookup_type_by_name (fp, CTF_K_ENUM, name);
+    }
+
+  if (type != 0 && ctf_type_kind (fp, type) == CTF_K_FORWARD)
+    dtd = ctf_dtd_lookup (fp, type);
+  else if ((type = ctf_add_generic (fp, flag, name, &dtd)) == CTF_ERR)
+    return CTF_ERR;		/* errno is set for us.  */
+
+  dtd->dtd_data.ctt_info = CTF_TYPE_INFO (CTF_K_ENUM, flag, 0);
+  dtd->dtd_data.ctt_size = fp->ctf_dmodel->ctd_int;
+
+  return type;
+}
+
+ctf_id_t
+ctf_add_enum_encoded (ctf_file_t *fp, uint32_t flag, const char *name,
+		      const ctf_encoding_t *ep)
+{
+  ctf_hash_t *hp = fp->ctf_enums;
+  ctf_id_t type = 0;
+
+  /* First, create the enum if need be, using most of the same machinery as
+     ctf_add_enum(), to ensure that we do not allow things past that are not
+     enums or forwards to them.  (This includes other slices: you cannot slice a
+     slice, which would be a useless thing to do anyway.)  */
+
+  if (name != NULL)
+    {
+      type = ctf_hash_lookup_type (hp, fp, name);
+      if (type == 0)
+	type = ctf_dtd_lookup_type_by_name (fp, CTF_K_ENUM, name);
+    }
+
+  if (type != 0)
+    {
+      if ((ctf_type_kind (fp, type) != CTF_K_FORWARD) &&
+	  (ctf_type_kind_unsliced (fp, type) != CTF_K_ENUM))
+	return (ctf_set_errno (fp, ECTF_NOTINTFP));
+    }
+  else if ((type = ctf_add_enum (fp, flag, name)) == CTF_ERR)
+    return CTF_ERR;		/* errno is set for us.  */
+
+  /* Now attach a suitable slice to it.  */
+
+  return ctf_add_slice (fp, flag, type, ep);
+}
+
+ctf_id_t
+ctf_add_forward (ctf_file_t *fp, uint32_t flag, const char *name,
+		 uint32_t kind)
+{
+  ctf_hash_t *hp;
+  ctf_dtdef_t *dtd;
+  ctf_id_t type = 0;
+
+  switch (kind)
+    {
+    case CTF_K_STRUCT:
+      hp = fp->ctf_structs;
+      break;
+    case CTF_K_UNION:
+      hp = fp->ctf_unions;
+      break;
+    case CTF_K_ENUM:
+      hp = fp->ctf_enums;
+      break;
+    default:
+      return (ctf_set_errno (fp, ECTF_NOTSUE));
+    }
+
+  /* If the type is already defined or exists as a forward tag, just
+     return the ctf_id_t of the existing definition.  */
+
+  if (name != NULL)
+    {
+      if (((type = ctf_hash_lookup_type (hp, fp, name)) != 0)
+	  || (type = ctf_dtd_lookup_type_by_name (fp, kind, name)) != 0)
+	return type;
+    }
+
+  if ((type = ctf_add_generic (fp, flag, name, &dtd)) == CTF_ERR)
+    return CTF_ERR;		/* errno is set for us.  */
+
+  dtd->dtd_data.ctt_info = CTF_TYPE_INFO (CTF_K_FORWARD, flag, 0);
+  dtd->dtd_data.ctt_type = kind;
+
+  return type;
+}
+
+ctf_id_t
+ctf_add_typedef (ctf_file_t *fp, uint32_t flag, const char *name,
+		 ctf_id_t ref)
+{
+  ctf_dtdef_t *dtd;
+  ctf_id_t type;
+  ctf_file_t *tmp = fp;
+
+  if (ref == CTF_ERR || ref < 0 || ref > CTF_MAX_TYPE)
+    return (ctf_set_errno (fp, EINVAL));
+
+  if (ctf_lookup_by_id (&tmp, ref) == NULL)
+    return CTF_ERR;		/* errno is set for us.  */
+
+  if ((type = ctf_add_generic (fp, flag, name, &dtd)) == CTF_ERR)
+    return CTF_ERR;		/* errno is set for us.  */
+
+  dtd->dtd_data.ctt_info = CTF_TYPE_INFO (CTF_K_TYPEDEF, flag, 0);
+  dtd->dtd_data.ctt_type = (uint32_t) ref;
+
+  return type;
+}
+
+ctf_id_t
+ctf_add_volatile (ctf_file_t *fp, uint32_t flag, ctf_id_t ref)
+{
+  return (ctf_add_reftype (fp, flag, ref, CTF_K_VOLATILE));
+}
+
+ctf_id_t
+ctf_add_const (ctf_file_t *fp, uint32_t flag, ctf_id_t ref)
+{
+  return (ctf_add_reftype (fp, flag, ref, CTF_K_CONST));
+}
+
+ctf_id_t
+ctf_add_restrict (ctf_file_t *fp, uint32_t flag, ctf_id_t ref)
+{
+  return (ctf_add_reftype (fp, flag, ref, CTF_K_RESTRICT));
+}
+
+int
+ctf_add_enumerator (ctf_file_t *fp, ctf_id_t enid, const char *name,
+		    int value)
+{
+  ctf_dtdef_t *dtd = ctf_dtd_lookup (fp, enid);
+  ctf_dmdef_t *dmd;
+
+  uint32_t kind, vlen, root;
+  char *s;
+
+  if (name == NULL)
+    return (ctf_set_errno (fp, EINVAL));
+
+  if (!(fp->ctf_flags & LCTF_RDWR))
+    return (ctf_set_errno (fp, ECTF_RDONLY));
+
+  if (dtd == NULL)
+    return (ctf_set_errno (fp, ECTF_BADID));
+
+  kind = LCTF_INFO_KIND (fp, dtd->dtd_data.ctt_info);
+  root = LCTF_INFO_ISROOT (fp, dtd->dtd_data.ctt_info);
+  vlen = LCTF_INFO_VLEN (fp, dtd->dtd_data.ctt_info);
+
+  if (kind != CTF_K_ENUM)
+    return (ctf_set_errno (fp, ECTF_NOTENUM));
+
+  if (vlen == CTF_MAX_VLEN)
+    return (ctf_set_errno (fp, ECTF_DTFULL));
+
+  for (dmd = ctf_list_next (&dtd->dtd_u.dtu_members);
+       dmd != NULL; dmd = ctf_list_next (dmd))
+    {
+      if (strcmp (dmd->dmd_name, name) == 0)
+	return (ctf_set_errno (fp, ECTF_DUPLICATE));
+    }
+
+  if ((dmd = ctf_alloc (sizeof (ctf_dmdef_t))) == NULL)
+    return (ctf_set_errno (fp, EAGAIN));
+
+  if ((s = ctf_strdup (name)) == NULL)
+    {
+      ctf_free (dmd);
+      return (ctf_set_errno (fp, EAGAIN));
+    }
+
+  dmd->dmd_name = s;
+  dmd->dmd_type = CTF_ERR;
+  dmd->dmd_offset = 0;
+  dmd->dmd_value = value;
+
+  dtd->dtd_data.ctt_info = CTF_TYPE_INFO (kind, root, vlen + 1);
+  ctf_list_append (&dtd->dtd_u.dtu_members, dmd);
+
+  fp->ctf_dtvstrlen += strlen (s) + 1;
+  fp->ctf_flags |= LCTF_DIRTY;
+
+  return 0;
+}
+
+int
+ctf_add_member_offset (ctf_file_t *fp, ctf_id_t souid, const char *name,
+		       ctf_id_t type, unsigned long bit_offset)
+{
+  ctf_dtdef_t *dtd = ctf_dtd_lookup (fp, souid);
+  ctf_dmdef_t *dmd;
+
+  ssize_t msize, malign, ssize;
+  uint32_t kind, vlen, root;
+  char *s = NULL;
+
+  if (!(fp->ctf_flags & LCTF_RDWR))
+    return (ctf_set_errno (fp, ECTF_RDONLY));
+
+  if (dtd == NULL)
+    return (ctf_set_errno (fp, ECTF_BADID));
+
+  kind = LCTF_INFO_KIND (fp, dtd->dtd_data.ctt_info);
+  root = LCTF_INFO_ISROOT (fp, dtd->dtd_data.ctt_info);
+  vlen = LCTF_INFO_VLEN (fp, dtd->dtd_data.ctt_info);
+
+  if (kind != CTF_K_STRUCT && kind != CTF_K_UNION)
+    return (ctf_set_errno (fp, ECTF_NOTSOU));
+
+  if (vlen == CTF_MAX_VLEN)
+    return (ctf_set_errno (fp, ECTF_DTFULL));
+
+  if (name != NULL)
+    {
+      for (dmd = ctf_list_next (&dtd->dtd_u.dtu_members);
+	   dmd != NULL; dmd = ctf_list_next (dmd))
+	{
+	  if (dmd->dmd_name != NULL && strcmp (dmd->dmd_name, name) == 0)
+	    return (ctf_set_errno (fp, ECTF_DUPLICATE));
+	}
+    }
+
+  if ((msize = ctf_type_size (fp, type)) == CTF_ERR ||
+      (malign = ctf_type_align (fp, type)) == CTF_ERR)
+    return CTF_ERR;		/* errno is set for us.  */
+
+  if ((dmd = ctf_alloc (sizeof (ctf_dmdef_t))) == NULL)
+    return (ctf_set_errno (fp, EAGAIN));
+
+  if (name != NULL && (s = ctf_strdup (name)) == NULL)
+    {
+      ctf_free (dmd);
+      return (ctf_set_errno (fp, EAGAIN));
+    }
+
+  dmd->dmd_name = s;
+  dmd->dmd_type = type;
+  dmd->dmd_value = -1;
+
+  if (kind == CTF_K_STRUCT && vlen != 0)
+    {
+      if (bit_offset == (unsigned long) - 1)
+	{
+	  /* Natural alignment.  */
+
+	  ctf_dmdef_t *lmd = ctf_list_prev (&dtd->dtd_u.dtu_members);
+	  ctf_id_t ltype = ctf_type_resolve (fp, lmd->dmd_type);
+	  size_t off = lmd->dmd_offset;
+
+	  ctf_encoding_t linfo;
+	  ssize_t lsize;
+
+	  if (ctf_type_encoding (fp, ltype, &linfo) != CTF_ERR)
+	    off += linfo.cte_bits;
+	  else if ((lsize = ctf_type_size (fp, ltype)) != CTF_ERR)
+	    off += lsize * NBBY;
+
+	  /* Round up the offset of the end of the last member to
+	     the next byte boundary, convert 'off' to bytes, and
+	     then round it up again to the next multiple of the
+	     alignment required by the new member.  Finally,
+	     convert back to bits and store the result in
+	     dmd_offset.  Technically we could do more efficient
+	     packing if the new member is a bit-field, but we're
+	     the "compiler" and ANSI says we can do as we choose.  */
+
+	  off = roundup (off, NBBY) / NBBY;
+	  off = roundup (off, MAX (malign, 1));
+	  dmd->dmd_offset = off * NBBY;
+	  ssize = off + msize;
+	}
+      else
+	{
+	  /* Specified offset in bits.  */
+
+	  dmd->dmd_offset = bit_offset;
+	  ssize = ctf_get_ctt_size (fp, &dtd->dtd_data, NULL, NULL);
+	  ssize = MAX (ssize, (bit_offset / NBBY) + msize);
+	}
+    }
+  else
+    {
+      dmd->dmd_offset = 0;
+      ssize = ctf_get_ctt_size (fp, &dtd->dtd_data, NULL, NULL);
+      ssize = MAX (ssize, msize);
+    }
+
+  if (ssize > CTF_MAX_SIZE)
+    {
+      dtd->dtd_data.ctt_size = CTF_LSIZE_SENT;
+      dtd->dtd_data.ctt_lsizehi = CTF_SIZE_TO_LSIZE_HI (ssize);
+      dtd->dtd_data.ctt_lsizelo = CTF_SIZE_TO_LSIZE_LO (ssize);
+    }
+  else
+    dtd->dtd_data.ctt_size = (uint32_t) ssize;
+
+  dtd->dtd_data.ctt_info = CTF_TYPE_INFO (kind, root, vlen + 1);
+  ctf_list_append (&dtd->dtd_u.dtu_members, dmd);
+
+  if (s != NULL)
+    fp->ctf_dtvstrlen += strlen (s) + 1;
+
+  fp->ctf_flags |= LCTF_DIRTY;
+  return 0;
+}
+
+int
+ctf_add_member_encoded (ctf_file_t *fp, ctf_id_t souid, const char *name,
+			ctf_id_t type, unsigned long bit_offset,
+			const ctf_encoding_t encoding)
+{
+  ctf_dtdef_t *dtd = ctf_dtd_lookup (fp, type);
+  int kind = LCTF_INFO_KIND (fp, dtd->dtd_data.ctt_info);
+  int otype = type;
+
+  if ((kind != CTF_K_INTEGER) && (kind != CTF_K_FLOAT) && (kind != CTF_K_ENUM))
+    return (ctf_set_errno (fp, ECTF_NOTINTFP));
+
+  if ((type = ctf_add_slice (fp, CTF_ADD_NONROOT, otype, &encoding)) == CTF_ERR)
+    return CTF_ERR;		/* errno is set for us.  */
+
+  return ctf_add_member_offset (fp, souid, name, type, bit_offset);
+}
+
+int
+ctf_add_member (ctf_file_t *fp, ctf_id_t souid, const char *name,
+		ctf_id_t type)
+{
+  return ctf_add_member_offset (fp, souid, name, type, (unsigned long) - 1);
+}
+
+int
+ctf_add_variable (ctf_file_t *fp, const char *name, ctf_id_t ref)
+{
+  ctf_dvdef_t *dvd;
+  ctf_file_t *tmp = fp;
+
+  if (!(fp->ctf_flags & LCTF_RDWR))
+    return (ctf_set_errno (fp, ECTF_RDONLY));
+
+  if (ctf_dvd_lookup (fp, name) != NULL)
+    return (ctf_set_errno (fp, ECTF_DUPLICATE));
+
+  if (ctf_lookup_by_id (&tmp, ref) == NULL)
+    return CTF_ERR;		/* errno is set for us.  */
+
+  if ((dvd = ctf_alloc (sizeof (ctf_dvdef_t))) == NULL)
+    return (ctf_set_errno (fp, EAGAIN));
+
+  if (name != NULL && (dvd->dvd_name = ctf_strdup (name)) == NULL)
+    {
+      ctf_free (dvd);
+      return (ctf_set_errno (fp, EAGAIN));
+    }
+  dvd->dvd_type = ref;
+  dvd->dvd_snapshots = fp->ctf_snapshots;
+
+  ctf_dvd_insert (fp, dvd);
+
+  fp->ctf_dtvstrlen += strlen (name) + 1;
+  fp->ctf_flags |= LCTF_DIRTY;
+  return 0;
+}
+
+/* Write the compressed CTF data stream to the specified gzFile descriptor.
+   This is useful for saving the results of dynamic CTF containers.  */
+int
+ctf_gzwrite (ctf_file_t *fp, gzFile fd)
+{
+  const unsigned char *buf = fp->ctf_base;
+  ssize_t resid = fp->ctf_size;
+  ssize_t len;
+
+  while (resid != 0)
+    {
+      if ((len = gzwrite (fd, buf, resid)) <= 0)
+	return (ctf_set_errno (fp, errno));
+      resid -= len;
+      buf += len;
+    }
+
+  return 0;
+}
+
+/* Compress the specified CTF data stream and write it to the specified file
+   descriptor.  */
+int
+ctf_compress_write (ctf_file_t *fp, int fd)
+{
+  unsigned char *buf;
+  unsigned char *bp;
+  ctf_header_t h;
+  ctf_header_t *hp = &h;
+  ssize_t header_len = sizeof (ctf_header_t);
+  ssize_t compress_len;
+  size_t max_compress_len = compressBound (fp->ctf_size - header_len);
+  ssize_t len;
+  int rc;
+  int err = 0;
+
+  memcpy (hp, fp->ctf_base, header_len);
+  hp->cth_flags |= CTF_F_COMPRESS;
+
+  if ((buf = ctf_data_alloc (max_compress_len)) == NULL)
+    return (ctf_set_errno (fp, ECTF_ZALLOC));
+
+  compress_len = max_compress_len;
+  if ((rc = compress (buf, (uLongf *) & compress_len,
+		      fp->ctf_base + header_len,
+		      fp->ctf_size - header_len)) != Z_OK)
+    {
+      ctf_dprintf ("zlib deflate err: %s\n", zError (rc));
+      err = ctf_set_errno (fp, ECTF_COMPRESS);
+      goto ret;
+    }
+
+  while (header_len > 0)
+    {
+      if ((len = write (fd, hp, header_len)) < 0)
+	{
+	  err = ctf_set_errno (fp, errno);
+	  goto ret;
+	}
+      header_len -= len;
+      hp += len;
+    }
+
+  bp = buf;
+  while (compress_len > 0)
+    {
+      if ((len = write (fd, bp, compress_len)) < 0)
+	{
+	  err = ctf_set_errno (fp, errno);
+	  goto ret;
+	}
+      compress_len -= len;
+      bp += len;
+    }
+
+ret:
+  ctf_data_free (buf, max_compress_len);
+  return err;
+}
+
+/* Write the uncompressed CTF data stream to the specified file descriptor.
+   This is useful for saving the results of dynamic CTF containers.  */
+int
+ctf_write (ctf_file_t *fp, int fd)
+{
+  const unsigned char *buf = fp->ctf_base;
+  ssize_t resid = fp->ctf_size;
+  ssize_t len;
+
+  while (resid != 0)
+    {
+      if ((len = write (fd, buf, resid)) < 0)
+	return (ctf_set_errno (fp, errno));
+      resid -= len;
+      buf += len;
+    }
+
+  return 0;
+}
diff --git a/libctf/ctf-lookup.c b/libctf/ctf-lookup.c
new file mode 100644
index 0000000000..e76afb6e0a
--- /dev/null
+++ b/libctf/ctf-lookup.c
@@ -0,0 +1,63 @@ 
+/* Type lookup.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+
+   This file is part of libctf.
+
+   libctf 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, 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; see the file COPYING.  If not see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <ctf-impl.h>
+#include <elf.h>
+#include <string.h>
+
+/* Return the pointer to the internal CTF type data corresponding to the
+   given type ID.  If the ID is invalid, the function returns NULL.
+   This function is not exported outside of the library.  */
+
+const ctf_type_t *
+ctf_lookup_by_id (ctf_file_t **fpp, ctf_id_t type)
+{
+  ctf_file_t *fp = *fpp;	/* Caller passes in starting CTF container.  */
+  ctf_id_t idx;
+
+  if ((fp->ctf_flags & LCTF_CHILD) && LCTF_TYPE_ISPARENT (fp, type)
+      && (fp = fp->ctf_parent) == NULL)
+    {
+      (void) ctf_set_errno (*fpp, ECTF_NOPARENT);
+      return NULL;
+    }
+
+  idx = LCTF_TYPE_TO_INDEX (fp, type);
+  if (idx > 0 && (unsigned long) idx <= fp->ctf_typemax)
+    {
+      *fpp = fp;		/* Function returns ending CTF container.  */
+      return (LCTF_INDEX_TO_TYPEPTR (fp, idx));
+    }
+
+  /* If this container is writable, check for a dynamic type.  */
+
+  if (fp->ctf_flags & LCTF_RDWR)
+    {
+      ctf_dtdef_t *dtd;
+
+      if ((dtd = ctf_dynamic_type (fp, type)) != NULL)
+	{
+	  *fpp = fp;
+	  return &dtd->dtd_data;
+	}
+    }
+  (void) ctf_set_errno (*fpp, ECTF_BADID);
+  return NULL;
+}
+