casadi_os.cpp
1 /*
2  * This file is part of CasADi.
3  *
4  * CasADi -- A symbolic framework for dynamic optimization.
5  * Copyright (C) 2010 by Joel Andersson, Moritz Diehl, K.U.Leuven. All rights reserved.
6  *
7  * CasADi is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 3 of the License, or (at your option) any later version.
11  *
12  * CasADi is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with CasADi; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20  *
21  */
22 
23 #include "casadi_os.hpp"
24 #include "exception.hpp"
25 #include "global_options.hpp"
26 #include <bitset>
27 #include <cstdlib>
28 #include <cstring>
29 #ifdef CASADI_WITH_THREAD
30 #ifdef CASADI_WITH_THREAD_MINGW
31 #include <mingw.mutex.h>
32 #else // CASADI_WITH_THREAD_MINGW
33 #include <mutex>
34 #endif // CASADI_WITH_THREAD_MINGW
35 #endif //CASADI_WITH_THREAD
36 #ifdef __EMSCRIPTEN__
37 #include <set>
38 #endif
39 
40 #ifndef _WIN32
41 #ifdef WITH_DEEPBIND
42 #ifndef __APPLE__
43 #if __GLIBC__
44 extern char **environ;
45 #endif
46 #endif
47 #endif
48 #endif
49 
50 
51 #ifdef _WIN32
52 #include <windows.h>
53 #include <fcntl.h>
54 #include <io.h>
55 #endif
56 
57 namespace casadi {
58 
59 // http://stackoverflow.com/questions/303562/c-format-macro-inline-ostringstream
60 #define STRING(ITEMS) \
61  ((dynamic_cast<std::ostringstream &>(std::ostringstream() \
62  . seekp(0, std::ios_base::cur) << (ITEMS))) . str())
63 
64 char pathsep() {
65  #ifdef _WIN32
66  return ';';
67  #else
68  return ':';
69  #endif
70 }
71 std::string filesep() {
72  #ifdef _WIN32
73  return "\\";
74  #else
75  return "/";
76  #endif
77 }
78 
79 std::vector<std::string> get_search_paths() {
80 
81  // Build up search paths;
82  std::vector<std::string> search_paths;
83 
84  // Search path: CASADI_PLUGIN_SEARCH_PATH env variable
85  // (highest priority; takes precedence over the bundled install dir
86  // that the Python wrapper writes into GlobalOptions::casadipath)
87  char* pPLUGIN = getenv("CASADI_PLUGIN_SEARCH_PATH");
88  if (pPLUGIN!=nullptr) {
89  std::stringstream pluginpaths(pPLUGIN);
90  std::string pluginpath;
91  while (std::getline(pluginpaths, pluginpath, pathsep())) {
92  search_paths.push_back(pluginpath);
93  }
94  }
95 
96  // Search path: global casadipath option
97  std::stringstream casadipaths(GlobalOptions::getCasadiPath());
98  std::string casadipath;
99  while (std::getline(casadipaths, casadipath, pathsep())) {
100  search_paths.push_back(casadipath);
101  }
102 
103  // Search path: CASADIPATH env variable
104  char* pLIBDIR;
105  pLIBDIR = getenv("CASADIPATH");
106 
107  if (pLIBDIR!=nullptr) {
108  std::stringstream casadipaths(pLIBDIR);
109  std::string casadipath;
110  while (std::getline(casadipaths, casadipath, pathsep())) {
111  search_paths.push_back(casadipath);
112  }
113  }
114 
115  // Search path: bare
116  search_paths.push_back("");
117 
118  // Search path : PLUGIN_EXTRA_SEARCH_PATH
119  #ifdef PLUGIN_EXTRA_SEARCH_PATH
120  search_paths.push_back(
121  std::string("") + PLUGIN_EXTRA_SEARCH_PATH);
122  #endif // PLUGIN_EXTRA_SEARCH_PATH
123 
124  // Search path : current directory
125  search_paths.push_back(".");
126 
127  return search_paths;
128 }
129 
130 #ifdef _WIN32
131 // Forward declaration; defined below.
132 std::wstring utf8_to_utf16(const std::string& s);
133 #endif
134 
135 #ifdef WITH_DL
136 
137 #ifndef _WIN32
138 #ifdef WITH_DEEPBIND
139 #if !defined(__APPLE__) && !defined(__EMSCRIPTEN__)
140 #if __GLIBC__
141 namespace {
142  // Copy relocation gives a process two distinct `environ` objects: the live one in the
143  // executable's .bss, and a permanently-NULL one in glibc's .bss. Code loaded under
144  // RTLD_DEEPBIND binds to the latter and therefore sees no environment at all --
145  // getenv() is unaffected, but anything iterating environ directly (every OpenMP
146  // runtime does, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111556) silently
147  // reads nothing. See also https://github.com/conda-forge/casadi-feedstock/issues/93
148  //
149  // We publish a snapshot into that dead slot. The snapshot is ours, so glibc can never
150  // free it and it never has to be restored -- restoring is what caused casadi#4317
151  // (writes NULL back, later readers null-deref) and casadi#4373 (leaves a pointer that
152  // glibc's setenv may realloc away).
153  //
154  // A superseded snapshot can still be held by an earlier plugin, so it is never freed.
155  // valgrind reports those as definitely lost; see the casadi/environ_snapshot entry in
156  // test/internal/valgrind-casadi.supp.
157  char** environ_snapshot = nullptr;
158  std::size_t environ_snapshot_n = 0;
159 #ifdef CASADI_WITH_THREAD
160  std::mutex environ_snapshot_mutex;
161 #endif //CASADI_WITH_THREAD
162 
163  void publish_environ_snapshot() {
164  char*** slot = reinterpret_cast<char***>(dlsym(RTLD_NEXT, "environ"));
165  if (!slot || slot == &environ) return; // no duplicate symbol: nothing to do
166 #ifdef CASADI_WITH_THREAD
167  std::lock_guard<std::mutex> lock(environ_snapshot_mutex);
168 #endif //CASADI_WITH_THREAD
169  std::size_t n = 0;
170  if (environ) while (environ[n]) ++n;
171  if (environ_snapshot && n == environ_snapshot_n &&
172  std::memcmp(environ_snapshot, environ, n * sizeof(char*)) == 0) {
173  *slot = environ_snapshot; // unchanged: republish, allocate nothing
174  return;
175  }
176  char** fresh = static_cast<char**>(std::malloc((n + 1) * sizeof(char*)));
177  if (!fresh) return; // OOM: leave the slot as it was
178  if (n) std::memcpy(fresh, environ, n * sizeof(char*));
179  fresh[n] = nullptr;
180  environ_snapshot = fresh; // previous snapshot deliberately leaked:
181  environ_snapshot_n = n; // an earlier plugin may still hold it
182  *slot = environ_snapshot;
183  }
184 } // namespace
185 #endif
186 #endif
187 #endif
188 #endif
189 
190 handle_t open_shared_library(const std::string& lib, const std::vector<std::string> &search_paths,
191  const std::string& caller, bool global) {
192  std::string resultpath;
193  return open_shared_library(lib, search_paths, resultpath, caller, global);
194 }
195 
196 int close_shared_library(handle_t handle) {
197  #ifdef _WIN32
198  return !FreeLibrary(handle);
199  #else // _WIN32
200  return dlclose(handle);
201  #endif // _WIN32
202 }
203 
204 handle_t open_shared_library(const std::string& lib, const std::vector<std::string> &search_paths,
205  std::string& resultpath, const std::string& caller, bool global) {
206  // Alocate a handle
207  handle_t handle = nullptr;
208 
209  // Alocate a handle pointer
210  #ifndef _WIN32
211  int flag;
212  if (global) {
213  flag = RTLD_NOW | RTLD_GLOBAL;
214  } else {
215  flag = RTLD_LAZY | RTLD_LOCAL;
216  }
217  #ifdef WITH_DEEPBIND
218  #if !defined(__APPLE__) && !defined(__EMSCRIPTEN__)
219  flag |= RTLD_DEEPBIND;
220 
221  #if __GLIBC__
222  // Hand DEEPBIND-loaded code an environment it can iterate. Never restored;
223  // see the comment on publish_environ_snapshot above.
224  publish_environ_snapshot();
225  #endif
226  #endif
227  #endif
228  #endif
229 
230 
231  // Prepare error string
232  std::stringstream errors;
233  errors << caller << ": Cannot load shared library '"
234  << lib << "': " << std::endl;
235  errors << " (\n"
236  << " Searched directories: 1. CASADI_PLUGIN_SEARCH_PATH env var\n"
237  << " 2. casadipath from GlobalOptions\n"
238  << " 3. CASADIPATH env var\n"
239  << " 4. PATH env var (Windows)\n"
240  << " 5. LD_LIBRARY_PATH env var (Linux)\n"
241  << " 6. DYLD_LIBRARY_PATH env var (osx)\n"
242  << " A library may be 'not found' even if the file exists:\n"
243  << " * library is not ABI-compatible (different compiler/bitness)\n"
244  << " * the dependencies are not found\n"
245  << " * the dependencies are found but have an ABI-incompatible version/compiler/bitness\n" // NOLINT(whitespace/line_length)
246  << " )";
247 
248  std::string searchpath;
249 
250 #ifdef _WIN32
251  // Pass 1 (Windows): strict per-path search.
252  //
253  // For each non-empty searchpath, do AddDllDirectory + LoadLibraryEx with
254  // LOAD_LIBRARY_SEARCH_USER_DIRS | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
255  // | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR.
256  // PATH and CWD are NOT consulted in this pass. Transitive deps in the
257  // same folder as the wrapper resolve via DLL_LOAD_DIR; deps already in
258  // the process resolve via the loaded-module list (a pre-filesystem
259  // rule). Pass 1 succeeds only when the search dir is self-contained for
260  // anything not already loaded -- on failure we fall through to pass 2,
261  // which preserves CasADi's legacy semantics (incl. PATH).
262  {
263  std::wstring libW = utf8_to_utf16(lib);
264  for (const std::string& sp : search_paths) {
265  if (sp.empty()) continue;
266  std::wstring spW = utf8_to_utf16(sp);
267  DLL_DIRECTORY_COOKIE cookie = AddDllDirectory(spW.c_str());
268  handle = LoadLibraryExW(libW.c_str(), NULL,
269  LOAD_LIBRARY_SEARCH_USER_DIRS |
270  LOAD_LIBRARY_SEARCH_DEFAULT_DIRS |
271  LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR);
272  if (cookie) RemoveDllDirectory(cookie);
273  if (handle) {
274  resultpath = sp;
275  break;
276  }
277  }
278  }
279 #endif // _WIN32
280 
281  // Pass 2 (Windows fallback; sole pass on Linux/macOS):
282  // Existing legacy loop. On Windows, preserves SetDllDirectory's slot-2
283  // hint for transitive deps and the standard search incl. PATH.
284  if (!handle) {
285 #ifdef __EMSCRIPTEN__
286  // Emscripten resolves "lib" and "./lib" to the SAME MEMFS module, and a
287  // first dlopen that can't complete synchronously (module not resident --
288  // e.g. a not-yet-fetched lazy plugin) leaves a poisoned "loading" entry;
289  // a second dlopen of the same module then aborts with "...a second time".
290  // So attempt each canonical name at most once. (Native loaders keep the
291  // full per-search-path loop below.)
292  std::set<std::string> em_tried;
293 #endif // __EMSCRIPTEN__
294  for (casadi_int i=0;i<search_paths.size();++i) {
295  searchpath = search_paths[i];
296 #ifdef _WIN32
297  SetDllDirectory(TEXT(searchpath.c_str()));
298  handle = LoadLibrary(TEXT(lib.c_str()));
299  SetDllDirectory(NULL);
300 #else // _WIN32
301  std::string libname = searchpath.empty() ? lib : searchpath + filesep() + lib;
302 #ifdef __EMSCRIPTEN__
303  if (libname.rfind("./", 0) == 0) libname.erase(0, 2); // canonicalize
304  if (!em_tried.insert(libname).second) continue; // already tried
305 #endif // __EMSCRIPTEN__
306  handle = dlopen(libname.c_str(), flag);
307 #endif // _WIN32
308  if (handle) {
309  resultpath = searchpath;
310  break;
311  } else {
312  errors << std::endl << " Tried '" << searchpath << "' :";
313 #ifdef _WIN32
314  errors << std::endl << " Error code (WIN32): " << STRING(GetLastError());
315 #else // _WIN32
316  errors << std::endl << " Error code: " << dlerror();
317 #endif // _WIN32
318  }
319  }
320  }
321 
322  #ifndef _WIN32
323  #ifdef WITH_DEEPBIND
324  #if !defined(__APPLE__) && !defined(__EMSCRIPTEN__)
325  #if __GLIBC__
326  // Pick up any setenv the constructors just performed, so code that reads environ
327  // lazily (rather than at load time) does not lag a load behind. Allocates nothing
328  // unless they changed something.
329  publish_environ_snapshot();
330  #endif
331  #endif
332  #endif
333  #endif
334 
335  casadi_assert(handle!=nullptr, errors.str());
336 
337  return handle;
338 }
339 
340 #endif // WITH_DL
341 
342 
343 // Convert UTF-8 to UTF-16 on Windows
344 #ifdef _WIN32
345 std::wstring utf8_to_utf16(const std::string& s) {
346  int wlen = MultiByteToWideChar(CP_UTF8, 0, s.data(), static_cast<int>(s.size()), nullptr, 0);
347  if (wlen == 0) return {};
348  std::wstring ws(wlen, 0);
349  MultiByteToWideChar(CP_UTF8, 0, s.data(), static_cast<int>(s.size()), &ws[0], wlen);
350  return ws;
351 }
352 #endif
353 
354 #ifdef _WIN32
355 class FdStreamBuf : public std::streambuf {
356 public:
357  explicit FdStreamBuf(int fd, size_t bufsize = 4096)
358  : fd_(fd), buffer_(bufsize) {
359  setg(buffer_.data(), buffer_.data(), buffer_.data());
360  }
361 
362  ~FdStreamBuf() override {
363  if (fd_ >= 0) {
364  _close(fd_);
365  }
366  }
367 
368 protected:
369  int_type underflow() override {
370  if (gptr() < egptr()) {
371  return traits_type::to_int_type(*gptr());
372  }
373 
374  int n = _read(fd_, buffer_.data(), static_cast<unsigned int>(buffer_.size()));
375  if (n <= 0) {
376  return traits_type::eof();
377  }
378 
379  setg(buffer_.data(), buffer_.data(), buffer_.data() + n);
380  return traits_type::to_int_type(*gptr());
381  }
382 
383  std::streampos seekoff(std::streamoff off, std::ios_base::seekdir dir,
384  std::ios_base::openmode which = std::ios_base::in) override {
385  if (!(which & std::ios_base::in)) return -1;
386 
387  __int64 whence;
388  switch (dir) {
389  case std::ios_base::beg: whence = SEEK_SET; break;
390  case std::ios_base::cur:
391  // Need to include the offset in buffer
392  off -= egptr() - gptr();
393  whence = SEEK_CUR;
394  break;
395  case std::ios_base::end: whence = SEEK_END; break;
396  default: return -1;
397  }
398 
399  __int64 result = _lseeki64(fd_, off, static_cast<int>(whence));
400  if (result == -1) {
401  return -1;
402  }
403 
404  // Invalidate the buffer
405  setg(buffer_.data(), buffer_.data(), buffer_.data());
406  return result;
407  }
408 
409  std::streampos seekpos(std::streampos pos,
410  std::ios_base::openmode which = std::ios_base::in) override {
411  return seekoff(static_cast<std::streamoff>(pos), std::ios_base::beg, which);
412  }
413 
414 
415 private:
416  int fd_;
417  std::vector<char> buffer_;
418 };
419 
420 class FdOStreamBuf : public std::streambuf {
421 public:
422  explicit FdOStreamBuf(int fd, size_t bufsize = 4096)
423  : fd_(fd), buffer_(bufsize) {
424  setp(buffer_.data(), buffer_.data() + buffer_.size());
425  }
426 
427  ~FdOStreamBuf() override {
428  sync(); // flush on destruction
429  if (fd_ >= 0) {
430  _close(fd_);
431  }
432  }
433 
434 protected:
435  int_type overflow(int_type ch) override {
436  if (flush_buffer() == -1) return traits_type::eof();
437 
438  if (ch != traits_type::eof()) {
439  *pptr() = static_cast<char>(ch);
440  pbump(1);
441  }
442 
443  return ch;
444  }
445 
446  int sync() override {
447  return flush_buffer() == -1 ? -1 : 0;
448  }
449 
450 private:
451  int flush_buffer() {
452  int len = static_cast<int>(pptr() - pbase());
453  if (len > 0) {
454  int written = _write(fd_, pbase(), len);
455  if (written != len) return -1;
456  pbump(-len);
457  }
458  return 0;
459  }
460 
461  int fd_;
462  std::vector<char> buffer_;
463 };
464 
465 struct OwnedIStream {
466  std::unique_ptr<FdStreamBuf> buffer;
467  std::unique_ptr<std::istream> stream;
468 
469  explicit OwnedIStream(int fd)
470  : buffer(std::make_unique<FdStreamBuf>(fd)),
471  stream(std::make_unique<std::istream>(buffer.get())) {}
472 };
473 
474 struct StreamWithOwnedBuffer : public std::istream {
475  std::shared_ptr<OwnedIStream> owned;
476  explicit StreamWithOwnedBuffer(std::shared_ptr<OwnedIStream> o)
477  : std::istream(o->buffer.get()), owned(std::move(o)) {}
478 };
479 
480 struct OwnedOStream {
481  std::unique_ptr<FdOStreamBuf> buffer;
482  std::unique_ptr<std::ostream> stream;
483 
484  explicit OwnedOStream(int fd)
485  : buffer(std::make_unique<FdOStreamBuf>(fd)),
486  stream(std::make_unique<std::ostream>(buffer.get())) {}
487 };
488 
489 struct StreamWithOwnedOBuffer : public std::ostream {
490  std::shared_ptr<OwnedOStream> owned;
491  explicit StreamWithOwnedOBuffer(std::shared_ptr<OwnedOStream> o)
492  : std::ostream(o->buffer.get()), owned(std::move(o)) {}
493 };
494 #endif
495 
496 // Portable ifstream opener that supports UTF-8 filenames on Windows
497 std::unique_ptr<std::istream> ifstream_compat(const std::string& utf8_path,
498  std::ios::openmode mode) {
499 #ifdef _WIN32
500  std::wstring utf16_path = utf8_to_utf16(utf8_path);
501  DWORD access = 0;
502  access |= GENERIC_READ;
503  if (mode & std::ios::out) access |= GENERIC_WRITE;
504 
505  HANDLE h = CreateFileW(utf16_path.c_str(), access,
506  FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
507  OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
508  if (h == INVALID_HANDLE_VALUE) return {};
509 
510  int flags = (mode & std::ios::out) ? _O_RDWR : _O_RDONLY;
511  if (mode & std::ios::binary) flags |= _O_BINARY;
512 
513  int fd = _open_osfhandle(reinterpret_cast<intptr_t>(h), flags);
514  if (fd == -1) {
515  CloseHandle(h);
516  return {};
517  }
518  auto owned = std::make_shared<OwnedIStream>(fd);
519  return std::unique_ptr<StreamWithOwnedBuffer>(new StreamWithOwnedBuffer(std::move(owned)));
520 #else
521  auto ifs = std::unique_ptr<std::ifstream>(new std::ifstream(utf8_path, mode));
522  if (!*ifs) return {};
523  return std::unique_ptr<std::istream>(std::move(ifs));
524 #endif
525 }
526 
527 std::unique_ptr<std::ostream> ofstream_compat(const std::string& utf8_path,
528  std::ios::openmode mode) {
529 #ifdef _WIN32
530  std::wstring utf16_path = utf8_to_utf16(utf8_path);
531 
532  DWORD access = 0;
533  access |= GENERIC_WRITE;
534  if (mode & std::ios::in) access |= GENERIC_READ;
535 
536  DWORD creation = (mode & std::ios::app) ? OPEN_ALWAYS : CREATE_ALWAYS;
537 
538  HANDLE h = CreateFileW(utf16_path.c_str(), access,
539  FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
540  creation, FILE_ATTRIBUTE_NORMAL, nullptr);
541  if (h == INVALID_HANDLE_VALUE) return {};
542 
543  int flags = (mode & std::ios::in) ? _O_RDWR : _O_WRONLY;
544  if (mode & std::ios::app) flags |= _O_APPEND;
545  if (mode & std::ios::binary) {
546  flags |= _O_BINARY;
547  } else {
548  flags |= _O_TEXT;
549  }
550 
551  int fd = _open_osfhandle(reinterpret_cast<intptr_t>(h), flags);
552  if (fd == -1) {
553  CloseHandle(h);
554  return {};
555  }
556 
557  auto owned = std::make_shared<OwnedOStream>(fd);
558  return std::unique_ptr<StreamWithOwnedOBuffer>(new StreamWithOwnedOBuffer(std::move(owned)));
559 #else
560  auto ofs = std::unique_ptr<std::ofstream>(new std::ofstream(utf8_path, mode));
561  if (!*ofs) return {};
562  return std::unique_ptr<std::ostream>(std::move(ofs));
563 #endif
564 }
565 
566 } // namespace casadi
static std::string getCasadiPath()
The casadi namespace.
Definition: archiver.cpp:28
std::string filesep()
Definition: casadi_os.cpp:71
std::unique_ptr< std::istream > ifstream_compat(const std::string &utf8_path, std::ios::openmode mode)
Definition: casadi_os.cpp:497
std::unique_ptr< std::ostream > ofstream_compat(const std::string &utf8_path, std::ios::openmode mode)
Definition: casadi_os.cpp:527
std::vector< std::string > get_search_paths()
Definition: casadi_os.cpp:79
void * handle_t
Definition: casadi_os.hpp:109
char pathsep()
Definition: casadi_os.cpp:64
Definition: sx_elem.cpp:508