Merge "Fix which maps to search for globals."
diff --git a/adb/Android.bp b/adb/Android.bp
index 53f4404..2a88de5 100644
--- a/adb/Android.bp
+++ b/adb/Android.bp
@@ -486,7 +486,7 @@
},
}
-python_binary_host {
+python_test_host {
name: "adb_integration_test_device",
main: "test_device.py",
srcs: [
@@ -495,6 +495,8 @@
libs: [
"adb_py",
],
+ test_config: "adb_integration_test_device.xml",
+ test_suites: ["general-tests"],
version: {
py2: {
enabled: true,
diff --git a/adb/adb_integration_test_device.xml b/adb/adb_integration_test_device.xml
new file mode 100644
index 0000000..b892377
--- /dev/null
+++ b/adb/adb_integration_test_device.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ 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.
+-->
+<configuration description="Config to run adb integration tests for device">
+ <option name="test-suite-tag" value="adb_tests" />
+ <option name="test-suite-tag" value="adb_integration_device" />
+ <target_preparer class="com.android.tradefed.targetprep.SemaphoreTokenTargetPreparer">
+ <option name="disable" value="false" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.adb.AdbStopServerPreparer" />
+ <test class="com.android.tradefed.testtype.python.PythonBinaryHostTest" >
+ <option name="par-file-name" value="adb_integration_test_device" />
+ <option name="inject-android-serial" value="true" />
+ <option name="test-timeout" value="2m" />
+ </test>
+</configuration>
diff --git a/adb/test_adb.py b/adb/test_adb.py
index 7e73818..430fc3d 100755
--- a/adb/test_adb.py
+++ b/adb/test_adb.py
@@ -31,6 +31,7 @@
import threading
import time
import unittest
+import warnings
@contextlib.contextmanager
@@ -229,6 +230,10 @@
stderr=subprocess.STDOUT)
try:
+ # We get warnings for unclosed files for the subprocess's pipes,
+ # and it's somewhat cumbersome to close them, so just ignore this.
+ warnings.simplefilter("ignore", ResourceWarning)
+
# Run the adb client and have it start the adb server.
proc = subprocess.Popen(["adb", "-P", str(port), "start-server"],
stdin=subprocess.PIPE,
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 5962650..e27a897 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -155,10 +155,10 @@
{ nullptr, "system_other.img", "system.sig", "system", true, ImageType::Normal },
{ "userdata", "userdata.img", "userdata.sig", "userdata", true, ImageType::Extra },
{ "vbmeta", "vbmeta.img", "vbmeta.sig", "vbmeta", true, ImageType::BootCritical },
- { "vbmeta_mainline",
- "vbmeta_mainline.img",
- "vbmeta_mainline.sig",
- "vbmeta_mainline",
+ { "vbmeta_system",
+ "vbmeta_system.img",
+ "vbmeta_system.sig",
+ "vbmeta_system",
true, ImageType::BootCritical },
{ "vendor", "vendor.img", "vendor.sig", "vendor", true, ImageType::Normal },
{ nullptr, "vendor_other.img", "vendor.sig", "vendor", true, ImageType::Normal },
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index 743a3fe..03fd5f9 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -268,39 +268,19 @@
}
// Compute the first free sector, factoring in alignment.
- uint64_t free_area =
+ uint64_t free_area_start =
AlignTo(total_reserved, device_info.alignment, device_info.alignment_offset);
- uint64_t first_sector = free_area / LP_SECTOR_SIZE;
+ uint64_t first_sector = free_area_start / LP_SECTOR_SIZE;
- // Compute the last free sector, which is inclusive. We subtract 1 to make
- // sure that logical partitions won't overlap with the same sector as the
- // backup metadata, which could happen if the block device was not aligned
- // to LP_SECTOR_SIZE.
- uint64_t last_sector = (device_info.size / LP_SECTOR_SIZE) - 1;
-
- // If this check fails, it means either (1) we did not have free space to
- // allocate a single sector, or (2) we did, but the alignment was high
- // enough to bump the first sector out of range. Either way, we cannot
- // continue.
- if (first_sector > last_sector) {
- LERROR << "Not enough space to allocate any partition tables.";
+ // There must be one logical block of free space remaining (enough for one partition).
+ uint64_t minimum_disk_size = (first_sector * LP_SECTOR_SIZE) + device_info.logical_block_size;
+ if (device_info.size < minimum_disk_size) {
+ LERROR << "Device must be at least " << minimum_disk_size << " bytes, only has "
+ << device_info.size;
return false;
}
- // Finally, the size of the allocatable space must be a multiple of the
- // logical block size. If we have no more free space after this
- // computation, then we abort. Note that the last sector is inclusive,
- // so we have to account for that.
- uint64_t num_free_sectors = last_sector - first_sector + 1;
- uint64_t sectors_per_block = device_info.logical_block_size / LP_SECTOR_SIZE;
- if (num_free_sectors < sectors_per_block) {
- LERROR << "Not enough space to allocate any partition tables.";
- return false;
- }
- last_sector = first_sector + (num_free_sectors / sectors_per_block) * sectors_per_block - 1;
-
geometry_.first_logical_sector = first_sector;
- geometry_.last_logical_sector = last_sector;
geometry_.metadata_max_size = metadata_max_size;
geometry_.metadata_slot_count = metadata_slot_count;
geometry_.alignment = device_info.alignment;
@@ -452,8 +432,10 @@
uint64_t last_free_extent_start =
extents.empty() ? geometry_.first_logical_sector : extents.back().end;
last_free_extent_start = AlignSector(last_free_extent_start);
- if (last_free_extent_start <= geometry_.last_logical_sector) {
- free_regions.emplace_back(last_free_extent_start, geometry_.last_logical_sector + 1);
+
+ uint64_t last_sector = geometry_.block_device_size / LP_SECTOR_SIZE;
+ if (last_free_extent_start < last_sector) {
+ free_regions.emplace_back(last_free_extent_start, last_sector);
}
const uint64_t sectors_per_block = geometry_.logical_block_size / LP_SECTOR_SIZE;
@@ -523,6 +505,7 @@
strncpy(out.name, group->name().c_str(), sizeof(out.name));
out.maximum_size = group->maximum_size();
+ group_indices[group->name()] = metadata->groups.size();
metadata->groups.push_back(out);
}
@@ -546,6 +529,14 @@
part.num_extents = static_cast<uint32_t>(partition->extents().size());
part.attributes = partition->attributes();
+ auto iter = group_indices.find(partition->group_name());
+ if (iter == group_indices.end()) {
+ LERROR << "Partition " << partition->name() << " is a member of unknown group "
+ << partition->group_name();
+ return nullptr;
+ }
+ part.group_index = iter->second;
+
for (const auto& extent : partition->extents()) {
extent->AddTo(metadata.get());
}
@@ -559,7 +550,7 @@
}
uint64_t MetadataBuilder::AllocatableSpace() const {
- return (geometry_.last_logical_sector - geometry_.first_logical_sector + 1) * LP_SECTOR_SIZE;
+ return geometry_.block_device_size - (geometry_.first_logical_sector * LP_SECTOR_SIZE);
}
uint64_t MetadataBuilder::UsedSpace() const {
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index ffa7d3b..eb65f89 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -127,7 +127,6 @@
unique_ptr<LpMetadata> exported = builder->Export();
ASSERT_NE(exported, nullptr);
EXPECT_EQ(exported->geometry.first_logical_sector, 1536);
- EXPECT_EQ(exported->geometry.last_logical_sector, 2047);
// Test a large alignment offset thrown in.
device_info.alignment_offset = 753664;
@@ -136,7 +135,6 @@
exported = builder->Export();
ASSERT_NE(exported, nullptr);
EXPECT_EQ(exported->geometry.first_logical_sector, 1472);
- EXPECT_EQ(exported->geometry.last_logical_sector, 2047);
// Alignment offset without alignment doesn't mean anything.
device_info.alignment = 0;
@@ -151,7 +149,6 @@
exported = builder->Export();
ASSERT_NE(exported, nullptr);
EXPECT_EQ(exported->geometry.first_logical_sector, 150);
- EXPECT_EQ(exported->geometry.last_logical_sector, 2045);
// Test a small alignment with no alignment offset.
device_info.alignment = 11 * 1024;
@@ -160,7 +157,6 @@
exported = builder->Export();
ASSERT_NE(exported, nullptr);
EXPECT_EQ(exported->geometry.first_logical_sector, 160);
- EXPECT_EQ(exported->geometry.last_logical_sector, 2047);
}
TEST(liblp, InternalPartitionAlignment) {
@@ -298,7 +294,6 @@
EXPECT_EQ(geometry.metadata_max_size, 1024);
EXPECT_EQ(geometry.metadata_slot_count, 2);
EXPECT_EQ(geometry.first_logical_sector, 24);
- EXPECT_EQ(geometry.last_logical_sector, 2047);
static const size_t kMetadataSpace =
((kMetadataSize * kMetadataSlots) + LP_METADATA_GEOMETRY_SIZE) * 2;
diff --git a/fs_mgr/liblp/include/liblp/metadata_format.h b/fs_mgr/liblp/include/liblp/metadata_format.h
index a4ff1a7..711ff95 100644
--- a/fs_mgr/liblp/include/liblp/metadata_format.h
+++ b/fs_mgr/liblp/include/liblp/metadata_format.h
@@ -38,7 +38,7 @@
#define LP_METADATA_HEADER_MAGIC 0x414C5030
/* Current metadata version. */
-#define LP_METADATA_MAJOR_VERSION 4
+#define LP_METADATA_MAJOR_VERSION 5
#define LP_METADATA_MINOR_VERSION 0
/* Attributes for the LpMetadataPartition::attributes field.
@@ -104,12 +104,6 @@
*/
uint64_t first_logical_sector;
- /* 56: Last usable sector, inclusive, for allocating logical partitions.
- * At the end of this sector will follow backup metadata slots and the
- * backup geometry block at the very end.
- */
- uint64_t last_logical_sector;
-
/* 64: Alignment for defining partitions or partition extents. For example,
* an alignment of 1MiB will require that all partitions have a size evenly
* divisible by 1MiB, and that the smallest unit the partition can grow by
diff --git a/fs_mgr/liblp/io_test.cpp b/fs_mgr/liblp/io_test.cpp
index 220d651..92696f5 100644
--- a/fs_mgr/liblp/io_test.cpp
+++ b/fs_mgr/liblp/io_test.cpp
@@ -161,7 +161,6 @@
EXPECT_EQ(exported->geometry.metadata_max_size, imported->geometry.metadata_max_size);
EXPECT_EQ(exported->geometry.metadata_slot_count, imported->geometry.metadata_slot_count);
EXPECT_EQ(exported->geometry.first_logical_sector, imported->geometry.first_logical_sector);
- EXPECT_EQ(exported->geometry.last_logical_sector, imported->geometry.last_logical_sector);
EXPECT_EQ(exported->header.major_version, imported->header.major_version);
EXPECT_EQ(exported->header.minor_version, imported->header.minor_version);
EXPECT_EQ(exported->header.header_size, imported->header.header_size);
@@ -207,13 +206,14 @@
ASSERT_EQ(imported->partitions.size(), 1);
EXPECT_EQ(GetPartitionName(imported->partitions[0]), "vendor");
+ uint64_t last_sector = imported->geometry.block_device_size / LP_SECTOR_SIZE;
+
// Verify that we didn't overwrite anything in the logical paritition area.
// We expect the disk to be filled with 0xcc on creation so we can read
// this back and compare it.
char expected[LP_SECTOR_SIZE];
memset(expected, 0xcc, sizeof(expected));
- for (uint64_t i = imported->geometry.first_logical_sector;
- i <= imported->geometry.last_logical_sector; i++) {
+ for (uint64_t i = imported->geometry.first_logical_sector; i < last_sector; i++) {
char buffer[LP_SECTOR_SIZE];
ASSERT_GE(lseek(fd, i * LP_SECTOR_SIZE, SEEK_SET), 0);
ASSERT_TRUE(android::base::ReadFully(fd, buffer, sizeof(buffer)));
@@ -261,8 +261,6 @@
imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
- imported->geometry.last_logical_sector--;
- ASSERT_FALSE(UpdatePartitionTable(fd, *imported.get(), 1));
}
// Test that changing one bit of metadata is enough to break the checksum.
@@ -370,9 +368,6 @@
ASSERT_GE(lseek(fd, exported->geometry.first_logical_sector * LP_SECTOR_SIZE, SEEK_SET), 0);
ASSERT_TRUE(android::base::ReadFully(fd, buffer, sizeof(buffer)));
EXPECT_EQ(memcmp(expected, buffer, LP_SECTOR_SIZE), 0);
- ASSERT_GE(lseek(fd, exported->geometry.last_logical_sector * LP_SECTOR_SIZE, SEEK_SET), 0);
- ASSERT_TRUE(android::base::ReadFully(fd, buffer, sizeof(buffer)));
- EXPECT_EQ(memcmp(expected, buffer, LP_SECTOR_SIZE), 0);
}
// Test that we can read and write image files.
diff --git a/fs_mgr/liblp/utility_test.cpp b/fs_mgr/liblp/utility_test.cpp
index ff50e09..46ed2e6 100644
--- a/fs_mgr/liblp/utility_test.cpp
+++ b/fs_mgr/liblp/utility_test.cpp
@@ -37,7 +37,6 @@
16384,
4,
10000,
- 80000,
0,
0,
1024 * 1024,
diff --git a/fs_mgr/liblp/writer.cpp b/fs_mgr/liblp/writer.cpp
index 0c0cc49..5cf1a2c 100644
--- a/fs_mgr/liblp/writer.cpp
+++ b/fs_mgr/liblp/writer.cpp
@@ -43,8 +43,9 @@
static bool CompareGeometry(const LpMetadataGeometry& g1, const LpMetadataGeometry& g2) {
return g1.metadata_max_size == g2.metadata_max_size &&
g1.metadata_slot_count == g2.metadata_slot_count &&
- g1.first_logical_sector == g2.first_logical_sector &&
- g1.last_logical_sector == g2.last_logical_sector;
+ g1.block_device_size == g2.block_device_size &&
+ g1.logical_block_size == g2.logical_block_size &&
+ g1.first_logical_sector == g2.first_logical_sector;
}
std::string SerializeMetadata(const LpMetadata& input) {
@@ -86,15 +87,11 @@
return false;
}
- *blob = SerializeMetadata(metadata);
-
const LpMetadataHeader& header = metadata.header;
const LpMetadataGeometry& geometry = metadata.geometry;
- // Validate the usable sector range.
- if (geometry.first_logical_sector > geometry.last_logical_sector) {
- LERROR << "Logical partition metadata has invalid sector range.";
- return false;
- }
+
+ *blob = SerializeMetadata(metadata);
+
// Make sure we're writing within the space reserved.
if (blob->size() > geometry.metadata_max_size) {
LERROR << "Logical partition metadata is too large. " << blob->size() << " > "
@@ -128,11 +125,12 @@
}
// Make sure all linear extents have a valid range.
+ uint64_t last_sector = geometry.block_device_size / LP_SECTOR_SIZE;
for (const auto& extent : metadata.extents) {
if (extent.target_type == LP_TARGET_TYPE_LINEAR) {
uint64_t physical_sector = extent.target_data;
if (physical_sector < geometry.first_logical_sector ||
- physical_sector + extent.num_sectors > geometry.last_logical_sector) {
+ physical_sector + extent.num_sectors > last_sector) {
LERROR << "Extent table entry is out of bounds.";
return false;
}
@@ -167,7 +165,7 @@
}
if (abs_offset >= int64_t(geometry.first_logical_sector) * LP_SECTOR_SIZE) {
PERROR << __PRETTY_FUNCTION__ << "backup offset " << abs_offset
- << " is within logical partition bounds, sector " << geometry.last_logical_sector;
+ << " is within logical partition bounds, sector " << geometry.first_logical_sector;
return false;
}
if (!writer(fd, blob)) {
diff --git a/init/Android.mk b/init/Android.mk
index 5554995..ef08329 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -51,7 +51,8 @@
uevent_listener.cpp \
util.cpp \
-LOCAL_MODULE := init
+LOCAL_MODULE := init_first_stage
+LOCAL_MODULE_STEM := init
LOCAL_FORCE_STATIC_EXECUTABLE := true
@@ -87,8 +88,32 @@
libselinux \
libcap \
-LOCAL_REQUIRED_MODULES := \
- init_second_stage \
-
LOCAL_SANITIZE := signed-integer-overflow
include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := init_system
+ifeq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
+LOCAL_REQUIRED_MODULES := \
+ init_first_stage \
+ init_second_stage \
+
+else
+LOCAL_REQUIRED_MODULES := \
+ init_second_stage \
+
+endif
+include $(BUILD_PHONY_PACKAGE)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := init_vendor
+ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
+LOCAL_REQUIRED_MODULES := \
+ init_first_stage \
+
+endif
+include $(BUILD_PHONY_PACKAGE)
+
+
diff --git a/init/README.md b/init/README.md
index 02a65d5..f938ccc 100644
--- a/init/README.md
+++ b/init/README.md
@@ -189,7 +189,7 @@
`critical`
> This is a device-critical service. If it exits more than four times in
- four minutes, the device will reboot into recovery mode.
+ four minutes, the device will reboot into bootloader.
`disabled`
> This service will not automatically start with its class.
@@ -213,7 +213,8 @@
`interface <interface name> <instance name>`
> Associates this service with a list of the HIDL services that it provides. The interface name
must be a fully-qualified name and not a value name. This is used to allow hwservicemanager to
- lazily start services.
+ lazily start services. When multiple interfaces are served, this tag should be used multiple
+ times.
For example: interface vendor.foo.bar@1.0::IBaz default
`ioprio <class> <priority>`
diff --git a/init/service.cpp b/init/service.cpp
index a3e5953..0b1425d 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -369,7 +369,7 @@
return;
}
- // If we crash > 4 times in 4 minutes, reboot into recovery.
+ // If we crash > 4 times in 4 minutes, reboot into bootloader.
boot_clock::time_point now = boot_clock::now();
if ((flags_ & SVC_CRITICAL) && !(flags_ & SVC_RESTART)) {
if (now < time_crashed_ + 4min) {
diff --git a/init/service.h b/init/service.h
index e8d5ead..ee53adf 100644
--- a/init/service.h
+++ b/init/service.h
@@ -43,7 +43,7 @@
#define SVC_RUNNING 0x004 // currently active
#define SVC_RESTARTING 0x008 // waiting to restart
#define SVC_CONSOLE 0x010 // requires console
-#define SVC_CRITICAL 0x020 // will reboot into recovery if keeps crashing
+#define SVC_CRITICAL 0x020 // will reboot into bootloader if keeps crashing
#define SVC_RESET 0x040 // Use when stopping a process,
// but not disabling so it can be restarted with its class.
#define SVC_RC_DISABLED 0x080 // Remember if the disabled flag was set in the rc script.
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index 95be6af..66491dd 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -175,7 +175,7 @@
//
// When a subprocess crashes, we fatally abort from ueventd. init will restart ueventd when
// init reaps it, and the cold boot process will start again. If this continues to fail, then
- // since ueventd is marked as a critical service, init will reboot to recovery.
+ // since ueventd is marked as a critical service, init will reboot to bootloader.
//
// When a subprocess gets stuck, keep ueventd spinning waiting for it. init has a timeout for
// cold boot and will reboot to the bootloader if ueventd does not complete in time.
diff --git a/libstats/stats_event_list.c b/libstats/stats_event_list.c
index 735088a..8eedc60 100644
--- a/libstats/stats_event_list.c
+++ b/libstats/stats_event_list.c
@@ -125,6 +125,7 @@
void stats_log_close() {
statsd_writer_init_lock();
+ write_to_statsd = __write_to_statsd_init;
if (statsdLoggerWrite.close) {
(*statsdLoggerWrite.close)();
}
diff --git a/libutils/include/utils/Errors.h b/libutils/include/utils/Errors.h
index 7aafe42..1e03677 100644
--- a/libutils/include/utils/Errors.h
+++ b/libutils/include/utils/Errors.h
@@ -14,22 +14,19 @@
* limitations under the License.
*/
-#ifndef ANDROID_ERRORS_H
-#define ANDROID_ERRORS_H
+#pragma once
-#include <sys/types.h>
#include <errno.h>
+#include <stdint.h>
+#include <sys/types.h>
namespace android {
-// use this type to return error codes
-#ifdef _WIN32
-typedef int status_t;
-#else
-typedef int32_t status_t;
-#endif
-
-/* the MS C runtime lacks a few error codes */
+/**
+ * The type used to return success/failure from frameworks APIs.
+ * See the anonymous enum below for valid values.
+ */
+typedef int32_t status_t;
/*
* Error codes.
@@ -82,7 +79,3 @@
#endif
} // namespace android
-
-// ---------------------------------------------------------------------------
-
-#endif // ANDROID_ERRORS_H
diff --git a/libziparchive/include/ziparchive/zip_archive.h b/libziparchive/include/ziparchive/zip_archive.h
index 018b1a9..32d7901 100644
--- a/libziparchive/include/ziparchive/zip_archive.h
+++ b/libziparchive/include/ziparchive/zip_archive.h
@@ -103,7 +103,8 @@
off64_t offset;
};
-typedef void* ZipArchiveHandle;
+struct ZipArchive;
+typedef ZipArchive* ZipArchiveHandle;
/*
* Open a Zip archive, and sets handle to the value of the opaque
@@ -144,7 +145,7 @@
* this handle for any further operations without an intervening
* call to one of the OpenArchive variants.
*/
-void CloseArchive(ZipArchiveHandle handle);
+void CloseArchive(ZipArchiveHandle archive);
/*
* Find an entry in the Zip archive, by name. |entryName| must be a null
@@ -162,7 +163,7 @@
* On non-Windows platforms this method does not modify internal state and
* can be called concurrently.
*/
-int32_t FindEntry(const ZipArchiveHandle handle, const ZipString& entryName, ZipEntry* data);
+int32_t FindEntry(const ZipArchiveHandle archive, const ZipString& entryName, ZipEntry* data);
/*
* Start iterating over all entries of a zip file. The order of iteration
@@ -177,8 +178,8 @@
*
* Returns 0 on success and negative values on failure.
*/
-int32_t StartIteration(ZipArchiveHandle handle, void** cookie_ptr, const ZipString* optional_prefix,
- const ZipString* optional_suffix);
+int32_t StartIteration(ZipArchiveHandle archive, void** cookie_ptr,
+ const ZipString* optional_prefix, const ZipString* optional_suffix);
/*
* Advance to the next element in the zipfile in iteration order.
@@ -203,7 +204,7 @@
*
* Returns 0 on success and negative values on failure.
*/
-int32_t ExtractEntryToFile(ZipArchiveHandle handle, ZipEntry* entry, int fd);
+int32_t ExtractEntryToFile(ZipArchiveHandle archive, ZipEntry* entry, int fd);
/**
* Uncompress a given zip entry to the memory region at |begin| and of
@@ -213,9 +214,9 @@
*
* Returns 0 on success and negative values on failure.
*/
-int32_t ExtractToMemory(ZipArchiveHandle handle, ZipEntry* entry, uint8_t* begin, uint32_t size);
+int32_t ExtractToMemory(ZipArchiveHandle archive, ZipEntry* entry, uint8_t* begin, uint32_t size);
-int GetFileDescriptor(const ZipArchiveHandle handle);
+int GetFileDescriptor(const ZipArchiveHandle archive);
const char* ErrorCodeString(int32_t error_code);
@@ -226,7 +227,7 @@
* Stream the uncompressed data through the supplied function,
* passing cookie to it each time it gets called.
*/
-int32_t ProcessZipEntryContents(ZipArchiveHandle handle, ZipEntry* entry,
+int32_t ProcessZipEntryContents(ZipArchiveHandle archive, ZipEntry* entry,
ProcessZipEntryFunction func, void* cookie);
#endif
diff --git a/libziparchive/zip_archive.cc b/libziparchive/zip_archive.cc
index 6a3db6b..4221ee7 100644
--- a/libziparchive/zip_archive.cc
+++ b/libziparchive/zip_archive.cc
@@ -517,8 +517,7 @@
/*
* Close a ZipArchive, closing the file and freeing the contents.
*/
-void CloseArchive(ZipArchiveHandle handle) {
- ZipArchive* archive = reinterpret_cast<ZipArchive*>(handle);
+void CloseArchive(ZipArchiveHandle archive) {
ALOGV("Closing archive %p", archive);
delete archive;
}
@@ -745,10 +744,8 @@
}
};
-int32_t StartIteration(ZipArchiveHandle handle, void** cookie_ptr, const ZipString* optional_prefix,
- const ZipString* optional_suffix) {
- ZipArchive* archive = reinterpret_cast<ZipArchive*>(handle);
-
+int32_t StartIteration(ZipArchiveHandle archive, void** cookie_ptr,
+ const ZipString* optional_prefix, const ZipString* optional_suffix) {
if (archive == NULL || archive->hash_table == NULL) {
ALOGW("Zip: Invalid ZipArchiveHandle");
return kInvalidHandle;
@@ -766,8 +763,7 @@
delete reinterpret_cast<IterationHandle*>(cookie);
}
-int32_t FindEntry(const ZipArchiveHandle handle, const ZipString& entryName, ZipEntry* data) {
- const ZipArchive* archive = reinterpret_cast<ZipArchive*>(handle);
+int32_t FindEntry(const ZipArchiveHandle archive, const ZipString& entryName, ZipEntry* data) {
if (entryName.name_length == 0) {
ALOGW("Zip: Invalid filename %.*s", entryName.name_length, entryName.name);
return kInvalidEntryName;
@@ -1116,8 +1112,7 @@
return 0;
}
-int32_t ExtractToWriter(ZipArchiveHandle handle, ZipEntry* entry, zip_archive::Writer* writer) {
- ZipArchive* archive = reinterpret_cast<ZipArchive*>(handle);
+int32_t ExtractToWriter(ZipArchiveHandle archive, ZipEntry* entry, zip_archive::Writer* writer) {
const uint16_t method = entry->method;
// this should default to kUnknownCompressionMethod.
@@ -1145,18 +1140,18 @@
return return_value;
}
-int32_t ExtractToMemory(ZipArchiveHandle handle, ZipEntry* entry, uint8_t* begin, uint32_t size) {
+int32_t ExtractToMemory(ZipArchiveHandle archive, ZipEntry* entry, uint8_t* begin, uint32_t size) {
MemoryWriter writer(begin, size);
- return ExtractToWriter(handle, entry, &writer);
+ return ExtractToWriter(archive, entry, &writer);
}
-int32_t ExtractEntryToFile(ZipArchiveHandle handle, ZipEntry* entry, int fd) {
+int32_t ExtractEntryToFile(ZipArchiveHandle archive, ZipEntry* entry, int fd) {
auto writer = FileWriter::Create(fd, entry);
if (!writer.IsValid()) {
return kIoError;
}
- return ExtractToWriter(handle, entry, &writer);
+ return ExtractToWriter(archive, entry, &writer);
}
const char* ErrorCodeString(int32_t error_code) {
@@ -1173,8 +1168,8 @@
return "Unknown return code";
}
-int GetFileDescriptor(const ZipArchiveHandle handle) {
- return reinterpret_cast<ZipArchive*>(handle)->mapped_zip.GetFileDescriptor();
+int GetFileDescriptor(const ZipArchiveHandle archive) {
+ return archive->mapped_zip.GetFileDescriptor();
}
ZipString::ZipString(const char* entry_name) : name(reinterpret_cast<const uint8_t*>(entry_name)) {
@@ -1198,10 +1193,10 @@
void* cookie_;
};
-int32_t ProcessZipEntryContents(ZipArchiveHandle handle, ZipEntry* entry,
+int32_t ProcessZipEntryContents(ZipArchiveHandle archive, ZipEntry* entry,
ProcessZipEntryFunction func, void* cookie) {
ProcessWriter writer(func, cookie);
- return ExtractToWriter(handle, entry, &writer);
+ return ExtractToWriter(archive, entry, &writer);
}
#endif //! defined(_WIN32)
diff --git a/llkd/include/llkd.h b/llkd/include/llkd.h
index 1e2df2f..4d39dd9 100644
--- a/llkd/include/llkd.h
+++ b/llkd/include/llkd.h
@@ -57,7 +57,7 @@
#define LLK_BLACKLIST_UID_PROPERTY "ro.llk.blacklist.uid"
#define LLK_BLACKLIST_UID_DEFAULT ""
#define LLK_BLACKLIST_STACK_PROPERTY "ro.llk.blacklist.process.stack"
-#define LLK_BLACKLIST_STACK_DEFAULT "init,lmkd.llkd,llkd,keystore,/system/bin/keystore,ueventd"
+#define LLK_BLACKLIST_STACK_DEFAULT "init,lmkd.llkd,llkd,keystore,/system/bin/keystore,ueventd,apexd"
/* clang-format on */
__END_DECLS
diff --git a/lmkd/include/lmkd.h b/lmkd/include/lmkd.h
index fe6364d..e8f51da 100644
--- a/lmkd/include/lmkd.h
+++ b/lmkd/include/lmkd.h
@@ -30,6 +30,7 @@
LMK_TARGET = 0, /* Associate minfree with oom_adj_score */
LMK_PROCPRIO, /* Register a process and set its oom_adj_score */
LMK_PROCREMOVE, /* Unregister a process */
+ LMK_PROCPURGE, /* Purge all registered processes */
};
/*
@@ -142,6 +143,15 @@
return 2 * sizeof(int);
}
+/*
+ * Prepare LMK_PROCPURGE packet and return packet size in bytes.
+ * Warning: no checks performed, caller should ensure valid parameters.
+ */
+inline size_t lmkd_pack_set_procpurge(LMKD_CTRL_PACKET packet) {
+ packet[0] = htonl(LMK_PROCPURGE);
+ return sizeof(int);
+}
+
__END_DECLS
#endif /* _LMKD_H_ */
diff --git a/lmkd/lmkd.c b/lmkd/lmkd.c
index cf0bef8..0a469e8 100644
--- a/lmkd/lmkd.c
+++ b/lmkd/lmkd.c
@@ -559,7 +559,7 @@
} else if (params.oomadj >= 300) {
soft_limit_mult = 1;
} else if (params.oomadj >= 200) {
- soft_limit_mult = 2;
+ soft_limit_mult = 8;
} else if (params.oomadj >= 100) {
soft_limit_mult = 10;
} else if (params.oomadj >= 0) {
@@ -619,6 +619,31 @@
pid_remove(params.pid);
}
+static void cmd_procpurge() {
+ int i;
+ struct proc *procp;
+ struct proc *next;
+
+ if (use_inkernel_interface) {
+ return;
+ }
+
+ for (i = 0; i <= ADJTOSLOT(OOM_SCORE_ADJ_MAX); i++) {
+ procadjslot_list[i].next = &procadjslot_list[i];
+ procadjslot_list[i].prev = &procadjslot_list[i];
+ }
+
+ for (i = 0; i < PIDHASH_SZ; i++) {
+ procp = pidhash[i];
+ while (procp) {
+ next = procp->pidhash_next;
+ free(procp);
+ procp = next;
+ }
+ }
+ memset(&pidhash[0], 0, sizeof(pidhash));
+}
+
static void cmd_target(int ntargets, LMKD_CTRL_PACKET packet) {
int i;
struct lmk_target target;
@@ -761,6 +786,11 @@
goto wronglen;
cmd_procremove(packet);
break;
+ case LMK_PROCPURGE:
+ if (nargs != 0)
+ goto wronglen;
+ cmd_procpurge();
+ break;
default:
ALOGE("Received unknown command code %d", cmd);
return;
diff --git a/logd/FlushCommand.cpp b/logd/FlushCommand.cpp
index 658e079..bd17555 100644
--- a/logd/FlushCommand.cpp
+++ b/logd/FlushCommand.cpp
@@ -42,7 +42,7 @@
LogTimeEntry::wrlock();
LastLogTimes::iterator it = times.begin();
while (it != times.end()) {
- entry = (*it);
+ entry = it->get();
if (entry->mClient == client) {
if (!entry->isWatchingMultiple(mLogMask)) {
LogTimeEntry::unlock();
@@ -63,31 +63,12 @@
}
}
entry->triggerReader_Locked();
- if (entry->runningReader_Locked()) {
- LogTimeEntry::unlock();
- return;
- }
- entry->incRef_Locked();
- break;
+ LogTimeEntry::unlock();
+ return;
}
it++;
}
- if (it == times.end()) {
- // Create LogTimeEntry in notifyNewLog() ?
- if (mTail == (unsigned long)-1) {
- LogTimeEntry::unlock();
- return;
- }
- entry = new LogTimeEntry(mReader, client, mNonBlock, mTail, mLogMask,
- mPid, mStart, mTimeout);
- times.push_front(entry);
- }
-
- client->incRef();
-
- // release client and entry reference counts once done
- entry->startReader_Locked();
LogTimeEntry::unlock();
}
diff --git a/logd/FlushCommand.h b/logd/FlushCommand.h
index 543dfc3..ceaf393 100644
--- a/logd/FlushCommand.h
+++ b/logd/FlushCommand.h
@@ -27,36 +27,11 @@
class FlushCommand : public SocketClientCommand {
LogReader& mReader;
- bool mNonBlock;
- unsigned long mTail;
log_mask_t mLogMask;
- pid_t mPid;
- log_time mStart;
- uint64_t mTimeout;
public:
- // for opening a reader
- explicit FlushCommand(LogReader& reader, bool nonBlock, unsigned long tail,
- log_mask_t logMask, pid_t pid, log_time start,
- uint64_t timeout)
- : mReader(reader),
- mNonBlock(nonBlock),
- mTail(tail),
- mLogMask(logMask),
- mPid(pid),
- mStart(start),
- mTimeout((start != log_time::EPOCH) ? timeout : 0) {
- }
-
- // for notification of an update
explicit FlushCommand(LogReader& reader, log_mask_t logMask)
- : mReader(reader),
- mNonBlock(false),
- mTail(-1),
- mLogMask(logMask),
- mPid(0),
- mStart(log_time::EPOCH),
- mTimeout(0) {
+ : mReader(reader), mLogMask(logMask) {
}
virtual void runSocketCommand(SocketClient* client);
diff --git a/logd/LogBuffer.cpp b/logd/LogBuffer.cpp
index fd1b8b2..fbdbf79 100644
--- a/logd/LogBuffer.cpp
+++ b/logd/LogBuffer.cpp
@@ -105,10 +105,8 @@
LastLogTimes::iterator times = mTimes.begin();
while (times != mTimes.end()) {
- LogTimeEntry* entry = (*times);
- if (entry->owned_Locked()) {
- entry->triggerReader_Locked();
- }
+ LogTimeEntry* entry = times->get();
+ entry->triggerReader_Locked();
times++;
}
@@ -409,17 +407,15 @@
LastLogTimes::iterator times = mTimes.begin();
while (times != mTimes.end()) {
- LogTimeEntry* entry = (*times);
- if (entry->owned_Locked()) {
- if (!entry->mNonBlock) {
- end_always = true;
- break;
- }
- // it passing mEnd is blocked by the following checks.
- if (!end_set || (end <= entry->mEnd)) {
- end = entry->mEnd;
- end_set = true;
- }
+ LogTimeEntry* entry = times->get();
+ if (!entry->mNonBlock) {
+ end_always = true;
+ break;
+ }
+ // it passing mEnd is blocked by the following checks.
+ if (!end_set || (end <= entry->mEnd)) {
+ end = entry->mEnd;
+ end_set = true;
}
times++;
}
@@ -710,8 +706,8 @@
// Region locked?
LastLogTimes::iterator times = mTimes.begin();
while (times != mTimes.end()) {
- LogTimeEntry* entry = (*times);
- if (entry->owned_Locked() && entry->isWatching(id) &&
+ LogTimeEntry* entry = times->get();
+ if (entry->isWatching(id) &&
(!oldest || (oldest->mStart > entry->mStart) ||
((oldest->mStart == entry->mStart) &&
(entry->mTimeout.tv_sec || entry->mTimeout.tv_nsec)))) {
@@ -1052,9 +1048,9 @@
LogTimeEntry::wrlock();
LastLogTimes::iterator times = mTimes.begin();
while (times != mTimes.end()) {
- LogTimeEntry* entry = (*times);
+ LogTimeEntry* entry = times->get();
// Killer punch
- if (entry->owned_Locked() && entry->isWatching(id)) {
+ if (entry->isWatching(id)) {
entry->release_Locked();
}
times++;
diff --git a/logd/LogReader.cpp b/logd/LogReader.cpp
index 2b6556d..13c7af3 100644
--- a/logd/LogReader.cpp
+++ b/logd/LogReader.cpp
@@ -41,6 +41,7 @@
runOnEachSocket(&command);
}
+// Note returning false will release the SocketClient instance.
bool LogReader::onDataAvailable(SocketClient* cli) {
static bool name_set;
if (!name_set) {
@@ -57,6 +58,18 @@
}
buffer[len] = '\0';
+ // Clients are only allowed to send one command, disconnect them if they
+ // send another.
+ LogTimeEntry::wrlock();
+ for (const auto& entry : mLogbuf.mTimes) {
+ if (entry->mClient == cli) {
+ entry->release_Locked();
+ LogTimeEntry::unlock();
+ return false;
+ }
+ }
+ LogTimeEntry::unlock();
+
unsigned long tail = 0;
static const char _tail[] = " tail=";
char* cp = strstr(buffer, _tail);
@@ -199,14 +212,25 @@
cli->getUid(), cli->getGid(), cli->getPid(), nonBlock ? 'n' : 'b', tail,
logMask, (int)pid, sequence.nsec(), timeout);
- FlushCommand command(*this, nonBlock, tail, logMask, pid, sequence, timeout);
+ LogTimeEntry::wrlock();
+ auto entry = std::make_unique<LogTimeEntry>(
+ *this, cli, nonBlock, tail, logMask, pid, sequence, timeout);
+ if (!entry->startReader_Locked()) {
+ LogTimeEntry::unlock();
+ return false;
+ }
+
+ // release client and entry reference counts once done
+ cli->incRef();
+ mLogbuf.mTimes.emplace_front(std::move(entry));
// Set acceptable upper limit to wait for slow reader processing b/27242723
struct timeval t = { LOGD_SNDTIMEO, 0 };
setsockopt(cli->getSocket(), SOL_SOCKET, SO_SNDTIMEO, (const char*)&t,
sizeof(t));
- command.runSocketCommand(cli);
+ LogTimeEntry::unlock();
+
return true;
}
@@ -215,9 +239,8 @@
LogTimeEntry::wrlock();
LastLogTimes::iterator it = times.begin();
while (it != times.end()) {
- LogTimeEntry* entry = (*it);
+ LogTimeEntry* entry = it->get();
if (entry->mClient == cli) {
- times.erase(it);
entry->release_Locked();
break;
}
diff --git a/logd/LogTimes.cpp b/logd/LogTimes.cpp
index 7a6f84b..1715501 100644
--- a/logd/LogTimes.cpp
+++ b/logd/LogTimes.cpp
@@ -30,11 +30,7 @@
LogTimeEntry::LogTimeEntry(LogReader& reader, SocketClient* client,
bool nonBlock, unsigned long tail, log_mask_t logMask,
pid_t pid, log_time start, uint64_t timeout)
- : mRefCount(1),
- mRelease(false),
- mError(false),
- threadRunning(false),
- leadingDropped(false),
+ : leadingDropped(false),
mReader(reader),
mLogMask(logMask),
mPid(pid),
@@ -52,65 +48,21 @@
cleanSkip_Locked();
}
-void LogTimeEntry::startReader_Locked(void) {
+bool LogTimeEntry::startReader_Locked() {
pthread_attr_t attr;
- threadRunning = true;
-
if (!pthread_attr_init(&attr)) {
if (!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) {
if (!pthread_create(&mThread, &attr, LogTimeEntry::threadStart,
this)) {
pthread_attr_destroy(&attr);
- return;
+ return true;
}
}
pthread_attr_destroy(&attr);
}
- threadRunning = false;
- if (mClient) {
- mClient->decRef();
- }
- decRef_Locked();
-}
-void LogTimeEntry::threadStop(void* obj) {
- LogTimeEntry* me = reinterpret_cast<LogTimeEntry*>(obj);
-
- wrlock();
-
- if (me->mNonBlock) {
- me->error_Locked();
- }
-
- SocketClient* client = me->mClient;
-
- if (me->isError_Locked()) {
- LogReader& reader = me->mReader;
- LastLogTimes& times = reader.logbuf().mTimes;
-
- LastLogTimes::iterator it = times.begin();
- while (it != times.end()) {
- if (*it == me) {
- times.erase(it);
- me->release_nodelete_Locked();
- break;
- }
- it++;
- }
-
- me->mClient = nullptr;
- reader.release(client);
- }
-
- if (client) {
- client->decRef();
- }
-
- me->threadRunning = false;
- me->decRef_Locked();
-
- unlock();
+ return false;
}
void* LogTimeEntry::threadStart(void* obj) {
@@ -118,13 +70,7 @@
LogTimeEntry* me = reinterpret_cast<LogTimeEntry*>(obj);
- pthread_cleanup_push(threadStop, obj);
-
SocketClient* client = me->mClient;
- if (!client) {
- me->error();
- return nullptr;
- }
LogBuffer& logbuf = me->mReader.logbuf();
@@ -137,14 +83,14 @@
log_time start = me->mStart;
- while (me->threadRunning && !me->isError_Locked()) {
+ while (!me->mRelease) {
if (me->mTimeout.tv_sec || me->mTimeout.tv_nsec) {
if (pthread_cond_timedwait(&me->threadTriggeredCondition,
×Lock, &me->mTimeout) == ETIMEDOUT) {
me->mTimeout.tv_sec = 0;
me->mTimeout.tv_nsec = 0;
}
- if (!me->threadRunning || me->isError_Locked()) {
+ if (me->mRelease) {
break;
}
}
@@ -162,13 +108,12 @@
wrlock();
if (start == LogBufferElement::FLUSH_ERROR) {
- me->error_Locked();
break;
}
me->mStart = start + log_time(0, 1);
- if (me->mNonBlock || !me->threadRunning || me->isError_Locked()) {
+ if (me->mNonBlock || me->mRelease) {
break;
}
@@ -179,9 +124,21 @@
}
}
- unlock();
+ LogReader& reader = me->mReader;
+ reader.release(client);
- pthread_cleanup_pop(true);
+ client->decRef();
+
+ LastLogTimes& times = reader.logbuf().mTimes;
+ auto it =
+ std::find_if(times.begin(), times.end(),
+ [&me](const auto& other) { return other.get() == me; });
+
+ if (it != times.end()) {
+ times.erase(it);
+ }
+
+ unlock();
return nullptr;
}
@@ -247,10 +204,6 @@
goto skip;
}
- if (me->isError_Locked()) {
- goto stop;
- }
-
if (!me->mTail) {
goto ok;
}
diff --git a/logd/LogTimes.h b/logd/LogTimes.h
index 76d016c..f4e165f 100644
--- a/logd/LogTimes.h
+++ b/logd/LogTimes.h
@@ -22,6 +22,7 @@
#include <time.h>
#include <list>
+#include <memory>
#include <log/log.h>
#include <sysutils/SocketClient.h>
@@ -33,16 +34,12 @@
class LogTimeEntry {
static pthread_mutex_t timesLock;
- unsigned int mRefCount;
- bool mRelease;
- bool mError;
- bool threadRunning;
+ bool mRelease = false;
bool leadingDropped;
pthread_cond_t threadTriggeredCondition;
pthread_t mThread;
LogReader& mReader;
static void* threadStart(void* me);
- static void threadStop(void* me);
const log_mask_t mLogMask;
const pid_t mPid;
unsigned int skipAhead[LOG_ID_MAX];
@@ -73,11 +70,8 @@
pthread_mutex_unlock(×Lock);
}
- void startReader_Locked(void);
+ bool startReader_Locked();
- bool runningReader_Locked(void) const {
- return threadRunning || mRelease || mError || mNonBlock;
- }
void triggerReader_Locked(void) {
pthread_cond_signal(&threadTriggeredCondition);
}
@@ -87,54 +81,11 @@
}
void cleanSkip_Locked(void);
- // These called after LogTimeEntry removed from list, lock implicitly held
- void release_nodelete_Locked(void) {
- mRelease = true;
- pthread_cond_signal(&threadTriggeredCondition);
- // assumes caller code path will call decRef_Locked()
- }
-
void release_Locked(void) {
mRelease = true;
pthread_cond_signal(&threadTriggeredCondition);
- if (mRefCount || threadRunning) {
- return;
- }
- // No one else is holding a reference to this
- delete this;
}
- // Called to mark socket in jeopardy
- void error_Locked(void) {
- mError = true;
- }
- void error(void) {
- wrlock();
- error_Locked();
- unlock();
- }
-
- bool isError_Locked(void) const {
- return mRelease || mError;
- }
-
- // Mark Used
- // Locking implied, grabbed when protection around loop iteration
- void incRef_Locked(void) {
- ++mRefCount;
- }
-
- bool owned_Locked(void) const {
- return mRefCount != 0;
- }
-
- void decRef_Locked(void) {
- if ((mRefCount && --mRefCount) || !mRelease || threadRunning) {
- return;
- }
- // No one else is holding a reference to this
- delete this;
- }
bool isWatching(log_id_t id) const {
return mLogMask & (1 << id);
}
@@ -146,6 +97,6 @@
static int FilterSecondPass(const LogBufferElement* element, void* me);
};
-typedef std::list<LogTimeEntry*> LastLogTimes;
+typedef std::list<std::unique_ptr<LogTimeEntry>> LastLogTimes;
#endif // _LOGD_LOG_TIMES_H__
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index e4e1650..393e204 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -124,6 +124,7 @@
LOCAL_POST_INSTALL_CMD += ; ln -sf /vendor/odm/lib64 $(TARGET_ROOT_OUT)/odm/lib64
LOCAL_POST_INSTALL_CMD += ; ln -sf /vendor/odm/overlay $(TARGET_ROOT_OUT)/odm/overlay
LOCAL_POST_INSTALL_CMD += ; ln -sf /vendor/odm/priv-app $(TARGET_ROOT_OUT)/odm/priv-app
+LOCAL_POST_INSTALL_CMD += ; ln -sf /vendor/odm/usr $(TARGET_ROOT_OUT)/odm/usr
ifdef BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
LOCAL_POST_INSTALL_CMD += ; mkdir -p $(TARGET_ROOT_OUT)/cache
diff --git a/rootdir/OWNERS b/rootdir/OWNERS
index 6029ae7..ca22eb8 100644
--- a/rootdir/OWNERS
+++ b/rootdir/OWNERS
@@ -1,3 +1,4 @@
+ccross@google.com
jeffv@google.com
jiyong@google.com
smoreland@google.com
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 9aaad8f..21d234f 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -336,6 +336,8 @@
start servicemanager
start hwservicemanager
start vndservicemanager
+ start vold
+ exec - system system -- /system/bin/vdc checkpoint markBootAttempt
# Once everything is setup, no need to modify /.
# The bind+remount combination allows this to work in containers.