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