]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blobdiff - libctf/ctf-archive.c
Update year range in copyright notice of binutils files
[thirdparty/binutils-gdb.git] / libctf / ctf-archive.c
index 73d772ffc3846e2d7a4c1c12c9a78b74589f72ca..d8c4845b643b93287036c6f2d535430b03c3f04a 100644 (file)
@@ -1,5 +1,5 @@
 /* CTF archive files.
-   Copyright (C) 2019-2020 Free Software Foundation, Inc.
+   Copyright (C) 2019-2021 Free Software Foundation, Inc.
 
    This file is part of libctf.
 
 #include <sys/mman.h>
 #endif
 
-static off_t arc_write_one_ctf (ctf_file_t * f, int fd, size_t threshold);
-static ctf_file_t *ctf_arc_open_by_offset (const struct ctf_archive *arc,
-                                          const ctf_sect_t *symsect,
-                                          const ctf_sect_t *strsect,
-                                          size_t offset, int *errp);
+static off_t arc_write_one_ctf (ctf_dict_t * f, int fd, size_t threshold);
+static ctf_dict_t *ctf_dict_open_by_offset (const struct ctf_archive *arc,
+                                           const ctf_sect_t *symsect,
+                                           const ctf_sect_t *strsect,
+                                           size_t offset, int little_endian,
+                                           int *errp);
 static int sort_modent_by_name (const void *one, const void *two, void *n);
 static void *arc_mmap_header (int fd, size_t headersz);
 static void *arc_mmap_file (int fd, size_t size);
 static int arc_mmap_writeout (int fd, void *header, size_t headersz,
                              const char **errmsg);
 static int arc_mmap_unmap (void *header, size_t headersz, const char **errmsg);
+static void ctf_arc_import_parent (const ctf_archive_t *arc, ctf_dict_t *fp);
 
-/* bsearch() internal state.  */
-static __thread char *search_nametbl;
+/* Flag to indicate "symbol not present" in
+   ctf_archive_internal.ctfi_symdicts.  Never initialized.  */
+static ctf_dict_t enosym;
 
 /* Write out a CTF archive to the start of the file referenced by the passed-in
-   fd.  The entries in CTF_FILES are referenced by name: the names are passed in
-   the names array, which must have CTF_FILES entries.
+   fd.  The entries in CTF_DICTS are referenced by name: the names are passed in
+   the names array, which must have CTF_DICTS entries.
 
    Returns 0 on success, or an errno, or an ECTF_* value.  */
 int
-ctf_arc_write_fd (int fd, ctf_file_t **ctf_files, size_t ctf_file_cnt,
+ctf_arc_write_fd (int fd, ctf_dict_t **ctf_dicts, size_t ctf_dict_cnt,
                  const char **names, size_t threshold)
 {
   const char *errmsg;
@@ -69,14 +72,14 @@ ctf_arc_write_fd (int fd, ctf_file_t **ctf_files, size_t ctf_file_cnt,
   struct ctf_archive_modent *modent;
 
   ctf_dprintf ("Writing CTF archive with %lu files\n",
-              (unsigned long) ctf_file_cnt);
+              (unsigned long) ctf_dict_cnt);
 
   /* Figure out the size of the mmap()ed header, including the
      ctf_archive_modent array.  We assume that all of this needs no
      padding: a likely assumption, given that it's all made up of
      uint64_t's.  */
   headersz = sizeof (struct ctf_archive)
-    + (ctf_file_cnt * sizeof (uint64_t) * 2);
+    + (ctf_dict_cnt * sizeof (uint64_t) * 2);
   ctf_dprintf ("headersz is %lu\n", (unsigned long) headersz);
 
   /* From now on we work in two pieces: an mmap()ed region from zero up to the
@@ -85,26 +88,26 @@ ctf_arc_write_fd (int fd, ctf_file_t **ctf_files, size_t ctf_file_cnt,
   ctf_startoffs = headersz;
   if (lseek (fd, ctf_startoffs - 1, SEEK_SET) < 0)
     {
-      errmsg = "ctf_arc_write(): cannot extend file while writing: %s\n";
+      errmsg = N_("ctf_arc_write(): cannot extend file while writing");
       goto err;
     }
 
   if (write (fd, &dummy, 1) < 0)
     {
-      errmsg = "ctf_arc_write(): cannot extend file while writing: %s\n";
+      errmsg = N_("ctf_arc_write(): cannot extend file while writing");
       goto err;
     }
 
   if ((archdr = arc_mmap_header (fd, headersz)) == NULL)
     {
-      errmsg = "ctf_arc_write(): Cannot mmap(): %s\n";
+      errmsg = N_("ctf_arc_write(): cannot mmap");
       goto err;
     }
 
   /* Fill in everything we can, which is everything other than the name
      table offset.  */
   archdr->ctfa_magic = htole64 (CTFA_MAGIC);
-  archdr->ctfa_nfiles = htole64 (ctf_file_cnt);
+  archdr->ctfa_ndicts = htole64 (ctf_dict_cnt);
   archdr->ctfa_ctfs = htole64 (ctf_startoffs);
 
   /* We could validate that all CTF files have the same data model, but
@@ -115,8 +118,8 @@ ctf_arc_write_fd (int fd, ctf_file_t **ctf_files, size_t ctf_file_cnt,
      this case, but we must be sure not to dereference uninitialized
      memory.)  */
 
-  if (ctf_file_cnt > 0)
-    archdr->ctfa_model = htole64 (ctf_getmodel (ctf_files[0]));
+  if (ctf_dict_cnt > 0)
+    archdr->ctfa_model = htole64 (ctf_getmodel (ctf_dicts[0]));
 
   /* Now write out the CTFs: ctf_archive_modent array via the mapping,
      ctfs via write().  The names themselves have not been written yet: we
@@ -125,35 +128,35 @@ ctf_arc_write_fd (int fd, ctf_file_t **ctf_files, size_t ctf_file_cnt,
 
     The name table is not sorted.  */
 
-  for (i = 0, namesz = 0; i < le64toh (archdr->ctfa_nfiles); i++)
+  for (i = 0, namesz = 0; i < le64toh (archdr->ctfa_ndicts); i++)
     namesz += strlen (names[i]) + 1;
 
   nametbl = malloc (namesz);
   if (nametbl == NULL)
     {
-      errmsg = "Error writing named CTF to archive: %s\n";
+      errmsg = N_("ctf_arc_write(): error writing named CTF to archive");
       goto err_unmap;
     }
 
   for (i = 0, namesz = 0,
        modent = (ctf_archive_modent_t *) ((char *) archdr
                                          + sizeof (struct ctf_archive));
-       i < le64toh (archdr->ctfa_nfiles); i++)
+       i < le64toh (archdr->ctfa_ndicts); i++)
     {
       off_t off;
 
       strcpy (&nametbl[namesz], names[i]);
 
-      off = arc_write_one_ctf (ctf_files[i], fd, threshold);
+      off = arc_write_one_ctf (ctf_dicts[i], fd, threshold);
       if ((off < 0) && (off > -ECTF_BASE))
        {
-         errmsg = "ctf_arc_write(): Cannot determine file "
-           "position while writing to archive: %s";
+         errmsg = N_("ctf_arc_write(): cannot determine file "
+                     "position while writing to archive");
          goto err_free;
        }
       if (off < 0)
        {
-         errmsg = "ctf_arc_write(): Cannot write CTF file to archive: %s\n";
+         errmsg = N_("ctf_arc_write(): cannot write CTF file to archive");
          errno = off * -1;
          goto err_free;
        }
@@ -166,7 +169,7 @@ ctf_arc_write_fd (int fd, ctf_file_t **ctf_files, size_t ctf_file_cnt,
 
   ctf_qsort_r ((ctf_archive_modent_t *) ((char *) archdr
                                         + sizeof (struct ctf_archive)),
-              le64toh (archdr->ctfa_nfiles),
+              le64toh (archdr->ctfa_ndicts),
               sizeof (struct ctf_archive_modent), sort_modent_by_name,
               nametbl);
 
@@ -174,8 +177,8 @@ ctf_arc_write_fd (int fd, ctf_file_t **ctf_files, size_t ctf_file_cnt,
 
   if ((nameoffs = lseek (fd, 0, SEEK_CUR)) < 0)
     {
-      errmsg = "ctf_arc_write(): Cannot get current file position "
-       "in archive: %s\n";
+      errmsg = N_("ctf_arc_write(): cannot get current file position "
+                 "in archive");
       goto err_free;
     }
   archdr->ctfa_names = htole64 (nameoffs);
@@ -185,7 +188,7 @@ ctf_arc_write_fd (int fd, ctf_file_t **ctf_files, size_t ctf_file_cnt,
       ssize_t len;
       if ((len = write (fd, np, namesz)) < 0)
        {
-         errmsg = "ctf_arc_write(): Cannot write name table to archive: %s\n";
+         errmsg = N_("ctf_arc_write(): cannot write name table to archive");
          goto err_free;
        }
       namesz -= len;
@@ -204,19 +207,22 @@ err_free:
 err_unmap:
   arc_mmap_unmap (archdr, headersz, NULL);
 err:
-  ctf_dprintf (errmsg, errno < ECTF_BASE ? strerror (errno) :
-              ctf_errmsg (errno));
+  /* We report errors into the first file in the archive, if any: if this is a
+     zero-file archive, put it in the open-errors stream for lack of anywhere
+     else for it to go.  */
+  ctf_err_warn (ctf_dict_cnt > 0 ? ctf_dicts[0] : NULL, 0, errno, "%s",
+               gettext (errmsg));
   return errno;
 }
 
-/* Write out a CTF archive.  The entries in CTF_FILES are referenced by name:
-   the names are passed in the names array, which must have CTF_FILES entries.
+/* Write out a CTF archive.  The entries in CTF_DICTS are referenced by name:
+   the names are passed in the names array, which must have CTF_DICTS entries.
 
    If the filename is NULL, create a temporary file and return a pointer to it.
 
    Returns 0 on success, or an errno, or an ECTF_* value.  */
 int
-ctf_arc_write (const char *file, ctf_file_t ** ctf_files, size_t ctf_file_cnt,
+ctf_arc_write (const char *file, ctf_dict_t **ctf_dicts, size_t ctf_dict_cnt,
               const char **names, size_t threshold)
 {
   int err;
@@ -224,30 +230,23 @@ ctf_arc_write (const char *file, ctf_file_t ** ctf_files, size_t ctf_file_cnt,
 
   if ((fd = open (file, O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0666)) < 0)
     {
-      ctf_dprintf ("ctf_arc_write(): cannot create %s: %s\n", file,
-                  strerror (errno));
+      ctf_err_warn (ctf_dict_cnt > 0 ? ctf_dicts[0] : NULL, 0, errno,
+                   _("ctf_arc_write(): cannot create %s"), file);
       return errno;
     }
 
-  err = ctf_arc_write_fd (fd, ctf_files, ctf_file_cnt, names, threshold);
+  err = ctf_arc_write_fd (fd, ctf_dicts, ctf_dict_cnt, names, threshold);
   if (err)
-    goto err;
+    goto err_close;
 
   if ((err = close (fd)) < 0)
-    {
-      ctf_dprintf ("ctf_arc_write(): Cannot close after writing to archive: "
-                  "%s\n", strerror (errno));
-      goto err_close;
-    }
-
- err:
-  close (fd);
-  if (err < 0)
-    unlink (file);
-
-  return err;
+    ctf_err_warn (ctf_dict_cnt > 0 ? ctf_dicts[0] : NULL, 0, errno,
+                 _("ctf_arc_write(): cannot close after writing to archive"));
+  goto err;
 
  err_close:
+  (void) close (fd);
+ err:
   if (err < 0)
     unlink (file);
 
@@ -259,13 +258,13 @@ ctf_arc_write (const char *file, ctf_file_t ** ctf_files, size_t ctf_file_cnt,
    negative errno or ctf_errno value.  On error, the file position may no longer
    be at the end of the file.  */
 static off_t
-arc_write_one_ctf (ctf_file_t * f, int fd, size_t threshold)
+arc_write_one_ctf (ctf_dict_t * f, int fd, size_t threshold)
 {
   off_t off, end_off;
   uint64_t ctfsz = 0;
   char *ctfszp;
   size_t ctfsz_len;
-  int (*writefn) (ctf_file_t * fp, int fd);
+  int (*writefn) (ctf_dict_t * fp, int fd);
 
   if (ctf_serialize (f) < 0)
     return f->ctf_errno * -1;
@@ -332,32 +331,124 @@ sort_modent_by_name (const void *one, const void *two, void *n)
                 &nametbl[le64toh (b->name_offset)]);
 }
 
-/* bsearch() function to search for a given name in the sorted array of struct
+/* bsearch_r() function to search for a given name in the sorted array of struct
    ctf_archive_modents.  */
 static int
-search_modent_by_name (const void *key, const void *ent)
+search_modent_by_name (const void *key, const void *ent, void *arg)
 {
   const char *k = key;
   const struct ctf_archive_modent *v = ent;
+  const char *search_nametbl = arg;
 
   return strcmp (k, &search_nametbl[le64toh (v->name_offset)]);
 }
 
-/* A trivial wrapper: open a CTF archive, from data in a buffer (which the
-   caller must preserve until ctf_arc_close() time).  Returns the archive, or
-   NULL and an error in *err (if not NULL).  */
-struct ctf_archive *
-ctf_arc_bufopen (const void *buf, size_t size _libctf_unused_, int *errp)
+/* Make a new struct ctf_archive_internal wrapper for a ctf_archive or a
+   ctf_dict.  Closes ARC and/or FP on error.  Arrange to free the SYMSECT or
+   STRSECT, as needed, on close.  Possibly do not unmap on close.  */
+
+struct ctf_archive_internal *
+ctf_new_archive_internal (int is_archive, int unmap_on_close,
+                         struct ctf_archive *arc,
+                         ctf_dict_t *fp, const ctf_sect_t *symsect,
+                         const ctf_sect_t *strsect,
+                         int *errp)
 {
-  struct ctf_archive *arc = (struct ctf_archive *) buf;
+  struct ctf_archive_internal *arci;
 
-  if (le64toh (arc->ctfa_magic) != CTFA_MAGIC)
+  if ((arci = calloc (1, sizeof (struct ctf_archive_internal))) == NULL)
     {
-      if (errp)
-       *errp = ECTF_FMT;
-      return NULL;
+      if (is_archive)
+       {
+         if (unmap_on_close)
+           ctf_arc_close_internal (arc);
+       }
+      else
+       ctf_dict_close (fp);
+      return (ctf_set_open_errno (errp, errno));
     }
-  return arc;
+  arci->ctfi_is_archive = is_archive;
+  if (is_archive)
+    arci->ctfi_archive = arc;
+  else
+    arci->ctfi_dict = fp;
+  if (symsect)
+     memcpy (&arci->ctfi_symsect, symsect, sizeof (struct ctf_sect));
+  if (strsect)
+     memcpy (&arci->ctfi_strsect, strsect, sizeof (struct ctf_sect));
+  arci->ctfi_free_symsect = 0;
+  arci->ctfi_free_strsect = 0;
+  arci->ctfi_unmap_on_close = unmap_on_close;
+  arci->ctfi_symsect_little_endian = -1;
+
+  return arci;
+}
+
+/* Set the symbol-table endianness of an archive (defaulting the symtab
+   endianness of all ctf_file_t's opened from that archive).  */
+void
+ctf_arc_symsect_endianness (ctf_archive_t *arc, int little_endian)
+{
+  arc->ctfi_symsect_little_endian = !!little_endian;
+  if (!arc->ctfi_is_archive)
+    ctf_symsect_endianness (arc->ctfi_dict, arc->ctfi_symsect_little_endian);
+}
+
+/* Get the CTF preamble from data in a buffer, which may be either an archive or
+   a CTF dict.  If multiple dicts are present in an archive, the preamble comes
+   from an arbitrary dict.  The preamble is a pointer into the ctfsect passed
+   in.  */
+
+const ctf_preamble_t *
+ctf_arc_bufpreamble (const ctf_sect_t *ctfsect)
+{
+  if (ctfsect->cts_size > sizeof (uint64_t) &&
+      (le64toh ((*(uint64_t *) ctfsect->cts_data)) == CTFA_MAGIC))
+    {
+      struct ctf_archive *arc = (struct ctf_archive *) ctfsect->cts_data;
+      return (const ctf_preamble_t *) ((char *) arc + le64toh (arc->ctfa_ctfs)
+                                      + sizeof (uint64_t));
+    }
+  else
+    return (const ctf_preamble_t *) ctfsect->cts_data;
+}
+
+/* Open a CTF archive or dictionary from data in a buffer (which the caller must
+   preserve until ctf_arc_close() time).  Returns the archive, or NULL and an
+   error in *err (if not NULL).  */
+ctf_archive_t *
+ctf_arc_bufopen (const ctf_sect_t *ctfsect, const ctf_sect_t *symsect,
+                const ctf_sect_t *strsect, int *errp)
+{
+  struct ctf_archive *arc = NULL;
+  int is_archive;
+  ctf_dict_t *fp = NULL;
+
+  if (ctfsect->cts_size > sizeof (uint64_t) &&
+      (le64toh ((*(uint64_t *) ctfsect->cts_data)) == CTFA_MAGIC))
+    {
+      /* The archive is mmappable, so this operation is trivial.
+
+        This buffer is nonmodifiable, so the trick involving mmapping only part
+        of it and storing the length in the magic number is not applicable: so
+        record this fact in the archive-wrapper header.  (We cannot record it
+        in the archive, because the archive may very well be a read-only
+        mapping.)  */
+
+      is_archive = 1;
+      arc = (struct ctf_archive *) ctfsect->cts_data;
+    }
+  else
+    {
+      is_archive = 0;
+      if ((fp = ctf_bufopen (ctfsect, symsect, strsect, errp)) == NULL)
+       {
+         ctf_err_warn (NULL, 0, *errp, _("ctf_arc_bufopen(): cannot open CTF"));
+         return NULL;
+       }
+    }
+  return ctf_new_archive_internal (is_archive, 0, arc, fp, symsect, strsect,
+                                  errp);
 }
 
 /* Open a CTF archive.  Returns the archive, or NULL and an error in *err (if
@@ -373,24 +464,24 @@ ctf_arc_open_internal (const char *filename, int *errp)
   libctf_init_debug();
   if ((fd = open (filename, O_RDONLY)) < 0)
     {
-      errmsg = "ctf_arc_open(): cannot open %s: %s\n";
+      errmsg = N_("ctf_arc_open(): cannot open %s");
       goto err;
     }
   if (fstat (fd, &s) < 0)
     {
-      errmsg = "ctf_arc_open(): cannot stat %s: %s\n";
+      errmsg = N_("ctf_arc_open(): cannot stat %s");
       goto err_close;
     }
 
   if ((arc = arc_mmap_file (fd, s.st_size)) == NULL)
     {
-      errmsg = "ctf_arc_open(): Cannot read in %s: %s\n";
+      errmsg = N_("ctf_arc_open(): cannot read in %s");
       goto err_close;
     }
 
   if (le64toh (arc->ctfa_magic) != CTFA_MAGIC)
     {
-      errmsg = "ctf_arc_open(): Invalid magic number";
+      errmsg = N_("ctf_arc_open(): %s: invalid magic number");
       errno = ECTF_FMT;
       goto err_unmap;
     }
@@ -409,8 +500,7 @@ err_close:
 err:
   if (errp)
     *errp = errno;
-  ctf_dprintf (errmsg, filename, errno < ECTF_BASE ? strerror (errno) :
-              ctf_errmsg (errno));
+  ctf_err_warn (NULL, 0, errno, gettext (errmsg), filename);
   return NULL;
 }
 
@@ -433,39 +523,49 @@ ctf_arc_close (ctf_archive_t *arc)
     return;
 
   if (arc->ctfi_is_archive)
-    ctf_arc_close_internal (arc->ctfi_archive);
+    {
+      if (arc->ctfi_unmap_on_close)
+       ctf_arc_close_internal (arc->ctfi_archive);
+    }
   else
-    ctf_file_close (arc->ctfi_file);
-  free ((void *) arc->ctfi_symsect.cts_data);
-  /* Do not free the ctfi_strsect: it is bound to the bfd.  */
+    ctf_dict_close (arc->ctfi_dict);
+  free (arc->ctfi_syms);
+  free (arc->ctfi_symdicts);
+  ctf_dynhash_destroy (arc->ctfi_dicts);
+  if (arc->ctfi_free_symsect)
+    free ((void *) arc->ctfi_symsect.cts_data);
+  if (arc->ctfi_free_strsect)
+    free ((void *) arc->ctfi_strsect.cts_data);
   free (arc->ctfi_data);
   if (arc->ctfi_bfd_close)
     arc->ctfi_bfd_close (arc);
   free (arc);
 }
 
-/* Return the ctf_file_t with the given name, or NULL if none, setting 'err' if
+/* Return the ctf_dict_t with the given name, or NULL if none, setting 'err' if
    non-NULL.  A name of NULL means to open the default file.  */
-static ctf_file_t *
-ctf_arc_open_by_name_internal (const struct ctf_archive *arc,
-                              const ctf_sect_t *symsect,
-                              const ctf_sect_t *strsect,
-                              const char *name, int *errp)
+static ctf_dict_t *
+ctf_dict_open_internal (const struct ctf_archive *arc,
+                       const ctf_sect_t *symsect,
+                       const ctf_sect_t *strsect,
+                       const char *name, int little_endian,
+                       int *errp)
 {
   struct ctf_archive_modent *modent;
+  const char *search_nametbl;
 
   if (name == NULL)
     name = _CTF_SECTION;                /* The default name.  */
 
-  ctf_dprintf ("ctf_arc_open_by_name(%s): opening\n", name);
+  ctf_dprintf ("ctf_dict_open_internal(%s): opening\n", name);
 
   modent = (ctf_archive_modent_t *) ((char *) arc
                                     + sizeof (struct ctf_archive));
 
-  search_nametbl = (char *) arc + le64toh (arc->ctfa_names);
-  modent = bsearch (name, modent, le64toh (arc->ctfa_nfiles),
-                   sizeof (struct ctf_archive_modent),
-                   search_modent_by_name);
+  search_nametbl = (const char *) arc + le64toh (arc->ctfa_names);
+  modent = bsearch_r (name, modent, le64toh (arc->ctfa_ndicts),
+                     sizeof (struct ctf_archive_modent),
+                     search_modent_by_name, (void *) search_nametbl);
 
   /* This is actually a common case and normal operation: no error
      debug output.  */
@@ -476,30 +576,35 @@ ctf_arc_open_by_name_internal (const struct ctf_archive *arc,
       return NULL;
     }
 
-  return ctf_arc_open_by_offset (arc, symsect, strsect,
-                                le64toh (modent->ctf_offset), errp);
+  return ctf_dict_open_by_offset (arc, symsect, strsect,
+                                 le64toh (modent->ctf_offset),
+                                 little_endian, errp);
 }
 
-/* Return the ctf_file_t with the given name, or NULL if none, setting 'err' if
+/* Return the ctf_dict_t with the given name, or NULL if none, setting 'err' if
    non-NULL.  A name of NULL means to open the default file.
 
    Use the specified string and symbol table sections.
 
    Public entry point.  */
-ctf_file_t *
-ctf_arc_open_by_name_sections (const ctf_archive_t *arc,
-                              const ctf_sect_t *symsect,
-                              const ctf_sect_t *strsect,
-                              const char *name,
-                              int *errp)
+ctf_dict_t *
+ctf_dict_open_sections (const ctf_archive_t *arc,
+                       const ctf_sect_t *symsect,
+                       const ctf_sect_t *strsect,
+                       const char *name,
+                       int *errp)
 {
   if (arc->ctfi_is_archive)
     {
-      ctf_file_t *ret;
-      ret = ctf_arc_open_by_name_internal (arc->ctfi_archive, symsect, strsect,
-                                          name, errp);
+      ctf_dict_t *ret;
+      ret = ctf_dict_open_internal (arc->ctfi_archive, symsect, strsect,
+                                   name, arc->ctfi_symsect_little_endian,
+                                   errp);
       if (ret)
-       ret->ctf_archive = (ctf_archive_t *) arc;
+       {
+         ret->ctf_archive = (ctf_archive_t *) arc;
+         ctf_arc_import_parent (arc, ret);
+       }
       return ret;
     }
 
@@ -509,19 +614,19 @@ ctf_arc_open_by_name_sections (const ctf_archive_t *arc,
        *errp = ECTF_ARNNAME;
       return NULL;
     }
-  arc->ctfi_file->ctf_archive = (ctf_archive_t *) arc;
+  arc->ctfi_dict->ctf_archive = (ctf_archive_t *) arc;
 
-  /* Bump the refcount so that the user can ctf_file_close() it.  */
-  arc->ctfi_file->ctf_refcnt++;
-  return arc->ctfi_file;
+  /* Bump the refcount so that the user can ctf_dict_close() it.  */
+  arc->ctfi_dict->ctf_refcnt++;
+  return arc->ctfi_dict;
 }
 
-/* Return the ctf_file_t with the given name, or NULL if none, setting 'err' if
+/* Return the ctf_dict_t with the given name, or NULL if none, setting 'err' if
    non-NULL.  A name of NULL means to open the default file.
 
    Public entry point.  */
-ctf_file_t *
-ctf_arc_open_by_name (const ctf_archive_t *arc, const char *name, int *errp)
+ctf_dict_t *
+ctf_dict_open (const ctf_archive_t *arc, const char *name, int *errp)
 {
   const ctf_sect_t *symsect = &arc->ctfi_symsect;
   const ctf_sect_t *strsect = &arc->ctfi_strsect;
@@ -531,21 +636,82 @@ ctf_arc_open_by_name (const ctf_archive_t *arc, const char *name, int *errp)
   if (strsect->cts_name == NULL)
     strsect = NULL;
 
-  return ctf_arc_open_by_name_sections (arc, symsect, strsect, name, errp);
+  return ctf_dict_open_sections (arc, symsect, strsect, name, errp);
+}
+
+static void
+ctf_cached_dict_close (void *fp)
+{
+  ctf_dict_close ((ctf_dict_t *) fp);
 }
 
-/* Return the ctf_file_t at the given ctfa_ctfs-relative offset, or NULL if
+/* Return the ctf_dict_t with the given name and cache it in the
+   archive's ctfi_dicts.  */
+static ctf_dict_t *
+ctf_dict_open_cached (ctf_archive_t *arc, const char *name, int *errp)
+{
+  ctf_dict_t *fp;
+  char *dupname;
+
+  /* Just return from the cache if possible.  */
+  if (arc->ctfi_dicts
+      && ((fp = ctf_dynhash_lookup (arc->ctfi_dicts, name)) != NULL))
+    {
+      fp->ctf_refcnt++;
+      return fp;
+    }
+
+  /* Not yet cached: open it.  */
+  fp = ctf_dict_open (arc, name, errp);
+  dupname = strdup (name);
+
+  if (!fp || !dupname)
+    goto oom;
+
+  if (arc->ctfi_dicts == NULL)
+    if ((arc->ctfi_dicts
+        = ctf_dynhash_create (ctf_hash_string, ctf_hash_eq_string,
+                              free, ctf_cached_dict_close)) == NULL)
+      goto oom;
+
+  if (ctf_dynhash_insert (arc->ctfi_dicts, dupname, fp) < 0)
+    goto oom;
+  fp->ctf_refcnt++;
+
+  return fp;
+
+ oom:
+  ctf_dict_close (fp);
+  free (dupname);
+  if (errp)
+    *errp = ENOMEM;
+  return NULL;
+}
+
+/* Flush any caches the CTF archive may have open.  */
+void
+ctf_arc_flush_caches (ctf_archive_t *wrapper)
+{
+  free (wrapper->ctfi_symdicts);
+  free (wrapper->ctfi_syms);
+  ctf_dynhash_destroy (wrapper->ctfi_dicts);
+  wrapper->ctfi_symdicts = NULL;
+  wrapper->ctfi_syms = NULL;
+  wrapper->ctfi_dicts = NULL;
+}
+
+/* Return the ctf_dict_t at the given ctfa_ctfs-relative offset, or NULL if
    none, setting 'err' if non-NULL.  */
-static ctf_file_t *
-ctf_arc_open_by_offset (const struct ctf_archive *arc,
-                       const ctf_sect_t *symsect,
-                       const ctf_sect_t *strsect, size_t offset,
-                       int *errp)
+static ctf_dict_t *
+ctf_dict_open_by_offset (const struct ctf_archive *arc,
+                        const ctf_sect_t *symsect,
+                        const ctf_sect_t *strsect, size_t offset,
+                        int little_endian, int *errp)
 {
   ctf_sect_t ctfsect;
-  ctf_file_t *fp;
+  ctf_dict_t *fp;
 
-  ctf_dprintf ("ctf_arc_open_by_offset(%lu): opening\n", (unsigned long) offset);
+  ctf_dprintf ("ctf_dict_open_by_offset(%lu): opening\n", (unsigned long) offset);
 
   memset (&ctfsect, 0, sizeof (ctf_sect_t));
 
@@ -557,10 +723,194 @@ ctf_arc_open_by_offset (const struct ctf_archive *arc,
   ctfsect.cts_data = (void *) ((char *) arc + offset + sizeof (uint64_t));
   fp = ctf_bufopen (&ctfsect, symsect, strsect, errp);
   if (fp)
-    ctf_setmodel (fp, le64toh (arc->ctfa_model));
+    {
+      ctf_setmodel (fp, le64toh (arc->ctfa_model));
+      if (little_endian >= 0)
+       ctf_symsect_endianness (fp, little_endian);
+    }
   return fp;
 }
 
+/* Backward compatibility.  */
+ctf_dict_t *
+ctf_arc_open_by_name (const ctf_archive_t *arc, const char *name,
+                     int *errp)
+{
+  return ctf_dict_open (arc, name, errp);
+}
+
+ctf_dict_t *
+ctf_arc_open_by_name_sections (const ctf_archive_t *arc,
+                              const ctf_sect_t *symsect,
+                              const ctf_sect_t *strsect,
+                              const char *name,
+                              int *errp)
+{
+  return ctf_dict_open_sections (arc, symsect, strsect, name, errp);
+}
+
+/* Import the parent into a ctf archive, if this is a child, the parent is not
+   already set, and a suitable archive member exists.  No error is raised if
+   this is not possible: this is just a best-effort helper operation to give
+   people useful dicts to start with.  */
+static void
+ctf_arc_import_parent (const ctf_archive_t *arc, ctf_dict_t *fp)
+{
+  if ((fp->ctf_flags & LCTF_CHILD) && fp->ctf_parname && !fp->ctf_parent)
+    {
+      ctf_dict_t *parent = ctf_dict_open_cached ((ctf_archive_t *) arc,
+                                                fp->ctf_parname, NULL);
+      if (parent)
+       {
+         ctf_import (fp, parent);
+         ctf_dict_close (parent);
+       }
+    }
+}
+
+/* Return the number of members in an archive.  */
+size_t
+ctf_archive_count (const ctf_archive_t *wrapper)
+{
+  if (!wrapper->ctfi_is_archive)
+    return 1;
+
+  return wrapper->ctfi_archive->ctfa_ndicts;
+}
+
+/* Look up a symbol in an archive.  Return the dict in the archive that the
+   symbol is found in, and (optionally) the ctf_id_t of the symbol in that dict
+   (so you don't have to look it up yourself).  The dict and mapping are both
+   cached, so repeated lookups are nearly free.
+
+   As usual, you should ctf_dict_close() the returned dict once you are done
+   with it.
+
+   Returns NULL on error, and an error in errp (if set).  */
+
+ctf_dict_t *
+ctf_arc_lookup_symbol (ctf_archive_t *wrapper, unsigned long symidx,
+                      ctf_id_t *typep, int *errp)
+{
+  ctf_dict_t *fp;
+  ctf_id_t type;
+
+  /* The usual non-archive-transparent-wrapper special case.  */
+  if (!wrapper->ctfi_is_archive)
+    {
+      if ((type = ctf_lookup_by_symbol (wrapper->ctfi_dict, symidx)) == CTF_ERR)
+       {
+         if (errp)
+           *errp = ctf_errno (wrapper->ctfi_dict);
+         return NULL;
+       }
+      if (typep)
+       *typep = type;
+      wrapper->ctfi_dict->ctf_refcnt++;
+      return wrapper->ctfi_dict;
+    }
+
+  if (wrapper->ctfi_symsect.cts_name == NULL
+      || wrapper->ctfi_symsect.cts_data == NULL
+      || wrapper->ctfi_symsect.cts_size == 0
+      || wrapper->ctfi_symsect.cts_entsize == 0)
+    {
+      if (errp)
+       *errp = ECTF_NOSYMTAB;
+      return NULL;
+    }
+
+  /* Make enough space for all possible symbols, if not already done.
+     We cache both the ctf_id_t and the originating dictionary of all symbols.
+     The dict links are weak, to the dictionaries cached in ctfi_dicts: their
+     refcnts are *not* bumped.  */
+
+  if (!wrapper->ctfi_syms)
+    {
+      if ((wrapper->ctfi_syms = calloc (wrapper->ctfi_symsect.cts_size
+                                       / wrapper->ctfi_symsect.cts_entsize,
+                                       sizeof (ctf_id_t))) == NULL)
+       {
+         if (errp)
+           *errp = ENOMEM;
+         return NULL;
+       }
+    }
+  if (!wrapper->ctfi_symdicts)
+    {
+      if ((wrapper->ctfi_symdicts = calloc (wrapper->ctfi_symsect.cts_size
+                                           / wrapper->ctfi_symsect.cts_entsize,
+                                           sizeof (ctf_dict_t *))) == NULL)
+       {
+         if (errp)
+           *errp = ENOMEM;
+         return NULL;
+       }
+    }
+
+  /* Perhaps it's cached.  */
+  if (wrapper->ctfi_symdicts[symidx] != NULL)
+    {
+      if (wrapper->ctfi_symdicts[symidx] == &enosym)
+       {
+         if (errp)
+           *errp = ECTF_NOTYPEDAT;
+         if (typep)
+           *typep = CTF_ERR;
+         return NULL;
+       }
+
+      if (typep)
+       *typep = wrapper->ctfi_syms[symidx];
+      wrapper->ctfi_symdicts[symidx]->ctf_refcnt++;
+      return wrapper->ctfi_symdicts[symidx];
+    }
+
+  /* Not cached: find it and cache it.  We must track open errors ourselves even
+     if our caller doesn't, to be able to distinguish no-error end-of-iteration
+     from open errors.  */
+
+  int local_err;
+  int *local_errp;
+  ctf_next_t *i = NULL;
+  const char *name;
+
+  if (errp)
+    local_errp = errp;
+  else
+    local_errp = &local_err;
+
+  while ((fp = ctf_archive_next (wrapper, &i, &name, 0, local_errp)) != NULL)
+    {
+      if ((type = ctf_lookup_by_symbol (fp, symidx)) != CTF_ERR)
+       {
+         wrapper->ctfi_syms[symidx] = type;
+         wrapper->ctfi_symdicts[symidx] = fp;
+         ctf_next_destroy (i);
+
+         if (typep)
+           *typep = type;
+         return fp;
+       }
+      ctf_dict_close (fp);
+    }
+  if (*local_errp != ECTF_NEXT_END)
+    {
+      ctf_next_destroy (i);
+      return NULL;
+    }
+  /* Don't leak end-of-iteration to the caller.  */
+  *local_errp = 0;
+
+  wrapper->ctfi_symdicts[symidx] = &enosym;
+
+  if (errp)
+    *errp = ECTF_NOTYPEDAT;
+  if (typep)
+    *typep = CTF_ERR;
+  return NULL;
+}
+
 /* Raw iteration over all CTF files in an archive.  We pass the raw data for all
    CTF files in turn to the specified callback function.  */
 static int
@@ -576,7 +926,7 @@ ctf_archive_raw_iter_internal (const struct ctf_archive *arc,
                                     + sizeof (struct ctf_archive));
   nametbl = (((const char *) arc) + le64toh (arc->ctfa_names));
 
-  for (i = 0; i < le64toh (arc->ctfa_nfiles); i++)
+  for (i = 0; i < le64toh (arc->ctfa_ndicts); i++)
     {
       const char *name;
       char *fp;
@@ -616,7 +966,7 @@ ctf_archive_iter_internal (const ctf_archive_t *wrapper,
 {
   int rc;
   size_t i;
-  ctf_file_t *f;
+  ctf_dict_t *f;
   struct ctf_archive_modent *modent;
   const char *nametbl;
 
@@ -624,23 +974,26 @@ ctf_archive_iter_internal (const ctf_archive_t *wrapper,
                                     + sizeof (struct ctf_archive));
   nametbl = (((const char *) arc) + le64toh (arc->ctfa_names));
 
-  for (i = 0; i < le64toh (arc->ctfa_nfiles); i++)
+  for (i = 0; i < le64toh (arc->ctfa_ndicts); i++)
     {
       const char *name;
 
       name = &nametbl[le64toh (modent[i].name_offset)];
-      if ((f = ctf_arc_open_by_name_internal (arc, symsect, strsect,
-                                             name, &rc)) == NULL)
+      if ((f = ctf_dict_open_internal (arc, symsect, strsect,
+                                      name,
+                                      wrapper->ctfi_symsect_little_endian,
+                                      &rc)) == NULL)
        return rc;
 
       f->ctf_archive = (ctf_archive_t *) wrapper;
+      ctf_arc_import_parent (wrapper, f);
       if ((rc = func (f, name, data)) != 0)
        {
-         ctf_file_close (f);
+         ctf_dict_close (f);
          return rc;
        }
 
-      ctf_file_close (f);
+      ctf_dict_close (f);
     }
   return 0;
 }
@@ -663,7 +1016,103 @@ ctf_archive_iter (const ctf_archive_t *arc, ctf_archive_member_f *func,
     return ctf_archive_iter_internal (arc, arc->ctfi_archive, symsect, strsect,
                                      func, data);
 
-  return func (arc->ctfi_file, _CTF_SECTION, data);
+  return func (arc->ctfi_dict, _CTF_SECTION, data);
+}
+
+/* Iterate over all CTF files in an archive, returning each dict in turn as a
+   ctf_dict_t, and NULL on error or end of iteration.  It is the caller's
+   responsibility to close it.  Parent dicts may be skipped.  Regardless of
+   whether they are skipped or not, the caller must ctf_import the parent if
+   need be.
+
+   The archive member is cached for rapid return on future calls.
+
+   We identify parents by name rather than by flag value: for now, with the
+   linker only emitting parents named _CTF_SECTION, this works well enough.  */
+
+ctf_dict_t *
+ctf_archive_next (const ctf_archive_t *wrapper, ctf_next_t **it, const char **name,
+                 int skip_parent, int *errp)
+{
+  ctf_dict_t *f;
+  ctf_next_t *i = *it;
+  struct ctf_archive *arc;
+  struct ctf_archive_modent *modent;
+  const char *nametbl;
+  const char *name_;
+
+  if (!i)
+    {
+      if ((i = ctf_next_create()) == NULL)
+       {
+         if (errp)
+           *errp = ENOMEM;
+         return NULL;
+       }
+      i->cu.ctn_arc = wrapper;
+      i->ctn_iter_fun = (void (*) (void)) ctf_archive_next;
+      *it = i;
+    }
+
+  if ((void (*) (void)) ctf_archive_next != i->ctn_iter_fun)
+    {
+      if (errp)
+       *errp = ECTF_NEXT_WRONGFUN;
+      return NULL;
+    }
+
+  if (wrapper != i->cu.ctn_arc)
+    {
+      if (errp)
+       *errp = ECTF_NEXT_WRONGFP;
+      return NULL;
+    }
+
+  /* Iteration is made a bit more complex by the need to handle ctf_dict_t's
+     transparently wrapped in a single-member archive.  These are parents: if
+     skip_parent is on, they are skipped and the iterator terminates
+     immediately.  */
+
+  if (!wrapper->ctfi_is_archive && i->ctn_n == 0)
+    {
+      i->ctn_n++;
+      if (!skip_parent)
+       {
+         wrapper->ctfi_dict->ctf_refcnt++;
+         return wrapper->ctfi_dict;
+       }
+    }
+
+  arc = wrapper->ctfi_archive;
+
+  /* The loop keeps going when skip_parent is on as long as the member we find
+     is the parent (i.e. at most two iterations, but possibly an early return if
+     *all* we have is a parent).  */
+
+  do
+    {
+      if ((!wrapper->ctfi_is_archive) || (i->ctn_n >= le64toh (arc->ctfa_ndicts)))
+       {
+         ctf_next_destroy (i);
+         *it = NULL;
+         if (errp)
+           *errp = ECTF_NEXT_END;
+         return NULL;
+       }
+
+      modent = (ctf_archive_modent_t *) ((char *) arc
+                                        + sizeof (struct ctf_archive));
+      nametbl = (((const char *) arc) + le64toh (arc->ctfa_names));
+
+      name_ = &nametbl[le64toh (modent[i->ctn_n].name_offset)];
+      i->ctn_n++;
+    } while (skip_parent && strcmp (name_, _CTF_SECTION) == 0);
+
+  if (name)
+    *name = name_;
+
+  f = ctf_dict_open_cached ((ctf_archive_t *) wrapper, name_, errp);
+  return f;
 }
 
 #ifdef HAVE_MMAP
@@ -696,7 +1145,8 @@ static int arc_mmap_writeout (int fd _libctf_unused_, void *header,
     if (msync (header, headersz, MS_ASYNC) < 0)
     {
       if (errmsg)
-       *errmsg = "arc_mmap_writeout(): Cannot sync after writing to %s: %s\n";
+       *errmsg = N_("arc_mmap_writeout(): cannot sync after writing "
+                    "to %s: %s");
       return -1;
     }
     return 0;
@@ -708,7 +1158,8 @@ static int arc_mmap_unmap (void *header, size_t headersz, const char **errmsg)
   if (munmap (header, headersz) < 0)
     {
       if (errmsg)
-       *errmsg = "arc_mmap_munmap(): Cannot unmap after writing to %s: %s\n";
+       *errmsg = N_("arc_mmap_munmap(): cannot unmap after writing "
+                    "to %s: %s");
       return -1;
     }
     return 0;
@@ -752,8 +1203,8 @@ static int arc_mmap_writeout (int fd, void *header, size_t headersz,
   if ((lseek (fd, 0, SEEK_SET)) < 0)
     {
       if (errmsg)
-       *errmsg = "arc_mmap_writeout(): Cannot seek while writing header to "
-         "%s: %s\n";
+       *errmsg = N_("arc_mmap_writeout(): cannot seek while writing header to "
+                    "%s: %s");
       return -1;
     }
 
@@ -762,7 +1213,7 @@ static int arc_mmap_writeout (int fd, void *header, size_t headersz,
       if ((len = write (fd, data, count)) < 0)
        {
          if (errmsg)
-           *errmsg = "arc_mmap_writeout(): Cannot write header to %s: %s\n";
+           *errmsg = N_("arc_mmap_writeout(): cannot write header to %s: %s");
          return len;
        }
       if (len == EINTR)