// 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_t 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_t 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_t 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 (std::unique_ptr<Import::Stream> is) { this->inputs_.push_back (std::move (is)); } protected: bool do_peek (size_t, const char **); void do_advance (size_t); private: std::list<std::unique_ptr<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; 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; } this->inputs_.pop_front (); } } // Import data from an archive. We walk through the archive and // import data from each member. std::unique_ptr<Import::Stream> Import::find_archive_export_data (const std::string &filename, int fd, location_t location) { Archive_file afile (filename, fd, location); if (!afile.initialize ()) return nullptr; auto ret = std::make_unique<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 nullptr; std::unique_ptr<Import::Stream> is = Import::find_object_export_data (member_name, member_fd, member_off, location); if (is != nullptr) { ret->add (std::move (is)); any_data = true; } } if (!any_members) { // It's normal to have an empty archive file when using gobuild. return std::make_unique<Stream_from_string> (""); } if (!any_data) { return nullptr; } return std::unique_ptr<Stream>{static_cast<Stream *> (ret.release ())}; } } // namespace Rust