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 
28 #ifndef _WIN32
29 #ifdef WITH_DEEPBIND
30 #ifndef __APPLE__
31 #if __GLIBC__
32 extern char **environ;
33 #endif
34 #endif
35 #endif
36 #endif
37 
38 
39 #ifdef _WIN32
40 #include <windows.h>
41 #include <fcntl.h>
42 #include <io.h>
43 #endif
44 
45 namespace casadi {
46 
47 // http://stackoverflow.com/questions/303562/c-format-macro-inline-ostringstream
48 #define STRING(ITEMS) \
49  ((dynamic_cast<std::ostringstream &>(std::ostringstream() \
50  . seekp(0, std::ios_base::cur) << ITEMS)) . str())
51 
52 char pathsep() {
53  #ifdef _WIN32
54  return ';';
55  #else
56  return ':';
57  #endif
58 }
59 std::string filesep() {
60  #ifdef _WIN32
61  return "\\";
62  #else
63  return "/";
64  #endif
65 }
66 
67 std::vector<std::string> get_search_paths() {
68 
69  // Build up search paths;
70  std::vector<std::string> search_paths;
71 
72  // Search path: global casadipath option
73  std::stringstream casadipaths(GlobalOptions::getCasadiPath());
74  std::string casadipath;
75  while (std::getline(casadipaths, casadipath, pathsep())) {
76  search_paths.push_back(casadipath);
77  }
78 
79  // Search path: CASADIPATH env variable
80  char* pLIBDIR;
81  pLIBDIR = getenv("CASADIPATH");
82 
83  if (pLIBDIR!=nullptr) {
84  std::stringstream casadipaths(pLIBDIR);
85  std::string casadipath;
86  while (std::getline(casadipaths, casadipath, pathsep())) {
87  search_paths.push_back(casadipath);
88  }
89  }
90 
91  // Search path: bare
92  search_paths.push_back("");
93 
94  // Search path : PLUGIN_EXTRA_SEARCH_PATH
95  #ifdef PLUGIN_EXTRA_SEARCH_PATH
96  search_paths.push_back(
97  std::string("") + PLUGIN_EXTRA_SEARCH_PATH);
98  #endif // PLUGIN_EXTRA_SEARCH_PATH
99 
100  // Search path : current directory
101  search_paths.push_back(".");
102 
103  return search_paths;
104 }
105 
106 #ifdef WITH_DL
107 
108 handle_t open_shared_library(const std::string& lib, const std::vector<std::string> &search_paths,
109  const std::string& caller, bool global) {
110  std::string resultpath;
111  return open_shared_library(lib, search_paths, resultpath, caller, global);
112 }
113 
114 int close_shared_library(handle_t handle) {
115  #ifdef _WIN32
116  return !FreeLibrary(handle);
117  #else // _WIN32
118  return dlclose(handle);
119  #endif // _WIN32
120 }
121 
122 handle_t open_shared_library(const std::string& lib, const std::vector<std::string> &search_paths,
123  std::string& resultpath, const std::string& caller, bool global) {
124  // Alocate a handle
125  handle_t handle = 0;
126 
127  // Alocate a handle pointer
128  #ifndef _WIN32
129  int flag;
130  if (global) {
131  flag = RTLD_NOW | RTLD_GLOBAL;
132  } else {
133  flag = RTLD_LAZY | RTLD_LOCAL;
134  }
135  #ifdef WITH_DEEPBIND
136  #if !defined(__APPLE__) && !defined(__EMSCRIPTEN__)
137  flag |= RTLD_DEEPBIND;
138 
139  #if __GLIBC__
140  // Workaround for https://github.com/conda-forge/casadi-feedstock/issues/93
141  // and https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111556
142  // In a nutshell, if RTLD_DEEPBIND is used and multiple symbols of environ
143  // (one in executable's .bss and one in glibc .bss)
144  // are present in the process due to copy relocations, make sure that the
145  // environ in glibc .bss has the same value of environ in executable .bss
146  // To avoid that over time the two values diverse due to the use of setenv,
147  // we restore the original value of glibc .bss's environ at the end of the function
148 
149  // Check if there is a duplicate environ
150  char*** p_environ_rtdl_next = reinterpret_cast<char ***>(dlsym(RTLD_NEXT, "environ"));
151  bool environ_rtdl_next_overridden = false;
152  char** environ_rtld_next_original_value = NULL;
153  if (p_environ_rtdl_next && p_environ_rtdl_next != &environ) {
154  environ_rtld_next_original_value = *p_environ_rtdl_next;
155  *p_environ_rtdl_next = environ;
156  environ_rtdl_next_overridden = true;
157  }
158  #endif
159  #endif
160  #endif
161  #endif
162 
163 
164  // Prepare error string
165  std::stringstream errors;
166  errors << caller << ": Cannot load shared library '"
167  << lib << "': " << std::endl;
168  errors << " (\n"
169  << " Searched directories: 1. casadipath from GlobalOptions\n"
170  << " 2. CASADIPATH env var\n"
171  << " 3. PATH env var (Windows)\n"
172  << " 4. LD_LIBRARY_PATH env var (Linux)\n"
173  << " 5. DYLD_LIBRARY_PATH env var (osx)\n"
174  << " A library may be 'not found' even if the file exists:\n"
175  << " * library is not compatible (different compiler/bitness)\n"
176  << " * the dependencies are not found\n"
177  << " )";
178 
179  std::string searchpath;
180 
181  // Try getting a handle
182  for (casadi_int i=0;i<search_paths.size();++i) {
183  searchpath = search_paths[i];
184 #ifdef _WIN32
185  SetDllDirectory(TEXT(searchpath.c_str()));
186  handle = LoadLibrary(TEXT(lib.c_str()));
187  SetDllDirectory(NULL);
188 #else // _WIN32
189  std::string libname = searchpath.empty() ? lib : searchpath + filesep() + lib;
190  handle = dlopen(libname.c_str(), flag);
191 #endif // _WIN32
192  if (handle) {
193  resultpath = searchpath;
194  break;
195  } else {
196  errors << std::endl << " Tried '" << searchpath << "' :";
197 #ifdef _WIN32
198  errors << std::endl << " Error code (WIN32): " << STRING(GetLastError());
199 #else // _WIN32
200  errors << std::endl << " Error code: " << dlerror();
201 #endif // _WIN32
202  }
203  }
204 
205  #ifndef _WIN32
206  #ifdef WITH_DEEPBIND
207  #ifndef __APPLE__
208  #if __GLIBC__
209  if (environ_rtdl_next_overridden) {
210  *p_environ_rtdl_next = environ_rtld_next_original_value;
211  environ_rtdl_next_overridden = false;
212  }
213  #endif
214  #endif
215  #endif
216  #endif
217 
218  casadi_assert(handle!=nullptr, errors.str());
219 
220  return handle;
221 }
222 
223 #endif // WITH_DL
224 
225 
226 // Convert UTF-8 to UTF-16 on Windows
227 #ifdef _WIN32
228 std::wstring utf8_to_utf16(const std::string& s) {
229  int wlen = MultiByteToWideChar(CP_UTF8, 0, s.data(), static_cast<int>(s.size()), nullptr, 0);
230  if (wlen == 0) return {};
231  std::wstring ws(wlen, 0);
232  MultiByteToWideChar(CP_UTF8, 0, s.data(), static_cast<int>(s.size()), &ws[0], wlen);
233  return ws;
234 }
235 #endif
236 
237 #ifdef _WIN32
238 class FdStreamBuf : public std::streambuf {
239 public:
240  explicit FdStreamBuf(int fd, size_t bufsize = 4096)
241  : fd_(fd), buffer_(bufsize) {
242  setg(buffer_.data(), buffer_.data(), buffer_.data());
243  }
244 
245  ~FdStreamBuf() override {
246  if (fd_ >= 0) {
247  _close(fd_);
248  }
249  }
250 
251 protected:
252  int_type underflow() override {
253  if (gptr() < egptr()) {
254  return traits_type::to_int_type(*gptr());
255  }
256 
257  int n = _read(fd_, buffer_.data(), static_cast<unsigned int>(buffer_.size()));
258  if (n <= 0) {
259  return traits_type::eof();
260  }
261 
262  setg(buffer_.data(), buffer_.data(), buffer_.data() + n);
263  return traits_type::to_int_type(*gptr());
264  }
265 
266  std::streampos seekoff(std::streamoff off, std::ios_base::seekdir dir,
267  std::ios_base::openmode which = std::ios_base::in) override {
268  if (!(which & std::ios_base::in)) return -1;
269 
270  __int64 whence;
271  switch (dir) {
272  case std::ios_base::beg: whence = SEEK_SET; break;
273  case std::ios_base::cur:
274  // Need to include the offset in buffer
275  off -= egptr() - gptr();
276  whence = SEEK_CUR;
277  break;
278  case std::ios_base::end: whence = SEEK_END; break;
279  default: return -1;
280  }
281 
282  __int64 result = _lseeki64(fd_, off, static_cast<int>(whence));
283  if (result == -1) {
284  return -1;
285  }
286 
287  // Invalidate the buffer
288  setg(buffer_.data(), buffer_.data(), buffer_.data());
289  return result;
290  }
291 
292  std::streampos seekpos(std::streampos pos,
293  std::ios_base::openmode which = std::ios_base::in) override {
294  return seekoff(static_cast<std::streamoff>(pos), std::ios_base::beg, which);
295  }
296 
297 
298 private:
299  int fd_;
300  std::vector<char> buffer_;
301 };
302 
303 class FdOStreamBuf : public std::streambuf {
304 public:
305  explicit FdOStreamBuf(int fd, size_t bufsize = 4096)
306  : fd_(fd), buffer_(bufsize) {
307  setp(buffer_.data(), buffer_.data() + buffer_.size());
308  }
309 
310  ~FdOStreamBuf() override {
311  sync(); // flush on destruction
312  if (fd_ >= 0) {
313  _close(fd_);
314  }
315  }
316 
317 protected:
318  int_type overflow(int_type ch) override {
319  if (flush_buffer() == -1) return traits_type::eof();
320 
321  if (ch != traits_type::eof()) {
322  *pptr() = static_cast<char>(ch);
323  pbump(1);
324  }
325 
326  return ch;
327  }
328 
329  int sync() override {
330  return flush_buffer() == -1 ? -1 : 0;
331  }
332 
333 private:
334  int flush_buffer() {
335  int len = static_cast<int>(pptr() - pbase());
336  if (len > 0) {
337  int written = _write(fd_, pbase(), len);
338  if (written != len) return -1;
339  pbump(-len);
340  }
341  return 0;
342  }
343 
344  int fd_;
345  std::vector<char> buffer_;
346 };
347 
348 struct OwnedIStream {
349  std::unique_ptr<FdStreamBuf> buffer;
350  std::unique_ptr<std::istream> stream;
351 
352  explicit OwnedIStream(int fd)
353  : buffer(std::make_unique<FdStreamBuf>(fd)),
354  stream(std::make_unique<std::istream>(buffer.get())) {}
355 };
356 
357 struct StreamWithOwnedBuffer : public std::istream {
358  std::shared_ptr<OwnedIStream> owned;
359  explicit StreamWithOwnedBuffer(std::shared_ptr<OwnedIStream> o)
360  : std::istream(o->buffer.get()), owned(std::move(o)) {}
361 };
362 
363 struct OwnedOStream {
364  std::unique_ptr<FdOStreamBuf> buffer;
365  std::unique_ptr<std::ostream> stream;
366 
367  explicit OwnedOStream(int fd)
368  : buffer(std::make_unique<FdOStreamBuf>(fd)),
369  stream(std::make_unique<std::ostream>(buffer.get())) {}
370 };
371 
372 struct StreamWithOwnedOBuffer : public std::ostream {
373  std::shared_ptr<OwnedOStream> owned;
374  explicit StreamWithOwnedOBuffer(std::shared_ptr<OwnedOStream> o)
375  : std::ostream(o->buffer.get()), owned(std::move(o)) {}
376 };
377 #endif
378 
379 // Portable ifstream opener that supports UTF-8 filenames on Windows
380 std::unique_ptr<std::istream> ifstream_compat(const std::string& utf8_path,
381  std::ios::openmode mode) {
382 #ifdef _WIN32
383  std::wstring utf16_path = utf8_to_utf16(utf8_path);
384  DWORD access = 0;
385  access |= GENERIC_READ;
386  if (mode & std::ios::out) access |= GENERIC_WRITE;
387 
388  HANDLE h = CreateFileW(utf16_path.c_str(), access,
389  FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
390  OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
391  if (h == INVALID_HANDLE_VALUE) return {};
392 
393  int flags = (mode & std::ios::out) ? _O_RDWR : _O_RDONLY;
394  if (mode & std::ios::binary) flags |= _O_BINARY;
395 
396  int fd = _open_osfhandle(reinterpret_cast<intptr_t>(h), flags);
397  if (fd == -1) {
398  CloseHandle(h);
399  return {};
400  }
401  auto owned = std::make_shared<OwnedIStream>(fd);
402  return std::unique_ptr<StreamWithOwnedBuffer>(new StreamWithOwnedBuffer(std::move(owned)));
403 #else
404  auto ifs = std::unique_ptr<std::ifstream>(new std::ifstream(utf8_path, mode));
405  if (!*ifs) return {};
406  return std::unique_ptr<std::istream>(std::move(ifs));
407 #endif
408 }
409 
410 std::unique_ptr<std::ostream> ofstream_compat(const std::string& utf8_path,
411  std::ios::openmode mode) {
412 #ifdef _WIN32
413  std::wstring utf16_path = utf8_to_utf16(utf8_path);
414 
415  DWORD access = 0;
416  access |= GENERIC_WRITE;
417  if (mode & std::ios::in) access |= GENERIC_READ;
418 
419  DWORD creation = (mode & std::ios::app) ? OPEN_ALWAYS : CREATE_ALWAYS;
420 
421  HANDLE h = CreateFileW(utf16_path.c_str(), access,
422  FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
423  creation, FILE_ATTRIBUTE_NORMAL, nullptr);
424  if (h == INVALID_HANDLE_VALUE) return {};
425 
426  int flags = (mode & std::ios::in) ? _O_RDWR : _O_WRONLY;
427  if (mode & std::ios::app) flags |= _O_APPEND;
428  if (mode & std::ios::binary) flags |= _O_BINARY;
429 
430  int fd = _open_osfhandle(reinterpret_cast<intptr_t>(h), flags);
431  if (fd == -1) {
432  CloseHandle(h);
433  return {};
434  }
435 
436  auto owned = std::make_shared<OwnedOStream>(fd);
437  return std::unique_ptr<StreamWithOwnedOBuffer>(new StreamWithOwnedOBuffer(std::move(owned)));
438 #else
439  auto ofs = std::unique_ptr<std::ofstream>(new std::ofstream(utf8_path, mode));
440  if (!*ofs) return {};
441  return std::unique_ptr<std::ostream>(std::move(ofs));
442 #endif
443 }
444 
445 } // namespace casadi
static std::string getCasadiPath()
The casadi namespace.
Definition: archiver.cpp:28
std::string filesep()
Definition: casadi_os.cpp:59
std::unique_ptr< std::istream > ifstream_compat(const std::string &utf8_path, std::ios::openmode mode)
Definition: casadi_os.cpp:380
std::unique_ptr< std::ostream > ofstream_compat(const std::string &utf8_path, std::ios::openmode mode)
Definition: casadi_os.cpp:410
std::vector< std::string > get_search_paths()
Definition: casadi_os.cpp:67
char pathsep()
Definition: casadi_os.cpp:52