927 lines
23 KiB
C++
927 lines
23 KiB
C++
// 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 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 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)
|
|
{
|
|
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 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)
|
|
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: 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)
|
|
{
|
|
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;
|
|
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;
|
|
}
|