blob: 5678d486f17d38b78a5f87fbbb8194b4b9095492 [file] [log] [blame]
// 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 "rust-system.h"
#include "rust-diagnostics.h"
#include "rust-imports.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'};
namespace Rust {
// 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];
};
// 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)
{
const int archive_magic_len = 8;
return (memcmp (bytes, armag, archive_magic_len) == 0
|| memcmp (bytes, armagt, archive_magic_len) == 0
|| memcmp (bytes, armagb, 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)
{
rust_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))
{
rust_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))
{
rust_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));
rust_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)
{
rust_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)
{
rust_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)
{
rust_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)
rust_error_at (this->location_, "%s: %m", this->filename_.c_str ());
else if (got > 0)
rust_error_at (this->location_, "%s: short entry header at %ld",
this->filename_.c_str (), static_cast<long> (off));
else
rust_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));
rust_error_at (this->location_,
("%s: malformed 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));
rust_error_at (this->location_,
("%s: malformed name length 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)
{
rust_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));
rust_error_at (this->location_,
("%s: malformed next member offset 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)
rust_error_at (this->location_, "%s: %m", this->filename_.c_str ());
else if (got > 0)
rust_error_at (this->location_, "%s: short archive header at %ld",
this->filename_.c_str (), static_cast<long> (off));
else
rust_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)
{
rust_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))
{
rust_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))
{
rust_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 ())
{
rust_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] != '/')
{
rust_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)
{
rust_error_at (this->location_,
"%s: cannot 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)
{
rust_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;
this->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;
}
} // namespace Rust