d9d14bcdb13f1bc9e28fe85ce2b597eaeb46a020
[platform/core/dotnet/launcher.git] / NativeLauncher / launcher / lib / dotnet_launcher.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
21 #include <string>
22 #include <fstream>
23 #include <vector>
24 #include <sstream>
25
26 #include <locale>
27 #include <codecvt>
28
29 #include <fcntl.h>
30 #include <sys/stat.h>
31 #include <sys/types.h>
32 #include <sys/wait.h>
33 #include <unistd.h>
34 #include <linux/limits.h>
35
36 #include <storage.h>
37 #include <vconf.h>
38 #include <app_common.h>
39
40 #include <Ecore.h>
41
42 #include "injection.h"
43 #include "utils.h"
44 #include "log.h"
45 #include "dotnet_launcher.h"
46 #include "plugin_manager.h"
47 #include "path_manager.h"
48 #include "log_manager.h"
49
50 #define __XSTR(x) #x
51 #define __STR(x) __XSTR(x)
52 static const char* __FRAMEWORK_DIR = __STR(FRAMEWORK_DIR);
53 #undef __STR
54 #undef __XSTR
55
56 namespace tizen {
57 namespace runtime {
58 namespace dotnetcore {
59
60 #if defined (__aarch64__)
61 #define ARCHITECTURE_IDENTIFIER "arm64"
62
63 #elif defined (__arm__)
64 #define ARCHITECTURE_IDENTIFIER "armel"
65
66 #elif defined (__x86_64__)
67 #define ARCHITECTURE_IDENTIFIER "x64"
68
69 #elif defined (__i386__)
70 #define ARCHITECTURE_IDENTIFIER "x86"
71
72 #else
73 #error "Unknown target"
74 #endif
75
76 static const char* __TIZEN_RID_VERSION_KEY = "db/dotnet/tizen_rid_version";
77
78 // The sequence of RID_FALLBACK graphs must be:
79 // 1. Tizen + Version + Architecture
80 // 2. Tizen + Version
81 // 3. OS(tizen, linux, unix) + Architecture
82 // 4. OS(tizen, linux, unix)
83 // 5. any, base
84 static std::string getExtraNativeLibDirs(const std::string& appRoot)
85 {
86         std::vector<std::string> RID_FALLBACK_GRAPH;
87         std::vector<std::string> RID_FALLBACK_TIZEN;
88         char* tizen_rid = vconf_get_str(__TIZEN_RID_VERSION_KEY);
89         if (tizen_rid) {
90                 std::vector<std::string> version;
91                 splitPath(tizen_rid, version);
92                 std::reverse(std::begin(version), std::end(version));
93                 for (unsigned int i = 0; i < version.size(); i++) {
94                         RID_FALLBACK_TIZEN.push_back(std::string("tizen." + version[i] + "-" + ARCHITECTURE_IDENTIFIER));
95                         RID_FALLBACK_TIZEN.push_back(std::string("tizen." + version[i]));
96                 }
97                 free(tizen_rid);
98         }
99
100         std::vector<std::string> RID_FALLBACK_OS = {"tizen", "linux", "unix"};
101         for (unsigned int i = 0; i < RID_FALLBACK_OS.size(); i++) {
102                 RID_FALLBACK_GRAPH.push_back(std::string(RID_FALLBACK_OS[i] + "-" +  ARCHITECTURE_IDENTIFIER));
103                 RID_FALLBACK_GRAPH.push_back(std::string(RID_FALLBACK_OS[i]));
104         }
105         RID_FALLBACK_GRAPH.push_back("any");
106         RID_FALLBACK_GRAPH.push_back("base");
107
108         std::string candidate;
109         for (unsigned int i = 0; i < RID_FALLBACK_GRAPH.size(); i++) {
110                 if (!candidate.empty()) {
111                         candidate += ":";
112                 }
113                 candidate += concatPath(appRoot, "bin/runtimes/" + RID_FALLBACK_GRAPH[i] + "/native");
114         }
115
116         candidate = candidate + ":" + concatPath(appRoot, "lib/" ARCHITECTURE_IDENTIFIER);
117         if (!strncmp(ARCHITECTURE_IDENTIFIER, "arm64", 5)) {
118                 candidate = candidate + ":" + concatPath(appRoot, "lib/aarch64");
119         } else if (!strncmp(ARCHITECTURE_IDENTIFIER, "armel", 5)) {
120                 candidate = candidate + ":" + concatPath(appRoot, "lib/arm");
121         }
122
123         return candidate;
124 }
125
126
127 static std::vector<std::string> __envList;
128
129 static void setEnvFromFile()
130 {
131         std::string envList;
132         std::ifstream inFile(ENV_FILE_PATH);
133
134         __envList.clear();
135
136         if (inFile) {
137                 _INFO("coreclr_env.list is found");
138
139                 std::string token;
140                 while (std::getline(inFile, token, '\n')) {
141                         if (!token.empty()) {
142                                 __envList.push_back(token);
143                         }
144                 }
145
146                 for (unsigned int i = 0; i < __envList.size(); i++) {
147                         putenv(const_cast<char *>(__envList[i].c_str()));
148                 }
149         } else {
150                 _INFO("coreclr_env.list file is not found. skip");
151         }
152 }
153
154 #define _unused(x) ((void)(x))
155
156 struct sigaction sig_abrt_new;
157 struct sigaction sig_abrt_old;
158
159 static bool checkOnSigabrt = false;
160 static bool checkOnTerminate = false;
161
162 static void onSigabrt(int signum)
163 {
164         // use unused variable to avoid build warning
165         ssize_t ret = write(STDERR_FILENO, "onSigabrt called\n", 17);
166
167         if (checkOnTerminate) {
168                 ret = write(STDERR_FILENO, "onSigabrt called while terminate. go to exit\n", 45);
169                 _unused(ret);
170                 exit(0);
171         }
172
173         if (checkOnSigabrt) {
174                 ret = write(STDERR_FILENO, "onSigabrt called again. go to exit\n", 35);
175                 _unused(ret);
176                 exit(0);
177         }
178
179         checkOnSigabrt = true;
180         if (sigaction(SIGABRT, &sig_abrt_old, NULL) == 0) {
181                 if (raise(signum) < 0) {
182                         ret = write(STDERR_FILENO, "Fail to raise SIGABRT\n", 22);
183                 }
184         } else {
185                 ret = write(STDERR_FILENO, "Fail to set original SIGABRT handler\n", 37);
186         }
187         _unused(ret);
188 }
189
190 static void registerSigHandler()
191 {
192         sig_abrt_new.sa_handler = onSigabrt;
193         if (sigemptyset(&sig_abrt_new.sa_mask) != 0) {
194                 _ERR("Fail to sigemptyset");
195         }
196
197         if (sigaction(SIGABRT, &sig_abrt_new, &sig_abrt_old) < 0) {
198                 _ERR("Fail to add sig handler");
199         }
200 }
201
202 static bool storage_cb(int id, storage_type_e type, storage_state_e state, const char *path, void *user_data)
203 {
204         int* tmp = (int*)user_data;
205         if (type == STORAGE_TYPE_INTERNAL)
206         {
207                 *tmp = id;
208                 return false;
209         }
210
211         return true;
212 }
213
214 static void initEnvForSpecialFolder()
215 {
216         int storageId;
217         int error;
218         char *path = NULL;
219
220         error = storage_foreach_device_supported(storage_cb, &storageId);
221         if (error != STORAGE_ERROR_NONE) {
222                 return;
223         }
224
225         error = storage_get_directory(storageId, STORAGE_DIRECTORY_IMAGES, &path);
226         if (error == STORAGE_ERROR_NONE && path != NULL) {
227                 setenv("XDG_PICTURES_DIR", const_cast<char *>(path), 1);
228                 free(path);
229                 path = NULL;
230         }
231
232         error = storage_get_directory(storageId, STORAGE_DIRECTORY_MUSIC, &path);
233         if (error == STORAGE_ERROR_NONE && path != NULL) {
234                 setenv("XDG_MUSIC_DIR", const_cast<char *>(path), 1);
235                 free(path);
236                 path = NULL;
237         }
238
239         error = storage_get_directory(storageId, STORAGE_DIRECTORY_VIDEOS, &path);
240         if (error == STORAGE_ERROR_NONE && path != NULL) {
241                 setenv("XDG_VIDEOS_DIR", const_cast<char *>(path), 1);
242                 free(path);
243                 path = NULL;
244         }
245 }
246
247 // terminate candidate process when language changed
248 // icu related data (CultureInfo, etc) should be recreated.
249 static void langChangedCB(keynode_t *key, void* data)
250 {
251         _INFO("terminiate candidate process to update language.");
252         exit(0);
253 }
254
255 static void setLang()
256 {
257         char* lang = vconf_get_str(VCONFKEY_LANGSET);
258         if (!lang) {
259                 _ERR("Fail to get language from vconf");
260                 return;
261         }
262
263         // In order to operate ICU (used for globalization) normally, the following
264         // environment variables must be set before using ICU API.
265         // When running Applicaiton, the following environment variables are set by AppFW.
266         // But when preloading the dll in the candidate process, the following environment variables are not set
267         // As a result, CultureInfo is incorrectly generated and malfunctions.
268         // For example, uloc_getDefault() returns en_US_POSIX, CultureInfo is set to invariant mode.
269         setenv("LANG", const_cast<char *>(lang), 1);
270         setlocale(LC_ALL, const_cast<char *>(lang));
271
272         free(lang);
273 }
274
275 void CoreRuntime::preloadTypes()
276 {
277         const static std::string initDllPath = concatPath(__FRAMEWORK_DIR, "Tizen.Init.dll");
278         if (!isFileExist(initDllPath)) {
279                 _ERR("Failed to locate Tizen.Init.dll");
280                 return;
281         }
282
283         typedef void (*InitDelegate)();
284         InitDelegate initDelegate;
285
286         int ret = createDelegate(__hostHandle,
287                 __domainId,
288                 "Tizen.Init",
289                 "Tizen.Init.TypeLoader",
290                 "PreloadTypes",
291                 (void**)&initDelegate);
292
293         if (ret < 0) {
294                 _ERR("Failed to create delegate for PreloadTypes (0x%08x)", ret);
295         } else {
296                 initDelegate();
297         }
298 }
299
300 CoreRuntime::CoreRuntime(const char* mode) :
301         initializeClr(nullptr),
302         executeAssembly(nullptr),
303         shutdown(nullptr),
304         createDelegate(nullptr),
305         setEnvironmentVariable(nullptr),
306         __coreclrLib(nullptr),
307         __hostHandle(nullptr),
308         __domainId(-1),
309         __fd(-1),
310         __initialized(false),
311         __isProfileMode(false)
312 {
313         _INFO("Constructor called!!");
314
315         // Intiailize ecore first (signal handlers, etc.) before runtime init.
316         ecore_init();
317
318         // set language environment to support ICU
319         setLang();
320
321         char *env = nullptr;
322         env = getenv("CORECLR_ENABLE_PROFILING");
323         if (env != nullptr && !strcmp(env, "1")) {
324                 _INFO("profiling mode on");
325                 __isProfileMode = true;
326         }
327
328         // plugin initialize should be called before start loader mainloop.
329         // In case of VD plugins, attaching secure zone is done in the plugin_initialize().
330         // When attaching to a secure zone, if there is a created thread, it will failed.
331         // So, plugin initialize should be called before mainloop start.
332         if (initializePluginManager(mode) < 0) {
333                 _ERR("Failed to initialize PluginManager");
334         }
335 }
336
337 CoreRuntime::~CoreRuntime()
338 {
339         // workaround : to prevent crash while process terminate on profiling mode,
340         //              kill process immediately.
341         // see https://github.com/dotnet/coreclr/issues/26687
342         if (__isProfileMode) {
343                 _INFO("shutdown process immediately.");
344                 _exit(0);
345         }
346
347         dispose();
348 }
349
350 int CoreRuntime::initialize(LaunchMode launchMode, bool useDlog, const char* rootPath)
351 {
352         // checkInjection checks dotnet-launcher run mode
353         // At the moment, this mechanism is used only when the Memory Profiler is started.
354         int res = checkInjection();
355         if (res != 0) {
356                 _ERR("Failed to initnialize Memory Profiler");
357                 return -1;
358         }
359 #define __XSTR(x) #x
360 #define __STR(x) __XSTR(x)
361
362 #ifdef NATIVE_LIB_DIR
363         __nativeLibDirectory = __STR(NATIVE_LIB_DIR);
364 #endif
365
366 #undef __STR
367 #undef __XSTR
368
369 #ifdef __arm__
370         // libunwind library is used to unwind stack frame, but libunwind for ARM
371         // does not support ARM vfpv3/NEON registers in DWARF format correctly.
372         // Therefore let's disable stack unwinding using DWARF information
373         // See https://github.com/dotnet/coreclr/issues/6698
374         //
375         // libunwind use following methods to unwind stack frame.
376         // UNW_ARM_METHOD_ALL          0xFF
377         // UNW_ARM_METHOD_DWARF        0x01
378         // UNW_ARM_METHOD_FRAME        0x02
379         // UNW_ARM_METHOD_EXIDX        0x04
380         putenv(const_cast<char *>("UNW_ARM_UNWIND_METHOD=6"));
381 #endif // __arm__
382
383         // Disable debug pipes and semaphores creation in case of non-standlone mode
384         if (launchMode == LaunchMode::loader)
385                 putenv(const_cast<char *>("COMPlus_EnableDiagnostics=0"));
386
387         // Write Debug.WriteLine to stderr
388         putenv(const_cast<char *>("COMPlus_DebugWriteToStdErr=1"));
389
390 #ifdef USE_DEFAULT_BASE_ADDR
391         putenv(const_cast<char *>("COMPlus_UseDefaultBaseAddr=1"));
392 #endif // USE_DEFAULT_BASE_ADDR
393
394         // read string from external file and set them to environment value.
395         setEnvFromFile();
396
397         if (initializePathManager(std::string(), std::string(), std::string()) < 0) {
398                 _ERR("Failed to initialize PathManager");
399                 return -1;
400         }
401
402         if (useDlog && !pluginHasLogControl()) {
403                 if (initializeLogManager() < 0) {
404                         _ERR("Failed to initnialize LogManager");
405                         return -1;
406                 }
407         }
408
409         std::string libCoreclr(concatPath(getRuntimeDir(), "libcoreclr.so"));
410
411         __coreclrLib = dlopen(libCoreclr.c_str(), RTLD_NOW | RTLD_LOCAL);
412         if (__coreclrLib == nullptr) {
413                 char *err = dlerror();
414                 _ERR("dlopen failed to open libcoreclr.so with error %s", err);
415                 if (access(libCoreclr.c_str(), R_OK) == -1)
416                         _ERR("access '%s': %s\n", libCoreclr.c_str(), strerror(errno));
417                 return -1;
418         }
419
420 #define CORELIB_RETURN_IF_NOSYM(type, variable, name) \
421         do { \
422                 variable = (type)dlsym(__coreclrLib, name); \
423                 if (variable == nullptr) { \
424                         _ERR(name " is not found in the libcoreclr.so"); \
425                         return -1; \
426                 } \
427         } while (0)
428
429         CORELIB_RETURN_IF_NOSYM(coreclr_initialize_ptr, initializeClr, "coreclr_initialize");
430         CORELIB_RETURN_IF_NOSYM(coreclr_execute_assembly_ptr, executeAssembly, "coreclr_execute_assembly");
431         CORELIB_RETURN_IF_NOSYM(coreclr_shutdown_ptr, shutdown, "coreclr_shutdown");
432         CORELIB_RETURN_IF_NOSYM(coreclr_create_delegate_ptr, createDelegate, "coreclr_create_delegate");
433
434 #undef CORELIB_RETURN_IF_NOSYM
435
436         _INFO("libcoreclr dlopen and dlsym success");
437
438         if (launchMode == LaunchMode::loader) {
439                 pluginPreload();
440
441                 // terminate candidate process if language is changed.
442                 // CurrentCulture created for preloaded dlls should be updated.
443                 vconf_notify_key_changed(VCONFKEY_LANGSET, langChangedCB, NULL);
444         }
445
446         // Set environment for System.Environment.SpecialFolder
447         // Below function creates dbus connection by callging storage API.
448         // If dbus connection is created bofere fork(), forked process cannot use dbus.
449         // To avoid gdbus blocking issue, below function should be called after fork()
450         initEnvForSpecialFolder();
451
452         std::string tpa = getTPA();
453         std::string runtimeDir = getRuntimeDir();
454         std::string appName = std::string("dotnet-launcher-") + std::to_string(getpid());
455         std::string probePath;
456         std::string NIprobePath;
457         std::string nativeLibPath;
458
459         if (launchMode == LaunchMode::corerun) {
460                 probePath = rootPath;
461                 NIprobePath = rootPath;
462                 nativeLibPath = rootPath;
463         } else  {
464                 __fd = open("/proc/self", O_DIRECTORY);
465                 if (__fd < 0) {
466                         _ERR("Failed to open /proc/self");
467                         return -1;
468                 }
469                 std::string appRoot = std::string("/proc/self/fd/") + std::to_string(__fd);
470                 std::string appBin = concatPath(appRoot, "bin");
471                 std::string appLib = concatPath(appRoot, "lib");
472                 std::string appTac = concatPath(appBin, TAC_SYMLINK_SUB_DIR);
473                 probePath = appRoot + ":" + appBin + ":" + appLib + ":" + appTac;
474                 NIprobePath = concatPath(appBin, APP_NI_SUB_DIR) + ":" + concatPath(appLib, APP_NI_SUB_DIR) + ":" + appTac;
475                 nativeLibPath = getExtraNativeLibDirs(appRoot) + ":" + appBin + ":" + appLib + ":" + __nativeLibDirectory + ":" + runtimeDir;
476         }
477
478         if (!initializeCoreClr(appName.c_str(), probePath.c_str(), NIprobePath.c_str(), nativeLibPath.c_str(), tpa.c_str())) {
479                 _ERR("Failed to initialize coreclr");
480                 return -1;
481         }
482
483         int st = createDelegate(__hostHandle, __domainId, "Dotnet.Launcher", "Dotnet.Launcher.Environment", "SetEnvironmentVariable", (void**)&setEnvironmentVariable);
484         if (st < 0 || setEnvironmentVariable == nullptr) {
485                 _ERR("Create delegate for Dotnet.Launcher.dll -> Dotnet.Launcher.Environment -> SetEnvironmentVariable failed (0x%08x)", st);
486                 return -1;
487         }
488
489         __initialized = true;
490
491         if (launchMode == LaunchMode::loader) {
492                 preloadTypes();         // Preload common managed code
493         }
494
495         _INFO("CoreRuntime initialize success");
496
497         return 0;
498 }
499
500 bool CoreRuntime::initializeCoreClr(const char* appId,
501                                                                          const char* assemblyProbePaths,
502                                                                          const char* NIProbePaths,
503                                                                          const char* pinvokeProbePaths,
504                                                                          const char* tpaList)
505 {
506         const char *propertyKeys[] = {
507                 "TRUSTED_PLATFORM_ASSEMBLIES",
508                 "APP_PATHS",
509                 "APP_NI_PATHS",
510                 "NATIVE_DLL_SEARCH_DIRECTORIES",
511                 "AppDomainCompatSwitch"
512         };
513
514         const char *propertyValues[] = {
515                 tpaList,
516                 assemblyProbePaths,
517                 NIProbePaths,
518                 pinvokeProbePaths,
519                 "UseLatestBehaviorWhenTFMNotSpecified"
520         };
521
522         std::string selfPath = readSelfPath();
523
524         int st = initializeClr(selfPath.c_str(),
525                                                         appId,
526                                                         sizeof(propertyKeys) / sizeof(propertyKeys[0]),
527                                                         propertyKeys,
528                                                         propertyValues,
529                                                         &__hostHandle,
530                                                         &__domainId);
531
532         if (st < 0) {
533                 _ERR("initialize core clr fail! (0x%08x)", st);
534                 return false;
535         }
536
537         pluginSetCoreclrInfo(__hostHandle, __domainId, createDelegate);
538
539         _INFO("Initialize core clr success");
540         return true;
541 }
542
543 void CoreRuntime::dispose()
544 {
545         // call plugin finalize function to notify finalize to plugin
546         // dlclose shoud be done after coreclr shutdown to avoid breaking signal chain
547         pluginFinalize();
548
549         // ignore the signal generated by an exception that occurred during shutdown
550         checkOnTerminate = true;
551
552         if (__hostHandle != nullptr) {
553                 int st = shutdown(__hostHandle, __domainId);
554                 if (st < 0)
555                         _ERR("shutdown core clr fail! (0x%08x)", st);
556                 __hostHandle = nullptr;
557         }
558
559         if (__coreclrLib != nullptr) {
560                 if (dlclose(__coreclrLib) != 0) {
561                         _ERR("libcoreclr.so close failed");
562                 }
563
564                 __coreclrLib = nullptr;
565         }
566
567         finalizePluginManager();
568         finalizePathManager();
569
570         __envList.clear();
571
572         _INFO("Dotnet runtime disposed");
573 }
574
575 int CoreRuntime::launch(const char* appId, const char* root, const char* path, int argc, char* argv[])
576 {
577         if (!__initialized) {
578                 _ERR("Runtime is not initialized");
579                 return -1;
580         }
581
582         if (path == nullptr) {
583                 _ERR("executable path is null");
584                 return -1;
585         }
586
587         if (!isFileExist(path)) {
588                 _ERR("File not exist : %s", path);
589                 return -1;
590         }
591
592         // launchpad override stdout and stderr to journalctl before launch application.
593         // we have to re-override that to input pipe for logging thread.
594         // if LogManager is not initialized, below redirectFD will return 0;
595         if (redirectFD() < 0) {
596                 _ERR("Failed to redirect FD");
597                 return -1;
598         }
599
600         // VD has their own signal handler.
601         if (!pluginHasLogControl()) {
602                 registerSigHandler();
603         }
604
605         pluginSetAppInfo(appId, path);
606
607         // override root path for application launch mode (candidate / standalone mode)
608         if (__fd >= 0) {
609                 int fd2 = open(root, O_DIRECTORY);
610                 dup3(fd2, __fd, O_CLOEXEC);
611                 if (fd2 >= 0)
612                         close(fd2);
613         }
614
615         // set application data path to coreclr environment.
616         // application data path can be changed by owner. So, we have to set data path just before launching.
617         char* localDataPath = app_get_data_path();
618         if (localDataPath != nullptr) {
619                 setEnvironmentVariable("XDG_DATA_HOME", localDataPath);
620                 free(localDataPath);
621         }
622
623         vconf_ignore_key_changed(VCONFKEY_LANGSET, langChangedCB);
624
625         pluginBeforeExecute();
626
627         _INFO("execute assembly : %s", path);
628
629         unsigned int ret = 0;
630         int st = executeAssembly(__hostHandle, __domainId, argc, (const char**)argv, path, &ret);
631         if (st < 0)
632                 _ERR("Failed to Execute Assembly %s (0x%08x)", path, st);
633         return ret;
634 }
635
636 }  // namespace dotnetcore
637 }  // namespace runtime
638 }  // namespace tizen