]>
Commit | Line | Data |
---|---|---|
83ffe9cd | 1 | // Copyright (C) 2020-2023 Free Software Foundation, Inc. |
509e4c32 PH |
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 | add_search_path (const std::string &path) | |
38 | { | |
39 | search_path.push_back (path); | |
40 | } | |
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 Go 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 | Import::Stream * | |
66 | Import::open_package (const std::string &filename, Location location, | |
67 | const std::string &relative_import_path) | |
68 | { | |
69 | bool is_local; | |
70 | if (IS_ABSOLUTE_PATH (filename)) | |
71 | is_local = true; | |
72 | else if (filename[0] == '.' | |
73 | && (filename[1] == '\0' || IS_DIR_SEPARATOR (filename[1]))) | |
74 | is_local = true; | |
75 | else if (filename[0] == '.' && filename[1] == '.' | |
76 | && (filename[2] == '\0' || IS_DIR_SEPARATOR (filename[2]))) | |
77 | is_local = true; | |
78 | else | |
79 | is_local = false; | |
80 | ||
81 | std::string fn = filename; | |
82 | if (is_local && !IS_ABSOLUTE_PATH (filename) | |
83 | && !relative_import_path.empty ()) | |
84 | { | |
85 | if (fn == ".") | |
86 | { | |
87 | // A special case. | |
88 | fn = relative_import_path; | |
89 | } | |
90 | else if (fn[0] == '.' && fn[1] == '.' | |
91 | && (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 | size_t index; | |
100 | for (index = relative_import_path.length () - 1; | |
101 | index > 0 && !IS_DIR_SEPARATOR (relative_import_path[index]); | |
102 | index--) | |
103 | ; | |
104 | if (index > 0) | |
105 | fn = relative_import_path.substr (0, index) + fn.substr (2); | |
106 | else | |
107 | fn = relative_import_path + '/' + fn; | |
108 | } | |
109 | else | |
110 | fn = relative_import_path + '/' + fn; | |
111 | is_local = false; | |
112 | } | |
113 | ||
114 | if (!is_local) | |
115 | { | |
116 | for (std::vector<std::string>::const_iterator p = search_path.begin (); | |
117 | p != search_path.end (); ++p) | |
118 | { | |
119 | std::string indir = *p; | |
120 | if (!indir.empty () && indir[indir.size () - 1] != '/') | |
121 | indir += '/'; | |
122 | indir += fn; | |
123 | Stream *s = Import::try_package_in_directory (indir, location); | |
124 | if (s != NULL) | |
125 | return s; | |
126 | } | |
127 | } | |
128 | ||
129 | Stream *s = Import::try_package_in_directory (fn, location); | |
130 | if (s != NULL) | |
131 | return s; | |
132 | ||
133 | return NULL; | |
134 | } | |
135 | ||
136 | // Try to find the export data for FILENAME. | |
137 | ||
138 | Import::Stream * | |
139 | Import::try_package_in_directory (const std::string &filename, | |
140 | Location location) | |
141 | { | |
142 | std::string found_filename = filename; | |
143 | int fd = open (found_filename.c_str (), O_RDONLY | O_BINARY); | |
144 | ||
145 | if (fd >= 0) | |
146 | { | |
147 | struct stat s; | |
148 | if (fstat (fd, &s) >= 0 && S_ISDIR (s.st_mode)) | |
149 | { | |
150 | close (fd); | |
151 | fd = -1; | |
152 | errno = EISDIR; | |
153 | } | |
154 | } | |
155 | ||
156 | if (fd < 0) | |
157 | { | |
158 | if (errno != ENOENT && errno != EISDIR) | |
159 | rust_warning_at (location, 0, "%s: %m", filename.c_str ()); | |
160 | ||
161 | fd = Import::try_suffixes (&found_filename); | |
162 | if (fd < 0) | |
163 | return NULL; | |
164 | } | |
165 | ||
166 | // The export data may not be in this file. | |
167 | Stream *s = Import::find_export_data (found_filename, fd, location); | |
168 | if (s != NULL) | |
169 | return s; | |
170 | ||
171 | close (fd); | |
172 | ||
173 | rust_error_at (location, "%s exists but does not contain any Go export data", | |
174 | found_filename.c_str ()); | |
175 | ||
176 | return NULL; | |
177 | } | |
178 | ||
179 | // Given import "*PFILENAME", where *PFILENAME does not exist, try | |
180 | // various suffixes. If we find one, set *PFILENAME to the one we | |
181 | // found. Return the open file descriptor. | |
182 | ||
183 | int | |
184 | Import::try_suffixes (std::string *pfilename) | |
185 | { | |
186 | std::string filename = *pfilename + ".rox"; | |
187 | int fd = open (filename.c_str (), O_RDONLY | O_BINARY); | |
188 | if (fd >= 0) | |
189 | { | |
190 | *pfilename = filename; | |
191 | return fd; | |
192 | } | |
193 | ||
194 | const char *basename = lbasename (pfilename->c_str ()); | |
195 | size_t basename_pos = basename - pfilename->c_str (); | |
196 | filename = pfilename->substr (0, basename_pos) + "lib" + basename + ".so"; | |
197 | fd = open (filename.c_str (), O_RDONLY | O_BINARY); | |
198 | if (fd >= 0) | |
199 | { | |
200 | *pfilename = filename; | |
201 | return fd; | |
202 | } | |
203 | ||
204 | filename = pfilename->substr (0, basename_pos) + "lib" + basename + ".a"; | |
205 | fd = open (filename.c_str (), O_RDONLY | O_BINARY); | |
206 | if (fd >= 0) | |
207 | { | |
208 | *pfilename = filename; | |
209 | return fd; | |
210 | } | |
211 | ||
212 | filename = *pfilename + ".o"; | |
213 | fd = open (filename.c_str (), O_RDONLY | O_BINARY); | |
214 | if (fd >= 0) | |
215 | { | |
216 | *pfilename = filename; | |
217 | return fd; | |
218 | } | |
219 | ||
220 | return -1; | |
221 | } | |
222 | ||
223 | // Look for export data in the file descriptor FD. | |
224 | ||
225 | Import::Stream * | |
226 | Import::find_export_data (const std::string &filename, int fd, | |
227 | Location location) | |
228 | { | |
229 | // See if we can read this as an object file. | |
230 | Import::Stream *stream | |
231 | = Import::find_object_export_data (filename, fd, 0, location); | |
232 | if (stream != NULL) | |
233 | return stream; | |
234 | ||
235 | const int len = sizeof (Metadata::kMagicHeader); | |
236 | if (lseek (fd, 0, SEEK_SET) < 0) | |
237 | { | |
238 | rust_error_at (location, "lseek %s failed: %m", filename.c_str ()); | |
239 | return NULL; | |
240 | } | |
241 | ||
242 | char buf[len]; | |
243 | ssize_t c = ::read (fd, buf, len); | |
244 | if (c < len) | |
245 | return NULL; | |
246 | ||
247 | // Check for a file containing nothing but Go export data. | |
248 | // if (memcmp (buf, Export::cur_magic, Export::magic_len) == 0 | |
249 | // || memcmp (buf, Export::v1_magic, Export::magic_len) == 0 | |
250 | // || memcmp (buf, Export::v2_magic, Export::magic_len) == 0) | |
251 | // | |
252 | // FIXME we need to work out a better header | |
253 | // | |
254 | if (memcmp (buf, Metadata::kMagicHeader, sizeof (Metadata::kMagicHeader)) | |
255 | == 0) | |
256 | return new Stream_from_file (fd); | |
257 | ||
258 | // See if we can read this as an archive. | |
259 | if (Import::is_archive_magic (buf)) | |
260 | return Import::find_archive_export_data (filename, fd, location); | |
261 | ||
262 | return NULL; | |
263 | } | |
264 | ||
265 | // Look for export data in an object file. | |
266 | ||
267 | Import::Stream * | |
268 | Import::find_object_export_data (const std::string &filename, int fd, | |
269 | off_t offset, Location location) | |
270 | { | |
271 | char *buf; | |
272 | size_t len; | |
273 | int err; | |
274 | const char *errmsg = rust_read_export_data (fd, offset, &buf, &len, &err); | |
275 | if (errmsg != NULL) | |
276 | { | |
277 | if (err == 0) | |
278 | rust_error_at (location, "%s: %s", filename.c_str (), errmsg); | |
279 | else | |
280 | rust_error_at (location, "%s: %s: %s", filename.c_str (), errmsg, | |
281 | xstrerror (err)); | |
282 | return NULL; | |
283 | } | |
284 | ||
285 | if (buf == NULL) | |
286 | return NULL; | |
287 | ||
288 | return new Stream_from_buffer (buf, len); | |
289 | } | |
290 | ||
291 | // Class Import. | |
292 | ||
293 | // Construct an Import object. We make the builtin_types_ vector | |
294 | // large enough to hold all the builtin types. | |
295 | ||
296 | Import::Import (Stream *stream, Location location) | |
297 | : stream_ (stream), location_ (location) | |
298 | {} | |
299 | ||
300 | // Import the data in the associated stream. | |
301 | ||
302 | // Read LENGTH bytes from the stream. | |
303 | ||
304 | void | |
305 | Import::read (size_t length, std::string *out) | |
306 | { | |
307 | const char *data; | |
308 | if (!this->stream_->peek (length, &data)) | |
309 | { | |
310 | if (!this->stream_->saw_error ()) | |
311 | rust_error_at (this->location_, "import error at %d: expected %d bytes", | |
312 | this->stream_->pos (), static_cast<int> (length)); | |
313 | this->stream_->set_saw_error (); | |
314 | *out = std::string (""); | |
315 | return; | |
316 | } | |
317 | *out = std::string (data, length); | |
318 | this->advance (length); | |
319 | } | |
320 | ||
321 | // Class Import::Stream. | |
322 | ||
323 | Import::Stream::Stream () : pos_ (0), saw_error_ (false) {} | |
324 | ||
325 | Import::Stream::~Stream () {} | |
326 | ||
327 | // Return the next character to come from the stream. | |
328 | ||
329 | int | |
330 | Import::Stream::peek_char () | |
331 | { | |
332 | const char *read; | |
333 | if (!this->do_peek (1, &read)) | |
334 | return -1; | |
335 | // Make sure we return an unsigned char, so that we don't get | |
336 | // confused by \xff. | |
337 | unsigned char ret = *read; | |
338 | return ret; | |
339 | } | |
340 | ||
341 | // Return true if the next LENGTH characters from the stream match | |
342 | // BYTES | |
343 | ||
344 | bool | |
345 | Import::Stream::match_bytes (const char *bytes, size_t length) | |
346 | { | |
347 | const char *read; | |
348 | if (!this->do_peek (length, &read)) | |
349 | return false; | |
350 | return memcmp (bytes, read, length) == 0; | |
351 | } | |
352 | ||
353 | // Require that the next LENGTH bytes from the stream match BYTES. | |
354 | ||
355 | void | |
356 | Import::Stream::require_bytes (Location location, const char *bytes, | |
357 | size_t length) | |
358 | { | |
359 | const char *read; | |
360 | if (!this->do_peek (length, &read) || memcmp (bytes, read, length) != 0) | |
361 | { | |
362 | if (!this->saw_error_) | |
363 | rust_error_at (location, "import error at %d: expected %<%.*s%>", | |
364 | this->pos (), static_cast<int> (length), bytes); | |
365 | this->saw_error_ = true; | |
366 | return; | |
367 | } | |
368 | this->advance (length); | |
369 | } | |
370 | ||
371 | // Class Stream_from_file. | |
372 | ||
373 | Stream_from_file::Stream_from_file (int fd) : fd_ (fd), data_ () | |
374 | { | |
375 | if (lseek (fd, 0, SEEK_SET) != 0) | |
376 | { | |
377 | rust_fatal_error (Linemap::unknown_location (), "lseek failed: %m"); | |
378 | this->set_saw_error (); | |
379 | } | |
380 | } | |
381 | ||
382 | Stream_from_file::~Stream_from_file () { close (this->fd_); } | |
383 | ||
384 | // Read next bytes. | |
385 | ||
386 | bool | |
387 | Stream_from_file::do_peek (size_t length, const char **bytes) | |
388 | { | |
389 | if (this->data_.length () >= length) | |
390 | { | |
391 | *bytes = this->data_.data (); | |
392 | return true; | |
393 | } | |
394 | ||
395 | this->data_.resize (length); | |
396 | ssize_t got = ::read (this->fd_, &this->data_[0], length); | |
397 | ||
398 | if (got < 0) | |
399 | { | |
400 | if (!this->saw_error ()) | |
401 | rust_fatal_error (Linemap::unknown_location (), "read failed: %m"); | |
402 | this->set_saw_error (); | |
403 | return false; | |
404 | } | |
405 | ||
406 | if (lseek (this->fd_, -got, SEEK_CUR) < 0) | |
407 | { | |
408 | if (!this->saw_error ()) | |
409 | rust_fatal_error (Linemap::unknown_location (), "lseek failed: %m"); | |
410 | this->set_saw_error (); | |
411 | return false; | |
412 | } | |
413 | ||
414 | if (static_cast<size_t> (got) < length) | |
415 | return false; | |
416 | ||
417 | *bytes = this->data_.data (); | |
418 | return true; | |
419 | } | |
420 | ||
421 | // Advance. | |
422 | ||
423 | void | |
424 | Stream_from_file::do_advance (size_t skip) | |
425 | { | |
426 | if (lseek (this->fd_, skip, SEEK_CUR) < 0) | |
427 | { | |
428 | if (!this->saw_error ()) | |
429 | rust_fatal_error (Linemap::unknown_location (), "lseek failed: %m"); | |
430 | this->set_saw_error (); | |
431 | } | |
432 | if (!this->data_.empty ()) | |
433 | { | |
434 | if (this->data_.length () > skip) | |
435 | this->data_.erase (0, skip); | |
436 | else | |
437 | this->data_.clear (); | |
438 | } | |
439 | } | |
440 | ||
441 | } // namespace Rust |