From 8bbccf0f3894bf46ba34d7010ced8b6f4c59121f Mon Sep 17 00:00:00 2001 From: Jon Henderson Date: Thu, 30 May 2024 09:48:59 +0000 Subject: [PATCH 1/5] Support for running Android containers - Manage mounting android partitions - OCI Template modifications for Android - Extra settings for Android specific bind mounts and devices --- CMakeLists.txt | 5 + bundle/lib/include/DobbyRootfs.h | 1 + bundle/lib/include/DobbySpecConfig.h | 8 + bundle/lib/source/DobbyRootfs.cpp | 49 +++- bundle/lib/source/DobbySpecConfig.cpp | 141 +++++++++- .../OciConfigJson1.0.2-dobby.template | 241 ++++++++++++++++-- bundle/runtime-schemas/defs-plugins.json | 37 +++ daemon/lib/source/Dobby.cpp | 2 +- settings/include/IDobbySettings.h | 49 ++++ settings/include/Settings.h | 11 + settings/source/Settings.cpp | 159 +++++++++++- utils/source/DobbyUtils.cpp | 36 ++- 12 files changed, 705 insertions(+), 34 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a4ce50c..510dbe2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -296,6 +296,7 @@ option(PLUGIN_STORAGE "Include Storage plugin" ON) option(PLUGIN_MINIDUMP "Include Minidump plugin" ON) option(PLUGIN_THUNDER "Include Thunder plugin" ON) option(PLUGIN_OOMCRASH "Include OOMCrash plugin" ON) +option(PLUGIN_ANDROIDRUNTIME "Include Android plugin" ON) # Optional RDK plugins option(PLUGIN_TESTPLUGIN "Include TestPlugin plugin" OFF) @@ -373,6 +374,10 @@ if(PLUGIN_GAMEPAD) add_subdirectory(rdkPlugins/Gamepad) endif() +if(PLUGIN_ANDROIDRUNTIME) + add_subdirectory(rdkPlugins/AndroidRuntime) +endif() + # Export targets in Dobby package # --------------------------------------------------------- diff --git a/bundle/lib/include/DobbyRootfs.h b/bundle/lib/include/DobbyRootfs.h index c8a172ab..1582a4c1 100644 --- a/bundle/lib/include/DobbyRootfs.h +++ b/bundle/lib/include/DobbyRootfs.h @@ -100,6 +100,7 @@ class DobbyRootfs std::string mPath; int mDirFd; bool mPersist; + bool androidRootfs; }; diff --git a/bundle/lib/include/DobbySpecConfig.h b/bundle/lib/include/DobbySpecConfig.h index bc10574a..b595dcb9 100644 --- a/bundle/lib/include/DobbySpecConfig.h +++ b/bundle/lib/include/DobbySpecConfig.h @@ -109,6 +109,9 @@ class DobbySpecConfig : public DobbyConfig } MountPoint; std::vector mountPoints() const; +public: + bool androidEnabled() const; + public: const std::string& rootfsPath() const override; @@ -143,6 +146,7 @@ class DobbySpecConfig : public DobbyConfig JSON_FIELD_PROCESSOR(processDevices); JSON_FIELD_PROCESSOR(processCapabilities); JSON_FIELD_PROCESSOR(processSeccomp); + JSON_FIELD_PROCESSOR(processAndroid); #undef JSON_FIELD_PROCESSOR @@ -180,6 +184,7 @@ class DobbySpecConfig : public DobbyConfig const std::shared_ptr mUtilities; const std::shared_ptr mGpuSettings; const std::shared_ptr mVpuSettings; + const std::shared_ptr mAndroidSettings; const std::vector mDefaultPlugins; const Json::Value mRdkPluginsData; @@ -230,6 +235,9 @@ class DobbySpecConfig : public DobbyConfig std::string mEtcGroup; std::string mEtcLdSoPreload; +private: + bool mAndroidEnabled; + private: static int mNumCores; diff --git a/bundle/lib/source/DobbyRootfs.cpp b/bundle/lib/source/DobbyRootfs.cpp index d226dc8d..a82002a8 100644 --- a/bundle/lib/source/DobbyRootfs.cpp +++ b/bundle/lib/source/DobbyRootfs.cpp @@ -53,6 +53,7 @@ DobbyRootfs::DobbyRootfs(const std::shared_ptr& utils, , mBundle(bundle) , mDirFd(-1) , mPersist(false) + , androidRootfs(false) { AI_LOG_FN_ENTRY(); @@ -72,6 +73,12 @@ DobbyRootfs::DobbyRootfs(const std::shared_ptr& utils, return; } + if (config->androidEnabled()) + { + AI_LOG_WARN("Android container, using Android rootfs"); + androidRootfs = true; + } + // try and open the new directory mDirFd = openat(bundle->dirFd(), dirName.c_str(), O_CLOEXEC | O_DIRECTORY); if (mDirFd < 0) @@ -82,14 +89,41 @@ DobbyRootfs::DobbyRootfs(const std::shared_ptr& utils, return; } - // and finally construct the rootfs contents based on the config - if (!constructRootfs(mDirFd, config)) + if (!androidRootfs) { - AI_LOG_ERROR_EXIT("failed to construct bundle rootfs"); - cleanUp(); - return; + if (!constructRootfs(mDirFd, config)) + { + AI_LOG_ERROR_EXIT("failed to construct bundle rootfs"); + cleanUp(); + return; + } } + else + { + if (!createStandardMountPoints(mDirFd)) + { + AI_LOG_ERROR("Failed to create standard mount points"); + cleanUp(); + return; + } + + // process any extra mounts added by the client + const std::vector extraMounts = config->mountPoints(); + for (const DobbySpecConfig::MountPoint &mountPoint : extraMounts) + { + AI_LOG_DEBUG("attempting to create mount point '%s' %s", + mountPoint.destination.c_str(), + (mountPoint.type == DobbySpecConfig::MountPoint::Directory) ? + "directory" : "file"); + if (!createMountPoint(mDirFd, mountPoint.destination, + (mountPoint.type == DobbySpecConfig::MountPoint::Directory))) + { + AI_LOG_FN_EXIT(); + return; + } + } + } // store the complete path mPath = bundle->path() + "/" + dirName + "/"; @@ -113,6 +147,7 @@ DobbyRootfs::DobbyRootfs(const std::shared_ptr& utils, , mBundle(bundle) , mDirFd(-1) , mPersist(false) + , androidRootfs(false) { AI_LOG_FN_ENTRY(); @@ -282,7 +317,7 @@ void DobbyRootfs::cleanUp() if (mDirFd >= 0) { - if (!mPersist) + if (!mPersist && !androidRootfs) { if (!mUtilities->rmdirContents(mDirFd)) { @@ -296,7 +331,7 @@ void DobbyRootfs::cleanUp() } // the rootfs directory should now be empty, so can now delete it - if (!mPath.empty() && (rmdir(mPath.c_str()) != 0)) + if (!androidRootfs && !mPath.empty() && (rmdir(mPath.c_str()) != 0)) { AI_LOG_SYS_ERROR(errno, "failed to delete rootfs dir"); } diff --git a/bundle/lib/source/DobbySpecConfig.cpp b/bundle/lib/source/DobbySpecConfig.cpp index adeaae05..67b6b3e0 100644 --- a/bundle/lib/source/DobbySpecConfig.cpp +++ b/bundle/lib/source/DobbySpecConfig.cpp @@ -60,6 +60,11 @@ static const ctemplate::StaticTemplateString USERNS_ENABLED = static const ctemplate::StaticTemplateString USERNS_DISABLED = STS_INIT(USERNS_DISABLED, "USERNS_DISABLED"); +static const ctemplate::StaticTemplateString ANDROID_ENABLED = + STS_INIT(ANDROID_ENABLED, "ANDROID_ENABLED"); +static const ctemplate::StaticTemplateString ANDROID_DISABLED = + STS_INIT(ANDROID_DISABLED, "ANDROID_DISABLED"); + static const ctemplate::StaticTemplateString MEM_LIMIT = STS_INIT(MEM_LIMIT, "MEM_LIMIT"); @@ -187,15 +192,31 @@ static const ctemplate::StaticTemplateString SECCOMP_SYSCALLS = #define JSON_FLAG_FILECAPABILITIES (0x1U << 20) #define JSON_FLAG_VPU (0x1U << 21) #define JSON_FLAG_SECCOMP (0x1U << 22) +#define JSON_FLAG_ANDROID (0x1U << 23) int DobbySpecConfig::mNumCores = -1; // TODO: should we only allowed these if a network namespace is enabled ? const std::map DobbySpecConfig::mAllowedCaps = { + { "CAP_AUDIT_CONTROL", CAP_AUDIT_CONTROL }, + { "CAP_CHOWN", CAP_CHOWN }, + { "CAP_DAC_OVERRIDE", CAP_DAC_OVERRIDE }, + { "CAP_DAC_READ_SEARCH", CAP_DAC_READ_SEARCH }, + { "CAP_FOWNER", CAP_FOWNER }, + { "CAP_KILL", CAP_KILL }, + { "CAP_MKNOD", CAP_MKNOD }, + { "CAP_NET_ADMIN", CAP_NET_ADMIN }, { "CAP_NET_BIND_SERVICE", CAP_NET_BIND_SERVICE }, { "CAP_NET_BROADCAST", CAP_NET_BROADCAST }, { "CAP_NET_RAW", CAP_NET_RAW }, + { "CAP_SETGID", CAP_SETGID }, + { "CAP_SETPCAP", CAP_SETPCAP }, + { "CAP_SETUID", CAP_SETUID }, + { "CAP_SYSLOG", CAP_SYSLOG }, + { "CAP_SYS_ADMIN", CAP_SYS_ADMIN }, + { "CAP_SYS_PTRACE", CAP_SYS_PTRACE }, + { "CAP_SYS_RESOURCE", CAP_SYS_RESOURCE } }; // ----------------------------------------------------------------------------- @@ -216,6 +237,7 @@ DobbySpecConfig::DobbySpecConfig(const std::shared_ptr &utils, : mUtilities(utils) , mGpuSettings(settings->gpuAccessSettings()) , mVpuSettings(settings->vpuAccessSettings()) + , mAndroidSettings(settings->androidAccessSettings()) , mDefaultPlugins(settings->defaultPlugins()) , mRdkPluginsData(settings->rdkPluginsData()) , mDictionary(nullptr) @@ -229,6 +251,7 @@ DobbySpecConfig::DobbySpecConfig(const std::shared_ptr &utils, , mDebugDbus(IDobbyIPCUtils::BusType::NoneBus) , mConsoleDisabled(true) , mConsoleLimit(-1) + , mAndroidEnabled(false) , mRootfsPath("rootfs") { // get the number of (online) cpu cores on the system if we haven't already @@ -296,6 +319,7 @@ DobbySpecConfig::DobbySpecConfig(const std::shared_ptr &utils, : mUtilities(utils) , mGpuSettings(settings->gpuAccessSettings()) , mVpuSettings(settings->vpuAccessSettings()) + , mAndroidSettings(settings->androidAccessSettings()) , mDictionary(nullptr) , mConf(nullptr) , mSpecVersion(SpecVersion::Unknown) @@ -405,6 +429,11 @@ const std::string& DobbySpecConfig::consolePath() const return mConsolePath; } +bool DobbySpecConfig::androidEnabled() const +{ + return mAndroidEnabled; +} + const std::map& DobbySpecConfig::legacyPlugins() const { return mLegacyPlugins; @@ -479,6 +508,7 @@ bool DobbySpecConfig::parseSpec(ctemplate::TemplateDictionary* dictionary, static const ProcessorMap processors = { + { "android", { JSON_FLAG_ANDROID, &DobbySpecConfig::processAndroid } }, /* NOTE: process android before user */ { "env", { JSON_FLAG_ENV, &DobbySpecConfig::processEnv } }, { "args", { JSON_FLAG_ARGS, &DobbySpecConfig::processArgs } }, { "cwd", { JSON_FLAG_CWD, &DobbySpecConfig::processCwd } }, @@ -627,6 +657,11 @@ bool DobbySpecConfig::parseSpec(ctemplate::TemplateDictionary* dictionary, dictionary->SetValue(NO_NEW_PRIVS, "true"); } + if (!(flags & JSON_FLAG_ANDROID)) + { + dictionary->ShowSection(ANDROID_DISABLED); + } + // step 6 - enable the RDK plugins section dictionary->ShowSection(ENABLE_RDK_PLUGINS); @@ -829,9 +864,10 @@ bool DobbySpecConfig::processUser(const Json::Value& value, mUserId = uid.asUInt(); mGroupId = gid.asUInt(); + // sanity check the uid and gid are valid, and make sure we aren't being - // asked to start the container as root - if (mUserId == 0) + // asked to start the container as root unless it is Android + if ((mUserId == 0) && (!mAndroidEnabled)) { AI_LOG_ERROR("the user.uid cannot be root (0)"); return false; @@ -841,13 +877,112 @@ bool DobbySpecConfig::processUser(const Json::Value& value, AI_LOG_ERROR("invalid uid or gid field, values must be less than 65535"); return false; } - dictionary->SetIntValue(USER_ID, mUserId); dictionary->SetIntValue(GROUP_ID, mGroupId); return true; } +// ----------------------------------------------------------------------------- +/** + * @brief Processes the android field of the json spec + * + * Example json: + * + * "android": true + * "android": false + * + * This field controls whether to enable android customisations or not. + * + * @param[in] value The json spec document from the client + * @param[in] dictionary Pointer to the OCI dictionary to populate + * + * @return true if correctly processed the value, otherwise false. + */ +bool DobbySpecConfig::processAndroid(const Json::Value& value, + ctemplate::TemplateDictionary* dictionary) +{ + bool enabled; + mAndroidEnabled = false; + if (value.isBool()) + { + enabled = value.asBool(); + } + else if (value.isNull()) + { + enabled = false; + } + else + { + AI_LOG_ERROR("invalid android field"); + return false; + } + + mAndroidEnabled = enabled; + + AI_LOG_INFO("Android is %s", enabled ? "enabled" : "disabled"); + + dictionary->ShowSection(enabled ? ANDROID_ENABLED : ANDROID_DISABLED); + + if(!enabled) // No need for extra settings if Android disabled + { + return true; + } + + // add any extra mounts (ie ipc sockets, shared memory files, etc) + for (const auto& extraMount : mAndroidSettings->extraMounts) + { + ctemplate::TemplateDictionary *subDict = dictionary->AddSectionDictionary(MOUNT_SECTION); + subDict->SetValue(MOUNT_SRC, extraMount.source); + subDict->SetValue(MOUNT_DST, extraMount.target); + subDict->SetValue(MOUNT_TYPE, extraMount.type); + + for (const std::string& flag : extraMount.flags) + { + ctemplate::TemplateDictionary *optSubDict = subDict->AddSectionDictionary(MOUNT_OPT_SECTION); + optSubDict->SetValue(MOUNT_OPT, flag); + } + + // store the mount point for rootfs construction + storeMountPoint(extraMount.type, extraMount.source, extraMount.target); + } + + // device nodes should be static, so get the details once and store for + // all subsequent calls + static std::mutex scanningLock; + static std::atomic scannedDevNodes(false); + static std::list devNodes; + + if (!scannedDevNodes) + { + std::lock_guard locker(scanningLock); + + if (!scannedDevNodes) + { + for (const auto& devNode : mAndroidSettings->deviceNodes) + { + devNodes.emplace_back(DevNode{ devNode.path, + devNode.major, + devNode.minor, + devNode.filemode }); + } + scannedDevNodes = true; + } + } + + // add to the additional device node section + for (const DobbyConfig::DevNode &devNode : devNodes) + { + ctemplate::TemplateDictionary *subDict = dictionary->AddSectionDictionary(ADDITIONAL_DEVICE_NODES); + subDict->SetValue(DEVICE_PATH, devNode.path); + subDict->SetIntValue(DEVICE_MAJOR, devNode.major); + subDict->SetIntValue(DEVICE_MINOR, devNode.minor); + subDict->SetIntValue(DEVICE_FILE_MODE, devNode.mode); + } + + return true; +} + // ----------------------------------------------------------------------------- /** * @brief Processes the userNs field of the json spec diff --git a/bundle/lib/source/templates/OciConfigJson1.0.2-dobby.template b/bundle/lib/source/templates/OciConfigJson1.0.2-dobby.template index 9bd0afe2..a3099d74 100644 --- a/bundle/lib/source/templates/OciConfigJson1.0.2-dobby.template +++ b/bundle/lib/source/templates/OciConfigJson1.0.2-dobby.template @@ -27,8 +27,21 @@ static const char* ociJsonTemplate = R"JSON( "arch": "arm" }, - "process": { + {{#ANDROID_ENABLED}} + "annotations": { + "run.oci.hooks.stderr": "/dev/stderr", + "run.oci.hooks.stdout": "/dev/stdout" + }, + {{/ANDROID_ENABLED}} + + + "process": { + {{#ANDROID_DISABLED}} "terminal": false, + {{/ANDROID_DISABLED}} + {{#ANDROID_ENABLED}} + "terminal": true, + {{/ANDROID_ENABLED}} "user": { {{#USERNS_DISABLED}} "uid": {{USER_ID}}, @@ -41,16 +54,122 @@ static const char* ociJsonTemplate = R"JSON( "additionalGids": [ {{#ADDITIONAL_GIDS}} {{ADDITIONAL_GID}}{{#ADDITIONAL_GIDS_separator}},{{/ADDITIONAL_GIDS_separator}} {{/ADDITIONAL_GIDS}} ] }, "args": [ + {{#ANDROID_DISABLED}} "/usr/libexec/DobbyInit", {{#ARGS_VAR_SECTION}}"{{ARGS_VAR_VALUE:json_escape}}"{{#ARGS_VAR_SECTION_separator}},{{/ARGS_VAR_SECTION_separator}} {{/ARGS_VAR_SECTION}} ], + {{/ANDROID_DISABLED}} + {{#ANDROID_ENABLED}} + {{#ARGS_VAR_SECTION}}"{{ARGS_VAR_VALUE:json_escape}}"{{#ARGS_VAR_SECTION_separator}},{{/ARGS_VAR_SECTION_separator}} {{/ARGS_VAR_SECTION}} + ], + {{/ANDROID_ENABLED}} "cwd": "{{WORKING_DIRECTORY}}", + {{#ANDROID_DISABLED}} "env": [ {{#ENV_VAR_SECTION}}"{{ENV_VAR_VALUE:json_escape}}", {{/ENV_VAR_SECTION}} {{EXTRA_ENV_VARS}} "HOME=/home/private", "PATH=/usr/sbin:/usr/bin:/sbin:/bin" ], + {{/ANDROID_DISABLED}} + {{#ANDROID_ENABLED}} + "env": [ + {{#ENV_VAR_SECTION}}"{{ENV_VAR_VALUE:json_escape}}", {{/ENV_VAR_SECTION}} + {{EXTRA_ENV_VARS}} + "PATH=/system/bin:/" + ], + {{/ANDROID_ENABLED}} + {{#ANDROID_ENABLED}} + "capabilities": { + "bounding": [ + "CAP_AUDIT_CONTROL", + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_DAC_READ_SEARCH", + "CAP_FOWNER", + "CAP_KILL", + "CAP_MKNOD", + "CAP_NET_ADMIN", + "CAP_SETGID", + "CAP_SETPCAP", + "CAP_SETUID", + "CAP_SYSLOG", + "CAP_SYS_ADMIN", + "CAP_SYS_PTRACE", + "CAP_SYS_RESOURCE" + {{#EXTRA_CAPS_SECTION}},"{{EXTRA_CAPS_VALUE}}"{{/EXTRA_CAPS_SECTION}} + ], + "effective": [ + "CAP_AUDIT_CONTROL", + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_DAC_READ_SEARCH", + "CAP_FOWNER", + "CAP_KILL", + "CAP_MKNOD", + "CAP_NET_ADMIN", + "CAP_SETGID", + "CAP_SETPCAP", + "CAP_SETUID", + "CAP_SYSLOG", + "CAP_SYS_ADMIN", + "CAP_SYS_PTRACE", + "CAP_SYS_RESOURCE" + ], + "inheritable": [ + "CAP_AUDIT_CONTROL", + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_DAC_READ_SEARCH", + "CAP_FOWNER", + "CAP_KILL", + "CAP_MKNOD", + "CAP_NET_ADMIN", + "CAP_SETGID", + "CAP_SETPCAP", + "CAP_SETUID", + "CAP_SYSLOG", + "CAP_SYS_ADMIN", + "CAP_SYS_PTRACE", + "CAP_SYS_RESOURCE" + ], + "permitted": [ + "CAP_AUDIT_CONTROL", + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_DAC_READ_SEARCH", + "CAP_FOWNER", + "CAP_KILL", + "CAP_MKNOD", + "CAP_NET_ADMIN", + "CAP_SETGID", + "CAP_SETPCAP", + "CAP_SETUID", + "CAP_SYSLOG", + "CAP_SYS_ADMIN", + "CAP_SYS_PTRACE", + "CAP_SYS_RESOURCE" + ], + "ambient": [ + "CAP_AUDIT_CONTROL", + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_DAC_READ_SEARCH", + "CAP_FOWNER", + "CAP_KILL", + "CAP_MKNOD", + "CAP_NET_ADMIN", + "CAP_SETGID", + "CAP_SETPCAP", + "CAP_SETUID", + "CAP_SYSLOG", + "CAP_SYS_ADMIN", + "CAP_SYS_PTRACE", + "CAP_SYS_RESOURCE" + ] + }, + {{/ANDROID_ENABLED}} + {{#ANDROID_DISABLED}} "capabilities": { "bounding": [ "CAP_AUDIT_WRITE", @@ -79,6 +198,8 @@ static const char* ociJsonTemplate = R"JSON( "CAP_NET_BIND_SERVICE" ] }, + {{/ANDROID_DISABLED}} + {{#ANDROID_DISABLED}} "rlimits": [ { "type": "RLIMIT_NOFILE", @@ -97,15 +218,26 @@ static const char* ociJsonTemplate = R"JSON( "soft": {{RLIMIT_RTPRIO}} }{{/RTLIMIT_ENABLED}} ], + {{/ANDROID_DISABLED}} "noNewPrivileges": {{NO_NEW_PRIVS}} }, + {{#ANDROID_DISABLED}} "root": { "path": "rootfs", "readonly": true }, "rootfsPropagation": "rprivate", + {{/ANDROID_DISABLED}} + + {{#ANDROID_ENABLED}} + "root": { + "path": "rootfs", + "readonly": false + }, + "rootfsPropagation": "rshared", + {{/ANDROID_ENABLED}} "hostname": "dobby", @@ -115,11 +247,16 @@ static const char* ociJsonTemplate = R"JSON( "type": "tmpfs", "source": "tmpfs", "options": [ + {{#ANDROID_DISABLED}} "nosuid", "noexec", "nodev", "size=65536k", "nr_inodes=8k" + {{/ANDROID_DISABLED}} + {{#ANDROID_ENABLED}} + "mode=0755" + {{/ANDROID_ENABLED}} ] }, { @@ -127,24 +264,18 @@ static const char* ociJsonTemplate = R"JSON( "type": "tmpfs", "source": "tmpfs", "options": [ + {{#ANDROID_DISABLED}} "nosuid", "noexec", "strictatime", "mode=755", "size=65536k" - ] - }, - { - "destination": "/dev/pts", - "type": "devpts", - "source": "devpts", - "options": [ - "nosuid", + {{/ANDROID_DISABLED}} + {{#ANDROID_ENABLED}} "noexec", - "newinstance", - "ptmxmode=0666", - "mode=0620", - "gid=5" + "strictatime", + "mode=755" + {{/ANDROID_ENABLED}} ] }, { @@ -166,8 +297,10 @@ static const char* ociJsonTemplate = R"JSON( "options": [ "nosuid", "noexec", - "nodev", - "ro" + {{#ANDROID_DISABLED}} + "ro", + {{/ANDROID_DISABLED}} + "nodev" ] }, { @@ -177,10 +310,18 @@ static const char* ociJsonTemplate = R"JSON( "options": [ "nosuid", "noexec", + {{#ANDROID_DISABLED}} "nodev", "relatime" + {{/ANDROID_DISABLED}} + {{#ANDROID_ENABLED}} + "none", + "name=", + "strictatime" + {{/ANDROID_ENABLED}} ] }, + {{#ANDROID_DISABLED}} { "destination": "/lib", "type": "bind", @@ -225,6 +366,26 @@ static const char* ociJsonTemplate = R"JSON( "ro" ] }, + {{/ANDROID_DISABLED}} + {{#ANDROID_DISABLED}} + { + "destination": "/proc", + "type": "proc", + "source": "proc", + "options": [ + "nosuid", + "noexec", + "nodev" + ] + }, + {{/ANDROID_DISABLED}} + {{#ANDROID_ENABLED}} {{! /proc before MOUNT_SECTION so we can override /proc/cmdline for Android }} + { + "destination": "/proc", + "type": "proc", + "source": "proc" + }, + {{/ANDROID_ENABLED}} {{#SYSLOG_SECTION}}{ "destination": "/dev/log", "type": "bind", @@ -244,13 +405,16 @@ static const char* ociJsonTemplate = R"JSON( ] },{{/MOUNT_SECTION}} { - "destination": "/proc", - "type": "proc", - "source": "proc", + "destination": "/dev/pts", + "type": "devpts", + "source": "devpts", "options": [ "nosuid", "noexec", - "nodev" + "newinstance", + "ptmxmode=0666", + "mode=0620", + "gid=5" ] } ], @@ -306,6 +470,7 @@ static const char* ociJsonTemplate = R"JSON( {{/ADDITIONAL_DEVICE_NODES}} ], "resources": { + {{#ANDROID_DISABLED}} "devices": [ { "allow": false, @@ -330,9 +495,22 @@ static const char* ociJsonTemplate = R"JSON( } {{/DEV_WHITELIST_SECTION}} ], + {{/ANDROID_DISABLED}} + {{#ANDROID_DISABLED}} "memory": { "limit": {{MEM_LIMIT}} }, + {{/ANDROID_DISABLED}} + {{#ANDROID_ENABLED}} + "memory": { + "kernel": -1, + "kernelTCP": -1, + "limit": {{MEM_LIMIT}}, + "swap": 1073741824, + "swappiness": 0, + "disableOOMKiller": false + }, + {{/ANDROID_ENABLED}} "cpu": { {{#CPU_SHARES_ENABLED}} "shares": {{CPU_SHARES_VALUE}}, @@ -345,6 +523,10 @@ static const char* ociJsonTemplate = R"JSON( } }, "namespaces": [ + {{#ANDROID_ENABLED}} + { + "type": "cgroup" + },{{/ANDROID_ENABLED}} { "type": "pid" },{{#NETNS_ENABLED}} @@ -364,6 +546,7 @@ static const char* ociJsonTemplate = R"JSON( "type": "mount" } ], + {{#ANDROID_DISABLED}} "maskedPaths": [ "/proc/kcore", "/proc/latency_stats", @@ -378,6 +561,26 @@ static const char* ociJsonTemplate = R"JSON( "/proc/sys", "/proc/sysrq-trigger" ] + {{/ANDROID_DISABLED}} + {{#ANDROID_ENABLED}} + "maskedPaths": [ + "/sys/block", + "/sys/bus/mmc", + "/sys/class/block", + "/sys/class/misc/binder", + "/sys/class/misc/hwbinder", + "/sys/class/misc/vndbinder", + "/sys/class/mmc_host", + "/sys/devices/platform/ffe05000.sdio", + "/sys/devices/platform/ffe07000.emmc", + "/sys/devices/virtual/misc/binder", + "/sys/devices/virtual/misc/hwbinder", + "/sys/devices/virtual/misc/vndbinder", + "/sys/firmware/devicetree/base/emmc@ffe07000", + "/sys/module/mmcblk", + "/sys/power" + ] + {{/ANDROID_ENABLED}} }, {{#ENABLE_LEGACY_PLUGINS}} "legacyPlugins": { diff --git a/bundle/runtime-schemas/defs-plugins.json b/bundle/runtime-schemas/defs-plugins.json index b93abbfb..d04f5b0e 100644 --- a/bundle/runtime-schemas/defs-plugins.json +++ b/bundle/runtime-schemas/defs-plugins.json @@ -654,6 +654,40 @@ } } }, + "AndroidRuntime": { + "type": "object", + "required": [ + "required" + ], + "properties": { + "required": { + "type": "boolean" + }, + "dependsOn": { + "$ref": "defs.json#/definitions/ArrayOfStrings" + }, + "data": { + "type": "object", + "properties": { + "systemPath": { + "type": "string" + }, + "vendorPath": { + "type": "string" + }, + "dataPath": { + "type": "string" + }, + "cachePath": { + "type": "string" + }, + "cmdlinePath": { + "type": "string" + } + } + } + } + }, "LegacyPlugins": { "type": "object", "properties": { @@ -715,6 +749,9 @@ }, "gamepad": { "$ref": "#/definitions/Gamepad" + }, + "androidruntime": { + "$ref": "#/definitions/AndroidRuntime" } } } diff --git a/daemon/lib/source/Dobby.cpp b/daemon/lib/source/Dobby.cpp index 7ccc9e15..f0ebb721 100644 --- a/daemon/lib/source/Dobby.cpp +++ b/daemon/lib/source/Dobby.cpp @@ -971,7 +971,7 @@ void Dobby::startFromSpec(std::shared_ptr replySender std::vector envVars; // The command argument might not be sent at all (e.g. from AI) so we should - // be able to withstand receiving 3 or 6 arguments + // be able to withstand receiving 3, 4 or 6 arguments bool parseArgsSuccess = false; if (replySender->getMethodCallArguments().size() == 3) { diff --git a/settings/include/IDobbySettings.h b/settings/include/IDobbySettings.h index e2afdb6a..d20d0a77 100644 --- a/settings/include/IDobbySettings.h +++ b/settings/include/IDobbySettings.h @@ -157,6 +157,55 @@ class IDobbySettings */ virtual std::shared_ptr vpuAccessSettings() const = 0; + /** + * Describes the details of any extra device nodes needed for Android Runtime. + * This is needed due to the need for specific major and minor numbers. + */ + struct AndroidDeviceNode + { + std::string path; + int major; + int minor; + int filemode; + }; + + // ------------------------------------------------------------------------- + /** + * Describes the details of anything extra needed to enable Android Runtime. + * + * - deviceNodes + * List of extra device nodes that need to be mapped into + * the container to allow the apps to use the H/W. + * - groupIds + * The group id that the app needs to be in to access the + * H/W device nodes. If not empty then the containered app will be + * in that supplementary group(s). + * - extraMounts + * The details of any additional mounts required to access + * the H/W. For example this is used on nexus platforms to map in + * the nexus server socket. This can also be used to map in + * extra files / sockets used by the software. + * - extraEnvVariables + * A list of extra environment variables that will be set for all + * containers if the given H/W access is requested. + * + */ + struct AndroidAccessSettings + { + std::list deviceNodes; + std::set groupIds; + std::list extraMounts; + std::map extraEnvVariables; + }; + + // ------------------------------------------------------------------------- + /** + * @brief Returns any extra details needed to access the VPU (video + * pipeline) inside the container. + * + */ + virtual std::shared_ptr androidAccessSettings() const = 0; + // ------------------------------------------------------------------------- /** * @brief Returns the set of external interface that container traffic diff --git a/settings/include/Settings.h b/settings/include/Settings.h index 075285ff..0aa06aea 100644 --- a/settings/include/Settings.h +++ b/settings/include/Settings.h @@ -68,6 +68,7 @@ class Settings final : public IDobbySettings public: std::shared_ptr gpuAccessSettings() const override; std::shared_ptr vpuAccessSettings() const override; + std::shared_ptr androidAccessSettings() const override; std::vector externalInterfaces() const override; std::string addressRangeStr() const override; @@ -110,6 +111,15 @@ class Settings final : public IDobbySettings std::shared_ptr getHardwareAccess(const Json::Value& root, const Json::Path& path) const; + std::list getAndroidNodes(const Json::Value& root, + const Json::Path& path) const; + + bool processAndroidDevice(const Json::Value& value, + AndroidDeviceNode* devNode) const; + + std::shared_ptr getAndroidAccess(const Json::Value& root, + const Json::Path& path) const; + void dumpHardwareAccess(int aiLogLevel, const std::string& name, const std::shared_ptr& hwAccess) const; @@ -122,6 +132,7 @@ class Settings final : public IDobbySettings std::shared_ptr mGpuHardwareAccess; std::shared_ptr mVpuHardwareAccess; + std::shared_ptr mAndroidHardwareAccess; std::vector mExternalInterfaces; std::pair mAddressRange; diff --git a/settings/source/Settings.cpp b/settings/source/Settings.cpp index 96081033..be670a97 100644 --- a/settings/source/Settings.cpp +++ b/settings/source/Settings.cpp @@ -164,6 +164,12 @@ Settings::Settings(const Json::Value& settings) getHardwareAccess(settings, Json::Path(".vpu")); } + // process the Android settings + { + mAndroidHardwareAccess = + getAndroidAccess(settings, Json::Path(".android")); + } + // process the network settings { Json::Value externalIfaces = Json::Path(".network.externalInterfaces").resolve(settings); @@ -473,6 +479,16 @@ std::shared_ptr Settings::vpuAccessSetti return mVpuHardwareAccess; } +// ----------------------------------------------------------------------------- +/** + * @brief + * + */ +std::shared_ptr Settings::androidAccessSettings() const +{ + return mAndroidHardwareAccess; +} + // ----------------------------------------------------------------------------- /** * @brief @@ -592,6 +608,7 @@ void Settings::dump(int aiLogLevel) const dumpHardwareAccess(aiLogLevel, "gpu", mGpuHardwareAccess); dumpHardwareAccess(aiLogLevel, "vpu", mVpuHardwareAccess); + //dumpHardwareAccess(aiLogLevel, "android", mAndroidHardwareAccess); } // ----------------------------------------------------------------------------- @@ -941,6 +958,66 @@ std::list Settings::getDevNodes(const Json::Value& root, return result; } +std::list Settings::getAndroidNodes(const Json::Value& root, + const Json::Path& path) const +{ + const Json::Value &devNodes = Json::Path(path).resolve(root); + if (devNodes.isNull()) + { + return std::list(); + } + else if (!devNodes.isArray()) + { + AI_LOG_ERROR("JSON value in settings file is not an array (of dev nodes)"); + return std::list(); + } + + std::list result; + for (const Json::Value &devNode : devNodes) + { + if (!devNode.isObject()) + { + AI_LOG_ERROR("invalid JSON value in dev nodes array in settings file"); + return std::list(); + } + + AndroidDeviceNode androidDevNode; + if (processAndroidDevice(devNode, &androidDevNode)) + { + result.emplace_back(androidDevNode); + } + } + + return result; +} + +bool Settings::processAndroidDevice(const Json::Value& value, AndroidDeviceNode* devNode) const +{ + const Json::Value& path = value["path"]; + const Json::Value& major = value["major"]; + const Json::Value& minor = value["minor"]; + const Json::Value& fileMode = value["fileMode"]; + + if (!path.isString()) + { + AI_LOG_ERROR("invalid 'path' JSON field"); + return false; + } + + if (!major.isInt() || !minor.isInt() || !fileMode.isInt()) + { + AI_LOG_ERROR("invalid 'major', 'minor' or 'fileMode' JSON field"); + return false; + } + + devNode->path = path.asString(); + devNode->major = major.asInt(); + devNode->minor = minor.asInt(); + devNode->filemode = fileMode.asInt(); + + return true; +} + // ----------------------------------------------------------------------------- /** * @brief Attempts to read the mount JSON structure(s) from the object. @@ -1022,7 +1099,8 @@ bool Settings::processMountObject(const Json::Value& value, ExtraMount* mount) c static const std::set mountFlags = { "rbind", "bind", "silent", "ro", "sync", "nosuid", "dirsync", - "nodiratime", "relatime", "noexec", "nodev", "noatime", "strictatime" + "nodiratime", "relatime", "noexec", "nodev", "noatime", "strictatime", + "mode=0755" }; // convert the mount flags @@ -1124,3 +1202,82 @@ std::shared_ptr Settings::getHardwareAcc return accessSettings; } + +// ----------------------------------------------------------------------------- +/** + * @brief Processes a json 'android' object. + * + * The JSON is expected to look like the following: + * + * { + * "groupIds": [ "video" ], + * "devNodes": [ + * { + * "path": "/dev/kmsg", + * "fileMode": 438, + * "major": 1, + * "minor": 11 + * }, + * "extraEnvVariables": [ + * "ENABLE_MEDIAINFO=0" + * ], + * "extraMounts": [ + * { + * "source": "/etc/xdg/gstomx.conf", + * "destination": "/etc/xdg/gstomx.conf", + * "type": "bind", + * "options": [ "bind", "ro", "nosuid", "nodev", "noexec" ] + * }, + * ... + * ] + * } + * + * @return + */ + +std::shared_ptr Settings::getAndroidAccess(const Json::Value& root, + const Json::Path& path) const +{ + auto accessSettings = + std::make_shared(); + + // get the 'android' object from the json + const Json::Value hw = Json::Path(path).resolve(root); + if (hw.isNull()) + { + // it's not an error if the value does not exist in the JSON + return accessSettings; + } + else if (!hw.isObject()) + { + // however it is an error if present but not a json object + AI_LOG_ERROR("invalid 'android' JSON field in dobby settings file"); + return accessSettings; + } + + + // get the group id(s) required + const Json::Value groupIds = hw["groupIds"]; + if (!groupIds.isNull()) + { + accessSettings->groupIds = getGroupIds(groupIds); + } + else + { + const Json::Value groupId = hw["groupId"]; + if (!groupId.isNull()) + accessSettings->groupIds = getGroupIds(groupId); + } + + // Nb: validation that the paths are actually dev nodes is done in the + // DobbyConfig code + accessSettings->deviceNodes = getAndroidNodes(hw, Json::Path(".devNodes")); + + // get any extra mounts + accessSettings->extraMounts = getExtraMounts(hw, Json::Path(".extraMounts")); + + // get any extra environment vars + accessSettings->extraEnvVariables = getEnvVarsFromJson(hw, Json::Path(".extraEnvVariables")); + + return accessSettings; +} diff --git a/utils/source/DobbyUtils.cpp b/utils/source/DobbyUtils.cpp index 4c803a3d..d5dd31bd 100644 --- a/utils/source/DobbyUtils.cpp +++ b/utils/source/DobbyUtils.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -242,9 +243,38 @@ bool DobbyUtils::deleteRecursive(int dirfd, int availDepth) // try unlinking the file / directory / symlink / whatever if (unlinkat(dirfd, entry->d_name, flags) != 0) { - AI_LOG_SYS_ERROR(errno, "failed to remove '%s'", entry->d_name); - success = false; - break; + if(errno == EBUSY) + { + AI_LOG_WARN("EBUSY whilst trying to remove '%s', try unmounting..", entry->d_name); + if (fchdir(dirfd) != 0) + { + AI_LOG_SYS_ERROR(errno, "failed to change to file directory"); + success = false; + break; + } + else if (umount2(entry->d_name, UMOUNT_NOFOLLOW) != 0) + { + AI_LOG_SYS_ERROR(errno, "failed to remove (unmount) '%s'", entry->d_name); + success = false; + break; + } + else if (unlinkat(dirfd, entry->d_name, flags) != 0) + { + AI_LOG_SYS_ERROR(errno, "failed to remove file after unmount '%s'", entry->d_name); + success = false; + break; + } + else + { + AI_LOG_INFO("unmounted and removed '%s'", entry->d_name); + } + } + else + { + AI_LOG_SYS_ERROR(errno, "failed to remove '%s'", entry->d_name); + success = false; + break; + } } } From d9a5f68d11a3858403f7e8869b099b5858d227be Mon Sep 17 00:00:00 2001 From: Jon Henderson Date: Thu, 6 Jun 2024 09:57:46 +0000 Subject: [PATCH 2/5] Add extra masked paths Signed-off-by: Jon Henderson --- .../lib/source/templates/OciConfigJson1.0.2-dobby.template | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bundle/lib/source/templates/OciConfigJson1.0.2-dobby.template b/bundle/lib/source/templates/OciConfigJson1.0.2-dobby.template index a3099d74..4393140f 100644 --- a/bundle/lib/source/templates/OciConfigJson1.0.2-dobby.template +++ b/bundle/lib/source/templates/OciConfigJson1.0.2-dobby.template @@ -578,7 +578,11 @@ static const char* ociJsonTemplate = R"JSON( "/sys/devices/virtual/misc/vndbinder", "/sys/firmware/devicetree/base/emmc@ffe07000", "/sys/module/mmcblk", - "/sys/power" + "/sys/power", + "/sys/devices/platform/rdb/84b1000.sdhci", + "/sys/devices/virtual/devlink/brcm_scmi@0--84b1000.sdhci", + "/sys/firmware/devicetree/base/rdb/sdhci@84b1000", + "/sys/firmware/devicetree/base/rdb/sdhci@84b0000" ] {{/ANDROID_ENABLED}} }, From 02c6c3848831e05b6a6f40db228c361c9810bf30 Mon Sep 17 00:00:00 2001 From: Jon Henderson Date: Wed, 12 Jun 2024 15:11:02 +0000 Subject: [PATCH 3/5] Add Android AppArmor profile setting Provide the ability to use an alternate AppArmor profile when running android containers, i.e. { "android": { "appArmorProfile": "aosp_default" } } If no setting is provided, the normal Dobby profile will be used. --- daemon/lib/source/DobbyManager.cpp | 9 ++++++- settings/include/IDobbySettings.h | 1 + settings/source/Settings.cpp | 38 ++++++++++++++++++++---------- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/daemon/lib/source/DobbyManager.cpp b/daemon/lib/source/DobbyManager.cpp index 272423f8..8a5326d4 100644 --- a/daemon/lib/source/DobbyManager.cpp +++ b/daemon/lib/source/DobbyManager.cpp @@ -850,7 +850,14 @@ int32_t DobbyManager::startContainerFromSpec(const ContainerId &id, // Set Apparmor profile if (mSettings->apparmorSettings().enabled) { - config->setApparmorProfile(mSettings->apparmorSettings().profileName); + if (config->androidEnabled() && !mSettings->androidAccessSettings()->appArmorProfile.empty()) + { + config->setApparmorProfile(mSettings->androidAccessSettings()->appArmorProfile); + } + else + { + config->setApparmorProfile(mSettings->apparmorSettings().profileName); + } } // Set pids limit diff --git a/settings/include/IDobbySettings.h b/settings/include/IDobbySettings.h index d20d0a77..edb2d308 100644 --- a/settings/include/IDobbySettings.h +++ b/settings/include/IDobbySettings.h @@ -196,6 +196,7 @@ class IDobbySettings std::set groupIds; std::list extraMounts; std::map extraEnvVariables; + std::string appArmorProfile; }; // ------------------------------------------------------------------------- diff --git a/settings/source/Settings.cpp b/settings/source/Settings.cpp index be670a97..c10d41d7 100644 --- a/settings/source/Settings.cpp +++ b/settings/source/Settings.cpp @@ -964,28 +964,28 @@ std::list Settings::getAndroidNodes(const Json::Val const Json::Value &devNodes = Json::Path(path).resolve(root); if (devNodes.isNull()) { - return std::list(); + return std::list(); } else if (!devNodes.isArray()) { AI_LOG_ERROR("JSON value in settings file is not an array (of dev nodes)"); - return std::list(); + return std::list(); } std::list result; for (const Json::Value &devNode : devNodes) { - if (!devNode.isObject()) - { - AI_LOG_ERROR("invalid JSON value in dev nodes array in settings file"); - return std::list(); - } + if (!devNode.isObject()) + { + AI_LOG_ERROR("invalid JSON value in dev nodes array in settings file"); + return std::list(); + } - AndroidDeviceNode androidDevNode; - if (processAndroidDevice(devNode, &androidDevNode)) - { - result.emplace_back(androidDevNode); - } + AndroidDeviceNode androidDevNode; + if (processAndroidDevice(devNode, &androidDevNode)) + { + result.emplace_back(androidDevNode); + } } return result; @@ -1279,5 +1279,19 @@ std::shared_ptr Settings::getAndroidAcces // get any extra environment vars accessSettings->extraEnvVariables = getEnvVarsFromJson(hw, Json::Path(".extraEnvVariables")); + const Json::Value appArmorProfile = hw["appArmorProfile"]; + if (appArmorProfile.isNull()) + { + AI_LOG_INFO("No Android AppArmor profile provided - will use default"); + } + else if (appArmorProfile.isString()) + { + accessSettings->appArmorProfile = appArmorProfile.asString(); + } + else + { + AI_LOG_ERROR("Invalid entry in android.appArmorProfile"); + } + return accessSettings; } From a193beb4d75b319150e3645424c57971f93abf25 Mon Sep 17 00:00:00 2001 From: Jon Henderson Date: Tue, 23 Jul 2024 11:55:55 +0100 Subject: [PATCH 4/5] Added missing rdkPlugins directory --- rdkPlugins/AndroidRuntime/CMakeLists.txt | 45 ++ rdkPlugins/AndroidRuntime/README.md | 0 .../AndroidRuntime/source/AndroidHelper.cpp | 553 ++++++++++++++++++ .../AndroidRuntime/source/AndroidHelper.h | 111 ++++ .../source/AndroidRuntimePlugin.cpp | 380 ++++++++++++ .../source/AndroidRuntimePlugin.h | 80 +++ 6 files changed, 1169 insertions(+) create mode 100644 rdkPlugins/AndroidRuntime/CMakeLists.txt create mode 100644 rdkPlugins/AndroidRuntime/README.md create mode 100644 rdkPlugins/AndroidRuntime/source/AndroidHelper.cpp create mode 100644 rdkPlugins/AndroidRuntime/source/AndroidHelper.h create mode 100644 rdkPlugins/AndroidRuntime/source/AndroidRuntimePlugin.cpp create mode 100644 rdkPlugins/AndroidRuntime/source/AndroidRuntimePlugin.h diff --git a/rdkPlugins/AndroidRuntime/CMakeLists.txt b/rdkPlugins/AndroidRuntime/CMakeLists.txt new file mode 100644 index 00000000..d30e53f5 --- /dev/null +++ b/rdkPlugins/AndroidRuntime/CMakeLists.txt @@ -0,0 +1,45 @@ +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2020 Sky UK +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reference CMake file for the simplest Dobby RDK Plugin + +# Project name should match the name of the plugin +# CMake will prefix this with "lib" +project(AndroidRuntimePlugin) + +add_library( ${PROJECT_NAME} + SHARED + source/AndroidRuntimePlugin.cpp + source/AndroidHelper.cpp + ) + +target_include_directories(${PROJECT_NAME} + PRIVATE + $ +) + +install( + TARGETS ${PROJECT_NAME} + LIBRARY DESTINATION lib/plugins/dobby + NAMELINK_SKIP + ) + +target_link_libraries(${PROJECT_NAME} + DobbyRdkPluginCommonLib +) + +set_target_properties( ${PROJECT_NAME} PROPERTIES SOVERSION 1 ) diff --git a/rdkPlugins/AndroidRuntime/README.md b/rdkPlugins/AndroidRuntime/README.md new file mode 100644 index 00000000..e69de29b diff --git a/rdkPlugins/AndroidRuntime/source/AndroidHelper.cpp b/rdkPlugins/AndroidRuntime/source/AndroidHelper.cpp new file mode 100644 index 00000000..c85e0137 --- /dev/null +++ b/rdkPlugins/AndroidRuntime/source/AndroidHelper.cpp @@ -0,0 +1,553 @@ +/* +* If not stated otherwise in this file or this component's LICENSE file the +* following copyright and licenses apply: +* +* Copyright 2020 Sky UK +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "AndroidHelper.h" +#include "DobbyRdkPluginUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) +#include +#endif + +// The major number of the loop back devices +#define LOOP_DEV_MAJOR_NUM 7 + +// Storage helper methods (doesn't require state to work) + +// ----------------------------------------------------------------------------- +/** + * @brief Attempts to open an available loop device + * + * WARNING this method requires sudo + * + * @param[out] loopDevice Loop device name + * + * @return on success a positive file descriptor corresponding to a free + * loop device, -1 on error. + */ +int AndroidHelper::openLoopDevice(std::string* loopDevice) +{ + AI_LOG_FN_ENTRY(); + + // This is the part which require sudo!! + int devCtlFd = open("/dev/loop-control", O_RDWR | O_CLOEXEC); + if (devCtlFd < 0) + { + AI_LOG_SYS_ERROR_EXIT(errno, "failed to open '/dev/loop-control'"); + return -1; + } + + int devFd = -1; + for (unsigned attempts = 0; (attempts < 5) && (devFd < 0); attempts++) + { + int devNum = ioctl(devCtlFd, LOOP_CTL_GET_FREE); + if (devNum < 0) + { + AI_LOG_SYS_ERROR_EXIT(errno, "failed to get free device from loop control"); + close(devCtlFd); + return -1; + } + + AI_LOG_DEBUG("found free loop device number %d", devNum); + + char loopDevPath[32]; + sprintf(loopDevPath, "/dev/loop%d", devNum); + + devFd = open(loopDevPath, O_RDWR | O_CLOEXEC); + if (devFd < 0) + { + // check if we failed because the devnode didn't exist, if this + // happens then we should go create the dev node ourselves, at this + // point we're racing against udev which may also be trying to + // create the dev node ... we don't care who wins as long as there + // is a dev node there when we try and open it + if (errno == ENOENT) + { + if (mknod(loopDevPath, (S_IFBLK | 0660), + makedev(LOOP_DEV_MAJOR_NUM, devNum)) != 0) + { + if (errno != EEXIST) + AI_LOG_SYS_ERROR(errno, "failed to mknod '%s'", loopDevPath); + } + } + + // try and open the devnode once again + devFd = open(loopDevPath, O_RDWR | O_CLOEXEC); + } + + // check again if managed to open the file + if (devFd < 0) + { + AI_LOG_SYS_ERROR(errno, "failed to open '%s'", loopDevPath); + + // try and release the loop device we created (but failed to + // connect to) + if (ioctl(devCtlFd, LOOP_CTL_REMOVE, devNum) != 0) + AI_LOG_SYS_ERROR(errno, "failed to free device from loop control"); + } + else if (loopDevice != nullptr) + { + loopDevice->assign(loopDevPath); + } + } + + if (close(devCtlFd) != 0) + { + AI_LOG_SYS_ERROR(errno, "failed to close '/dev/loop-control'"); + } + + AI_LOG_FN_EXIT(); + return devFd; +} + +// ----------------------------------------------------------------------------- +/** + * @brief Attempts to attach the file to the loop device + * + * @param[in] loopFd An open file descriptor to the loop device + * @param[in] fileFd An open file descriptor that should be associate + * with the loop device. + * + * @return on success a positive file desccriptor corresponding to a free + * loop device, -1 on error. + */ +bool AndroidHelper::attachFileToLoopDevice(int loopFd, int fileFd) +{ + AI_LOG_FN_ENTRY(); + + if (ioctl(loopFd, LOOP_SET_FD, fileFd) < 0) + { + AI_LOG_SYS_ERROR_EXIT(errno, "failed to attach to file to loop device"); + return false; + } + + struct loop_info64 loopInfo; + bzero(&loopInfo, sizeof(loopInfo)); + loopInfo.lo_flags = LO_FLAGS_AUTOCLEAR; + + if (ioctl(loopFd, LOOP_SET_STATUS64, &loopInfo) < 0) + { + AI_LOG_SYS_ERROR(errno, "failed to set the autoclear flag"); + + if (ioctl(loopFd, LOOP_CLR_FD, 0) < 0) + { + AI_LOG_SYS_WARN(errno, "failed to detach from loop device"); + } + + AI_LOG_FN_EXIT(); + return false; + } + + AI_LOG_DEBUG("attached file to loop device"); + + AI_LOG_FN_EXIT(); + return true; +} + + +// ----------------------------------------------------------------------------- +/** + * @brief Associates a give file descriptor with a loop device + * + * First the function attempts to get a free loop device, if that succeeds it + * attaches the supplied file descriptor to it and returns an fd to the loop + * device and (optionally) writes the path to the loop device in the + * @a loopDevPath string. + * + * @param[in] fileFd An open file descriptor to associate with + * the loop device. + * @param[out] loopDevPath If not null, the method will write the path + * to the loop device dev node into the string + * + * @return on success returns the open file descriptor to the loop device + * associated with the file, on failure -1. + */ +int AndroidHelper::loopDeviceAssociate(int fileFd, std::string* loopDevPath /*= nullptr*/) +{ + AI_LOG_FN_ENTRY(); + + int loopDevFd = openLoopDevice(loopDevPath); + if (loopDevFd < 0) + { + AI_LOG_ERROR_EXIT("failed to open loop device"); + return -1; + } + + if (!attachFileToLoopDevice(loopDevFd, fileFd)) + { + AI_LOG_ERROR_EXIT("failed to attach file to loop device"); + close(loopDevFd); + return -1; + } + + AI_LOG_FN_EXIT(); + return loopDevFd; +} + +// ----------------------------------------------------------------------------- +/** + * @brief Attaches the given file to an available loop device + * + * @param[in] sourceFile The path to the image file. + * @param[out] loopDevice The path to the loop device that was attached. + * + * @return the file descriptor to the loop device if attached, otherwise -1. + */ +int AndroidHelper::attachLoopDevice(const std::string& sourceFile, + std::string* loopDevice) +{ + AI_LOG_FN_ENTRY(); + + // check we managed to open the file + int fd = open(sourceFile.c_str(), O_CLOEXEC | O_RDWR); + if (fd < 0) + { + AI_LOG_SYS_ERROR_EXIT(errno, "failed to open file @ '%s'", + sourceFile.c_str()); + return -1; + } + + // associate the fd with a free loop device + int loopDevFd = loopDeviceAssociate(fd, loopDevice); + + // no longer need the backing file, can close + if (close(fd) != 0) + { + AI_LOG_SYS_ERROR(errno, "failed to close file"); + } + + AI_LOG_INFO("Attached sourcefile '%s' to loopdevice '%s' with file description %d", + sourceFile.c_str(), + loopDevice->c_str(), + loopDevFd); + + AI_LOG_FN_EXIT(); + return loopDevFd; +} + +// ------------------------------------------------------------------------- +/** + * @brief Removes a directory and all it's contents. + * + * This is equivalent to the 'rm -rf' command. + * + * If the pathname given in pathname is relative, then it is interpreted + * relative to the directory referred to by the file descriptor dirFd, if + * dirFd is not supplied then it's relative to the cwd. + * + * @warning This function only supports deleting directories with contents + * that are less than 128 levels deep, this is to avoid running out of + * file descriptors. + * + * @param[in] dirFd If specified the path should be relative to + * to this directory. + * @param[in] path The path to the directory to create. + * + * @return true on success, false on failure. + */ +bool AndroidHelper::rmdirRecursive(int dirFd, const std::string& path) +{ + AI_LOG_FN_ENTRY(); + + // remove the directory contents first + bool success = rmdirContents(dirFd, path); + if (success) + { + // then delete the directory itself + if (unlinkat(dirFd, path.c_str(), AT_REMOVEDIR) != 0) + { + AI_LOG_SYS_ERROR(errno, "failed to remove dir at '%s", path.c_str()); + success = false; + } + } + + AI_LOG_FN_EXIT(); + return success; +} + +// ------------------------------------------------------------------------- +/** + * @brief Removes the contents of a directory but leave the actual + * directory in place. + * + * This is equivalent to the 'cd ; rm -rf *' command. + * + * If the pathname given in @a path is relative, then it is interpreted + * relative to the directory referred to by the file descriptor @a dirFd, if + * @a dirFd is not supplied then it's relative to the cwd. + * + * @warning This function only supports deleting directories with contents + * that are less than 128 levels deep, this is to avoid running out of + * file descriptors. + * + * @param[in] dirFd If specified the path should be relative to + * to this directory. + * @param[in] path The path to the directory to create. + * + * @return true on success, false on failure. + */ +bool AndroidHelper::rmdirContents(int dirFd, const std::string& path) +{ + AI_LOG_FN_ENTRY(); + + // get the fd of the directory to delete + int toDeleteFd = openat(dirFd, path.c_str(), O_CLOEXEC | O_DIRECTORY | O_NOFOLLOW); + if (toDeleteFd < 0) + { + AI_LOG_SYS_ERROR_EXIT(errno, "failed to open dir @ '%s'", path.c_str()); + return false; + } + + // recursively walks the directory deleting all the files and directories + // within it, this will also close the file descriptor + bool success = deleteRecursive(toDeleteFd, 128); + + AI_LOG_FN_EXIT(); + return success; +} + +// ----------------------------------------------------------------------------- +/** + * @brief Recursive function that deletes everything within the supplied + * directory (as a descriptor). + * + * @param[in] dirFd If specified the path should be relative to + * to this directory. + * @param[in] availDepth Maximal depth of recursion + * + * @return true on success, false on failure. + * + * + */ +bool AndroidHelper::deleteRecursive(int dirfd, int availDepth) +{ + DIR* dir = fdopendir(dirfd); + if (!dir) + { + AI_LOG_SYS_ERROR(errno, "fdopendir failed"); + + // to maintain consistency we should close the fd in case of failure + if (close(dirfd) != 0) + { + AI_LOG_SYS_ERROR(errno, "failed to close dirfd"); + } + + return false; + } + + bool success = true; + + struct dirent* entry; + while ((entry = readdir(dir)) != nullptr) + { + // skip the '.' and '..' entries + if ((entry->d_name[0] == '.') && ((entry->d_name[1] == '\0') || + ((entry->d_name[1] == '.') && (entry->d_name[2] == '\0')))) + { + continue; + } + + // if a directory then recurse into it + if (entry->d_type == DT_DIR) + { + // check we're not going to deep + if (--availDepth <= 0) + { + AI_LOG_ERROR("recursing to deep, aborting"); + success = false; + break; + } + + // try and open the dir + int fd = openat(dirfd, entry->d_name, O_CLOEXEC | O_DIRECTORY | O_NOFOLLOW); + if (fd < 0) + { + AI_LOG_SYS_ERROR(errno, "failed to open directory '%s'", + entry->d_name); + success = false; + break; + } + else + { + // recurse into the directory deleting it's contents, the + // function assumes ownership of the fd and will free (closedir) + if (!deleteRecursive(fd, availDepth)) + { + success = false; + break; + } + } + } + + int flags = (entry->d_type == DT_DIR) ? AT_REMOVEDIR : 0; + + // try unlinking the file / directory / symlink / whatever + if (unlinkat(dirfd, entry->d_name, flags) != 0) + { + AI_LOG_SYS_ERROR(errno, "failed to remove '%s'", entry->d_name); + success = false; + break; + } + } + + closedir(dir); + + return success; +} + +// Tests Storage helpers +#ifdef ENABLE_TESTS +// cppcheck-suppress unusedFunction +bool AndroidHelper::Test_mkdirRecursive(const std::string& rootfsPath) +{ + std::string tmp = "/home/private/"; + tmp = rootfsPath + tmp; + + tmp.append(".temp"); + + AI_LOG_INFO("temp path = '%s'", tmp.c_str()); + + // step 1 - create a directory within the rootfs + if (DobbyRdkPluginUtils::mkdirRecursive(tmp, 0700)) + { + AI_LOG_INFO("Success"); + return true; + } + else + { + AI_LOG_INFO("Fail"); + return false; + } +} + +// cppcheck-suppress unusedFunction +bool AndroidHelper::Test_openLoopDevice() +{ + std::string loopDevPath; + + int loopDevFd = openLoopDevice(&loopDevPath); + if (loopDevFd < 0) + { + AI_LOG_ERROR_EXIT("failed to open loop device"); + return false; + } + else + { + AI_LOG_INFO("Opened loop mount =%s", loopDevPath.c_str()); + } + + if (close(loopDevFd) != 0) + { + AI_LOG_SYS_ERROR(errno, "failed to close file"); + return false; + } + return true; +} + +// cppcheck-suppress unusedFunction +bool AndroidHelper::Test_attachLoopDevice(std::string& imagePath) +{ + std::string loopDevice; + + createFileIfNeeded(imagePath, 1024*10*12, 123, "ext4"); + + int loopDevFd = attachLoopDevice(imagePath, &loopDevice); + if ((loopDevFd < 0) || (loopDevice.empty())) + { + AI_LOG_ERROR("failed to attach file to loop device"); + return false; + } + else if (close(loopDevFd) != 0) + { + AI_LOG_SYS_ERROR(errno, "failed to close file"); + return false; + } + else + { + AI_LOG_INFO("Successfully attached loop device =%s", loopDevice.c_str()); + } + + return true; +} + +// cppcheck-suppress unusedFunction +bool AndroidHelper::Test_cleanMountLostAndFound(const std::string& rootfsPath) +{ + std::string tmp = "/lost+found/some/long/path/file.xyz"; + tmp = rootfsPath + tmp; + + createFileIfNeeded(tmp, 1024*12*12, 123, "ext4"); + + cleanMountLostAndFound(rootfsPath, std::string("0")); +} + +// cppcheck-suppress unusedFunction +bool AndroidHelper::Test_checkWriteReadMount(const std::string& tmpPath) +{ + //Test + ssize_t nrd; + const char text[] = "Storage was runned\n"; + const unsigned int BUFFER_SIZE = 100; + + //std::string tmpPath = "/home/private/test.txt"; + AI_LOG_INFO("path = '%s'", tmpPath.c_str()); + + int fd = open(tmpPath.c_str(), O_RDWR | O_CREAT | O_APPEND, 0777); + if (fd < 0) + AI_LOG_SYS_ERROR(errno, "failed to open"); + else + { + AI_LOG_INFO("write fd = %d", fd); + + nrd = write(fd,text, sizeof(text)); + AI_LOG_INFO("write nrd = %d", nrd); + close(fd); + } + + fd = open(tmpPath.c_str(), O_RDONLY, 0777); + if (fd < 0) + AI_LOG_SYS_ERROR(errno, "failed to open"); + else + { + char buffer[BUFFER_SIZE] = ""; + nrd = read(fd,buffer,BUFFER_SIZE); + if (nrd > 0) { + AI_LOG_INFO("Test file content '%s'", buffer); + } + + AI_LOG_INFO("read nrd = %d", nrd); + close(fd); + } +} +#endif // ENABLE_TESTS diff --git a/rdkPlugins/AndroidRuntime/source/AndroidHelper.h b/rdkPlugins/AndroidRuntime/source/AndroidHelper.h new file mode 100644 index 00000000..f743e1de --- /dev/null +++ b/rdkPlugins/AndroidRuntime/source/AndroidHelper.h @@ -0,0 +1,111 @@ +/* +* If not stated otherwise in this file or this component's LICENSE file the +* following copyright and licenses apply: +* +* Copyright 2024 Sky UK +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +/* + * File: AnroidHelper.h + * + */ +#ifndef ANDROID_HELPER_H +#define ANDROID_HELPER_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +//#define ENABLE_TESTS 1 + +/** + * @brief Help functions for Storage related things + * + * All low level help functions that doesn't rely on current state + */ +class AndroidHelper +{ +public: + static int loopDeviceAssociate(int fileFd, std::string* loopDevPath); + static int openLoopDevice(std::string* loopDevice); + static bool attachFileToLoopDevice(int loopFd, int fileFd); + static int attachLoopDevice(const std::string& sourceFile, + std::string* loopDevice); + static int getMountOptions(const std::list &mountOptions); + + // ------------------------------------------------------------------------- + /** + * @brief Removes a directory and all it's contents. + * + * This is equivalent to the 'rm -rf ' command. + * + * If the pathname given in pathname is relative, then it is interpreted + * relative to the directory referred to by the file descriptor dirFd, if + * dirFd is not supplied then it's relative to the cwd. + * + * @warning This function only supports deleting directories with contents + * that are less than 128 levels deep, this is to avoid running out of + * file descriptors. + * + * @param[in] dirFd If specified the path should be relative to + * to this directory. + * @param[in] path The path to the directory to create. + * + * @return true on success, false on failure. + */ + static bool rmdirRecursive(int dirFd, const std::string& path); + + // ------------------------------------------------------------------------- + /** + * @brief Removes the contents of a directory but leave the actual + * directory in place. + * + * This is equivalent to the 'rm -rf / *' command. + * + * If the pathname given in pathname is relative, then it is interpreted + * relative to the directory referred to by the file descriptor dirFd, if + * dirFd is not supplied then it's relative to the cwd. + * + * @warning This function only supports deleting directories with contents + * that are less than 128 levels deep, this is to avoid running out of + * file descriptors. + * + * @param[in] dirFd If specified the path should be relative to + * to this directory. + * @param[in] path The path to the directory to create. + * + * @return true on success, false on failure. + */ + static bool rmdirContents(int dirFd, const std::string& path); + static bool deleteRecursive(int dirfd, int depth); + + // Tests +#ifdef ENABLE_TESTS + static bool Test_mkdirRecursive(const std::string& rootfsPath); + static bool Test_openLoopDevice(); + static bool Test_attachLoopDevice(std::string& imagePath); + static bool Test_cleanMountLostAndFound(const std::string& rootfsPath); + static bool Test_checkWriteReadMount(const std::string& tmpPath); +#endif // ENABLE_TESTS + +}; + +#endif // !defined(ANDROID_HELPER_H) diff --git a/rdkPlugins/AndroidRuntime/source/AndroidRuntimePlugin.cpp b/rdkPlugins/AndroidRuntime/source/AndroidRuntimePlugin.cpp new file mode 100644 index 00000000..c88e71f3 --- /dev/null +++ b/rdkPlugins/AndroidRuntime/source/AndroidRuntimePlugin.cpp @@ -0,0 +1,380 @@ +/* +* If not stated otherwise in this file or this component's LICENSE file the +* following copyright and licenses apply: +* +* Copyright 2024 Sky UK +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or imied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "AndroidRuntimePlugin.h" +#include "AndroidHelper.h" +#include "rt_defs_plugins.h" + +#include + +#include +#include +#include +#include +#include +#include + +REGISTER_RDK_PLUGIN(AndroidRuntimePlugin); + +AndroidRuntimePlugin::AndroidRuntimePlugin(std::shared_ptr &containerConfig, + const std::shared_ptr &utils, + const std::string &rootfsPath) + : mName("AndroidRuntime"), + mRootfsPath(rootfsPath), + mContainerConfig(containerConfig), + mUtils(utils) +{ + AI_LOG_FN_ENTRY(); + + if (mContainerConfig == nullptr || + mContainerConfig->rdk_plugins->androidruntime == nullptr || + mContainerConfig->rdk_plugins->androidruntime->data == nullptr) + { + AI_LOG_ERROR("No AndroidRuntime configuration provided"); + return; + } + + const rt_defs_plugins_android_runtime_data *pluginData = mContainerConfig->rdk_plugins->androidruntime->data; + + if(pluginData->system_path == nullptr) + { + AI_LOG_ERROR("No path to system.img provided"); + return; + } + mSystemPath = std::string(pluginData->system_path); + AI_LOG_INFO("Android system.img path is %s\n", mSystemPath.c_str()); + + if(pluginData->vendor_path == nullptr) + { + AI_LOG_ERROR("No path to vendor.img provided"); + return; + } + mVendorPath = std::string(pluginData->vendor_path); + AI_LOG_INFO("Android vendor.img path is %s\n", mVendorPath.c_str()); + + if(pluginData->data_path == nullptr) + { + AI_LOG_ERROR("No path to data directory provided"); + return; + } + mDataPath = std::string(pluginData->data_path); + AI_LOG_INFO("Android userdata path is %s\n", mDataPath.c_str()); + + if(pluginData->cache_path == nullptr) + { + AI_LOG_ERROR("No path to cache directory provided"); + return; + } + mCachePath = std::string(pluginData->cache_path); + AI_LOG_INFO("Android cache path is %s\n", mCachePath.c_str()); + + if(pluginData->cmdline_path == nullptr) + { + AI_LOG_ERROR("No path to kernel commandline file provided"); + return; + } + mCmdlinePath = std::string(pluginData->cmdline_path); + AI_LOG_INFO("Android cmdline path is %s\n", mCmdlinePath.c_str()); + + mValid = true; + AI_LOG_INFO("Started Android runtime plugin"); + AI_LOG_FN_EXIT(); +} + +unsigned AndroidRuntimePlugin::hookHints() const +{ + return IDobbyRdkPlugin::HintFlags::PostInstallationFlag | + IDobbyRdkPlugin::HintFlags::PostHaltFlag; +} + +// ----------------------------------------------------------------------------- +/** + * @brief postInstalllation OCI hook. + * + * If set_tz parameter is set then its value should be a path to file. + * Read this file and put its contents into containers TZ env var. + * + * @return true on success, false on failure. + */ +bool AndroidRuntimePlugin::postInstallation() +{ + AI_LOG_FN_ENTRY(); + + if (!mValid) + { + AI_LOG_ERROR("Configuration not valid - not mounting"); + return false; + } + + doMounts(); + + std::string etcPath = mRootfsPath + "etc"; + struct stat s; + + // There may be a symlink from /etc to /system/etc in the container, remove it if present + // so that the Networking plugin can populate /etc + if (lstat(etcPath.c_str(), &s) == 0) + { + if (S_ISLNK(s.st_mode)) + { + if(unlink(etcPath.c_str()) < 0) + { + AI_LOG_SYS_ERROR_EXIT(errno, "Failed to remove %s symlink", etcPath.c_str()); + return false; + } + else + { + AI_LOG_INFO("Removed symlink %s", etcPath.c_str()); + } + } + } + else + { + AI_LOG_INFO("%s cannot be statted", etcPath.c_str()); + } + + if (mUtils->mkdirRecursive(mRootfsPath + "/etc", 0755)) + { + AI_LOG_INFO("Ensured /etc exists in rootfs"); + } + + AI_LOG_FN_EXIT(); + return true; +} + +// ----------------------------------------------------------------------------- +/** + * @brief postHalt OCI hook. + * + * + * @return true on success, false on failure. + */ +bool AndroidRuntimePlugin::postHalt() +{ + AI_LOG_FN_ENTRY(); + + doUnmounts(); + + AI_LOG_FN_EXIT(); + return true; +} + + +/** + * @brief Should return the names of the plugins this plugin depends on. + * + * This can be used to determine the order in which the plugins should be + * processed when running hooks. + * + * @return Names of the plugins this plugin depends on. + */ +std::vector AndroidRuntimePlugin::getDependencies() const +{ + std::vector dependencies; + + return dependencies; +} + +bool AndroidRuntimePlugin::doLoopMount(const std::string &src, const std::string &dest) +{ + AI_LOG_FN_ENTRY(); + int fdSrc = open(src.c_str(), O_RDWR); + + if (fdSrc < 0) + { + AI_LOG_SYS_ERROR_EXIT(errno, "Failed to open source file %s", src.c_str()); + return false; + } + + if (!mUtils->mkdirRecursive(dest, 0755)) + { + AI_LOG_ERROR_EXIT("Failed to create loop mount destination directory %s", dest.c_str()); + return false; + } + + std::string deviceLoop; + int fdLoop = AndroidHelper::openLoopDevice(&deviceLoop); + if (fdLoop < 0) + { + close(fdSrc); + AI_LOG_SYS_ERROR_EXIT(errno, "Failed to open free loop device"); + return false; + } + + int fdAttached = AndroidHelper::attachFileToLoopDevice(fdLoop, fdSrc); + if (fdAttached < 0) + { + close(fdSrc); + close(fdLoop); + AI_LOG_SYS_ERROR_EXIT(errno, "Failed to open free loop device"); + return false; + } + + if (mount(deviceLoop.c_str(), dest.c_str(), "ext4", 0, NULL) < 0) + { + close(fdAttached); + close(fdSrc); + close(fdLoop); + AI_LOG_SYS_ERROR_EXIT(errno, "Failed to mount system.img"); + return false; + } + + close(fdLoop); // fd is associated to mount now, so close this one so loop device will auto-free when unmounted + + AI_LOG_FN_EXIT(); + return true; +} + +bool AndroidRuntimePlugin::doBindMount(const std::string &src, const std::string &dest) +{ + AI_LOG_FN_ENTRY(); + if (access(src.c_str(), F_OK) != 0) + { + AI_LOG_SYS_ERROR_EXIT(errno, "Failed to open source file %s", src.c_str()); + return false; + } + + if (!mUtils->mkdirRecursive(dest, 0755)) + { + AI_LOG_ERROR_EXIT("Failed to create bind mount destination directory %s", dest.c_str()); + return false; + } + + if (mount(src.c_str(), dest.c_str(), NULL, MS_BIND, nullptr) < 0) + { + AI_LOG_SYS_ERROR_EXIT(errno, "Mount failed %s->%s", src.c_str(), dest.c_str()); + return false; + } + + return true; +} + +bool AndroidRuntimePlugin::doBindFile(const std::string &src, const std::string &dest) +{ + AI_LOG_FN_ENTRY(); + if (access(src.c_str(), F_OK) != 0) + { + AI_LOG_SYS_ERROR_EXIT(errno, "Failed to open source file %s", src.c_str()); + return false; + } + + int fdDest = open(dest.c_str(), O_RDWR | O_CREAT); + if (fdDest < 0) + { + AI_LOG_SYS_ERROR_EXIT(errno, "Failed to open destination file for bind %s", dest.c_str()); + return false; + } + close(fdDest); + + if (mount(src.c_str(), dest.c_str(), NULL, MS_BIND, nullptr) < 0) + { + AI_LOG_SYS_ERROR_EXIT(errno, "Bind mount file failed %s->%s", src.c_str(), dest.c_str()); + return false; + } + + AI_LOG_FN_EXIT(); + return true; +} + +#define MOUNT_VENDOR "/vendor" +#define MOUNT_DATA "/data" +#define MOUNT_CACHE "/cache" +#define MOUNT_CMDLINE "/cmdline" + +bool AndroidRuntimePlugin::doMounts() +{ + AI_LOG_FN_ENTRY(); + + if(!mMounted.empty()) + { + AI_LOG_ERROR("Attempt to mount again before unmounting previous"); + return false; + } + + std::string dest; + + dest = mRootfsPath; + if (!doLoopMount(mSystemPath.c_str(), dest)) + { + AI_LOG_ERROR_EXIT("Failed to loop mount %s", dest.c_str()); + return false; + } + mMounted.push_back(dest.c_str()); + + dest = mRootfsPath + MOUNT_VENDOR; + if (!doLoopMount(mVendorPath.c_str(), dest)) + { + AI_LOG_ERROR_EXIT("Failed to loop mount %s", mVendorPath.c_str()); + return false; + } + mMounted.push_back(dest.c_str()); + + dest = mRootfsPath + MOUNT_DATA; + if(!doBindMount(mDataPath, dest)) + { + AI_LOG_ERROR_EXIT("Failed to bind mount %s->%s", mDataPath.c_str(), dest.c_str()); + return false; + } + mMounted.push_back(dest.c_str()); + + dest = mRootfsPath + MOUNT_DATA + MOUNT_CACHE; + if(!doBindMount(mCachePath, dest)) + { + AI_LOG_ERROR_EXIT("Failed to bind mount %s->%s", mCachePath.c_str(), dest.c_str()); + return false; + } + mMounted.push_back(dest.c_str()); + + dest = mRootfsPath + MOUNT_CMDLINE; + if(!doBindFile(mCmdlinePath, dest)) + { + AI_LOG_ERROR_EXIT("Failed to bind file %s->%s", mCmdlinePath.c_str(), dest.c_str()); + return false; + } + mMounted.push_back(dest.c_str()); + + for(std::string s : mMounted) + { + AI_LOG_INFO("Mounted %s", s.c_str()); + } + AI_LOG_FN_EXIT(); + return true; +} + +bool AndroidRuntimePlugin::doUnmounts() +{ + AI_LOG_FN_ENTRY(); + + AI_LOG_INFO("doUnmount complete"); + for(std::vector::reverse_iterator it = mMounted.rbegin(); it != mMounted.rend(); it++) + { + AI_LOG_INFO("Unmounting %s", it->c_str()); + if(umount2(it->c_str(), UMOUNT_NOFOLLOW) < 0) + { + AI_LOG_SYS_ERROR(errno, "Failed to unmount %s", it->c_str()); + } + } + mMounted.clear(); + + AI_LOG_INFO("doUnmount complete"); + + AI_LOG_FN_EXIT(); + + return true; +} diff --git a/rdkPlugins/AndroidRuntime/source/AndroidRuntimePlugin.h b/rdkPlugins/AndroidRuntime/source/AndroidRuntimePlugin.h new file mode 100644 index 00000000..a1578177 --- /dev/null +++ b/rdkPlugins/AndroidRuntime/source/AndroidRuntimePlugin.h @@ -0,0 +1,80 @@ +/* +* If not stated otherwise in this file or this component's LICENSE file the +* following copyright and licenses apply: +* +* Copyright 2024 Sky UK +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef ANDROIDRUNTIMEPLUGIN_H +#define ANDROIDRUNTIMEPLUGIN_H + +#include +#include + +/** + * @brief Dobby AndroidRuntime plugin. + * + * This plugin mounts and Android system into the container: + * - system.img + * - vendor.img + * - data and cache directory + * - kernel command line + * + */ +class AndroidRuntimePlugin : public RdkPluginBase +{ +public: + AndroidRuntimePlugin(std::shared_ptr &containerConfig, + const std::shared_ptr &utils, + const std::string &rootfsPath); + +public: + inline std::string name() const override + { + return mName; + }; + + unsigned hookHints() const override; + +public: + bool postInstallation() override; + bool postHalt() override; + +public: + std::vector getDependencies() const override; + +private: + bool doMounts(); + bool doLoopMount(const std::string &src, const std::string &dest); + bool doBindMount(const std::string &src, const std::string &dest); + bool doBindFile(const std::string &src, const std::string &dest); + bool doUnmounts(); + const std::string mName; + const std::string mRootfsPath; + std::shared_ptr mContainerConfig; + const std::shared_ptr mUtils; + + bool mValid = false; + + std::string mSystemPath; + std::string mVendorPath; + std::string mDataPath; + std::string mCachePath; + std::string mCmdlinePath; + std::string mApkPath; + std::vector mMounted; +}; + +#endif // !defined(ANDROIDRUNTIMEPLUGIN_H) From 835afe89e87528a82730e42c50e01c06d79fd5f4 Mon Sep 17 00:00:00 2001 From: Jon Henderson Date: Tue, 1 Oct 2024 14:08:12 +0100 Subject: [PATCH 5/5] Q3 Update Support single compressed system.img (sqfs or erofs) Remove special kernel commandline handling --- bundle/runtime-schemas/defs-plugins.json | 3 - rdkPlugins/AndroidRuntime/CMakeLists.txt | 1 + .../AndroidRuntime/source/AndroidHelper.cpp | 7 +- .../source/AndroidRuntimePlugin.cpp | 151 ++++++++++-------- .../source/AndroidRuntimePlugin.h | 3 + 5 files changed, 91 insertions(+), 74 deletions(-) diff --git a/bundle/runtime-schemas/defs-plugins.json b/bundle/runtime-schemas/defs-plugins.json index d04f5b0e..cf97cc72 100644 --- a/bundle/runtime-schemas/defs-plugins.json +++ b/bundle/runtime-schemas/defs-plugins.json @@ -680,9 +680,6 @@ }, "cachePath": { "type": "string" - }, - "cmdlinePath": { - "type": "string" } } } diff --git a/rdkPlugins/AndroidRuntime/CMakeLists.txt b/rdkPlugins/AndroidRuntime/CMakeLists.txt index d30e53f5..71a6c174 100644 --- a/rdkPlugins/AndroidRuntime/CMakeLists.txt +++ b/rdkPlugins/AndroidRuntime/CMakeLists.txt @@ -20,6 +20,7 @@ # Project name should match the name of the plugin # CMake will prefix this with "lib" project(AndroidRuntimePlugin) +set(CMAKE_CXX_STANDARD 17) add_library( ${PROJECT_NAME} SHARED diff --git a/rdkPlugins/AndroidRuntime/source/AndroidHelper.cpp b/rdkPlugins/AndroidRuntime/source/AndroidHelper.cpp index c85e0137..654af46b 100644 --- a/rdkPlugins/AndroidRuntime/source/AndroidHelper.cpp +++ b/rdkPlugins/AndroidRuntime/source/AndroidHelper.cpp @@ -18,22 +18,17 @@ */ #include "AndroidHelper.h" -#include "DobbyRdkPluginUtils.h" #include #include #include +#include #include -#include -#include #include #include #include #include -#include -#include #include -#include #include #if defined(__linux__) diff --git a/rdkPlugins/AndroidRuntime/source/AndroidRuntimePlugin.cpp b/rdkPlugins/AndroidRuntime/source/AndroidRuntimePlugin.cpp index c88e71f3..bbd12683 100644 --- a/rdkPlugins/AndroidRuntime/source/AndroidRuntimePlugin.cpp +++ b/rdkPlugins/AndroidRuntime/source/AndroidRuntimePlugin.cpp @@ -30,6 +30,10 @@ #include #include +#include +#include +namespace fs = std::filesystem; + REGISTER_RDK_PLUGIN(AndroidRuntimePlugin); AndroidRuntimePlugin::AndroidRuntimePlugin(std::shared_ptr &containerConfig, @@ -62,11 +66,13 @@ AndroidRuntimePlugin::AndroidRuntimePlugin(std::shared_ptr &con if(pluginData->vendor_path == nullptr) { - AI_LOG_ERROR("No path to vendor.img provided"); - return; + AI_LOG_WARN("No path to vendor.img provided - continuing with single image"); + } + else + { + mVendorPath = std::string(pluginData->vendor_path); + AI_LOG_INFO("Android vendor.img path is %s\n", mVendorPath.c_str()); } - mVendorPath = std::string(pluginData->vendor_path); - AI_LOG_INFO("Android vendor.img path is %s\n", mVendorPath.c_str()); if(pluginData->data_path == nullptr) { @@ -84,14 +90,6 @@ AndroidRuntimePlugin::AndroidRuntimePlugin(std::shared_ptr &con mCachePath = std::string(pluginData->cache_path); AI_LOG_INFO("Android cache path is %s\n", mCachePath.c_str()); - if(pluginData->cmdline_path == nullptr) - { - AI_LOG_ERROR("No path to kernel commandline file provided"); - return; - } - mCmdlinePath = std::string(pluginData->cmdline_path); - AI_LOG_INFO("Android cmdline path is %s\n", mCmdlinePath.c_str()); - mValid = true; AI_LOG_INFO("Started Android runtime plugin"); AI_LOG_FN_EXIT(); @@ -124,35 +122,6 @@ bool AndroidRuntimePlugin::postInstallation() doMounts(); - std::string etcPath = mRootfsPath + "etc"; - struct stat s; - - // There may be a symlink from /etc to /system/etc in the container, remove it if present - // so that the Networking plugin can populate /etc - if (lstat(etcPath.c_str(), &s) == 0) - { - if (S_ISLNK(s.st_mode)) - { - if(unlink(etcPath.c_str()) < 0) - { - AI_LOG_SYS_ERROR_EXIT(errno, "Failed to remove %s symlink", etcPath.c_str()); - return false; - } - else - { - AI_LOG_INFO("Removed symlink %s", etcPath.c_str()); - } - } - } - else - { - AI_LOG_INFO("%s cannot be statted", etcPath.c_str()); - } - - if (mUtils->mkdirRecursive(mRootfsPath + "/etc", 0755)) - { - AI_LOG_INFO("Ensured /etc exists in rootfs"); - } AI_LOG_FN_EXIT(); return true; @@ -226,7 +195,26 @@ bool AndroidRuntimePlugin::doLoopMount(const std::string &src, const std::string return false; } - if (mount(deviceLoop.c_str(), dest.c_str(), "ext4", 0, NULL) < 0) + std::vector fstypes + { + "erofs", + "ext4", + "squashfs" + }; + + bool mounted = false; + for(auto type : fstypes) + { + if (mount(deviceLoop.c_str(), dest.c_str(), type.c_str(), 0, NULL) == 0) + { + mRootFsType = type; + AI_LOG_INFO("Mounted %s as %s", dest.c_str(), mRootFsType.c_str()); + mounted = true; + break; + } + } + + if(!mounted) { close(fdAttached); close(fdSrc); @@ -246,8 +234,12 @@ bool AndroidRuntimePlugin::doBindMount(const std::string &src, const std::string AI_LOG_FN_ENTRY(); if (access(src.c_str(), F_OK) != 0) { - AI_LOG_SYS_ERROR_EXIT(errno, "Failed to open source file %s", src.c_str()); - return false; + AI_LOG_INFO("Creating source location for bind mount %s", src.c_str()); + if (!mUtils->mkdirRecursive(src, 0755)) + { + AI_LOG_ERROR_EXIT("Failed to create bind mount source directory %s", dest.c_str()); + return false; + } } if (!mUtils->mkdirRecursive(dest, 0755)) @@ -274,7 +266,7 @@ bool AndroidRuntimePlugin::doBindFile(const std::string &src, const std::string return false; } - int fdDest = open(dest.c_str(), O_RDWR | O_CREAT); + int fdDest = open(dest.c_str(), O_RDONLY | O_CREAT); if (fdDest < 0) { AI_LOG_SYS_ERROR_EXIT(errno, "Failed to open destination file for bind %s", dest.c_str()); @@ -288,20 +280,35 @@ bool AndroidRuntimePlugin::doBindFile(const std::string &src, const std::string return false; } + AI_LOG_INFO("Bind-mounted file %s to %s", src.c_str(), dest.c_str()); + AI_LOG_FN_EXIT(); return true; } -#define MOUNT_VENDOR "/vendor" -#define MOUNT_DATA "/data" -#define MOUNT_CACHE "/cache" -#define MOUNT_CMDLINE "/cmdline" +bool AndroidRuntimePlugin::doTmpfsMount(const std::string &dest) +{ + AI_LOG_FN_ENTRY(); + + if (mount("tmpfs", dest.c_str(), "tmpfs", 0, nullptr) < 0) + { + AI_LOG_SYS_ERROR_EXIT(errno, "tmpfs mount failed %s", dest.c_str()); + return false; + } + + return true; +} + +#define MOUNT_VENDOR "vendor" +#define MOUNT_DATA "data" +#define MOUNT_CACHE "cache" +#define MOUNT_RESOLV_CONF "resolv.conf" bool AndroidRuntimePlugin::doMounts() { AI_LOG_FN_ENTRY(); - if(!mMounted.empty()) + if (!mMounted.empty()) { AI_LOG_ERROR("Attempt to mount again before unmounting previous"); return false; @@ -317,39 +324,53 @@ bool AndroidRuntimePlugin::doMounts() } mMounted.push_back(dest.c_str()); - dest = mRootfsPath + MOUNT_VENDOR; - if (!doLoopMount(mVendorPath.c_str(), dest)) + if (!mVendorPath.empty()) { - AI_LOG_ERROR_EXIT("Failed to loop mount %s", mVendorPath.c_str()); - return false; + dest = mRootfsPath + MOUNT_VENDOR; + if (!doLoopMount(mVendorPath.c_str(), dest)) + { + AI_LOG_ERROR_EXIT("Failed to loop mount %s", mVendorPath.c_str()); + return false; + } + mMounted.push_back(dest.c_str()); } - mMounted.push_back(dest.c_str()); dest = mRootfsPath + MOUNT_DATA; - if(!doBindMount(mDataPath, dest)) + if (!doBindMount(mDataPath, dest)) { AI_LOG_ERROR_EXIT("Failed to bind mount %s->%s", mDataPath.c_str(), dest.c_str()); return false; } mMounted.push_back(dest.c_str()); - dest = mRootfsPath + MOUNT_DATA + MOUNT_CACHE; - if(!doBindMount(mCachePath, dest)) + dest = mRootfsPath + MOUNT_DATA + "/" + MOUNT_CACHE; + if (!doBindMount(mCachePath, dest)) { AI_LOG_ERROR_EXIT("Failed to bind mount %s->%s", mCachePath.c_str(), dest.c_str()); return false; } mMounted.push_back(dest.c_str()); - dest = mRootfsPath + MOUNT_CMDLINE; - if(!doBindFile(mCmdlinePath, dest)) + dest = mRootfsPath + "system/etc/" + MOUNT_RESOLV_CONF; + std::string src = mRootfsPath + "../" + MOUNT_RESOLV_CONF; + if (!fs::exists(src)) { + std::error_code ec; + fs::copy(dest, src, ec); + if (ec.value() ) { + AI_LOG_ERROR_EXIT("Error(%d) - %s for binding %s->%s", + ec.value(), ec.message().c_str(), src.c_str(), dest.c_str()); + return false; + } + } + + if (!doBindFile(src, dest)) { - AI_LOG_ERROR_EXIT("Failed to bind file %s->%s", mCmdlinePath.c_str(), dest.c_str()); + AI_LOG_ERROR_EXIT("Failed to bind file %s->%s", src.c_str(), dest.c_str()); return false; } mMounted.push_back(dest.c_str()); - for(std::string s : mMounted) + for (std::string s : mMounted) { AI_LOG_INFO("Mounted %s", s.c_str()); } @@ -361,11 +382,11 @@ bool AndroidRuntimePlugin::doUnmounts() { AI_LOG_FN_ENTRY(); - AI_LOG_INFO("doUnmount complete"); - for(std::vector::reverse_iterator it = mMounted.rbegin(); it != mMounted.rend(); it++) + AI_LOG_INFO("doUnmount start"); + for (std::vector::reverse_iterator it = mMounted.rbegin(); it != mMounted.rend(); it++) { AI_LOG_INFO("Unmounting %s", it->c_str()); - if(umount2(it->c_str(), UMOUNT_NOFOLLOW) < 0) + if (umount2(it->c_str(), UMOUNT_NOFOLLOW) < 0) { AI_LOG_SYS_ERROR(errno, "Failed to unmount %s", it->c_str()); } diff --git a/rdkPlugins/AndroidRuntime/source/AndroidRuntimePlugin.h b/rdkPlugins/AndroidRuntime/source/AndroidRuntimePlugin.h index a1578177..a862df0a 100644 --- a/rdkPlugins/AndroidRuntime/source/AndroidRuntimePlugin.h +++ b/rdkPlugins/AndroidRuntime/source/AndroidRuntimePlugin.h @@ -60,6 +60,7 @@ class AndroidRuntimePlugin : public RdkPluginBase bool doLoopMount(const std::string &src, const std::string &dest); bool doBindMount(const std::string &src, const std::string &dest); bool doBindFile(const std::string &src, const std::string &dest); + bool doTmpfsMount(const std::string &dest); bool doUnmounts(); const std::string mName; const std::string mRootfsPath; @@ -68,6 +69,8 @@ class AndroidRuntimePlugin : public RdkPluginBase bool mValid = false; + std::string mRootFsType; + std::string mSystemPath; std::string mVendorPath; std::string mDataPath;