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