diff --git a/linker/linker_config.cpp b/linker/linker_config.cpp index 1af5da801..60b7ad92a 100644 --- a/linker/linker_config.cpp +++ b/linker/linker_config.cpp @@ -50,7 +50,8 @@ class ConfigParser { public: enum { - kProperty, + kPropertyAssign, + kPropertyAppend, kSection, kEndOfFile, kError, @@ -61,7 +62,8 @@ class ConfigParser { /* * Possible return values - * kProperty: name is set to property name and value is set to property value + * kPropertyAssign: name is set to property name and value is set to property value + * kPropertyAppend: same as kPropertyAssign, but the value should be appended * kSection: name is set to section name. * kEndOfFile: reached end of file. * kError: error_msg is set. @@ -81,17 +83,24 @@ class ConfigParser { return kSection; } - found = line.find('='); - if (found == std::string::npos) { - *error_msg = std::string("invalid format: ") + - line + - ", expected \"name = property\" or \"[section]\""; - return kError; + size_t found_assign = line.find('='); + size_t found_append = line.find("+="); + if (found_assign != std::string::npos && found_append == std::string::npos) { + *name = android::base::Trim(line.substr(0, found_assign)); + *value = android::base::Trim(line.substr(found_assign + 1)); + return kPropertyAssign; } - *name = android::base::Trim(line.substr(0, found)); - *value = android::base::Trim(line.substr(found + 1)); - return kProperty; + if (found_append != std::string::npos) { + *name = android::base::Trim(line.substr(0, found_append)); + *value = android::base::Trim(line.substr(found_append + 2)); + return kPropertyAppend; + } + + *error_msg = std::string("invalid format: ") + + line + + ", expected \"name = property\", \"name += property\", or \"[section]\""; + return kError; } // to avoid infinite cycles when programmer makes a mistake @@ -142,6 +151,14 @@ class PropertyValue { return value_; } + void append_value(std::string&& value) { + value_ = value_ + value; + // lineno isn't updated as we might have cases like this: + // property.x = blah + // property.y = blah + // property.x += blah + } + size_t lineno() const { return lineno_; } @@ -195,7 +212,7 @@ static bool parse_config_file(const char* ld_config_file_path, return false; } - if (result == ConfigParser::kProperty) { + if (result == ConfigParser::kPropertyAssign) { if (!android::base::StartsWith(name, "dir.")) { DL_WARN("error parsing %s:%zd: unexpected property name \"%s\", " "expected format dir. (ignoring this line)", @@ -256,7 +273,7 @@ static bool parse_config_file(const char* ld_config_file_path, break; } - if (result == ConfigParser::kProperty) { + if (result == ConfigParser::kPropertyAssign) { if (properties->find(name) != properties->end()) { DL_WARN("%s:%zd: warning: property \"%s\" redefinition", ld_config_file_path, @@ -265,6 +282,29 @@ static bool parse_config_file(const char* ld_config_file_path, } (*properties)[name] = PropertyValue(std::move(value), cp.lineno()); + } else if (result == ConfigParser::kPropertyAppend) { + if (properties->find(name) == properties->end()) { + DL_WARN("%s:%zd: warning: appending to property \"%s\" which isn't defined", + ld_config_file_path, + cp.lineno(), + name.c_str()); + (*properties)[name] = PropertyValue(std::move(value), cp.lineno()); + } else { + if (android::base::EndsWith(name, ".links") || + android::base::EndsWith(name, ".namespaces")) { + value = "," + value; + (*properties)[name].append_value(std::move(value)); + } else if (android::base::EndsWith(name, ".paths") || + android::base::EndsWith(name, ".shared_libs")) { + value = ":" + value; + (*properties)[name].append_value(std::move(value)); + } else { + DL_WARN("%s:%zd: warning: += isn't allowed to property \"%s\". Ignoring.", + ld_config_file_path, + cp.lineno(), + name.c_str()); + } + } } if (result == ConfigParser::kError) { diff --git a/linker/tests/linker_config_test.cpp b/linker/tests/linker_config_test.cpp index c6fade913..4c0dcddca 100644 --- a/linker/tests/linker_config_test.cpp +++ b/linker/tests/linker_config_test.cpp @@ -56,19 +56,31 @@ static const char* config_str = "\n" "enable.target.sdk.version = true\n" "additional.namespaces=system\n" + "additional.namespaces+=vndk\n" "namespace.default.isolated = true\n" "namespace.default.search.paths = /vendor/${LIB}\n" "namespace.default.permitted.paths = /vendor/${LIB}\n" - "namespace.default.asan.search.paths = /data:/vendor/${LIB}\n" + "namespace.default.asan.search.paths = /data\n" + "namespace.default.asan.search.paths += /vendor/${LIB}\n" "namespace.default.asan.permitted.paths = /data:/vendor\n" "namespace.default.links = system\n" - "namespace.default.link.system.shared_libs = libc.so:libm.so:libdl.so:libstdc++.so\n" + "namespace.default.links += vndk\n" + // irregular whitespaces are added intentionally for testing purpose + "namespace.default.link.system.shared_libs= libc.so\n" + "namespace.default.link.system.shared_libs += libm.so:libdl.so\n" + "namespace.default.link.system.shared_libs +=libstdc++.so\n" + "namespace.default.link.vndk.shared_libs = libcutils.so:libbase.so\n" "namespace.system.isolated = true\n" "namespace.system.visible = true\n" "namespace.system.search.paths = /system/${LIB}\n" "namespace.system.permitted.paths = /system/${LIB}\n" "namespace.system.asan.search.paths = /data:/system/${LIB}\n" "namespace.system.asan.permitted.paths = /data:/system\n" + "namespace.vndk.isolated = tr\n" + "namespace.vndk.isolated += ue\n" // should be ignored and return as 'false'. + "namespace.vndk.search.paths = /system/${LIB}/vndk\n" + "namespace.vndk.asan.search.paths = /data\n" + "namespace.vndk.asan.search.paths += /system/${LIB}/vndk\n" "\n"; static bool write_version(const std::string& path, uint32_t version) { @@ -99,6 +111,10 @@ static void run_linker_config_smoke_test(bool is_asan) { resolve_paths(is_asan ? std::vector({ "/data", "/system" }) : std::vector({ "/system/lib" ARCH_SUFFIX })); + const std::vector kExpectedVndkSearchPath = + resolve_paths(is_asan ? std::vector({ "/data", "/system/lib" ARCH_SUFFIX "/vndk"}) : + std::vector({ "/system/lib" ARCH_SUFFIX "/vndk"})); + TemporaryFile tmp_file; close(tmp_file.fd); tmp_file.fd = -1; @@ -137,22 +153,27 @@ static void run_linker_config_smoke_test(bool is_asan) { ASSERT_EQ(kExpectedDefaultPermittedPath, default_ns_config->permitted_paths()); const auto& default_ns_links = default_ns_config->links(); - ASSERT_EQ(1U, default_ns_links.size()); + ASSERT_EQ(2U, default_ns_links.size()); ASSERT_EQ("system", default_ns_links[0].ns_name()); ASSERT_EQ("libc.so:libm.so:libdl.so:libstdc++.so", default_ns_links[0].shared_libs()); + ASSERT_EQ("vndk", default_ns_links[1].ns_name()); + ASSERT_EQ("libcutils.so:libbase.so", default_ns_links[1].shared_libs()); auto& ns_configs = config->namespace_configs(); - ASSERT_EQ(2U, ns_configs.size()); + ASSERT_EQ(3U, ns_configs.size()); // find second namespace const NamespaceConfig* ns_system = nullptr; + const NamespaceConfig* ns_vndk = nullptr; for (auto& ns : ns_configs) { std::string ns_name = ns->name(); - ASSERT_TRUE(ns_name == "system" || ns_name == "default") + ASSERT_TRUE(ns_name == "system" || ns_name == "default" || ns_name == "vndk") << "unexpected ns name: " << ns->name(); if (ns_name == "system") { ns_system = ns.get(); + } else if (ns_name == "vndk") { + ns_vndk = ns.get(); } } @@ -162,6 +183,12 @@ static void run_linker_config_smoke_test(bool is_asan) { ASSERT_TRUE(ns_system->visible()); ASSERT_EQ(kExpectedSystemSearchPath, ns_system->search_paths()); ASSERT_EQ(kExpectedSystemPermittedPath, ns_system->permitted_paths()); + + ASSERT_TRUE(ns_vndk != nullptr) << "vndk namespace was not found"; + + ASSERT_FALSE(ns_vndk->isolated()); // malformed bool property + ASSERT_FALSE(ns_vndk->visible()); // undefined bool property + ASSERT_EQ(kExpectedVndkSearchPath, ns_vndk->search_paths()); } TEST(linker_config, smoke) {