Line data Source code
1 : // Copyright (C) 2020-2026 Free Software Foundation, Inc.
2 :
3 : // This file is part of GCC.
4 :
5 : // GCC is free software; you can redistribute it and/or modify it under
6 : // the terms of the GNU General Public License as published by the Free
7 : // Software Foundation; either version 3, or (at your option) any later
8 : // version.
9 :
10 : // GCC is distributed in the hope that it will be useful, but WITHOUT ANY
11 : // WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 : // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 : // for more details.
14 :
15 : // You should have received a copy of the GNU General Public License
16 : // along with GCC; see the file COPYING3. If not see
17 : // <http://www.gnu.org/licenses/>.
18 :
19 : #include "rust-system.h"
20 : #include "rust-diagnostics.h"
21 : #include "rust-imports.h"
22 : #include "rust-object-export.h"
23 : #include "rust-export-metadata.h"
24 :
25 : #ifndef O_BINARY
26 : #define O_BINARY 0
27 : #endif
28 :
29 : namespace Rust {
30 :
31 : // The list of paths we search for import files.
32 : static std::vector<std::string> search_path;
33 :
34 : // Add a directory to the search path. This is called from the option
35 : // handling language hook.
36 : void
37 32450 : add_search_path (const std::string &path)
38 : {
39 32450 : search_path.push_back (path);
40 32450 : }
41 :
42 : // Find import data. This searches the file system for FILENAME and
43 : // returns a pointer to a Stream object to read the data that it
44 : // exports. If the file is not found, it returns NULL.
45 :
46 : // When FILENAME is not an absolute path and does not start with ./ or
47 : // ../, we use the search path provided by -I and -L options.
48 :
49 : // When FILENAME does start with ./ or ../, we use
50 : // RELATIVE_IMPORT_PATH as a prefix.
51 :
52 : // When FILENAME does not exist, we try modifying FILENAME to find the
53 : // file. We use the first of these which exists:
54 : // * We append ".gox".
55 : // * We turn the base of FILENAME into libFILENAME.so.
56 : // * We turn the base of FILENAME into libFILENAME.a.
57 : // * We append ".o".
58 :
59 : // When using a search path, we apply each of these transformations at
60 : // each entry on the search path before moving on to the next entry.
61 : // If the file exists, but does not contain any Rust export data, we
62 : // stop; we do not keep looking for another file with the same name
63 : // later in the search path.
64 :
65 : std::pair<std::unique_ptr<Import::Stream>, std::vector<ProcMacro::Procmacro>>
66 27 : Import::open_package (const std::string &filename, location_t location,
67 : const std::string &relative_import_path)
68 : {
69 27 : bool is_local;
70 27 : if (IS_ABSOLUTE_PATH (filename))
71 : is_local = true;
72 27 : else if (filename[0] == '.'
73 27 : && (filename[1] == '\0' || IS_DIR_SEPARATOR (filename[1])))
74 : is_local = true;
75 27 : else if (filename[0] == '.' && filename[1] == '.'
76 27 : && (filename[2] == '\0' || IS_DIR_SEPARATOR (filename[2])))
77 : is_local = true;
78 : else
79 : is_local = false;
80 :
81 27 : std::string fn = filename;
82 0 : if (is_local && !IS_ABSOLUTE_PATH (filename)
83 27 : && !relative_import_path.empty ())
84 : {
85 0 : if (fn == ".")
86 : {
87 : // A special case.
88 0 : fn = relative_import_path;
89 : }
90 0 : else if (fn[0] == '.' && fn[1] == '.'
91 0 : && (fn[2] == '\0' || IS_DIR_SEPARATOR (fn[2])))
92 : {
93 : // We are going to join relative_import_path and fn, and it
94 : // will look like DIR/../PATH. But DIR does not necessarily
95 : // exist in this case, and if it doesn't the use of .. will
96 : // fail although it shouldn't. The gc compiler uses
97 : // path.Join here, which cleans up the .., so we need to do
98 : // the same.
99 0 : size_t index;
100 0 : for (index = relative_import_path.length () - 1;
101 0 : index > 0 && !IS_DIR_SEPARATOR (relative_import_path[index]);
102 : index--)
103 : ;
104 0 : if (index > 0)
105 0 : fn = relative_import_path.substr (0, index) + fn.substr (2);
106 : else
107 0 : fn = relative_import_path + '/' + fn;
108 : }
109 : else
110 0 : fn = relative_import_path + '/' + fn;
111 : is_local = false;
112 : }
113 :
114 27 : if (!is_local)
115 : {
116 27 : for (std::vector<std::string>::const_iterator p = search_path.begin ();
117 216 : p != search_path.end (); ++p)
118 : {
119 189 : std::string indir = *p;
120 378 : if (!indir.empty () && indir[indir.size () - 1] != '/')
121 189 : indir += '/';
122 189 : indir += fn;
123 189 : auto s = Import::try_package_in_directory (indir, location);
124 189 : if (s.first != nullptr)
125 0 : return s;
126 189 : }
127 : }
128 :
129 27 : auto s = Import::try_package_in_directory (fn, location);
130 27 : if (s.first != nullptr)
131 24 : return s;
132 :
133 3 : return std::make_pair (nullptr, std::vector<ProcMacro::Procmacro>{});
134 27 : }
135 :
136 : // Try to find the export data for FILENAME.
137 :
138 : std::pair<std::unique_ptr<Import::Stream>, std::vector<ProcMacro::Procmacro>>
139 216 : Import::try_package_in_directory (const std::string &filename,
140 : location_t location)
141 : {
142 216 : std::string found_filename = filename;
143 216 : int fd = open (found_filename.c_str (), O_RDONLY | O_BINARY);
144 :
145 216 : if (fd >= 0)
146 : {
147 0 : struct stat s;
148 0 : if (fstat (fd, &s) >= 0 && S_ISDIR (s.st_mode))
149 : {
150 0 : close (fd);
151 0 : fd = -1;
152 0 : errno = EISDIR;
153 : }
154 : }
155 :
156 0 : if (fd < 0)
157 : {
158 216 : if (errno != ENOENT && errno != EISDIR)
159 0 : rust_warning_at (location, 0, "%s: %m", filename.c_str ());
160 :
161 216 : fd = Import::try_suffixes (&found_filename);
162 216 : if (fd < 0)
163 192 : return std::make_pair (nullptr, std::vector<ProcMacro::Procmacro>{});
164 : }
165 :
166 48 : auto macros = load_macros (found_filename);
167 :
168 : // The export data may not be in this file.
169 24 : std::unique_ptr<Stream> s
170 24 : = Import::find_export_data (found_filename, fd, location);
171 24 : if (s != nullptr)
172 24 : return std::make_pair (std::move (s), macros);
173 :
174 0 : close (fd);
175 :
176 0 : if (macros.empty ())
177 0 : rust_error_at (location,
178 : "%s exists but does not contain any Rust export data",
179 : found_filename.c_str ());
180 :
181 0 : return std::make_pair (nullptr, macros);
182 24 : }
183 :
184 : // Given import "*PFILENAME", where *PFILENAME does not exist, try
185 : // various suffixes. If we find one, set *PFILENAME to the one we
186 : // found. Return the open file descriptor.
187 :
188 : int
189 216 : Import::try_suffixes (std::string *pfilename)
190 : {
191 216 : std::string filename = *pfilename + ".rox";
192 216 : int fd = open (filename.c_str (), O_RDONLY | O_BINARY);
193 216 : if (fd >= 0)
194 : {
195 24 : *pfilename = filename;
196 24 : return fd;
197 : }
198 :
199 192 : const char *basename = lbasename (pfilename->c_str ());
200 192 : size_t basename_pos = basename - pfilename->c_str ();
201 768 : filename = pfilename->substr (0, basename_pos) + "lib" + basename + ".so";
202 192 : fd = open (filename.c_str (), O_RDONLY | O_BINARY);
203 192 : if (fd >= 0)
204 : {
205 0 : *pfilename = filename;
206 0 : return fd;
207 : }
208 :
209 768 : filename = pfilename->substr (0, basename_pos) + "lib" + basename + ".a";
210 192 : fd = open (filename.c_str (), O_RDONLY | O_BINARY);
211 192 : if (fd >= 0)
212 : {
213 0 : *pfilename = filename;
214 0 : return fd;
215 : }
216 :
217 192 : filename = *pfilename + ".o";
218 192 : fd = open (filename.c_str (), O_RDONLY | O_BINARY);
219 192 : if (fd >= 0)
220 : {
221 0 : *pfilename = filename;
222 0 : return fd;
223 : }
224 :
225 : return -1;
226 216 : }
227 :
228 : // Look for export data in the file descriptor FD.
229 :
230 : std::unique_ptr<Import::Stream>
231 24 : Import::find_export_data (const std::string &filename, int fd,
232 : location_t location)
233 : {
234 : // See if we can read this as an object file.
235 24 : std::unique_ptr<Import::Stream> stream
236 24 : = Import::find_object_export_data (filename, fd, 0, location);
237 24 : if (stream != nullptr)
238 0 : return stream;
239 :
240 24 : const int len = sizeof (Metadata::kMagicHeader);
241 24 : if (lseek (fd, 0, SEEK_SET) < 0)
242 : {
243 0 : rust_error_at (location, "lseek %s failed: %m", filename.c_str ());
244 0 : return nullptr;
245 : }
246 :
247 24 : char buf[len];
248 24 : ssize_t c = ::read (fd, buf, len);
249 24 : if (c < len)
250 0 : return nullptr;
251 :
252 : // Check for a file containing nothing but Rust export data.
253 : // if (memcmp (buf, Export::cur_magic, Export::magic_len) == 0
254 : // || memcmp (buf, Export::v1_magic, Export::magic_len) == 0
255 : // || memcmp (buf, Export::v2_magic, Export::magic_len) == 0)
256 : //
257 : // FIXME we need to work out a better header
258 : //
259 24 : if (memcmp (buf, Metadata::kMagicHeader, sizeof (Metadata::kMagicHeader))
260 : == 0)
261 24 : return std::make_unique<Stream_from_file> (fd);
262 :
263 : // See if we can read this as an archive.
264 0 : if (Import::is_archive_magic (buf))
265 0 : return Import::find_archive_export_data (filename, fd, location);
266 :
267 0 : return nullptr;
268 24 : }
269 :
270 : // Look for export data in an object file.
271 :
272 : std::unique_ptr<Import::Stream>
273 24 : Import::find_object_export_data (const std::string &filename, int fd,
274 : off_t offset, location_t location)
275 : {
276 24 : char *buf;
277 24 : size_t len;
278 24 : int err;
279 24 : const char *errmsg = rust_read_export_data (fd, offset, &buf, &len, &err);
280 24 : if (errmsg != nullptr)
281 : {
282 0 : if (err == 0)
283 0 : rust_error_at (location, "%s: %s", filename.c_str (), errmsg);
284 : else
285 0 : rust_error_at (location, "%s: %s: %s", filename.c_str (), errmsg,
286 : xstrerror (err));
287 0 : return nullptr;
288 : }
289 :
290 24 : if (buf == nullptr)
291 24 : return nullptr;
292 :
293 0 : return std::make_unique<Stream_from_buffer> (buf, len);
294 : }
295 :
296 : // Class Import.
297 :
298 : // Construct an Import object. We make the builtin_types_ vector
299 : // large enough to hold all the builtin types.
300 :
301 0 : Import::Import (std::unique_ptr<Stream> stream, location_t location)
302 0 : : stream_ (std::move (stream)), location_ (location)
303 0 : {}
304 :
305 : // Import the data in the associated stream.
306 :
307 : // Read LENGTH bytes from the stream.
308 :
309 : void
310 0 : Import::read (size_t length, std::string *out)
311 : {
312 0 : const char *data;
313 0 : if (!this->stream_->peek (length, &data))
314 : {
315 0 : if (!this->stream_->saw_error ())
316 0 : rust_error_at (this->location_, "import error at %d: expected %d bytes",
317 : this->stream_->pos (), static_cast<int> (length));
318 0 : this->stream_->set_saw_error ();
319 0 : *out = std::string ("");
320 0 : return;
321 : }
322 0 : *out = std::string (data, length);
323 0 : this->advance (length);
324 : }
325 :
326 : // Class Import::Stream.
327 :
328 24 : Import::Stream::Stream () : pos_ (0), saw_error_ (false) {}
329 :
330 24 : Import::Stream::~Stream () {}
331 :
332 : // Return the next character to come from the stream.
333 :
334 : int
335 6160 : Import::Stream::peek_char ()
336 : {
337 6160 : const char *read;
338 6160 : if (!this->do_peek (1, &read))
339 : return -1;
340 : // Make sure we return an unsigned char, so that we don't get
341 : // confused by \xff.
342 6160 : unsigned char ret = *read;
343 6160 : return ret;
344 : }
345 :
346 : // Return true if the next LENGTH characters from the stream match
347 : // BYTES
348 :
349 : bool
350 0 : Import::Stream::match_bytes (const char *bytes, size_t length)
351 : {
352 0 : const char *read;
353 0 : if (!this->do_peek (length, &read))
354 : return false;
355 0 : return memcmp (bytes, read, length) == 0;
356 : }
357 :
358 : // Require that the next LENGTH bytes from the stream match BYTES.
359 :
360 : void
361 48 : Import::Stream::require_bytes (location_t location, const char *bytes,
362 : size_t length)
363 : {
364 48 : const char *read;
365 48 : if (!this->do_peek (length, &read) || memcmp (bytes, read, length) != 0)
366 : {
367 0 : if (!this->saw_error_)
368 0 : rust_error_at (location, "import error at %d: expected %<%.*s%>",
369 : this->pos (), static_cast<int> (length), bytes);
370 0 : this->saw_error_ = true;
371 0 : return;
372 : }
373 48 : this->advance (length);
374 : }
375 :
376 : // Class Stream_from_file.
377 :
378 24 : Stream_from_file::Stream_from_file (int fd) : fd_ (fd), data_ ()
379 : {
380 24 : if (lseek (fd, 0, SEEK_SET) != 0)
381 : {
382 0 : rust_fatal_error (UNKNOWN_LOCATION, "lseek failed: %m");
383 : this->set_saw_error ();
384 : }
385 24 : }
386 :
387 48 : Stream_from_file::~Stream_from_file () { close (this->fd_); }
388 :
389 : // Read next bytes.
390 :
391 : bool
392 6232 : Stream_from_file::do_peek (size_t length, const char **bytes)
393 : {
394 6232 : if (this->data_.length () >= length)
395 : {
396 3080 : *bytes = this->data_.data ();
397 3080 : return true;
398 : }
399 :
400 3152 : this->data_.resize (length);
401 3152 : ssize_t got = ::read (this->fd_, &this->data_[0], length);
402 :
403 3152 : if (got < 0)
404 : {
405 0 : if (!this->saw_error ())
406 0 : rust_fatal_error (UNKNOWN_LOCATION, "read failed: %m");
407 0 : this->set_saw_error ();
408 0 : return false;
409 : }
410 :
411 3152 : if (lseek (this->fd_, -got, SEEK_CUR) < 0)
412 : {
413 0 : if (!this->saw_error ())
414 0 : rust_fatal_error (UNKNOWN_LOCATION, "lseek failed: %m");
415 0 : this->set_saw_error ();
416 0 : return false;
417 : }
418 :
419 3152 : if (static_cast<size_t> (got) < length)
420 : return false;
421 :
422 3152 : *bytes = this->data_.data ();
423 3152 : return true;
424 : }
425 :
426 : // Advance.
427 :
428 : void
429 3152 : Stream_from_file::do_advance (size_t skip)
430 : {
431 3152 : if (lseek (this->fd_, skip, SEEK_CUR) < 0)
432 : {
433 0 : if (!this->saw_error ())
434 0 : rust_fatal_error (UNKNOWN_LOCATION, "lseek failed: %m");
435 0 : this->set_saw_error ();
436 : }
437 3152 : if (!this->data_.empty ())
438 : {
439 3152 : if (this->data_.length () > skip)
440 0 : this->data_.erase (0, skip);
441 : else
442 3152 : this->data_.clear ();
443 : }
444 3152 : }
445 :
446 : } // namespace Rust
|