Add Launcher and Waiter for dotnet-launcher
authorpius.lee <pius.lee@samsung.com>
Mon, 25 Jul 2016 08:28:32 +0000 (17:28 +0900)
committerpius.lee <pius.lee@samsung.com>
Mon, 25 Jul 2016 08:28:32 +0000 (17:28 +0900)
12 files changed:
CMakeLists.txt [new file with mode: 0644]
packaging/dotnet-launcher.ini [new file with mode: 0644]
packaging/dotnet-launcher.service [new file with mode: 0644]
packaging/dotnet-launcher.spec [new file with mode: 0644]
packaging/dotnet.loader [new file with mode: 0644]
src/environment.h [new file with mode: 0644]
src/launcher.cc [new file with mode: 0644]
src/launcher.h [new file with mode: 0644]
src/log.h [new file with mode: 0644]
src/tini.hpp [new file with mode: 0644]
src/waiter.cc [new file with mode: 0644]
src/waiter.h [new file with mode: 0644]

diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..b711b0d
--- /dev/null
@@ -0,0 +1,47 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
+PROJECT("dotnet-launcher")
+
+INCLUDE(FindPkgConfig)
+PKG_CHECK_MODULES(${PROJECT_NAME} REQUIRED
+       aul
+       dlog
+       ecore
+       bundle
+       dlog
+       launchpad
+       )
+
+FOREACH(flag ${${PROJECT_NAME}_CFLAGS})
+       SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
+ENDFOREACH(flag)
+
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -Wl,-zdefs" )
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fvisibility=hidden")
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fPIE")
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fdata-sections -ffunction-sections -Wl,--gc-sections")
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -D_FILE_OFFSET_BITS=64")
+
+SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}")
+SET(CMAKE_C_FLAGS_DEBUG "-O0 -g")
+SET(CMAKE_C_FLAGS_RELEASE "-O2")
+SET(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed")
+
+SET(${PROJECT_NAME}_SOURCE_FILES
+       src/launcher.cc
+       src/waiter.cc
+)
+ADD_EXECUTABLE(${PROJECT_NAME} ${${PROJECT_NAME}_SOURCE_FILES})
+
+TARGET_LINK_LIBRARIES(${PROJECT_NAME} aul)
+TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${${PROJECT_NAME}_LDFLAGS} "-pie")
+
+SET_TARGET_PROPERTIES(${PROJECT_NAME}
+       PROPERTIES SKIP_BUILD_RPATH TRUE
+       ) # remove rpath option that is automatically generated by cmake.
+
+CONFIGURE_FILE(${PACKAGE_NAME}.xml.in ${PACKAGE_NAME}.xml)
+
+INSTALL(TARGETS ${PROJECT_NAME} DESTINATION bin)
+INSTALL(FILES ${PACKAGE_NAME}.xml DESTINATION ${MANIFESTDIR})
+INSTALL(FILES packaging/dotnet.loader DESTINATION ${LOADERDIR})
+
diff --git a/packaging/dotnet-launcher.ini b/packaging/dotnet-launcher.ini
new file mode 100644 (file)
index 0000000..700eb67
--- /dev/null
@@ -0,0 +1,5 @@
+[dotnet]
+libcoreclr = libcoreclr.so
+coreclr_dir = /usr/share/dotnet
+tpa_dirs = /usr/share/dotnet:/usr/share/assemblies
+native_so_search_dirs = /usr/share/dotnet:/usr/share/assemblies
diff --git a/packaging/dotnet-launcher.service b/packaging/dotnet-launcher.service
new file mode 100644 (file)
index 0000000..be05477
--- /dev/null
@@ -0,0 +1,16 @@
+[Unit]
+Description=Start dotnet launchpad daemon
+After=dbus.service buxton2.service ac.service
+Requires=csf-content-engine.service
+
+[Service]
+User=owner
+Group=users
+EnvironmentFile=/run/tizen-mobile-ui
+ExecStart=/usr/bin/launch_app dotnet-launcher
+
+TimeoutStopSec=3s
+RestartSec=5
+
+[Install]
+WantedBy=starter.target
diff --git a/packaging/dotnet-launcher.spec b/packaging/dotnet-launcher.spec
new file mode 100644 (file)
index 0000000..c9dca6e
--- /dev/null
@@ -0,0 +1,58 @@
+Name:       dotnet-launcher
+Summary:    Launchpad plugin for dotnet apps
+Version:    0.1.0
+Release:    1
+Group:      Application Framework/Daemons
+License:    Apache License, Version 2.0
+Source0:    %{name}-%{version}.tar.gz
+
+BuildRequires: cmake
+BuildRequires: pkgconfig(aul)
+BuildRequires: pkgconfig(bundle)
+BuildRequires: pkgconfig(dlog)
+BuildRequires: pkgconfig(ecore)
+BuildRequires: pkgconfig(launchpad)
+BuildRequires: aul-devel
+Requires: aul
+
+Requires(post): /sbin/ldconfig
+Requires(post): /usr/bin/systemctl
+Requires(postun): /sbin/ldconfig
+Requires(postun): /usr/bin/systemctl
+Requires(preun): /usr/bin/systemctl
+
+%define _sys_bin %{TZ_SYS_BIN}
+%define _sys_sbin %{TZ_SYS_SBIN}
+%define _sys_share %{TZ_SYS_RO_SHARE}
+%define _manifestdir %{TZ_SYS_RO_PACKAGES}
+%define _loaderdir %{TZ_SYS_RO_SHARE}/aul
+
+%description
+Launchpad for dotnet apps
+
+%prep
+%setup -q
+
+%build
+cmake \
+       -DCMAKE_INSTALL_PREFIX=%{_prefix} \
+       -DMANIFESTDIR=%{_manifestdir} \
+       -DPACKAGE_NAME=%{name} \
+       -DBINDIR=%{_bindir} \
+       -DLOADERDIR=%{_loaderdir} \
+       -DVERSION=%{version}
+
+make %{?jobs:-j%jobs}
+
+%install
+rm -rf %{buildroot}
+%make_install
+
+%post
+
+%files
+%manifest dotnet-launchpad.manifest
+%{_manifestdir}/%{name}.xml
+%{_loaderdir}/dotnet.loader
+%attr(0755,root,root) %{_bindir}/dotnet-loader
+%caps(cap_mac_admin,cap_mac_override,cap_setgid=ei) %{_bindir}/dotnet-loader
diff --git a/packaging/dotnet.loader b/packaging/dotnet.loader
new file mode 100644 (file)
index 0000000..43e2d21
--- /dev/null
@@ -0,0 +1,6 @@
+[LOADER]
+NAME    dotnet-loader
+EXE     /usr/bin/dotnet-loader
+APP_TYPE        dotnet
+DETECTION_METHOD        TIMEOUT|DEMAND
+TIMEOUT         5000
diff --git a/src/environment.h b/src/environment.h
new file mode 100644 (file)
index 0000000..6e4e57b
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef __ENVIRONMENT_H__
+
+#ifndef TIZEN_CORECLR_PATH
+#define TIZEN_CORECLR_PATH "/usr/share/coreclr/libcoreclr.so"
+#endif
+
+#ifndef TIZEN_NETCORE_TPA_PATH
+#define TIZEN_NETCORE_TPA_PATH "/usr/share/assemblies"
+#endif
+
+
+#endif // __ENVIRONMENT_H__
diff --git a/src/launcher.cc b/src/launcher.cc
new file mode 100644 (file)
index 0000000..631f7e5
--- /dev/null
@@ -0,0 +1,287 @@
+#include <cstdlib>
+#include <cstring>
+#include <vector>
+#include <string>
+#include <set>
+#include <sstream>
+
+#include <dirent.h>
+#include <sys/stat.h>
+
+#include "environment.h"
+#include "waiter.h"
+#include "log.h"
+
+#include "tini.hpp"
+
+#ifndef LAUNCHER_CONFIG
+#define LAUNCHER_CONFIG "/usr/share/dotnet/dotnet-launcher.ini"
+#endif
+
+static std::string AbsolutePath(const std::string& path)
+{
+  std::string absPath;
+
+  char realPath[PATH_MAX];
+  if (realpath(path, realPath) != nullptr && realPath[0] != '\0')
+  {
+    absPath.assign(realPath);
+  }
+
+  return absPath;
+}
+
+static std::string Basename(const std::string& path)
+{
+  auto pos = path.find_last_of('/');
+  if (pos != std::string::npos)
+  {
+    return path.substr(0, pos);
+  }
+  return path;
+}
+
+static std::string AssembliesInDirectory(const char *directory)
+{
+  const char * const nativeImageExtension = ".ni";
+  const char * const tpaExtensions[] = {".dll", ".exe"};
+  int niExtensionSize = strlen(nativeImageExtension);
+  int directoryPathSize = strlen(directory);
+
+  std::string tpaList;
+
+  DIR* dir = opendir(directory);
+
+  if (dir == nullptr) return std::string();
+
+  std::map<std::pair<std::string, std::string>, bool> addedAssemblies;
+
+  struct dirent *entry;
+  while ((entry = readdir(dir)) != nullptr)
+  {
+    switch (entry->d_type)
+    {
+      case DT_REG: break;
+      case DT_LNK:
+      case DT_UNKNOWN:
+        {
+          std::string fullname;
+          fullname.append(directory);
+          fullname.append("/");
+          fullname.append(entry->d_name);
+
+          struct stat sb;
+          if (stat(fullname.c_str(), &sb) == -1)
+            continue;
+
+          if (!S_ISREG(sb.st_mode))
+            continue;
+        }
+        break;
+      default:
+        continue;
+    }
+
+    // Check the extension.
+    std::string filename(entry->d_name);
+    int extPos = filename.find_last_of('.');
+    if (extPos <= 0) continue;
+    for (auto ext : tpaExtensions)
+    {
+      int extLength = strlen(ext);
+      if (filename.compare(extPos-1, extLength, ext) != 0)
+        continue;
+    }
+
+
+    std::string filenameWithoutExt;
+    bool isNativeImage = extPos > niExtensionSize ?
+      filename.compare(extPos-niExtensionSize, niExtensionSize, nativeImageExtension) == 0 : false;
+    if (isNativeImage)
+    {
+      filenameWithoutExt = filename.substr(0, extPos-niExtensionSize);
+    }
+    else
+    {
+      filenameWithoutExt = filename.substr(0, extPos);
+    }
+    std::string ext = filename.substr(extPos);
+
+    std::pair<std::string, std::string> key(filenameWithoutExt, ext);
+    std::cout << "  " << filenameWithoutExt << ext << "  " << isNativeImage << std::endl;
+    if (addedAssemblies.count(key))
+    {
+      isNativeImage = isNativeImage || addedAssemblies[key];
+    }
+    addedAssemblies[key] = isNativeImage;
+  }
+
+  for (auto pair : addedAssemblies)
+  {
+    tpaList.append(directory);
+    if (directory[directoryPathSize-1] != '/')
+      tpaList.append("/");
+    tpaList.append(pair.first.first);
+    if (pair.second)
+    {
+      tpaList.append(nativeImageExtension);
+    }
+    tpaList.append(pair.first.second);
+    tpaList.append(":");
+  }
+
+  closedir(dir);
+  return tpaList;
+}
+
+Launcher::Launcher()
+{
+}
+
+Launcher::~Launcher()
+{
+}
+
+void Launcher::Initialize()
+{
+  std::ifstream iniStream(LAUNCHER_CONFIG);
+  std::stringstream iniString;
+  iniString << iniStream.rdbuf();
+  tini::ini launcherIni(iniString);
+
+  auto &dotnet = ini["dotnet"];
+  if (dotnet.find("libcoreclr") == dotnet.end())
+    dotnet["libcoreclr"] = "libcoreclr.so";
+  if (dotnet.find("coreclr_dir") == dotnet.end())
+    dotnet["coreclr_dir"] = "/usr/share/dotnet";
+  if (dotnet.find("tpa_dir") == dotnet.end())
+    dotnet["tpa_dir"] = dotnet["coreclr_dir"] + ":" + "/usr/share/assemblies";
+  if (dontet.find("native_so_search_dirs") == dotnet.end())
+    dotnet["native_so_search_dirs"] = dotnet["tpa_dir"];
+
+  std::string libcoreclr = dotnet["coreclr_dir"] + "/" + dotnet["libcoreclr"];
+  std::string tpaDirs = dotnet["tpa_dirs"];
+  std::string nativeSoSearchDirs = dotnet["native_so_search_dirs"];
+
+  void* coreclrLib = dlopen(libcoreclr, RTLD_NOW | RTLD_LOCAL);
+  if (coreclrLib == nullptr)
+  {
+    char *err = dlerror();
+    _ERR("dlopen failed to open libcoreclr.so with error %s", err);
+    goto CoreClrLibClose;
+  }
+
+  initializeCoreCLR = (coreclr_initialize_ptr)dlsym(coreclrLib, "coreclr_initialize");
+  executeAssembly = (coreclr_execute_assembly_ptr)dlsym(coreclrLib, "coreclr_execute_assembly");
+  shutdownCoreCLR = (coreclr_shutdown_ptr)dlsym(coreclrLib, "coreclr_shutdown");
+
+  if (coreclr_initialize_ptr == nullptr)
+  {
+    _ERR("coreclr_initialize_ptr not found in the libcoreclr.so");
+  }
+  else if (coreclr_execute_assembly_ptr == nullptr)
+  {
+    _ERR("coreclr_execute_assembly_ptr not found in the libcoreclr.so");
+  }
+  else if (coreclr_shutdown_ptr == nullptr)
+  {
+    _ERR("coreclr_shutdown_ptr not found in the libcoreclr.so");
+  }
+  else
+  {
+    std::istringstream f(tpaDirs);
+    std::string s;
+    while (std::getline(f, s, ':'))
+    {
+      TrustedPlatformAssemblies += AssembliesInDirectory(s);
+    }
+    NativeDllSearchDirectories = nativeSoSearchDirs; 
+    AppDomainCompatSwitch = "UseLatestBehaviorWhenTFMNotSpecified";
+  }
+
+CoreClrLibClose:
+  if (dlclose(coreclrLib) != 0)
+  {
+    _ERR("libcoreclr.so close failed");
+  }
+}
+
+void Launcher::Dispose()
+{
+}
+
+void Launcher::Launch(const string& exe_path, int argc, char *argv[])
+{
+  std::string bin_path = Basename(exe_path);
+  std::string app_home = Basename(app_path);
+  std::string lib_path = app_home + "/lib";
+  std::string app_path = bin_path + ":" + lib_path
+  std::string app_ni_path = app_path;
+  std::string nativeDllSearchDirectories = NativeDllSearchDirectories + app_path;
+
+  const char *propertyKeys[] =
+  {
+    "TRUSTED_PLATFORM_ASSEMBLIES",
+    "APP_PATHS",
+    "APP_NI_PATHS",
+    "NATIVE_DLL_SEARCH_DIRECTORIES",
+    "AppDomainCompatSwitch"
+  };
+
+  const char *propertyValues[] =
+  {
+    TrustedPlatformAssemblies.c_str(),
+    app_path.c_str(),
+    app_ni_path.c_str(),
+    nativeDllSearchDirectories.c_str(),
+    AppDomainCompatSwitch.c_str()
+  };
+
+  void* hostHandle;
+  unsigned int domainId;
+
+  int st = initializeCoreCLR(exe_path,
+      "tizen_dotnet_launcher",
+      sizeof(propertyKeys) / sizeof(propertyKeys[0]),
+      propertyKeys,
+      propertyValues,
+      &hostHandle,
+      &domainId);
+  if (st < 0)
+  {
+    // initialize coreclr fail
+    exit(-1);
+  }
+
+  unsigned int exitCode;
+  st = executeAssembly(hostHandle, domainId, argc, argv, exe_path, &exitCode);
+  if (st < 0)
+  {
+    // execute coreclr fail
+    exit(-1);
+  }
+
+  st = shutdownCoreCLR(hostHandle, domainId);
+  if (st < 0)
+  {
+    // shutdown fail
+    exit(-1);
+  }
+}
+
+int main(int argc, char *argv[])
+{
+  std::unique_ptr<Launcher> launcher(new Launcher());
+  auto on_prepare = [&launcher]()
+  {
+    launcher.Initialize();
+  };
+  auto on_requested = [&launcher]()
+  {
+  };
+  auto on_executed = [&launcher](const std::string& path, int argc, char *argv[])
+  {
+    launcher.launch(path, argc, argv);
+  }
+  std::unique_ptr<Waiter> waiter(new Waiter(on_prepare, on_requested, on_executed));
+}
diff --git a/src/launcher.h b/src/launcher.h
new file mode 100644 (file)
index 0000000..eb53100
--- /dev/null
@@ -0,0 +1,51 @@
+#include <string>
+#include <vector>
+
+namespace dotnet {
+namespace runtime {
+
+typedef int (*coreclr_initialize_ptr)(
+    const char* exePath,
+    const char* appDomainFriendlyName,
+    int propertyCount,
+    const char** propertyKeys,
+    const char** propertyValues,
+    void** hostHandle,
+    unsigned int* domainId);
+
+typedef int (*coreclr_execute_assembly_ptr)(
+    void* hostHandle,
+    unsigned int domainId,
+    int argc,
+    const char** argv,
+    const char* managedAssemblyPath,
+    unsigned int* exitCode);
+
+typedef int (*coreclr_shutdown_ptr)(
+    void* hostHandle,
+    unsigned int domainId);
+
+using std::string;
+using std::vector;
+
+class Launcher
+{
+  public:
+    Launcher();
+    ~Launcher();
+    void Initialize();
+    void Dispose();
+    void Launch(const string& exe_path, int argc, char *argv[]);
+
+  private:
+    coreclr_initialize_ptr initializeCoreCLR;
+    coreclr_execute_assembly_ptr executeAssembly;
+    coreclr_shutdown_ptr shutdownCoreCLR;
+
+    string TrustedPlatformAssemblies;
+    string NativeDllSearchDirectories;
+    string AppDomainCompatSwitch;
+};
+
+}  // namespace runtime
+}  // namespace dotnet
diff --git a/src/log.h b/src/log.h
new file mode 100644 (file)
index 0000000..c5b6f9d
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,23 @@
+#ifndef __LOG_H__
+#define __LOG_H__
+
+#include <dlog.h>
+
+#ifdef  LOG_TAG
+#undef  LOG_TAG
+#endif
+#define LOG_TAG "NETCORE_LAUNCHER"
+
+#ifndef _ERR
+#define _ERR(fmt, args...) LOGE(fmt "\n", __func__, __LINE__, ##args)
+#endif
+
+#ifndef _DBG
+#define _DBG(fmt, args...) LOGD(fmt "\n", __func__, __LINE__, ##args)
+#endif
+
+#ifndef _INFO
+#define _INFO(fmt, args...) LOGI(fmt "\n", __func__, __LINE__, ##args)
+#endif
+
+#endif /* __LOG_H__ */
diff --git a/src/tini.hpp b/src/tini.hpp
new file mode 100644 (file)
index 0000000..fac3424
--- /dev/null
@@ -0,0 +1,162 @@
+#include <string>
+#include <map>
+#include <vector>
+#include <sstream>
+#include <algorithm> 
+#include <functional> 
+
+namespace tini
+{
+
+const std::string global = "___GLOBAL";
+
+inline void ltrim(std::string& s)
+{
+  s.erase(s.begin(), std::find_if(s.begin(), s.end(),
+        std::not1(std::ptr_fun<int, int>(std::isspace))));
+}
+
+inline void rtrim(std::string& s)
+{
+  s.erase(std::find_if(s.rbegin(), s.rend(),
+        std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
+}
+
+inline void trim(std::string& s)
+{
+  ltrim(s);
+  rtrim(s);
+}
+
+inline int find_first_nonescaped(const std::string& str, char ch)
+{
+  if (!str.empty())
+  {
+    if (str[0] == ch) return 0;
+    for (size_t i=1; i<str.length(); i++)
+    {
+      if (str[i] == ch && str[i-1] != '\\')
+        return i;
+    }
+  }
+  return -1;
+}
+
+inline std::string uncomment(const std::string& str)
+{
+  int comment_start = find_first_nonescaped(str, '#');
+  if (comment_start < 0) return str;
+  return str.substr(0, comment_start);
+}
+
+inline bool get_header(const std::string& str, std::string& header)
+{
+  if (str.length() < 2 || str.front() != '[' || str.back() != ']') return false;
+  std::string expected = std::string(str.begin()+1, str.end()-1);
+  trim(expected);
+  if (expected.compare(header) != 0)
+  {
+    header = expected;
+    return true;
+  }
+  return false;
+}
+
+inline std::pair<std::string, std::string> get_pair(const std::string& str)
+{
+  std::string key, value;
+  std::string::size_type eq_pos = str.find('=');
+  if (eq_pos == std::string::npos)
+  {
+    key = str;
+  }
+  else
+  {
+    key = str.substr(0, eq_pos);
+    value = str.substr(eq_pos+1);
+    trim(value);
+  }
+  trim(key);
+  return std::pair<std::string, std::string>(key, value);
+}
+
+class ini
+{
+  public:
+    using pairs = std::map<std::string, std::string>;
+    ini()
+    {
+    }
+    ini(const std::string& str)
+    {
+      std::istringstream input(str);
+      init(input);
+    }
+    ini(std::istream &input)
+    {
+      init(input);
+    }
+    inline void init (std::istream &input)
+    {
+      std::string line;
+      std::string title(global);
+      while (std::getline(input, line))
+      {
+        line = uncomment(line);
+        if (line.empty()) continue;
+        trim(line);
+        if (get_header(line, title) && groups.find(title) == groups.end())
+        {
+          groups[title] = std::map<std::string, std::string>();
+          continue;
+        }
+
+        auto pair = get_pair(line);
+        groups[title][pair.first] = pair.second;
+      }
+    }
+    std::string to_string() const
+    {
+      std::stringstream str_out;
+      auto gpair = groups.find(global);
+      if (gpair != groups.end())
+      {
+        for (auto &pair : gpair->second)
+        {
+          str_out << pair.first << " = " << pair.second << std::endl;
+        }
+      }
+      for (auto &group : groups)
+      {
+        auto &title = group.first;
+        if (title == global) continue;
+        auto &pairs = group.second;
+        str_out << '[' << title << ']' << std::endl;
+        for (auto &pair : pairs)
+        {
+          str_out << pair.first << " = " << pair.second << std::endl;
+        }
+      }
+
+      return str_out.str();
+    }
+
+    pairs& operator [](const std::string& group_name)
+    {
+      if (groups.find(group_name) == groups.end())
+      {
+        groups[group_name] = pairs();
+      }
+      return groups[group_name];
+    }
+
+    std::map<std::string, pairs> groups;
+};
+
+std::ostream& operator << (std::ostream& os, const ini& rhs)
+{
+  os << rhs.to_string();
+  return os;
+}
+
+}  // namespace tini
diff --git a/src/waiter.cc b/src/waiter.cc
new file mode 100644 (file)
index 0000000..0f622be
--- /dev/null
@@ -0,0 +1,170 @@
+#include "waiter.h"
+
+#include <poll.h>
+#include <launchpad.h>
+
+#include <memory>
+#include <vector>
+#include <map>
+#include <poll.h>
+
+namespace dotnet {
+namespace runtime {
+
+struct Waiter::FdHandler;
+{
+  pollfd *info;
+  Receiver re;
+};
+
+static volatile bool Waiting_;
+static std::vector<pollfd> Fdlist_;
+static std::map<int, FdHandler> Handlers_;
+static Waiter::AppInfo AppInfo_;
+
+void Waiter::OnPrepare()
+{
+  // preload the libraries.
+}
+
+void Waiter::OnLaunchRequested()
+{
+  // do some job on user id is still system
+}
+
+void Waiter::OnWaiting()
+{
+  // Start the loop
+  Waiting_ = true;
+
+  while (Waiting_)
+  {
+    if (poll(Fdlist_.data(), Fdlist_.size(), -1) < 0)
+      continue;
+
+    for (auto &p : Fdlist_)
+    {
+      if ( (p.revents | POLLIN) != 0 )
+        Handlers_[p.fd].receiver();
+    }
+  }
+}
+
+void Waiter::Stop()
+{
+  // Stop the loop
+
+  Waiting_ = false;
+}
+
+
+void Waiter::RegisterFd(int fd, Receiver receiver)
+{
+  // register fd should be caught in event loop
+
+  pollfd info;
+  info.fd = fd;
+  info.events = POLLIN;
+  info.revents = 0;
+
+  FdHandler handler;
+  Fdlist_.push_back(info);
+  handler.info = &Fdlist_.back(); 
+  handler.receiver = receiver;
+
+  Handlers_[fd] = handler;
+}
+
+void Waiter::DeregisterFd(int fd)
+{
+  // deregister fd should be caught in event loop
+  
+  pollfd *info = Handlers_[fd].info;
+  Fdlist_.erase(Fdlist_.begin() - (info - &Fdlist_.front()));
+  Handlers_.erase(fd);
+}
+
+
+Waiter::Waiter(Action prepare, Action requested, Executor executor)
+{
+  prepare_ = prepare;
+  requested_ = requested;
+  executor_ = executor;
+}
+
+Waiter::~Waiter()
+{
+}
+
+void Waiter::WaitToLaunching(int argc, char *argv[])
+{
+  auto on_create = [](bundle *extra, int type, void *user_data)
+  {
+    Waiter* waiter = static_cast<Waiter*>(user_data);
+    waiter->OnPrepare();
+  };
+
+  auto on_launch = [](int argc, char **argv, const char *app_path,
+      const char *appid, const char *pkgid,
+      const char *pkg_type, void *user_data)
+  {
+    Waiter* waiter = static_cast<Waiter*>(user_data);
+
+    AppInfo info = {
+      AppPath : app_path,
+      AppId : appid,
+      PkgId : pkgid,
+      PkgType : pkg_type
+    };
+
+    waiter->OnLaunchRequested(info);
+  };
+
+  auto on_terminate = [](int argc, char **argv, void *user_data)
+  {
+    Waiter* waiter = static_cast<Waiter*>(user_data);
+    waiter->executor_(argv[LOADER_ARG_PATH], argc, argv);
+  };
+
+  auto on_start_loop = [](void *user_data)
+  {
+    Waiter* waiter = static_cast<Waiter*>(user_data);
+    waiter->OnWaiting();
+  };
+
+  auto on_quit_loop = [](void *user_data)
+  {
+    Waiter* waiter = static_cast<Waiter*>(user_data);
+    waiter->Stop();
+  };
+
+  auto on_add_fd = [](void *user_data, int fd, loader_receiver_cb receiver)
+  {
+    Waiter* waiter = static_cast<Waiter*>(user_data);
+    waiter->RegisterFd(fd, receiver);
+  };
+
+  auto on_remove_fd = [](void *user_data, int fd)
+  {
+    Waiter* waiter = static_cast<Waiter*>(user_data);
+    waiter->DeregisterFd(fd);
+  };
+
+  loader_lifecycle_callback_s callbacks = {
+    .create = on_create,
+    .launch = on_launch,
+    .terminate = on_terminate
+  }
+
+       loader_adapter_s adapter = {
+               .loop_begin = on_start_loop,
+               .loop_quit = on_quit_loop,
+               .add_fd = on_add_fd,
+               .remove_fd = on_remove_fd
+       };
+
+  return launchpad_loader_main(argc, argv, &callbacks, &adapter, this);
+}
+
+}  // namespace runtime
+}  // namespace dotnet
diff --git a/src/waiter.h b/src/waiter.h
new file mode 100644 (file)
index 0000000..4315d0c
--- /dev/null
@@ -0,0 +1,44 @@
+#include <string>
+
+using std::string;
+
+namespace dotnet {
+namespace runtime {
+
+class Waiter
+{
+  public:
+    struct AppInfo
+    {
+      string AppPath;
+      string AppId;
+      string PkgId;
+      string PkgType;
+    };
+    using Action = std::function<void(void)>;
+    using Receiver = std::function<void(int)>;
+    using Executor = std::function<void(const string&, int, char**)>;
+
+    Waiter(Action prepare, Action requested, Executor executor);
+    ~Waiter();
+
+    int WaitToLaunching(int argc, char *argv[]);
+    void Stop();
+
+    void RegisterFd(int fd, Receiver receiver);
+    void DeregisterFd(int fd);
+
+  protected:
+    void OnPrepare();
+    void OnLaunchRequested(const AppInfo&);
+    void OnWaiting();
+
+  private:
+
+    Action prepare_; 
+    Action requested_;
+    Executor executor_;
+};
+
+}  // namespace runtime
+}  // namespace dotnet