diff gcc/go/gofrontend/import-archive.cc @ 111:04ced10e8804

gcc 7
author kono
date Fri, 27 Oct 2017 22:46:09 +0900
parents
children 1830386684a0
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gcc/go/gofrontend/import-archive.cc	Fri Oct 27 22:46:09 2017 +0900
@@ -0,0 +1,925 @@
+// import-archive.cc -- Go frontend read import data from an archive file.
+
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "go-system.h"
+
+#include "go-diagnostics.h"
+#include "import.h"
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+// Archive magic numbers.
+
+static const char armag[] =
+{
+  '!', '<', 'a', 'r', 'c', 'h', '>', '\n'
+};
+
+static const char armagt[] =
+{
+  '!', '<', 't', 'h', 'i', 'n', '>', '\n'
+};
+
+static const char armagb[] =
+{
+  '<', 'b', 'i', 'g', 'a', 'f', '>', '\n'
+};
+
+static const char arfmag[2] = { '`', '\n' };
+
+// Archive fixed length header for AIX big format.
+
+struct Archive_fl_header
+{
+  // Archive magic string.
+  char fl_magic[8];
+  // Offset to member table.
+  char fl_memoff[20];
+  // Offset to global symbol table.
+  char fl_gstoff[20];
+  // Offset to global symbol table for 64-bit objects.
+  char fl_gst64off[20];
+  // Offset to first archive member.
+  char fl_fstmoff[20];
+  // Offset to last archive member.
+  char fl_lstmoff[20];
+  // Offset to first member on free list.
+  char fl_freeoff[20];
+};
+
+// The header of an entry in an archive.  This is all readable text,
+// padded with spaces where necesary.
+
+struct Archive_header
+{
+  // The entry name.
+  char ar_name[16];
+  // The file modification time.
+  char ar_date[12];
+  // The user's UID in decimal.
+  char ar_uid[6];
+  // The user's GID in decimal.
+  char ar_gid[6];
+  // The file mode in octal.
+  char ar_mode[8];
+  // The file size in decimal.
+  char ar_size[10];
+  // The final magic code.
+  char ar_fmag[2];
+};
+
+// The header of an entry in an AIX big archive.
+// This is followed by ar_namlen bytes + 2 bytes for arfmag.
+
+struct Archive_big_header
+{
+  // The file size in decimal.
+  char ar_size[20];
+  // The next member offset in decimal.
+  char ar_nxtmem[20];
+  // The previous member offset in decimal.
+  char ar_prvmem[20];
+  // The file modification time in decimal.
+  char ar_date[12];
+  // The user's UID in decimal.
+  char ar_uid[12];
+  // The user's GID in decimal.
+  char ar_gid[12];
+  // The file mode in octal.
+  char ar_mode[12];
+  // The file name length in decimal.
+  char ar_namlen[4];
+};
+
+// The functions in this file extract Go export data from an archive.
+
+const int Import::archive_magic_len;
+
+// Return true if BYTES, which are from the start of the file, are an
+// archive magic number.
+
+bool
+Import::is_archive_magic(const char* bytes)
+{
+  return (memcmp(bytes, armag, Import::archive_magic_len) == 0
+	  || memcmp(bytes, armagt, Import::archive_magic_len) == 0
+	  || memcmp(bytes, armagb, Import::archive_magic_len) == 0);
+}
+
+// An object used to read an archive file.
+
+class Archive_file
+{
+ public:
+  Archive_file(const std::string& filename, int fd, Location location)
+    : filename_(filename), fd_(fd), filesize_(-1), first_member_offset_(0),
+      extended_names_(), is_thin_archive_(false), is_big_archive_(false),
+      location_(location), nested_archives_()
+  { }
+
+  // Initialize.
+  bool
+  initialize();
+
+  // Return the file name.
+  const std::string&
+  filename() const
+  { return this->filename_; }
+
+  // Get the file size.
+  off_t
+  filesize() const
+  { return this->filesize_; }
+
+  // Return the offset of the first member.
+  off_t
+  first_member_offset() const
+  { return this->first_member_offset_; }
+
+  // Return whether this is a thin archive.
+  bool
+  is_thin_archive() const
+  { return this->is_thin_archive_; }
+
+  // Return whether this is a big archive.
+  bool
+  is_big_archive() const
+  { return this->is_big_archive_; }
+
+  // Return the location of the import statement.
+  Location
+  location() const
+  { return this->location_; }
+
+  // Read bytes.
+  bool
+  read(off_t offset, off_t size, char*);
+
+  // Parse a decimal in readable text.
+  bool
+  parse_decimal(const char* str, off_t size, long* res) const;
+
+  // Read the archive header at OFF, setting *PNAME, *SIZE,
+  // *NESTED_OFF and *NEXT_OFF.
+  bool
+  read_header(off_t off, std::string* pname, off_t* size, off_t* nested_off,
+              off_t* next_off);
+
+  // Interpret the header of HDR, the header of the archive member at
+  // file offset OFF.  Return whether it succeeded.  Set *SIZE to the
+  // size of the member.  Set *PNAME to the name of the member.  Set
+  // *NESTED_OFF to the offset in a nested archive.
+  bool
+  interpret_header(const Archive_header* hdr, off_t off,
+		   std::string* pname, off_t* size, off_t* nested_off) const;
+
+  // Get the file and offset for an archive member.
+  bool
+  get_file_and_offset(off_t off, const std::string& hdrname,
+		      off_t nested_off, int* memfd, off_t* memoff,
+		      std::string* memname);
+
+ private:
+  // Initialize a big archive (AIX)
+  bool
+  initialize_big_archive();
+
+  // Initialize a normal archive
+  bool
+  initialize_archive();
+
+  // Read the big archive header at OFF, setting *PNAME, *SIZE and *NEXT_OFF.
+  bool
+  read_big_archive_header(off_t off, std::string* pname,
+                          off_t* size, off_t* next_off);
+
+  // Read the normal archive header at OFF, setting *PNAME, *SIZE,
+  // *NESTED_OFF and *NEXT_OFF.
+  bool
+  read_archive_header(off_t off, std::string* pname, off_t* size,
+                      off_t* nested_off, off_t* next_off);
+
+  // For keeping track of open nested archives in a thin archive file.
+  typedef std::map<std::string, Archive_file*> Nested_archive_table;
+
+  // The name of the file.
+  std::string filename_;
+  // The file descriptor.
+  int fd_;
+  // The file size;
+  off_t filesize_;
+  // The first member offset;
+  off_t first_member_offset_;
+  // The extended name table.
+  std::string extended_names_;
+  // Whether this is a thin archive.
+  bool is_thin_archive_;
+  // Whether this is a big archive.
+  bool is_big_archive_;
+  // The location of the import statements.
+  Location location_;
+  // Table of nested archives.
+  Nested_archive_table nested_archives_;
+};
+
+bool
+Archive_file::initialize()
+{
+  struct stat st;
+  if (fstat(this->fd_, &st) < 0)
+    {
+      go_error_at(this->location_, "%s: %m", this->filename_.c_str());
+      return false;
+    }
+  this->filesize_ = st.st_size;
+
+  char buf[sizeof(armagt)];
+  if (::lseek(this->fd_, 0, SEEK_SET) < 0
+      || ::read(this->fd_, buf, sizeof(armagt)) != sizeof(armagt))
+    {
+      go_error_at(this->location_, "%s: %m", this->filename_.c_str());
+      return false;
+    }
+  if (memcmp(buf, armagt, sizeof(armagt)) == 0)
+    this->is_thin_archive_ = true;
+  else if (memcmp(buf, armagb, sizeof(armagb)) == 0)
+    this->is_big_archive_ = true;
+
+  if (this->is_big_archive_)
+    return this->initialize_big_archive();
+  else
+    return this->initialize_archive();
+}
+
+// Initialize a big archive (AIX).
+
+bool
+Archive_file::initialize_big_archive()
+{
+  Archive_fl_header flhdr;
+
+  // Read the fixed length header.
+  if (::lseek(this->fd_, 0, SEEK_SET) < 0
+      || ::read(this->fd_, &flhdr, sizeof(flhdr)) != sizeof(flhdr))
+    {
+      go_error_at(this->location_, "%s: could not read archive header",
+                  this->filename_.c_str());
+      return false;
+    }
+
+  // Parse offset of the first member.
+  long off;
+  if (!this->parse_decimal(flhdr.fl_fstmoff, sizeof(flhdr.fl_fstmoff), &off))
+    {
+      char* buf = new char[sizeof(flhdr.fl_fstmoff) + 1];
+      memcpy(buf, flhdr.fl_fstmoff, sizeof(flhdr.fl_fstmoff));
+      go_error_at(this->location_,
+                  ("%s: malformed first member offset in archive header"
+                   " (expected decimal, got %s)"),
+                  this->filename_.c_str(), buf);
+      delete[] buf;
+      return false;
+    }
+  if (off == 0) // Empty archive.
+    this->first_member_offset_ = this->filesize_;
+  else
+    this->first_member_offset_ = off;
+  return true;
+}
+
+// Initialize a normal archive.
+
+bool
+Archive_file::initialize_archive()
+{
+  this->first_member_offset_ = sizeof(armag);
+  if (this->first_member_offset_ == this->filesize_)
+    {
+      // Empty archive.
+      return true;
+    }
+
+  // Look for the extended name table.
+  std::string filename;
+  off_t size;
+  off_t next_off;
+  if (!this->read_header(this->first_member_offset_, &filename,
+                         &size, NULL, &next_off))
+    return false;
+  if (filename.empty())
+    {
+      // We found the symbol table.
+      if (!this->read_header(next_off, &filename, &size, NULL, NULL))
+	filename.clear();
+    }
+  if (filename == "/")
+    {
+      char* rdbuf = new char[size];
+      if (::read(this->fd_, rdbuf, size) != size)
+	{
+	  go_error_at(this->location_, "%s: could not read extended names",
+		   filename.c_str());
+	  delete[] rdbuf;
+	  return false;
+	}
+      this->extended_names_.assign(rdbuf, size);
+      delete[] rdbuf;
+    }
+
+  return true;
+}
+
+// Read bytes from the file.
+
+bool
+Archive_file::read(off_t offset, off_t size, char* buf)
+{
+  if (::lseek(this->fd_, offset, SEEK_SET) < 0
+      || ::read(this->fd_, buf, size) != size)
+    {
+      go_error_at(this->location_, "%s: %m", this->filename_.c_str());
+      return false;
+    }
+  return true;
+}
+
+// Parse a decimal in readable text.
+
+bool
+Archive_file::parse_decimal(const char* str, off_t size, long* res) const
+{
+  char* buf = new char[size + 1];
+  memcpy(buf, str, size);
+  char* ps = buf + size;
+  while (ps > buf && ps[-1] == ' ')
+    --ps;
+  *ps = '\0';
+
+  errno = 0;
+  char* end;
+  *res = strtol(buf, &end, 10);
+  if (*end != '\0'
+      || *res < 0
+      || (*res == LONG_MAX && errno == ERANGE))
+    {
+      delete[] buf;
+      return false;
+    }
+  delete[] buf;
+  return true;
+}
+
+// Read the header at OFF.  Set *PNAME to the name, *SIZE to the size,
+// *NESTED_OFF to the nested offset, and *NEXT_OFF to the next member offset.
+
+bool
+Archive_file::read_header(off_t off, std::string* pname, off_t* size,
+			  off_t* nested_off, off_t* next_off)
+{
+  if (::lseek(this->fd_, off, SEEK_SET) < 0)
+    {
+      go_error_at(this->location_, "%s: %m", this->filename_.c_str());
+      return false;
+    }
+  if (this->is_big_archive_)
+    return this->read_big_archive_header(off, pname, size, next_off);
+  else
+    return this->read_archive_header(off, pname, size, nested_off, next_off);
+}
+
+// Read the big archive header at OFF, setting *PNAME, *SIZE and *NEXT_OFF.
+
+bool
+Archive_file::read_big_archive_header(off_t off, std::string* pname,
+                                      off_t* size, off_t* next_off)
+{
+  Archive_big_header hdr;
+  ssize_t got;
+
+  got = ::read(this->fd_, &hdr, sizeof hdr);
+  if (got != sizeof hdr)
+    {
+      if (got < 0)
+        go_error_at(this->location_, "%s: %m", this->filename_.c_str());
+      else if (got > 0)
+        go_error_at(this->location_, "%s: short entry header at %ld",
+                    this->filename_.c_str(), static_cast<long>(off));
+      else
+        go_error_at(this->location_, "%s: unexpected EOF at %ld",
+                    this->filename_.c_str(), static_cast<long>(off));
+    }
+
+  long local_size;
+  if (!this->parse_decimal(hdr.ar_size, sizeof(hdr.ar_size), &local_size))
+    {
+      char* buf = new char[sizeof(hdr.ar_size) + 1];
+      memcpy(buf, hdr.ar_size, sizeof(hdr.ar_size));
+      go_error_at(this->location_,
+                  ("%s: malformed ar_size in entry header at %ld"
+                   " (expected decimal, got %s)"),
+                  this->filename_.c_str(), static_cast<long>(off), buf);
+      delete[] buf;
+      return false;
+    }
+  *size = local_size;
+
+  long namlen;
+  if (!this->parse_decimal(hdr.ar_namlen, sizeof(hdr.ar_namlen), &namlen))
+    {
+      char* buf = new char[sizeof(hdr.ar_namlen) + 1];
+      memcpy(buf, hdr.ar_namlen, sizeof(hdr.ar_namlen));
+      go_error_at(this->location_,
+                  ("%s: malformed ar_namlen in entry header at %ld"
+                   " (expected decimal, got %s)"),
+                  this->filename_.c_str(), static_cast<long>(off), buf);
+      delete[] buf;
+      return false;
+    }
+  // Read member name following member header.
+  char* rdbuf = new char[namlen];
+  got = ::read(this->fd_, rdbuf, namlen);
+  if (got != namlen)
+    {
+      go_error_at(this->location_,
+                  "%s: malformed member name in entry header at %ld",
+                  this->filename_.c_str(), static_cast<long>(off));
+      delete[] rdbuf;
+      return false;
+    }
+  pname->assign(rdbuf, namlen);
+  delete[] rdbuf;
+
+  long local_next_off;
+  if (!this->parse_decimal(hdr.ar_nxtmem, sizeof(hdr.ar_nxtmem), &local_next_off))
+    {
+      char* buf = new char[sizeof(hdr.ar_nxtmem) + 1];
+      memcpy(buf, hdr.ar_nxtmem, sizeof(hdr.ar_nxtmem));
+      go_error_at(this->location_,
+                  ("%s: malformed ar_nxtmem in entry header at %ld"
+                   " (expected decimal, got %s)"),
+                  this->filename_.c_str(), static_cast<long>(off), buf);
+      delete[] buf;
+      return false;
+    }
+  if (next_off != NULL)
+    {
+      if (local_next_off == 0) // Last member.
+        *next_off = this->filesize_;
+      else
+        *next_off = local_next_off;
+    }
+  return true;
+}
+
+// Read the normal archive header at OFF, setting *PNAME, *SIZE,
+// *NESTED_OFF and *NEXT_OFF.
+
+bool
+Archive_file::read_archive_header(off_t off, std::string* pname, off_t* size,
+                                  off_t* nested_off, off_t* next_off)
+{
+  Archive_header hdr;
+  ssize_t got = ::read(this->fd_, &hdr, sizeof hdr);
+  if (got != sizeof hdr)
+    {
+      if (got < 0)
+	go_error_at(this->location_, "%s: %m", this->filename_.c_str());
+      else if (got > 0)
+	go_error_at(this->location_, "%s: short archive header at %ld",
+		    this->filename_.c_str(), static_cast<long>(off));
+      else
+	go_error_at(this->location_, "%s: unexpected EOF at %ld",
+		    this->filename_.c_str(), static_cast<long>(off));
+    }
+  off_t local_nested_off;
+  if (!this->interpret_header(&hdr, off, pname, size, &local_nested_off))
+    return false;
+  if (nested_off != NULL)
+    *nested_off = local_nested_off;
+
+  off_t local_next_off;
+  local_next_off = off + sizeof(Archive_header);
+  if (!this->is_thin_archive_ || pname->empty() || *pname == "/")
+    local_next_off += *size;
+  if ((local_next_off & 1) != 0)
+    ++local_next_off;
+  if (local_next_off > this->filesize_) // Last member.
+    local_next_off = this->filesize_;
+  if (next_off != NULL)
+    *next_off = local_next_off;
+  return true;
+}
+
+// Interpret the header of HDR, the header of the archive member at
+// file offset OFF.
+
+bool
+Archive_file::interpret_header(const Archive_header* hdr, off_t off,
+			       std::string* pname, off_t* size,
+			       off_t* nested_off) const
+{
+  if (memcmp(hdr->ar_fmag, arfmag, sizeof arfmag) != 0)
+    {
+      go_error_at(this->location_, "%s: malformed archive header at %lu",
+		  this->filename_.c_str(), static_cast<unsigned long>(off));
+      return false;
+    }
+
+  long local_size;
+  if (!this->parse_decimal(hdr->ar_size, sizeof hdr->ar_size, &local_size))
+    {
+      go_error_at(this->location_, "%s: malformed archive header size at %lu",
+		  this->filename_.c_str(), static_cast<unsigned long>(off));
+      return false;
+    }
+  *size = local_size;
+
+  *nested_off = 0;
+  if (hdr->ar_name[0] != '/')
+    {
+      const char* name_end = strchr(hdr->ar_name, '/');
+      if (name_end == NULL
+	  || name_end - hdr->ar_name >= static_cast<int>(sizeof hdr->ar_name))
+	{
+	  go_error_at(this->location_,
+		      "%s: malformed archive header name at %lu",
+		      this->filename_.c_str(), static_cast<unsigned long>(off));
+	  return false;
+	}
+      pname->assign(hdr->ar_name, name_end - hdr->ar_name);
+    }
+  else if (hdr->ar_name[1] == ' ')
+    {
+      // This is the symbol table.
+      pname->clear();
+    }
+  else if (hdr->ar_name[1] == 'S' && hdr->ar_name[2] == 'Y'
+	   && hdr->ar_name[3] == 'M' && hdr->ar_name[4] == '6'
+	   && hdr->ar_name[5] == '4' && hdr->ar_name[6] == '/'
+	   && hdr->ar_name[7] == ' '
+	  )
+    {
+      // 64-bit symbol table.
+      pname->clear();
+    }
+  else if (hdr->ar_name[1] == '/')
+    {
+      // This is the extended name table.
+      pname->assign(1, '/');
+    }
+  else
+    {
+      char* end;
+      errno = 0;
+      long x = strtol(hdr->ar_name + 1, &end, 10);
+      long y = 0;
+      if (*end == ':')
+        y = strtol(end + 1, &end, 10);
+      if (*end != ' '
+	  || x < 0
+	  || (x == LONG_MAX && errno == ERANGE)
+	  || static_cast<size_t>(x) >= this->extended_names_.size())
+	{
+	  go_error_at(this->location_, "%s: bad extended name index at %lu",
+		      this->filename_.c_str(), static_cast<unsigned long>(off));
+	  return false;
+	}
+
+      const char* name = this->extended_names_.data() + x;
+      const char* name_end = strchr(name, '\n');
+      if (static_cast<size_t>(name_end - name) > this->extended_names_.size()
+	  || name_end[-1] != '/')
+	{
+	  go_error_at(this->location_,
+		      "%s: bad extended name entry at header %lu",
+		      this->filename_.c_str(), static_cast<unsigned long>(off));
+	  return false;
+	}
+      pname->assign(name, name_end - 1 - name);
+      *nested_off = y;
+    }
+
+  return true;
+}
+
+// Get the file and offset for an archive member.
+
+bool
+Archive_file::get_file_and_offset(off_t off, const std::string& hdrname,
+				  off_t nested_off, int* memfd, off_t* memoff,
+				  std::string* memname)
+{
+  if (this->is_big_archive_)
+    {
+      *memfd = this->fd_;
+      *memoff = (off + sizeof(Archive_big_header) + hdrname.length()
+                 + sizeof(arfmag));
+      if ((*memoff & 1) != 0)
+        ++*memoff;
+      *memname = this->filename_ + '(' + hdrname + ')';
+      return true;
+    }
+  else if (!this->is_thin_archive_)
+    {
+      *memfd = this->fd_;
+      *memoff = off + sizeof(Archive_header);
+      *memname = this->filename_ + '(' + hdrname + ')';
+      return true;
+    }
+
+  std::string filename = hdrname;
+  if (!IS_ABSOLUTE_PATH(filename.c_str()))
+    {
+      const char* archive_path = this->filename_.c_str();
+      const char* basename = lbasename(archive_path);
+      if (basename > archive_path)
+	filename.replace(0, 0,
+			 this->filename_.substr(0, basename - archive_path));
+    }
+
+  if (nested_off > 0)
+    {
+      // This is a member of a nested archive.
+      Archive_file* nfile;
+      Nested_archive_table::const_iterator p =
+	this->nested_archives_.find(filename);
+      if (p != this->nested_archives_.end())
+	nfile = p->second;
+      else
+	{
+	  int nfd = open(filename.c_str(), O_RDONLY | O_BINARY);
+	  if (nfd < 0)
+	    {
+	      go_error_at(this->location_, "%s: can't open nested archive %s",
+			  this->filename_.c_str(), filename.c_str());
+	      return false;
+	    }
+	  nfile = new Archive_file(filename, nfd, this->location_);
+	  if (!nfile->initialize())
+	    {
+	      delete nfile;
+	      return false;
+	    }
+	  this->nested_archives_[filename] = nfile;
+	}
+
+      std::string nname;
+      off_t nsize;
+      off_t nnested_off;
+      if (!nfile->read_header(nested_off, &nname, &nsize, &nnested_off, NULL))
+	return false;
+      return nfile->get_file_and_offset(nested_off, nname, nnested_off,
+					memfd, memoff, memname);
+    }
+
+  // An external member of a thin archive.
+  *memfd = open(filename.c_str(), O_RDONLY | O_BINARY);
+  if (*memfd < 0)
+    {
+      go_error_at(this->location_, "%s: %m", filename.c_str());
+      return false;
+    }
+  *memoff = 0;
+  *memname = filename;
+  return true;
+}
+
+// An archive member iterator.  This is more-or-less copied from gold.
+
+class Archive_iterator
+{
+ public:
+  // The header of an archive member.  This is what this iterator
+  // points to.
+  struct Header
+  {
+    // The name of the member.
+    std::string name;
+    // The file offset of the member.
+    off_t off;
+    // The file offset of a nested archive member.
+    off_t nested_off;
+    // The size of the member.
+    off_t size;
+  };
+
+  Archive_iterator(Archive_file* afile, off_t off)
+    : afile_(afile), off_(off)
+  { this->read_next_header(); }
+
+  const Header&
+  operator*() const
+  { return this->header_; }
+
+  const Header*
+  operator->() const
+  { return &this->header_; }
+
+  Archive_iterator&
+  operator++()
+  {
+    if (this->off_ == this->afile_->filesize())
+      return *this;
+    this->off_ = this->next_off_;
+    this->read_next_header();
+    return *this;
+  }
+
+  Archive_iterator
+  operator++(int)
+  {
+    Archive_iterator ret = *this;
+    ++*this;
+    return ret;
+  }
+
+  bool
+  operator==(const Archive_iterator& p) const
+  { return this->off_ == p->off; }
+
+  bool
+  operator!=(const Archive_iterator& p) const
+  { return this->off_ != p->off; }
+
+ private:
+  void
+  read_next_header();
+
+  // The underlying archive file.
+  Archive_file* afile_;
+  // The current offset in the file.
+  off_t off_;
+  // The offset of the next member.
+  off_t next_off_;
+  // The current archive header.
+  Header header_;
+};
+
+// Read the next archive header.
+
+void
+Archive_iterator::read_next_header()
+{
+  off_t filesize = this->afile_->filesize();
+  while (true)
+    {
+      if (this->off_ == filesize)
+	{
+	  this->header_.off = filesize;
+	  return;
+	}
+
+      if (!this->afile_->read_header(this->off_, &this->header_.name,
+                                     &this->header_.size,
+                                     &this->header_.nested_off,
+                                     &this->next_off_))
+	{
+	  this->header_.off = filesize;
+	  return;
+	}
+      this->header_.off = this->off_;
+
+      // Skip special members.
+      if (!this->header_.name.empty() && this->header_.name != "/")
+	return;
+
+      this->off_ = this->next_off_;
+    }
+}
+
+// Initial iterator.
+
+Archive_iterator
+archive_begin(Archive_file* afile)
+{
+  return Archive_iterator(afile, afile->first_member_offset());
+}
+
+// Final iterator.
+
+Archive_iterator
+archive_end(Archive_file* afile)
+{
+  return Archive_iterator(afile, afile->filesize());
+}
+
+// A type of Import_stream which concatenates other Import_streams
+// together.
+
+class Stream_concatenate : public Import::Stream
+{
+ public:
+  Stream_concatenate()
+    : inputs_()
+  { }
+
+  // Add a new stream.
+  void
+  add(Import::Stream* is)
+  { this->inputs_.push_back(is); }
+
+ protected:
+  bool
+  do_peek(size_t, const char**);
+
+  void
+  do_advance(size_t);
+
+ private:
+  std::list<Import::Stream*> inputs_;
+};
+
+// Peek ahead.
+
+bool
+Stream_concatenate::do_peek(size_t length, const char** bytes)
+{
+  while (true)
+    {
+      if (this->inputs_.empty())
+	return false;
+      if (this->inputs_.front()->peek(length, bytes))
+	return true;
+      delete this->inputs_.front();
+      this->inputs_.pop_front();
+    }
+}
+
+// Advance.
+
+void
+Stream_concatenate::do_advance(size_t skip)
+{
+  while (true)
+    {
+      if (this->inputs_.empty())
+	return;
+      if (!this->inputs_.front()->at_eof())
+	{
+	  // We just assume that this will do the right thing.  It
+	  // should be OK since we should never want to skip past
+	  // multiple streams.
+	  this->inputs_.front()->advance(skip);
+	  return;
+	}
+      delete this->inputs_.front();
+      this->inputs_.pop_front();
+    }
+}
+
+// Import data from an archive.  We walk through the archive and
+// import data from each member.
+
+Import::Stream*
+Import::find_archive_export_data(const std::string& filename, int fd,
+				 Location location)
+{
+  Archive_file afile(filename, fd, location);
+  if (!afile.initialize())
+    return NULL;
+
+  Stream_concatenate* ret = new Stream_concatenate;
+
+  bool any_data = false;
+  bool any_members = false;
+  Archive_iterator pend = archive_end(&afile);
+  for (Archive_iterator p = archive_begin(&afile); p != pend; p++)
+    {
+      any_members = true;
+      int member_fd;
+      off_t member_off;
+      std::string member_name;
+      if (!afile.get_file_and_offset(p->off, p->name, p->nested_off,
+				     &member_fd, &member_off, &member_name))
+	return NULL;
+
+      Import::Stream* is = Import::find_object_export_data(member_name,
+							   member_fd,
+							   member_off,
+							   location);
+      if (is != NULL)
+	{
+	  ret->add(is);
+	  any_data = true;
+	}
+    }
+
+  if (!any_members)
+    {
+      // It's normal to have an empty archive file when using gobuild.
+      return new Stream_from_string("");
+    }
+
+  if (!any_data)
+    {
+      delete ret;
+      return NULL;
+    }
+
+  return ret;
+}