You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

321 lines
8.2KB

  1. #include <thread>
  2. #include <map>
  3. #include <stdexcept>
  4. #include <sys/types.h>
  5. #include <sys/stat.h>
  6. #include <unistd.h>
  7. #include <sys/param.h> // for MAXPATHLEN
  8. #include <fcntl.h>
  9. #if defined ARCH_WIN
  10. #include <windows.h>
  11. #include <direct.h>
  12. #else
  13. #include <dlfcn.h> // for dlopen
  14. #endif
  15. #include <dirent.h>
  16. #include <osdialog.h>
  17. #include <jansson.h>
  18. #include <plugin.hpp>
  19. #include <system.hpp>
  20. #include <asset.hpp>
  21. #include <string.hpp>
  22. #include <context.hpp>
  23. #include <plugin/callbacks.hpp>
  24. #include <settings.hpp>
  25. namespace rack {
  26. namespace core {
  27. void init(rack::plugin::Plugin* plugin);
  28. } // namespace core
  29. namespace plugin {
  30. ////////////////////
  31. // private API
  32. ////////////////////
  33. static void* loadLibrary(std::string libraryPath) {
  34. #if defined ARCH_WIN
  35. SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
  36. std::wstring libraryFilenameW = string::U8toU16(libraryPath);
  37. HINSTANCE handle = LoadLibraryW(libraryFilenameW.c_str());
  38. SetErrorMode(0);
  39. if (!handle) {
  40. int error = GetLastError();
  41. throw Exception(string::f("Failed to load library %s: code %d", libraryPath.c_str(), error));
  42. }
  43. #else
  44. // As of Rack v2.0, plugins are linked with `-rpath=.` so change current directory so it can find libRack.
  45. std::string cwd = system::getWorkingDirectory();
  46. system::setWorkingDirectory(asset::systemDir);
  47. // Change it back when we're finished
  48. DEFER({system::setWorkingDirectory(cwd);});
  49. // Load library with dlopen
  50. void* handle = dlopen(libraryPath.c_str(), RTLD_NOW | RTLD_LOCAL);
  51. if (!handle)
  52. throw Exception(string::f("Failed to load library %s: %s", libraryPath.c_str(), dlerror()));
  53. #endif
  54. return handle;
  55. }
  56. typedef void (*InitCallback)(Plugin*);
  57. static InitCallback loadPluginCallback(Plugin* plugin) {
  58. // Load plugin library
  59. std::string libraryExt;
  60. #if defined ARCH_LIN
  61. libraryExt = "so";
  62. #elif defined ARCH_WIN
  63. libraryExt = "dll";
  64. #elif ARCH_MAC
  65. libraryExt = "dylib";
  66. #endif
  67. std::string libraryPath = system::join(plugin->path, "plugin." + libraryExt);
  68. // Check file existence
  69. if (!system::isFile(libraryPath))
  70. throw Exception(string::f("Plugin binary not found at %s", libraryPath.c_str()));
  71. // Load dynamic/shared library
  72. plugin->handle = loadLibrary(libraryPath);
  73. // Get plugin's init() function
  74. InitCallback initCallback;
  75. #if defined ARCH_WIN
  76. initCallback = (InitCallback) GetProcAddress((HMODULE) plugin->handle, "init");
  77. #else
  78. initCallback = (InitCallback) dlsym(plugin->handle, "init");
  79. #endif
  80. if (!initCallback)
  81. throw Exception(string::f("Failed to read init() symbol in %s", libraryPath.c_str()));
  82. return initCallback;
  83. }
  84. /** If path is blank, loads Core */
  85. static Plugin* loadPlugin(std::string path) {
  86. Plugin* plugin = new Plugin;
  87. try {
  88. // Set plugin path
  89. plugin->path = (path == "") ? asset::systemDir : path;
  90. // Get modified timestamp
  91. if (path != "") {
  92. struct stat statbuf;
  93. if (!stat(path.c_str(), &statbuf)) {
  94. #if defined ARCH_MAC
  95. plugin->modifiedTimestamp = (double) statbuf.st_mtimespec.tv_sec + statbuf.st_mtimespec.tv_nsec * 1e-9;
  96. #elif defined ARCH_WIN
  97. plugin->modifiedTimestamp = (double) statbuf.st_mtime;
  98. #elif defined ARCH_LIN
  99. plugin->modifiedTimestamp = (double) statbuf.st_mtim.tv_sec + statbuf.st_mtim.tv_nsec * 1e-9;
  100. #endif
  101. }
  102. }
  103. // Load plugin.json
  104. std::string manifestFilename = (path == "") ? asset::system("Core.json") : system::join(path, "plugin.json");
  105. FILE* file = std::fopen(manifestFilename.c_str(), "r");
  106. if (!file)
  107. throw Exception(string::f("Manifest file %s does not exist", manifestFilename.c_str()));
  108. DEFER({std::fclose(file);});
  109. json_error_t error;
  110. json_t* rootJ = json_loadf(file, 0, &error);
  111. if (!rootJ)
  112. throw Exception(string::f("JSON parsing error at %s %d:%d %s", manifestFilename.c_str(), error.line, error.column, error.text));
  113. DEFER({json_decref(rootJ);});
  114. // Call init callback
  115. InitCallback initCallback;
  116. if (path == "") {
  117. initCallback = core::init;
  118. }
  119. else {
  120. initCallback = loadPluginCallback(plugin);
  121. }
  122. initCallback(plugin);
  123. // Load manifest
  124. plugin->fromJson(rootJ);
  125. // Reject plugin if slug already exists
  126. Plugin* oldPlugin = getPlugin(plugin->slug);
  127. if (oldPlugin)
  128. throw Exception(string::f("Plugin %s is already loaded, not attempting to load it again", plugin->slug.c_str()));
  129. plugins.push_back(plugin);
  130. INFO("Loaded plugin %s v%s from %s", plugin->slug.c_str(), plugin->version.c_str(), plugin->path.c_str());
  131. }
  132. catch (Exception& e) {
  133. WARN("Could not load plugin %s: %s", path.c_str(), e.what());
  134. delete plugin;
  135. plugin = NULL;
  136. }
  137. return plugin;
  138. }
  139. static void loadPlugins(std::string path) {
  140. for (std::string pluginPath : system::getEntries(path)) {
  141. if (!system::isDirectory(pluginPath))
  142. continue;
  143. if (!loadPlugin(pluginPath)) {
  144. // Ignore bad plugins. They are reported in the log.
  145. }
  146. }
  147. }
  148. static void extractPackages(std::string path) {
  149. std::string message;
  150. for (std::string packagePath : system::getEntries(path)) {
  151. if (!system::isFile(packagePath))
  152. continue;
  153. if (system::getExtension(packagePath) != ".vcvplugin")
  154. continue;
  155. // Extract package
  156. INFO("Extracting package %s", packagePath.c_str());
  157. try {
  158. system::unarchiveToFolder(packagePath, path);
  159. }
  160. catch (Exception& e) {
  161. WARN("Plugin package %s failed to extract: %s", packagePath.c_str(), e.what());
  162. message += string::f("Could not extract plugin package %s\n", packagePath.c_str());
  163. continue;
  164. }
  165. // Remove package
  166. system::remove(packagePath.c_str());
  167. }
  168. if (!message.empty()) {
  169. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
  170. }
  171. }
  172. ////////////////////
  173. // public API
  174. ////////////////////
  175. void init() {
  176. // Don't re-initialize
  177. if (!plugins.empty())
  178. return;
  179. // Load Core
  180. loadPlugin("");
  181. // Get user plugins directory
  182. system::createDirectory(asset::pluginsPath);
  183. // Extract packages and load plugins
  184. extractPackages(asset::pluginsPath);
  185. loadPlugins(asset::pluginsPath);
  186. // If Fundamental wasn't loaded, copy the bundled Fundamental package and load it
  187. std::string fundamentalSrc = asset::system("Fundamental.vcvplugin");
  188. std::string fundamentalDir = system::join(asset::pluginsPath, "Fundamental");
  189. if (!settings::devMode && !getPlugin("Fundamental") && system::isFile(fundamentalSrc)) {
  190. INFO("Extracting bundled Fundamental package");
  191. system::unarchiveToFolder(fundamentalSrc.c_str(), asset::pluginsPath.c_str());
  192. loadPlugin(fundamentalDir);
  193. }
  194. }
  195. void destroy() {
  196. for (Plugin* plugin : plugins) {
  197. // We must delete the plugin *before* freeing the library, because the vtable of Model subclasses are static in the plugin, and we need it for the virtual destructor.
  198. void* handle = plugin->handle;
  199. delete plugin;
  200. // Free library handle
  201. if (handle) {
  202. #if defined ARCH_WIN
  203. FreeLibrary((HINSTANCE) handle);
  204. #else
  205. dlclose(handle);
  206. #endif
  207. }
  208. }
  209. plugins.clear();
  210. }
  211. Plugin* getPlugin(const std::string& pluginSlug) {
  212. for (Plugin* plugin : plugins) {
  213. if (plugin->slug == pluginSlug) {
  214. return plugin;
  215. }
  216. }
  217. return NULL;
  218. }
  219. Model* getModel(const std::string& pluginSlug, const std::string& modelSlug) {
  220. Plugin* plugin = getPlugin(pluginSlug);
  221. if (!plugin)
  222. return NULL;
  223. Model* model = plugin->getModel(modelSlug);
  224. if (!model)
  225. return NULL;
  226. return model;
  227. }
  228. Model* modelFromJson(json_t* moduleJ) {
  229. // Get slugs
  230. json_t* pluginSlugJ = json_object_get(moduleJ, "plugin");
  231. if (!pluginSlugJ)
  232. throw Exception("\"plugin\" property not found in module JSON");
  233. std::string pluginSlug = json_string_value(pluginSlugJ);
  234. pluginSlug = normalizeSlug(pluginSlug);
  235. json_t* modelSlugJ = json_object_get(moduleJ, "model");
  236. if (!modelSlugJ)
  237. throw Exception("\"model\" property not found in module JSON");
  238. std::string modelSlug = json_string_value(modelSlugJ);
  239. modelSlug = normalizeSlug(modelSlug);
  240. // Get Model
  241. Model* model = getModel(pluginSlug, modelSlug);
  242. if (!model)
  243. throw Exception(string::f("Could not find module \"%s\" of plugin \"%s\"", modelSlug.c_str(), pluginSlug.c_str()));
  244. return model;
  245. }
  246. bool isSlugValid(const std::string& slug) {
  247. for (char c : slug) {
  248. if (!(std::isalnum(c) || c == '-' || c == '_'))
  249. return false;
  250. }
  251. return true;
  252. }
  253. std::string normalizeSlug(const std::string& slug) {
  254. std::string s;
  255. for (char c : slug) {
  256. if (!(std::isalnum(c) || c == '-' || c == '_'))
  257. continue;
  258. s += c;
  259. }
  260. return s;
  261. }
  262. std::vector<Plugin*> plugins;
  263. } // namespace plugin
  264. } // namespace rack