Add plugin api to add native dll searching path (#343)
[platform/core/dotnet/launcher.git] / NativeLauncher / launcher / lib / core_runtime.cc
1 /*
2  * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an AS IS BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17
18 #include <dlfcn.h>
19 #include <signal.h>
20 #include <dirent.h>
21
22 #include <string>
23 #include <fstream>
24 #include <vector>
25 #include <sstream>
26
27 #include <locale>
28 #include <codecvt>
29
30 #include <fcntl.h>
31 #include <sys/stat.h>
32 #include <sys/types.h>
33 #include <sys/wait.h>
34 #include <unistd.h>
35 #include <linux/limits.h>
36
37 #include <storage.h>
38 #include <vconf.h>
39 #include <app_common.h>
40
41 #include <Ecore.h>
42
43 #include "injection.h"
44 #include "utils.h"
45 #include "log.h"
46 #include "core_runtime.h"
47 #include "plugin_manager.h"
48 #include "path_manager.h"
49
50 namespace tizen {
51 namespace runtime {
52 namespace dotnetcore {
53
54 static coreclr_initialize_ptr initializeClr = nullptr;
55 static coreclr_execute_assembly_ptr executeAssembly = nullptr;
56 static coreclr_shutdown_ptr shutdown = nullptr;
57 static coreclr_create_delegate_ptr createDelegate = nullptr;
58 static set_environment_variable_ptr setEnvironmentVariable = nullptr;
59 static void* __coreclrLib = nullptr;
60 static void* __hostHandle = nullptr;
61 static unsigned int __domainId = -1;
62 static bool __initialized = false;
63 static bool __isProfileMode = false;
64 PathManager* CoreRuntime::__pm = nullptr;
65
66 static std::vector<std::string> __envList;
67
68 static void setEnvFromFile()
69 {
70         std::string envList;
71         std::ifstream inFile(ENV_FILE_PATH);
72
73         __envList.clear();
74
75         if (inFile) {
76                 _INFO("coreclr_env.list is found");
77
78                 std::string token;
79                 while (std::getline(inFile, token, '\n')) {
80                         if (!token.empty()) {
81                                 __envList.push_back(token);
82                         }
83                 }
84
85                 for (unsigned int i = 0; i < __envList.size(); i++) {
86                         putenv(const_cast<char *>(__envList[i].c_str()));
87                 }
88         } else {
89                 _INFO("coreclr_env.list file is not found. skip");
90         }
91 }
92
93 #define _unused(x) ((void)(x))
94
95 struct sigaction sig_abrt_new;
96 struct sigaction sig_abrt_old;
97
98 static bool checkOnSigabrt = false;
99 static bool checkOnTerminate = false;
100
101 static void onSigabrt(int signum)
102 {
103         // use unused variable to avoid build warning
104         ssize_t ret = write(STDERR_FILENO, "onSigabrt called\n", 17);
105
106         if (checkOnTerminate) {
107                 ret = write(STDERR_FILENO, "onSigabrt called while terminate. go to exit\n", 45);
108                 _unused(ret);
109                 exit(0);
110         }
111
112         if (checkOnSigabrt) {
113                 ret = write(STDERR_FILENO, "onSigabrt called again. go to exit\n", 35);
114                 _unused(ret);
115                 exit(0);
116         }
117
118         checkOnSigabrt = true;
119         if (sigaction(SIGABRT, &sig_abrt_old, NULL) == 0) {
120                 if (raise(signum) < 0) {
121                         ret = write(STDERR_FILENO, "Fail to raise SIGABRT\n", 22);
122                 }
123         } else {
124                 ret = write(STDERR_FILENO, "Fail to set original SIGABRT handler\n", 37);
125         }
126         _unused(ret);
127 }
128
129 static void registerSigHandler()
130 {
131         sig_abrt_new.sa_handler = onSigabrt;
132         if (sigemptyset(&sig_abrt_new.sa_mask) != 0) {
133                 _ERR("Fail to sigemptyset");
134         }
135
136         if (sigaction(SIGABRT, &sig_abrt_new, &sig_abrt_old) < 0) {
137                 _ERR("Fail to add sig handler");
138         }
139 }
140
141 static bool storage_cb(int id, storage_type_e type, storage_state_e state, const char *path, void *user_data)
142 {
143         int* tmp = (int*)user_data;
144         if (type == STORAGE_TYPE_INTERNAL)
145         {
146                 *tmp = id;
147                 return false;
148         }
149
150         return true;
151 }
152
153 static void initEnvForSpecialFolder()
154 {
155         int storageId;
156         int error;
157         char *path = NULL;
158
159         error = storage_foreach_device_supported(storage_cb, &storageId);
160         if (error != STORAGE_ERROR_NONE) {
161                 return;
162         }
163
164         error = storage_get_directory(storageId, STORAGE_DIRECTORY_IMAGES, &path);
165         if (error == STORAGE_ERROR_NONE && path != NULL) {
166                 setenv("XDG_PICTURES_DIR", const_cast<char *>(path), 1);
167                 free(path);
168                 path = NULL;
169         }
170
171         error = storage_get_directory(storageId, STORAGE_DIRECTORY_MUSIC, &path);
172         if (error == STORAGE_ERROR_NONE && path != NULL) {
173                 setenv("XDG_MUSIC_DIR", const_cast<char *>(path), 1);
174                 free(path);
175                 path = NULL;
176         }
177
178         error = storage_get_directory(storageId, STORAGE_DIRECTORY_VIDEOS, &path);
179         if (error == STORAGE_ERROR_NONE && path != NULL) {
180                 setenv("XDG_VIDEOS_DIR", const_cast<char *>(path), 1);
181                 free(path);
182                 path = NULL;
183         }
184 }
185
186 // terminate candidate process when language changed
187 // icu related data (CultureInfo, etc) should be recreated.
188 static void langChangedCB(keynode_t *key, void* data)
189 {
190         _INFO("terminiate candidate process to update language.");
191         exit(0);
192 }
193
194 static void setLang()
195 {
196         char* lang = vconf_get_str(VCONFKEY_LANGSET);
197         if (!lang) {
198                 _ERR("Fail to get language from vconf");
199                 return;
200         }
201
202         // In order to operate ICU (used for globalization) normally, the following
203         // environment variables must be set before using ICU API.
204         // When running Applicaiton, the following environment variables are set by AppFW.
205         // But when preloading the dll in the candidate process, the following environment variables are not set
206         // As a result, CultureInfo is incorrectly generated and malfunctions.
207         // For example, uloc_getDefault() returns en_US_POSIX, CultureInfo is set to invariant mode.
208         setenv("LANG", const_cast<char *>(lang), 1);
209         setlocale(LC_ALL, const_cast<char *>(lang));
210
211         free(lang);
212 }
213
214 static std::string readSelfPath()
215 {
216         char buff[PATH_MAX];
217         ssize_t len = ::readlink("/proc/self/exe", buff, sizeof(buff)-1);
218         if (len != -1) {
219                 buff[len] = '\0';
220                 return std::string(buff);
221         }
222
223         return "";
224 }
225
226 static void removeDebugPipe()
227 {
228         DIR *dir;
229         struct dirent* entry;
230         char debugPipeFiles[PATH_MAX];;
231         sprintf(debugPipeFiles, "/tmp/clr-debug-pipe-%d-", getpid());
232
233         dir = opendir("/tmp");
234         if (dir == nullptr) {
235                 _ERR("Fail to open /tmp directory");
236                 return;
237         }
238
239         while ((entry = readdir(dir)) != nullptr) {
240                 std::string path = concatPath("/tmp", entry->d_name);
241                 if (path.find(debugPipeFiles) != std::string::npos) {
242                         if (!removeFile(path)) {
243                                 _ERR("Fail to remove file (%s)", path.c_str());
244                         }
245                 }
246         }
247
248         closedir(dir);
249 }
250
251 void preload()
252 {
253         typedef void (*PreloadDelegate)();
254         PreloadDelegate preloadDelegate;
255
256         int ret = createDelegate(__hostHandle,
257                 __domainId,
258                 "Tizen.Runtime",
259                 "Tizen.Runtime.Preloader",
260                 "Preload",
261                 (void**)&preloadDelegate);
262
263         if (ret < 0) {
264                 _ERR("Failed to create delegate for Tizen.Runtime Preload (0x%08x)", ret);
265         } else {
266                 preloadDelegate();
267         }
268
269         pluginPreload();
270 }
271
272 bool initializeCoreClr(PathManager* pm, const std::string& tpa)
273 {
274         const char *propertyKeys[] = {
275                 "TRUSTED_PLATFORM_ASSEMBLIES",
276                 "APP_PATHS",
277                 "APP_NI_PATHS",
278                 "NATIVE_DLL_SEARCH_DIRECTORIES",
279                 "AppDomainCompatSwitch"
280         };
281
282         const char *propertyValues[] = {
283                 tpa.c_str(),
284                 pm->getAppPaths().c_str(),
285                 pm->getAppNIPaths().c_str(),
286                 pm->getNativeDllSearchingPaths().c_str(),
287                 "UseLatestBehaviorWhenTFMNotSpecified"
288         };
289
290         std::string selfPath = readSelfPath();
291
292         int st = initializeClr(selfPath.c_str(),
293                                                         "TizenDotnetApp",
294                                                         sizeof(propertyKeys) / sizeof(propertyKeys[0]),
295                                                         propertyKeys,
296                                                         propertyValues,
297                                                         &__hostHandle,
298                                                         &__domainId);
299
300         if (st < 0) {
301                 _ERR("initialize core clr fail! (0x%08x)", st);
302                 return false;
303         }
304
305         pluginSetCoreclrInfo(__hostHandle, __domainId, createDelegate);
306
307         _INFO("Initialize core clr success");
308         return true;
309 }
310
311 int CoreRuntime::initialize(const char* appType, LaunchMode launchMode)
312 {
313         if (__initialized) {
314                 _ERR("CoreRuntime is already initialized");
315                 return -1;
316         }
317
318         // Intiailize ecore first (signal handlers, etc.) before runtime init.
319         ecore_init();
320
321         // set language environment to support ICU
322         setLang();
323
324         char *env = nullptr;
325         env = getenv("CORECLR_ENABLE_PROFILING");
326         if (env != nullptr && !strcmp(env, "1")) {
327                 _INFO("profiling mode on");
328                 __isProfileMode = true;
329         }
330
331         // plugin initialize should be called before creating threads.
332         // In case of VD plugins, attaching secure zone is done in the plugin_initialize().
333         // When attaching to a secure zone, if there is a created thread, it will failed.
334         // So, plugin initialize should be called before creating threads.
335         if (initializePluginManager(appType) < 0) {
336                 _ERR("Failed to initialize PluginManager");
337         }
338
339         // checkInjection checks dotnet-launcher run mode
340         // At the moment, this mechanism is used only when the Memory Profiler is started.
341         int res = checkInjection();
342         if (res != 0) {
343                 _ERR("Failed to initnialize Memory Profiler");
344                 return -1;
345         }
346
347 #ifdef __arm__
348         // libunwind library is used to unwind stack frame, but libunwind for ARM
349         // does not support ARM vfpv3/NEON registers in DWARF format correctly.
350         // Therefore let's disable stack unwinding using DWARF information
351         // See https://github.com/dotnet/coreclr/issues/6698
352         //
353         // libunwind use following methods to unwind stack frame.
354         // UNW_ARM_METHOD_ALL          0xFF
355         // UNW_ARM_METHOD_DWARF        0x01
356         // UNW_ARM_METHOD_FRAME        0x02
357         // UNW_ARM_METHOD_EXIDX        0x04
358         putenv(const_cast<char *>("UNW_ARM_UNWIND_METHOD=6"));
359 #endif // __arm__
360
361         // Enable diagnostics.
362         // clr create clr-debug-pipe-xxx and dotnet-diagnostics-xxx file under /tmp dir.
363         putenv(const_cast<char *>("COMPlus_EnableDiagnostics=1"));
364
365         // Write Debug.WriteLine to stderr
366         putenv(const_cast<char *>("COMPlus_DebugWriteToStdErr=1"));
367
368 #ifdef USE_DEFAULT_BASE_ADDR
369         putenv(const_cast<char *>("COMPlus_UseDefaultBaseAddr=1"));
370 #endif // USE_DEFAULT_BASE_ADDR
371
372         // read string from external file and set them to environment value.
373         setEnvFromFile();
374
375         try {
376                 __pm = new PathManager();
377         } catch (const std::exception& e) {
378                 _ERR("Failed to create PathManager");
379                 return -1;
380         }
381
382         char* pluginDllPaths = pluginGetDllPath();
383         if (pluginDllPaths && pluginDllPaths[0] != '\0') {
384                 __pm->addPlatformAssembliesPaths(pluginDllPaths, true);
385         }
386
387         char* pluginNativePaths = pluginGetNativeDllSearchingPath();
388         if (pluginNativePaths && pluginNativePaths[0] != '\0') {
389                 __pm->addNativeDllSearchingPaths(pluginNativePaths, true);
390         }
391
392         pluginHasLogControl();
393
394         std::string libCoreclr(concatPath(__pm->getRuntimePath(), "libcoreclr.so"));
395
396         __coreclrLib = dlopen(libCoreclr.c_str(), RTLD_NOW | RTLD_LOCAL);
397         if (__coreclrLib == nullptr) {
398                 char *err = dlerror();
399                 _ERR("dlopen failed to open libcoreclr.so with error %s", err);
400                 return -1;
401         }
402
403 #define CORELIB_RETURN_IF_NOSYM(type, variable, name) \
404         do { \
405                 variable = (type)dlsym(__coreclrLib, name); \
406                 if (variable == nullptr) { \
407                         _ERR(name " is not found in the libcoreclr.so"); \
408                         return -1; \
409                 } \
410         } while (0)
411
412         CORELIB_RETURN_IF_NOSYM(coreclr_initialize_ptr, initializeClr, "coreclr_initialize");
413         CORELIB_RETURN_IF_NOSYM(coreclr_execute_assembly_ptr, executeAssembly, "coreclr_execute_assembly");
414         CORELIB_RETURN_IF_NOSYM(coreclr_shutdown_ptr, shutdown, "coreclr_shutdown");
415         CORELIB_RETURN_IF_NOSYM(coreclr_create_delegate_ptr, createDelegate, "coreclr_create_delegate");
416
417 #undef CORELIB_RETURN_IF_NOSYM
418
419         _INFO("libcoreclr dlopen and dlsym success");
420
421         // Set environment for System.Environment.SpecialFolder
422         // Below function creates dbus connection by callging storage API.
423         // If dbus connection is created bofere fork(), forked process cannot use dbus.
424         // To avoid gdbus blocking issue, below function should be called after fork()
425         initEnvForSpecialFolder();
426
427         std::string tpa;
428         char* pluginTPA = pluginGetTPA();
429         if (pluginTPA && pluginTPA[0] != '\0') {
430                 tpa = std::string(pluginTPA);
431         } else {
432                 addAssembliesFromDirectories(__pm->getPlatformAssembliesPaths(), tpa);
433         }
434
435         if (!initializeCoreClr(__pm, tpa)) {
436                 _ERR("Failed to initialize coreclr");
437                 return -1;
438         }
439
440         int st = createDelegate(__hostHandle, __domainId, "Tizen.Runtime", "Tizen.Runtime.Environment", "SetEnvironmentVariable", (void**)&setEnvironmentVariable);
441         if (st < 0 || setEnvironmentVariable == nullptr) {
442                 _ERR("Create delegate for Tizen.Runtime.dll -> Tizen.Runtime.Environment -> SetEnvironmentVariable failed (0x%08x)", st);
443                 return -1;
444         }
445
446         if (launchMode == LaunchMode::loader) {
447                 // terminate candidate process if language is changed.
448                 // CurrentCulture created for preloaded dlls should be updated.
449                 vconf_notify_key_changed(VCONFKEY_LANGSET, langChangedCB, NULL);
450
451                 // preload libraries and manage dlls for optimizing startup time
452                 preload();
453
454                 // The debug pipe created in the candidate process has a "User" label.
455                 // As a result, smack deny occurs when app process try to access the debug pipe.
456                 // Also, since debugging is performed only in standalone mode,
457                 // the debug pipe doesnot be used in the candidate process.
458                 // So, to avoid smack deny error, delete unused debug pipe files.
459                 removeDebugPipe();
460         }
461
462         __initialized = true;
463
464         _INFO("CoreRuntime initialize success");
465
466         return 0;
467 }
468
469 void CoreRuntime::finalize()
470 {
471         // call plugin finalize function to notify finalize to plugin
472         // dlclose shoud be done after coreclr shutdown to avoid breaking signal chain
473         pluginFinalize();
474
475         // ignore the signal generated by an exception that occurred during shutdown
476         checkOnTerminate = true;
477
478         // workaround : to prevent crash while process terminate on profiling mode,
479         //              kill process immediately.
480         // see https://github.com/dotnet/coreclr/issues/26687
481         if (__isProfileMode) {
482                 _INFO("shutdown process immediately.");
483                 _exit(0);
484         }
485
486         if (__hostHandle != nullptr) {
487                 int st = shutdown(__hostHandle, __domainId);
488                 if (st < 0)
489                         _ERR("shutdown core clr fail! (0x%08x)", st);
490                 __hostHandle = nullptr;
491         }
492
493         if (__coreclrLib != nullptr) {
494                 if (dlclose(__coreclrLib) != 0) {
495                         _ERR("libcoreclr.so close failed");
496                 }
497
498                 __coreclrLib = nullptr;
499         }
500
501         finalizePluginManager();
502
503         delete __pm;
504         __pm = NULL;
505
506         __envList.clear();
507
508         _INFO("CoreRuntime finalized");
509 }
510
511 int CoreRuntime::launch(const char* appId, const char* root, const char* path, int argc, char* argv[], bool profile)
512 {
513         if (!__initialized) {
514                 _ERR("Runtime is not initialized");
515                 return -1;
516         }
517
518         if (path == nullptr) {
519                 _ERR("executable path is null");
520                 return -1;
521         }
522
523         if (!isFile(path)) {
524                 _ERR("File not exist : %s", path);
525                 return -1;
526         }
527
528         // VD has their own signal handler.
529         if (!pluginHasLogControl()) {
530                 registerSigHandler();
531         }
532
533         pluginSetAppInfo(appId, path);
534
535         // temporal root path is overrided to real application root path
536         __pm->setAppRootPath(root);
537
538         // set application data path to coreclr environment.
539         // application data path can be changed by owner. So, we have to set data path just before launching.
540         char* localDataPath = app_get_data_path();
541         if (localDataPath != nullptr) {
542                 setEnvironmentVariable("XDG_DATA_HOME", localDataPath);
543
544                 // set profile.data path and collect/use it if it non-exists/exists.
545                 if (profile) {
546                         char multiCoreJitProfile[strlen(localDataPath) + strlen(PROFILE_BASENAME) + 1];
547                         strncpy(multiCoreJitProfile, localDataPath, strlen(localDataPath) + 1);
548                         strncat(multiCoreJitProfile, PROFILE_BASENAME, strlen(PROFILE_BASENAME));
549
550                         setEnvironmentVariable("COMPlus_MultiCoreJitProfile", multiCoreJitProfile);
551                         setEnvironmentVariable("COMPlus_MultiCoreJitMinNumCpus", "1");
552                         if (exist(multiCoreJitProfile)) {
553                                 setEnvironmentVariable("COMPlus_MultiCoreJitNoProfileGather", "1");
554                         }
555                 }
556                 free(localDataPath);
557         }
558
559         vconf_ignore_key_changed(VCONFKEY_LANGSET, langChangedCB);
560
561         pluginBeforeExecute();
562
563         _INFO("execute assembly : %s", path);
564
565         unsigned int ret = 0;
566         int st = executeAssembly(__hostHandle, __domainId, argc, (const char**)argv, path, &ret);
567         if (st < 0)
568                 _ERR("Failed to Execute Assembly %s (0x%08x)", path, st);
569         return ret;
570 }
571
572 }  // namespace dotnetcore
573 }  // namespace runtime
574 }  // namespace tizen