// Copyright (C) 2020-2023 Free Software Foundation, Inc. // This file is part of GCC. // GCC is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free // Software Foundation; either version 3, or (at your option) any later // version. // GCC is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License // for more details. // You should have received a copy of the GNU General Public License // along with GCC; see the file COPYING3. If not see // . #include "rust-system.h" #include "rust-diagnostics.h" #include "rust-imports.h" #include "rust-object-export.h" #include "rust-export-metadata.h" #ifndef O_BINARY #define O_BINARY 0 #endif namespace Rust { // The list of paths we search for import files. static std::vector search_path; // Add a directory to the search path. This is called from the option // handling language hook. void add_search_path (const std::string &path) { search_path.push_back (path); } // Find import data. This searches the file system for FILENAME and // returns a pointer to a Stream object to read the data that it // exports. If the file is not found, it returns NULL. // When FILENAME is not an absolute path and does not start with ./ or // ../, we use the search path provided by -I and -L options. // When FILENAME does start with ./ or ../, we use // RELATIVE_IMPORT_PATH as a prefix. // When FILENAME does not exist, we try modifying FILENAME to find the // file. We use the first of these which exists: // * We append ".gox". // * We turn the base of FILENAME into libFILENAME.so. // * We turn the base of FILENAME into libFILENAME.a. // * We append ".o". // When using a search path, we apply each of these transformations at // each entry on the search path before moving on to the next entry. // If the file exists, but does not contain any Go export data, we // stop; we do not keep looking for another file with the same name // later in the search path. Import::Stream * Import::open_package (const std::string &filename, Location location, const std::string &relative_import_path) { bool is_local; if (IS_ABSOLUTE_PATH (filename)) is_local = true; else if (filename[0] == '.' && (filename[1] == '\0' || IS_DIR_SEPARATOR (filename[1]))) is_local = true; else if (filename[0] == '.' && filename[1] == '.' && (filename[2] == '\0' || IS_DIR_SEPARATOR (filename[2]))) is_local = true; else is_local = false; std::string fn = filename; if (is_local && !IS_ABSOLUTE_PATH (filename) && !relative_import_path.empty ()) { if (fn == ".") { // A special case. fn = relative_import_path; } else if (fn[0] == '.' && fn[1] == '.' && (fn[2] == '\0' || IS_DIR_SEPARATOR (fn[2]))) { // We are going to join relative_import_path and fn, and it // will look like DIR/../PATH. But DIR does not necessarily // exist in this case, and if it doesn't the use of .. will // fail although it shouldn't. The gc compiler uses // path.Join here, which cleans up the .., so we need to do // the same. size_t index; for (index = relative_import_path.length () - 1; index > 0 && !IS_DIR_SEPARATOR (relative_import_path[index]); index--) ; if (index > 0) fn = relative_import_path.substr (0, index) + fn.substr (2); else fn = relative_import_path + '/' + fn; } else fn = relative_import_path + '/' + fn; is_local = false; } if (!is_local) { for (std::vector::const_iterator p = search_path.begin (); p != search_path.end (); ++p) { std::string indir = *p; if (!indir.empty () && indir[indir.size () - 1] != '/') indir += '/'; indir += fn; Stream *s = Import::try_package_in_directory (indir, location); if (s != NULL) return s; } } Stream *s = Import::try_package_in_directory (fn, location); if (s != NULL) return s; return NULL; } // Try to find the export data for FILENAME. Import::Stream * Import::try_package_in_directory (const std::string &filename, Location location) { std::string found_filename = filename; int fd = open (found_filename.c_str (), O_RDONLY | O_BINARY); if (fd >= 0) { struct stat s; if (fstat (fd, &s) >= 0 && S_ISDIR (s.st_mode)) { close (fd); fd = -1; errno = EISDIR; } } if (fd < 0) { if (errno != ENOENT && errno != EISDIR) rust_warning_at (location, 0, "%s: %m", filename.c_str ()); fd = Import::try_suffixes (&found_filename); if (fd < 0) return NULL; } // The export data may not be in this file. Stream *s = Import::find_export_data (found_filename, fd, location); if (s != NULL) return s; close (fd); rust_error_at (location, "%s exists but does not contain any Go export data", found_filename.c_str ()); return NULL; } // Given import "*PFILENAME", where *PFILENAME does not exist, try // various suffixes. If we find one, set *PFILENAME to the one we // found. Return the open file descriptor. int Import::try_suffixes (std::string *pfilename) { std::string filename = *pfilename + ".rox"; int fd = open (filename.c_str (), O_RDONLY | O_BINARY); if (fd >= 0) { *pfilename = filename; return fd; } const char *basename = lbasename (pfilename->c_str ()); size_t basename_pos = basename - pfilename->c_str (); filename = pfilename->substr (0, basename_pos) + "lib" + basename + ".so"; fd = open (filename.c_str (), O_RDONLY | O_BINARY); if (fd >= 0) { *pfilename = filename; return fd; } filename = pfilename->substr (0, basename_pos) + "lib" + basename + ".a"; fd = open (filename.c_str (), O_RDONLY | O_BINARY); if (fd >= 0) { *pfilename = filename; return fd; } filename = *pfilename + ".o"; fd = open (filename.c_str (), O_RDONLY | O_BINARY); if (fd >= 0) { *pfilename = filename; return fd; } return -1; } // Look for export data in the file descriptor FD. Import::Stream * Import::find_export_data (const std::string &filename, int fd, Location location) { // See if we can read this as an object file. Import::Stream *stream = Import::find_object_export_data (filename, fd, 0, location); if (stream != NULL) return stream; const int len = sizeof (Metadata::kMagicHeader); if (lseek (fd, 0, SEEK_SET) < 0) { rust_error_at (location, "lseek %s failed: %m", filename.c_str ()); return NULL; } char buf[len]; ssize_t c = ::read (fd, buf, len); if (c < len) return NULL; // Check for a file containing nothing but Go export data. // if (memcmp (buf, Export::cur_magic, Export::magic_len) == 0 // || memcmp (buf, Export::v1_magic, Export::magic_len) == 0 // || memcmp (buf, Export::v2_magic, Export::magic_len) == 0) // // FIXME we need to work out a better header // if (memcmp (buf, Metadata::kMagicHeader, sizeof (Metadata::kMagicHeader)) == 0) return new Stream_from_file (fd); // See if we can read this as an archive. if (Import::is_archive_magic (buf)) return Import::find_archive_export_data (filename, fd, location); return NULL; } // Look for export data in an object file. Import::Stream * Import::find_object_export_data (const std::string &filename, int fd, off_t offset, Location location) { char *buf; size_t len; int err; const char *errmsg = rust_read_export_data (fd, offset, &buf, &len, &err); if (errmsg != NULL) { if (err == 0) rust_error_at (location, "%s: %s", filename.c_str (), errmsg); else rust_error_at (location, "%s: %s: %s", filename.c_str (), errmsg, xstrerror (err)); return NULL; } if (buf == NULL) return NULL; return new Stream_from_buffer (buf, len); } // Class Import. // Construct an Import object. We make the builtin_types_ vector // large enough to hold all the builtin types. Import::Import (Stream *stream, Location location) : stream_ (stream), location_ (location) {} // Import the data in the associated stream. // Read LENGTH bytes from the stream. void Import::read (size_t length, std::string *out) { const char *data; if (!this->stream_->peek (length, &data)) { if (!this->stream_->saw_error ()) rust_error_at (this->location_, "import error at %d: expected %d bytes", this->stream_->pos (), static_cast (length)); this->stream_->set_saw_error (); *out = std::string (""); return; } *out = std::string (data, length); this->advance (length); } // Class Import::Stream. Import::Stream::Stream () : pos_ (0), saw_error_ (false) {} Import::Stream::~Stream () {} // Return the next character to come from the stream. int Import::Stream::peek_char () { const char *read; if (!this->do_peek (1, &read)) return -1; // Make sure we return an unsigned char, so that we don't get // confused by \xff. unsigned char ret = *read; return ret; } // Return true if the next LENGTH characters from the stream match // BYTES bool Import::Stream::match_bytes (const char *bytes, size_t length) { const char *read; if (!this->do_peek (length, &read)) return false; return memcmp (bytes, read, length) == 0; } // Require that the next LENGTH bytes from the stream match BYTES. void Import::Stream::require_bytes (Location location, const char *bytes, size_t length) { const char *read; if (!this->do_peek (length, &read) || memcmp (bytes, read, length) != 0) { if (!this->saw_error_) rust_error_at (location, "import error at %d: expected %<%.*s%>", this->pos (), static_cast (length), bytes); this->saw_error_ = true; return; } this->advance (length); } // Class Stream_from_file. Stream_from_file::Stream_from_file (int fd) : fd_ (fd), data_ () { if (lseek (fd, 0, SEEK_SET) != 0) { rust_fatal_error (Linemap::unknown_location (), "lseek failed: %m"); this->set_saw_error (); } } Stream_from_file::~Stream_from_file () { close (this->fd_); } // Read next bytes. bool Stream_from_file::do_peek (size_t length, const char **bytes) { if (this->data_.length () >= length) { *bytes = this->data_.data (); return true; } this->data_.resize (length); ssize_t got = ::read (this->fd_, &this->data_[0], length); if (got < 0) { if (!this->saw_error ()) rust_fatal_error (Linemap::unknown_location (), "read failed: %m"); this->set_saw_error (); return false; } if (lseek (this->fd_, -got, SEEK_CUR) < 0) { if (!this->saw_error ()) rust_fatal_error (Linemap::unknown_location (), "lseek failed: %m"); this->set_saw_error (); return false; } if (static_cast (got) < length) return false; *bytes = this->data_.data (); return true; } // Advance. void Stream_from_file::do_advance (size_t skip) { if (lseek (this->fd_, skip, SEEK_CUR) < 0) { if (!this->saw_error ()) rust_fatal_error (Linemap::unknown_location (), "lseek failed: %m"); this->set_saw_error (); } if (!this->data_.empty ()) { if (this->data_.length () > skip) this->data_.erase (0, skip); else this->data_.clear (); } } } // namespace Rust