1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 1999-2021 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING.  If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 #if defined (HAVE_CONFIG_H)
27 #  include "config.h"
28 #endif
29 
30 #include <map>
31 
32 extern "C"
33 {
34 #if defined (HAVE_DLOPEN_API)
35 #  if defined (HAVE_DLFCN_H)
36 #    include <dlfcn.h>
37 #  else
38 extern void * dlopen (const char *, int);
39 extern const char * dlerror (void);
40 extern void * dlsym (void *, const char *);
41 extern int dlclose (void *);
42 #  endif
43 #elif defined (HAVE_LOADLIBRARY_API)
44 #  define WIN32_LEAN_AND_MEAN 1
45 #  include <windows.h>
46 #  include <psapi.h>
47 #endif
48 }
49 
50 #include "file-ops.h"
51 #include "file-stat.h"
52 #include "lo-error.h"
53 #include "oct-shlib.h"
54 #include "str-vec.h"
55 
56 #if defined (HAVE_LOADLIBRARY_API)
57 #  include "lo-sysdep.h"
58 #endif
59 
60 namespace octave
61 {
dynlib_rep(const std::string & f)62   dynamic_library::dynlib_rep::dynlib_rep (const std::string& f)
63     : m_count (1), m_fcn_names (), m_file (f), m_time_loaded (),
64       m_search_all_loaded (false)
65   {
66     s_instances[f] = this;
67 
68     if (is_out_of_date ())
69       (*current_liboctave_warning_with_id_handler)
70         ("Octave:warn-future-time-stamp",
71          "timestamp on file %s is in the future", m_file.c_str ());
72   }
73 
74   bool
is_out_of_date(void) const75   dynamic_library::dynlib_rep::is_out_of_date (void) const
76   {
77     sys::file_stat fs (m_file);
78     return (fs && fs.is_newer (m_time_loaded));
79   }
80 
81   void
fake_reload(void)82   dynamic_library::dynlib_rep::fake_reload (void)
83   {
84     // We can't actually reload the library, but we'll pretend we did.
85     sys::file_stat fs (m_file);
86     if (fs && fs.is_newer (m_time_loaded))
87       {
88         m_time_loaded = fs.mtime ();
89 
90         (*current_liboctave_warning_with_id_handler)
91           ("Octave:library-reload",
92            "library %s not reloaded due to existing references", m_file.c_str ());
93       }
94   }
95 
96   dynamic_library::dynlib_rep *
get_instance(const std::string & f,bool fake)97   dynamic_library::dynlib_rep::get_instance (const std::string& f, bool fake)
98   {
99     dynlib_rep *retval = nullptr;
100     std::map<std::string, dynlib_rep *>::iterator p = s_instances.find (f);
101     if (p != s_instances.end ())
102       {
103         retval = p->second;
104         retval->m_count++;
105         if (fake)
106           retval->fake_reload ();
107       }
108     else
109       retval = new_instance (f);
110 
111     return retval;
112   }
113 
114   std::list<std::string>
function_names(void) const115   dynamic_library::dynlib_rep::function_names (void) const
116   {
117     std::list<std::string> retval;
118 
119     for (const auto& p : m_fcn_names)
120       retval.push_back (p.first);
121 
122     return retval;
123   }
124 
125   void
add_fcn_name(const std::string & name)126   dynamic_library::dynlib_rep::add_fcn_name (const std::string& name)
127   {
128     auto p = m_fcn_names.find (name);
129 
130     if (p == m_fcn_names.end ())
131       m_fcn_names[name] = 1;
132     else
133       ++(p->second);
134   }
135 
136   bool
remove_fcn_name(const std::string & fcn_name)137   dynamic_library::dynlib_rep::remove_fcn_name (const std::string& fcn_name)
138   {
139     bool retval = false;
140 
141     auto p = m_fcn_names.find (fcn_name);
142 
143     if (p != m_fcn_names.end () && --(p->second) == 0)
144       {
145         m_fcn_names.erase (fcn_name);
146         retval = true;
147       }
148 
149     return retval;
150   }
151 
152   std::map<std::string, dynamic_library::dynlib_rep *>
153     dynamic_library::dynlib_rep::s_instances;
154 
155   dynamic_library::dynlib_rep dynamic_library::s_nil_rep;
156 
157 #if defined (HAVE_DLOPEN_API)
158 
159   class
160   octave_dlopen_shlib : public dynamic_library::dynlib_rep
161   {
162   public:
163 
164     octave_dlopen_shlib (const std::string& f);
165 
166     // No copying!
167 
168     octave_dlopen_shlib (const octave_dlopen_shlib&) = delete;
169 
170     octave_dlopen_shlib& operator = (const octave_dlopen_shlib&) = delete;
171 
172     ~octave_dlopen_shlib (void);
173 
174     void * search (const std::string& name,
175                    const dynamic_library::name_mangler& mangler
176                      = dynamic_library::name_mangler ());
177 
178     // FIXME: this is possibly redundant because failure to open a library will
179     // normally throw an exception, avoiding the construction of an invalid
180     // library.  Leave it here for possible future use.
181 
is_open(void) const182     bool is_open (void) const
183     {
184       return (m_search_all_loaded || m_library != nullptr);
185     }
186 
187   private:
188 
189     void *m_library;
190   };
191 
octave_dlopen_shlib(const std::string & f)192   octave_dlopen_shlib::octave_dlopen_shlib (const std::string& f)
193     : dynamic_library::dynlib_rep (f), m_library (nullptr)
194   {
195     int flags = 0;
196 
197     // Use RTLD_NOW to resolve all symbols before dlopen returns.
198     // By using this option, dlopen will detect errors and Octave
199     // won't exit if there are unresolved symbols in the file we are
200     // loading, and we may even get a useful diagnostic.
201 #  if defined (RTLD_NOW)
202     flags |= RTLD_NOW;
203 #  endif
204 
205     // Use RTLD_GLOBAL to export symbols from loaded objects so they are
206     // available to other subsequently loaded libraries.
207 #  if defined (RTLD_GLOBAL)
208     flags |= RTLD_GLOBAL;
209 #  endif
210 
211     if (m_file.empty ())
212       {
213         m_search_all_loaded = true;
214         return;
215       }
216 
217     m_library = dlopen (m_file.c_str (), flags);
218 
219     if (! m_library)
220       {
221         const char *msg = dlerror ();
222 
223         if (msg)
224           (*current_liboctave_error_handler) ("%s: failed to load: %s",
225                                               m_file.c_str (), msg);
226         else
227           (*current_liboctave_error_handler) ("%s: failed to load",
228                                               m_file.c_str ());
229       }
230   }
231 
~octave_dlopen_shlib(void)232   octave_dlopen_shlib::~octave_dlopen_shlib (void)
233   {
234     if (m_library)
235       dlclose (m_library);
236   }
237 
238   void *
search(const std::string & name,const dynamic_library::name_mangler & mangler)239   octave_dlopen_shlib::search (const std::string& name,
240                                const dynamic_library::name_mangler& mangler)
241   {
242     void *function = nullptr;
243 
244     if (! is_open ())
245       (*current_liboctave_error_handler)
246         ("shared library %s is not open", m_file.c_str ());
247 
248     std::string sym_name = name;
249 
250     if (mangler)
251       sym_name = mangler (name);
252 
253     if (m_search_all_loaded)
254       function = dlsym (RTLD_DEFAULT, sym_name.c_str ());
255     else
256       function = dlsym (m_library, sym_name.c_str ());
257 
258     return function;
259   }
260 
261 #elif defined (HAVE_LOADLIBRARY_API)
262 
263   class
264   octave_w32_shlib: public dynamic_library::dynlib_rep
265   {
266   public:
267 
268     octave_w32_shlib (const std::string& f);
269 
270     // No copying!
271 
272     octave_w32_shlib (const octave_w32_shlib&) = delete;
273 
274     octave_w32_shlib& operator = (const octave_w32_shlib&) = delete;
275 
276     ~octave_w32_shlib (void);
277 
278     void * search (const std::string& name,
279                    const dynamic_library::name_mangler& mangler
280                      = dynamic_library::name_mangler ());
281 
282     void * global_search (const std::string& sym_name);
283 
is_open(void) const284     bool is_open (void) const
285     {
286       return (m_search_all_loaded || m_handle != nullptr);
287     }
288 
289   private:
290 
291     HINSTANCE m_handle;
292   };
293 
octave_w32_shlib(const std::string & f)294   octave_w32_shlib::octave_w32_shlib (const std::string& f)
295     : dynamic_library::dynlib_rep (f), m_handle (nullptr)
296   {
297     if (f.empty())
298       {
299         m_search_all_loaded = true;
300         return;
301       }
302 
303     std::string dir = sys::file_ops::dirname (f);
304     std::wstring wdir = sys::u8_to_wstring (dir);
305     SetDllDirectoryW (dir.empty ()
306                       ? nullptr : wdir.c_str ());
307 
308     std::wstring wfile = sys::u8_to_wstring (m_file);
309     m_handle = LoadLibraryW (wfile.c_str ());
310 
311     SetDllDirectoryW (nullptr);
312 
313     if (! m_handle)
314       {
315         DWORD last_error = GetLastError ();
316 
317         wchar_t *error_text = nullptr;
318         FormatMessageW (FORMAT_MESSAGE_FROM_SYSTEM |
319                         FORMAT_MESSAGE_ALLOCATE_BUFFER |
320                         FORMAT_MESSAGE_IGNORE_INSERTS,
321                         nullptr, last_error,
322                         MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
323                         reinterpret_cast <wchar_t *> (&error_text), 0, nullptr);
324 
325         std::ostringstream err_str;
326         err_str << "opening the library '" << m_file << "' failed (error "
327                 << last_error << "): ";
328         if (error_text != nullptr)
329           {
330             err_str << sys::u8_from_wstring (error_text);
331             LocalFree (error_text);
332           }
333         else
334           err_str << "Unknown error.";
335 
336         (*current_liboctave_error_handler) ("%s", err_str.str ().c_str ());
337       }
338   }
339 
~octave_w32_shlib(void)340   octave_w32_shlib::~octave_w32_shlib (void)
341   {
342     if (m_handle)
343       FreeLibrary (m_handle);
344   }
345 
346   void *
global_search(const std::string & sym_name)347   octave_w32_shlib::global_search (const std::string& sym_name)
348   {
349     void *function = nullptr;
350 
351     HANDLE proc = GetCurrentProcess ();
352 
353     if (! proc)
354       (*current_liboctave_error_handler)
355         ("Unable to get handle to own process.");
356 
357     std::size_t lib_num = 64;
358     std::size_t size_lib = sizeof (HMODULE);
359     HMODULE *h_libs;
360     DWORD bytes_all_libs;
361     bool got_libs;
362 
363     // Get a list of all the libraries in own process.
364     h_libs = static_cast<HMODULE *> (malloc (size_lib*lib_num));
365     got_libs = EnumProcessModules (proc, h_libs, size_lib*lib_num,
366                                    &bytes_all_libs);
367     int ii = 0;
368     while (((size_lib*lib_num) < bytes_all_libs) && ii++ < 3)
369       {
370         lib_num = bytes_all_libs / size_lib;
371         h_libs = static_cast<HMODULE *> (realloc (h_libs, bytes_all_libs));
372         got_libs = EnumProcessModules (proc, h_libs, bytes_all_libs,
373                                        &bytes_all_libs);
374       }
375 
376     if (got_libs)
377       {
378         for (std::size_t i = 0; i < (bytes_all_libs / size_lib); i++)
379           {
380             // Check for function in library.
381             function = reinterpret_cast<void *>
382                        (GetProcAddress (h_libs[i], sym_name.c_str ()));
383 
384             if (function)
385               break;
386           }
387       }
388 
389     // Release the handle to the process.
390     CloseHandle (proc);
391 
392     return function;
393   }
394 
395   void *
search(const std::string & name,const dynamic_library::name_mangler & mangler)396   octave_w32_shlib::search (const std::string& name,
397                             const dynamic_library::name_mangler& mangler)
398   {
399     void *function = nullptr;
400 
401     if (! m_search_all_loaded && ! is_open ())
402       (*current_liboctave_error_handler)
403         ("shared library %s is not open", m_file.c_str ());
404 
405     std::string sym_name = name;
406 
407     if (mangler)
408       sym_name = mangler (name);
409 
410     if (m_search_all_loaded)
411       function = global_search (sym_name);
412     else
413       function = reinterpret_cast<void *> (GetProcAddress (m_handle,
414                                                            sym_name.c_str ()));
415 
416     return function;
417   }
418 
419 #endif
420 
421   dynamic_library::dynlib_rep *
new_instance(const std::string & f)422   dynamic_library::dynlib_rep::new_instance (const std::string& f)
423   {
424 #if defined (HAVE_DLOPEN_API)
425     return new octave_dlopen_shlib (f);
426 #elif defined (HAVE_LOADLIBRARY_API)
427     return new octave_w32_shlib (f);
428 #else
429     (*current_liboctave_error_handler)
430       ("support for dynamically loaded libraries was unavailable or disabled when liboctave was built");
431 #endif
432   }
433 }
434