1 /*
2     SuperCollider real time audio synthesis system
3     Copyright (c) 2002 James McCartney. All rights reserved.
4     http://www.audiosynth.com
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
19 */
20 
21 
22 #include "SC_Lib_Cintf.h"
23 #include "SC_CoreAudio.h"
24 #include "SC_UnitDef.h"
25 #include "SC_BufGen.h"
26 #include "SC_World.h"
27 #include "SC_WorldOptions.h"
28 #include "SC_StringParser.h"
29 #include "SC_InterfaceTable.h"
30 #include "SC_Filesystem.hpp"
31 #include "ErrorMessage.hpp" // for apiVersionMismatch, apiVersionMissing
32 #include <stdexcept>
33 #include <iostream>
34 
35 #ifndef _MSC_VER
36 #    include <dirent.h>
37 #endif // _MSC_VER
38 
39 #ifdef _WIN32
40 #    include "SC_Win32Utils.h"
41 #else
42 #    include <dlfcn.h>
43 #    include <libgen.h>
44 #    include <sys/param.h>
45 #endif // _WIN32
46 
47 #include <boost/filesystem/path.hpp> // path
48 #include <boost/filesystem/operations.hpp> // is_directory
49 
50 #ifdef __APPLE__
51 extern "C" {
52 #    include <mach-o/dyld.h>
53 #    include <mach-o/getsect.h>
54 }
55 char gTempVal;
56 #endif // __APPLE__
57 
58 namespace bfs = boost::filesystem;
59 
60 Malloc gMalloc;
61 HashTable<SC_LibCmd, Malloc>* gCmdLib;
62 HashTable<struct UnitDef, Malloc>* gUnitDefLib = nullptr;
63 HashTable<struct BufGen, Malloc>* gBufGenLib = nullptr;
64 HashTable<struct PlugInCmd, Malloc>* gPlugInCmds = nullptr;
65 extern struct InterfaceTable gInterfaceTable;
66 SC_LibCmd* gCmdArray[NUMBER_OF_COMMANDS];
67 
68 void initMiscCommands();
69 static bool PlugIn_LoadDir(const bfs::path& dir, bool reportError);
70 std::vector<void*> open_handles;
71 #ifdef __APPLE__
read_section(const struct mach_header * mhp,unsigned long slide,const char * segname,const char * sectname)72 void read_section(const struct mach_header* mhp, unsigned long slide, const char* segname, const char* sectname) {
73     u_int32_t size;
74     char* sect = getsectdatafromheader(mhp, segname, sectname, &size);
75     if (!sect)
76         return;
77 
78     char* start = sect + slide;
79     char* end = start + size;
80 
81     while (start != end) {
82         gTempVal += *(char*)start;
83         start++;
84     }
85 }
86 #endif
87 
88 extern void IO_Load(InterfaceTable* table);
89 extern void Osc_Load(InterfaceTable* table);
90 extern void Delay_Load(InterfaceTable* table);
91 extern void BinaryOp_Load(InterfaceTable* table);
92 extern void Filter_Load(InterfaceTable* table);
93 extern void Gendyn_Load(InterfaceTable* table);
94 extern void LF_Load(InterfaceTable* table);
95 extern void Noise_Load(InterfaceTable* table);
96 extern void MulAdd_Load(InterfaceTable* table);
97 extern void Grain_Load(InterfaceTable* table);
98 extern void Pan_Load(InterfaceTable* table);
99 extern void Reverb_Load(InterfaceTable* table);
100 extern void Trigger_Load(InterfaceTable* table);
101 extern void UnaryOp_Load(InterfaceTable* table);
102 extern void DiskIO_Load(InterfaceTable* table);
103 extern void Test_Load(InterfaceTable* table);
104 extern void PhysicalModeling_Load(InterfaceTable* table);
105 extern void Demand_Load(InterfaceTable* table);
106 extern void DynNoise_Load(InterfaceTable* table);
107 extern void FFT_UGens_Load(InterfaceTable* table);
108 extern void iPhone_Load(InterfaceTable* table);
109 
110 
deinitialize_library()111 void deinitialize_library() {
112 #ifdef _WIN32
113     for (void* ptrhinstance : open_handles) {
114         HINSTANCE hinstance = (HINSTANCE)ptrhinstance;
115         void* ptr = (void*)GetProcAddress(hinstance, "unload");
116         if (ptr) {
117             UnLoadPlugInFunc unloadFunc = (UnLoadPlugInFunc)ptr;
118             (*unloadFunc)();
119         }
120     }
121     // FreeLibrary dlclose(handle);
122 #else
123     for (void* handle : open_handles) {
124         void* ptr = dlsym(handle, "unload");
125         if (ptr) {
126             UnLoadPlugInFunc unloadFunc = (UnLoadPlugInFunc)ptr;
127             (*unloadFunc)();
128         }
129     }
130 #endif
131     open_handles.clear();
132 }
initialize_library(const char * uGensPluginPath)133 void initialize_library(const char* uGensPluginPath) {
134     gCmdLib = new HashTable<SC_LibCmd, Malloc>(&gMalloc, 64, true);
135     gUnitDefLib = new HashTable<UnitDef, Malloc>(&gMalloc, 512, true);
136     gBufGenLib = new HashTable<BufGen, Malloc>(&gMalloc, 512, true);
137     gPlugInCmds = new HashTable<PlugInCmd, Malloc>(&gMalloc, 64, true);
138 
139     initMiscCommands();
140 
141 #ifdef STATIC_PLUGINS
142     IO_Load(&gInterfaceTable);
143     Osc_Load(&gInterfaceTable);
144     Delay_Load(&gInterfaceTable);
145     BinaryOp_Load(&gInterfaceTable);
146     Filter_Load(&gInterfaceTable);
147     Gendyn_Load(&gInterfaceTable);
148     LF_Load(&gInterfaceTable);
149     Noise_Load(&gInterfaceTable);
150     MulAdd_Load(&gInterfaceTable);
151     Grain_Load(&gInterfaceTable);
152     Pan_Load(&gInterfaceTable);
153     Reverb_Load(&gInterfaceTable);
154     Trigger_Load(&gInterfaceTable);
155     UnaryOp_Load(&gInterfaceTable);
156     DiskIO_Load(&gInterfaceTable);
157     PhysicalModeling_Load(&gInterfaceTable);
158     Test_Load(&gInterfaceTable);
159     Demand_Load(&gInterfaceTable);
160     DynNoise_Load(&gInterfaceTable);
161 #    if defined(SC_IPHONE) && !TARGET_IPHONE_SIMULATOR
162     iPhone_Load(&gInterfaceTable);
163 #    endif
164     FFT_UGens_Load(&gInterfaceTable);
165     return;
166 #endif // STATIC_PLUGINS
167 
168     // If uGensPluginPath is supplied, it is exclusive.
169     bool loadUGensExtDirs = true;
170     if (uGensPluginPath) {
171         loadUGensExtDirs = false;
172         SC_StringParser sp(uGensPluginPath, SC_STRPARSE_PATHDELIMITER);
173         while (!sp.AtEnd()) {
174             PlugIn_LoadDir(const_cast<char*>(sp.NextToken()), true);
175         }
176     }
177 
178     using DirName = SC_Filesystem::DirName;
179 
180     if (loadUGensExtDirs) {
181 #ifdef SC_PLUGIN_DIR
182         // load globally installed plugins
183         if (bfs::is_directory(SC_PLUGIN_DIR)) {
184             PlugIn_LoadDir(SC_PLUGIN_DIR, true);
185         }
186 #endif // SC_PLUGIN_DIR
187        // load default plugin directory
188         const bfs::path pluginDir = SC_Filesystem::instance().getDirectory(DirName::Resource) / SC_PLUGIN_DIR_NAME;
189 
190         if (bfs::is_directory(pluginDir)) {
191             PlugIn_LoadDir(pluginDir, true);
192         }
193     }
194 
195     // get extension directories
196     if (loadUGensExtDirs) {
197         // load system extension plugins
198         const bfs::path sysExtDir = SC_Filesystem::instance().getDirectory(DirName::SystemExtension);
199         PlugIn_LoadDir(sysExtDir, false);
200 
201         // load user extension plugins
202         const bfs::path userExtDir = SC_Filesystem::instance().getDirectory(DirName::UserExtension);
203         PlugIn_LoadDir(userExtDir, false);
204 
205         // load user plugin directories
206         SC_StringParser sp(getenv("SC_PLUGIN_PATH"), SC_STRPARSE_PATHDELIMITER);
207         while (!sp.AtEnd()) {
208             PlugIn_LoadDir(sp.NextToken(), true);
209         }
210     }
211 #ifdef __APPLE__
212     /* on darwin plugins are lazily loaded (dlopen uses mmap internally), which can produce audible
213         glitches when UGens have to be paged-in. to work around this we preload all the plugins by
214         iterating through their memory space. */
215 
216 #    ifndef __x86_64__
217     /* seems to cause a stack corruption on llvm-gcc-4.2, sdk 10.5 on 10.6 */
218 
219     unsigned long images = _dyld_image_count();
220     for (unsigned long i = 0; i < images; i++) {
221         const mach_header* hdr = _dyld_get_image_header(i);
222         unsigned long slide = _dyld_get_image_vmaddr_slide(i);
223         const char* name = _dyld_get_image_name(i);
224         uint32_t size;
225         char* sect;
226 
227         if (!strcmp(name + (strlen(name) - 4), ".scx")) {
228             read_section(hdr, slide, "__TEXT", "__text");
229             read_section(hdr, slide, "__TEXT", "__const");
230             read_section(hdr, slide, "__TEXT", "__cstring");
231             read_section(hdr, slide, "__TEXT", "__picsymbol_stub");
232             read_section(hdr, slide, "__TEXT", "__symbol_stub");
233             read_section(hdr, slide, "__TEXT", "__const");
234             read_section(hdr, slide, "__TEXT", "__literal4");
235             read_section(hdr, slide, "__TEXT", "__literal8");
236 
237             read_section(hdr, slide, "__DATA", "__data");
238             read_section(hdr, slide, "__DATA", "__la_symbol_ptr");
239             read_section(hdr, slide, "__DATA", "__nl_symbol_ptr");
240             read_section(hdr, slide, "__DATA", "__dyld");
241             read_section(hdr, slide, "__DATA", "__const");
242             read_section(hdr, slide, "__DATA", "__mod_init_func");
243             read_section(hdr, slide, "__DATA", "__bss");
244             read_section(hdr, slide, "__DATA", "__common");
245 
246             read_section(hdr, slide, "__IMPORT", "__jump_table");
247             read_section(hdr, slide, "__IMPORT", "__pointers");
248         }
249     }
250 #    endif // __x86_64__
251 
252 #endif // ifdef __APPLE__
253 }
254 
255 typedef int (*InfoFunction)();
256 
checkAPIVersion(void * f,const char * filename)257 bool checkAPIVersion(void* f, const char* filename) {
258     using namespace std;
259     using namespace scsynth;
260 
261     if (f) {
262         InfoFunction fn = (InfoFunction)f;
263         int pluginVersion = (*fn)();
264         if (pluginVersion == sc_api_version)
265             return true;
266         else
267             cout << ErrorMessage::apiVersionMismatch(filename, sc_api_version, pluginVersion) << endl;
268     } else {
269         cout << ErrorMessage::apiVersionNotFound(filename) << endl;
270     }
271 
272     return false;
273 }
274 
checkServerVersion(void * f,const char * filename)275 bool checkServerVersion(void* f, const char* filename) {
276     if (f) {
277         InfoFunction fn = (InfoFunction)f;
278         if ((*fn)() == 1)
279             return false;
280     }
281     return true;
282 }
283 
PlugIn_Load(const bfs::path & filename)284 static bool PlugIn_Load(const bfs::path& filename) {
285 #ifdef _WIN32
286     HINSTANCE hinstance = LoadLibraryW(filename.wstring().c_str());
287     // here, we have to use a utf-8 version of the string for printing
288     // because the native encoding on Windows is utf-16.
289     const std::string filename_utf8_str = SC_Codecvt::path_to_utf8_str(filename);
290     if (!hinstance) {
291         char* s;
292         DWORD lastErr = GetLastError();
293         FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
294                       lastErr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (char*)&s, 0, NULL);
295         scprintf("*** ERROR: LoadLibrary '%s' err '%s'\n", filename_utf8_str.c_str(), s);
296         LocalFree(s);
297         return false;
298     }
299 
300     void* apiVersionPtr = (void*)GetProcAddress(hinstance, "api_version");
301     if (!checkAPIVersion(apiVersionPtr, filename_utf8_str.c_str())) {
302         FreeLibrary(hinstance);
303         return false;
304     }
305 
306     void* serverCheckPtr = (void*)GetProcAddress(hinstance, "server_type");
307     if (!checkServerVersion(serverCheckPtr, filename_utf8_str.c_str())) {
308         FreeLibrary(hinstance);
309         return false;
310     }
311 
312     void* ptr = (void*)GetProcAddress(hinstance, "load");
313     if (!ptr) {
314         char* s;
315         FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
316                       GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (char*)&s, 0, NULL);
317         scprintf("*** ERROR: GetProcAddress err '%s'\n", s);
318         LocalFree(s);
319 
320         FreeLibrary(hinstance);
321         return false;
322     }
323 
324     LoadPlugInFunc loadFunc = (LoadPlugInFunc)ptr;
325     (*loadFunc)(&gInterfaceTable);
326 
327     // FIXME: at the moment we never call FreeLibrary() on a loaded plugin
328     open_handles.push_back(hinstance);
329     return true;
330 
331 #else // (ifndef _WIN32)
332     void* handle = dlopen(filename.c_str(), RTLD_NOW);
333 
334     if (!handle) {
335         scprintf("*** ERROR: dlopen '%s' err '%s'\n", filename.c_str(), dlerror());
336         dlclose(handle);
337         return false;
338     }
339 
340     void* apiVersionPtr = (void*)dlsym(handle, "api_version");
341     if (!checkAPIVersion(apiVersionPtr, filename.c_str())) {
342         dlclose(handle);
343         return false;
344     }
345 
346     void* serverCheckPtr = (void*)dlsym(handle, "server_type");
347     if (!checkServerVersion(serverCheckPtr, filename.c_str())) {
348         dlclose(handle);
349         return false;
350     }
351 
352     void* ptr = dlsym(handle, "load");
353     if (!ptr) {
354         scprintf("*** ERROR: dlsym load err '%s'\n", dlerror());
355         dlclose(handle);
356         return false;
357     }
358 
359     LoadPlugInFunc loadFunc = (LoadPlugInFunc)ptr;
360     (*loadFunc)(&gInterfaceTable);
361 
362     open_handles.push_back(handle);
363     return true;
364 
365 #endif // _WIN32
366 }
367 
PlugIn_LoadDir(const bfs::path & dir,bool reportError)368 static bool PlugIn_LoadDir(const bfs::path& dir, bool reportError) {
369     boost::system::error_code ec;
370     bfs::recursive_directory_iterator rditer(dir, bfs::symlink_option::recurse, ec);
371 
372     if (ec) {
373         if (reportError) {
374             scprintf("*** ERROR: open directory failed '%s': %s\n", SC_Codecvt::path_to_utf8_str(dir).c_str(),
375                      ec.message().c_str());
376             fflush(stdout);
377         }
378         return false;
379     }
380 
381     while (rditer != bfs::end(rditer)) {
382         const bfs::path path = *rditer;
383 
384         if (bfs::is_directory(path)) {
385             if (SC_Filesystem::instance().shouldNotCompileDirectory(path))
386                 rditer.no_push();
387             else
388                 ; // do nothing; recursion for free
389         } else if (path.extension() == SC_PLUGIN_EXT) {
390             // don't need to check result: PlugIn_Load does its own error handling and printing.
391             // A `false` return value here just means that loading didn't occur, which is not
392             // an error condition for us.
393             PlugIn_Load(path);
394         } else {
395             // not a plugin, do nothing
396         }
397 
398         rditer.increment(ec);
399         if (ec) {
400             scprintf("*** ERROR: Could not iterate on directory '%s': %s\n", SC_Codecvt::path_to_utf8_str(path).c_str(),
401                      ec.message().c_str());
402             return false;
403         }
404     }
405 
406     return true;
407 }
408