Merge UP1A.230503.003
Change-Id: I5f017b091cb2e8a9512d7812796f59a98b44cce6
This commit is contained in:
commit
7cbba88517
|
@ -20,6 +20,7 @@
|
|||
#include <fcntl.h>
|
||||
#include <linux/prctl.h>
|
||||
#include <malloc.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/capability.h>
|
||||
#include <sys/mman.h>
|
||||
|
@ -2703,3 +2704,58 @@ TEST_F(CrasherTest, verify_build_id) {
|
|||
}
|
||||
ASSERT_TRUE(found_valid_elf) << "Did not find any elf files with valid BuildIDs to check.";
|
||||
}
|
||||
|
||||
const char kLogMessage[] = "Should not see this log message.";
|
||||
|
||||
// Verify that the logd process does not read the log.
|
||||
TEST_F(CrasherTest, logd_skips_reading_logs) {
|
||||
StartProcess([]() {
|
||||
pthread_setname_np(pthread_self(), "logd");
|
||||
LOG(INFO) << kLogMessage;
|
||||
abort();
|
||||
});
|
||||
|
||||
unique_fd output_fd;
|
||||
StartIntercept(&output_fd);
|
||||
FinishCrasher();
|
||||
AssertDeath(SIGABRT);
|
||||
int intercept_result;
|
||||
FinishIntercept(&intercept_result);
|
||||
ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
|
||||
|
||||
std::string result;
|
||||
ConsumeFd(std::move(output_fd), &result);
|
||||
// logd should not contain our log message.
|
||||
ASSERT_NOT_MATCH(result, kLogMessage);
|
||||
}
|
||||
|
||||
// Verify that the logd process does not read the log when the non-main
|
||||
// thread crashes.
|
||||
TEST_F(CrasherTest, logd_skips_reading_logs_not_main_thread) {
|
||||
StartProcess([]() {
|
||||
pthread_setname_np(pthread_self(), "logd");
|
||||
LOG(INFO) << kLogMessage;
|
||||
|
||||
std::thread thread([]() {
|
||||
pthread_setname_np(pthread_self(), "not_logd_thread");
|
||||
// Raise the signal on the side thread.
|
||||
raise_debugger_signal(kDebuggerdTombstone);
|
||||
});
|
||||
thread.join();
|
||||
_exit(0);
|
||||
});
|
||||
|
||||
unique_fd output_fd;
|
||||
StartIntercept(&output_fd, kDebuggerdTombstone);
|
||||
FinishCrasher();
|
||||
AssertDeath(0);
|
||||
|
||||
int intercept_result;
|
||||
FinishIntercept(&intercept_result);
|
||||
ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
|
||||
|
||||
std::string result;
|
||||
ConsumeFd(std::move(output_fd), &result);
|
||||
ASSERT_BACKTRACE_FRAME(result, "raise_debugger_signal");
|
||||
ASSERT_NOT_MATCH(result, kLogMessage);
|
||||
}
|
||||
|
|
|
@ -690,7 +690,14 @@ void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::AndroidUnwinder*
|
|||
|
||||
// Only dump logs on debuggable devices.
|
||||
if (android::base::GetBoolProperty("ro.debuggable", false)) {
|
||||
dump_logcat(&result, main_thread.pid);
|
||||
// Get the thread that corresponds to the main pid of the process.
|
||||
const ThreadInfo& thread = threads.at(main_thread.pid);
|
||||
|
||||
// Do not attempt to dump logs of the logd process because the gathering
|
||||
// of logs can hang until a timeout occurs.
|
||||
if (thread.thread_name != "logd") {
|
||||
dump_logcat(&result, main_thread.pid);
|
||||
}
|
||||
}
|
||||
|
||||
dump_open_fds(&result, open_files);
|
||||
|
|
|
@ -381,6 +381,7 @@ cc_test_host {
|
|||
"socket_mock.cpp",
|
||||
"socket_test.cpp",
|
||||
"super_flash_helper_test.cpp",
|
||||
"task_test.cpp",
|
||||
"tcp_test.cpp",
|
||||
"udp_test.cpp",
|
||||
],
|
||||
|
|
|
@ -115,19 +115,6 @@ static bool g_disable_verification = false;
|
|||
|
||||
fastboot::FastBootDriver* fb = nullptr;
|
||||
|
||||
enum fb_buffer_type {
|
||||
FB_BUFFER_FD,
|
||||
FB_BUFFER_SPARSE,
|
||||
};
|
||||
|
||||
struct fastboot_buffer {
|
||||
enum fb_buffer_type type;
|
||||
std::vector<SparsePtr> files;
|
||||
int64_t sz;
|
||||
unique_fd fd;
|
||||
int64_t image_size;
|
||||
};
|
||||
|
||||
static std::vector<Image> images = {
|
||||
// clang-format off
|
||||
{ "boot", "boot.img", "boot.sig", "boot", false, ImageType::BootCritical },
|
||||
|
@ -1284,7 +1271,7 @@ static void flash_buf(const std::string& partition, struct fastboot_buffer* buf,
|
|||
}
|
||||
}
|
||||
|
||||
static std::string get_current_slot() {
|
||||
std::string get_current_slot() {
|
||||
std::string current_slot;
|
||||
if (fb->GetVar("current-slot", ¤t_slot) != fastboot::SUCCESS) return "";
|
||||
if (current_slot[0] == '_') current_slot.erase(0, 1);
|
||||
|
@ -1560,7 +1547,7 @@ static void CancelSnapshotIfNeeded() {
|
|||
}
|
||||
}
|
||||
|
||||
std::string GetPartitionName(const ImageEntry& entry, std::string& current_slot) {
|
||||
std::string GetPartitionName(const ImageEntry& entry, const std::string& current_slot) {
|
||||
auto slot = entry.second;
|
||||
if (slot.empty()) {
|
||||
slot = current_slot;
|
||||
|
@ -1574,23 +1561,215 @@ std::string GetPartitionName(const ImageEntry& entry, std::string& current_slot)
|
|||
return entry.first->part_name + "_" + slot;
|
||||
}
|
||||
|
||||
class FlashAllTool {
|
||||
public:
|
||||
FlashAllTool(FlashingPlan* fp);
|
||||
std::unique_ptr<FlashTask> ParseFlashCommand(const FlashingPlan* fp,
|
||||
const std::vector<std::string>& parts) {
|
||||
bool apply_vbmeta = false;
|
||||
std::string slot = fp->slot_override;
|
||||
std::string partition;
|
||||
std::string img_name;
|
||||
for (auto& part : parts) {
|
||||
if (part == "--apply-vbmeta") {
|
||||
apply_vbmeta = true;
|
||||
} else if (part == "--slot-other") {
|
||||
slot = fp->secondary_slot;
|
||||
} else if (partition.empty()) {
|
||||
partition = part;
|
||||
} else if (img_name.empty()) {
|
||||
img_name = part;
|
||||
} else {
|
||||
LOG(ERROR) << "unknown argument" << part
|
||||
<< " in fastboot-info.txt. parts: " << android::base::Join(parts, " ");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
if (partition.empty()) {
|
||||
LOG(ERROR) << "partition name not found when parsing fastboot-info.txt. parts: "
|
||||
<< android::base::Join(parts, " ");
|
||||
return nullptr;
|
||||
}
|
||||
if (img_name.empty()) {
|
||||
img_name = partition + ".img";
|
||||
}
|
||||
return std::make_unique<FlashTask>(slot, partition, img_name, apply_vbmeta);
|
||||
}
|
||||
|
||||
void Flash();
|
||||
std::unique_ptr<RebootTask> ParseRebootCommand(const FlashingPlan* fp,
|
||||
const std::vector<std::string>& parts) {
|
||||
if (parts.empty()) return std::make_unique<RebootTask>(fp);
|
||||
if (parts.size() > 1) {
|
||||
LOG(ERROR) << "unknown arguments in reboot {target} in fastboot-info.txt: "
|
||||
<< android::base::Join(parts, " ");
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<RebootTask>(fp, parts[0]);
|
||||
}
|
||||
|
||||
private:
|
||||
void CheckRequirements();
|
||||
void DetermineSlot();
|
||||
void CollectImages();
|
||||
void FlashImages(const std::vector<std::pair<const Image*, std::string>>& images);
|
||||
void FlashImage(const Image& image, const std::string& slot, fastboot_buffer* buf);
|
||||
std::unique_ptr<WipeTask> ParseWipeCommand(const FlashingPlan* fp,
|
||||
const std::vector<std::string>& parts) {
|
||||
if (parts.size() != 1) {
|
||||
LOG(ERROR) << "unknown arguments in erase {partition} in fastboot-info.txt: "
|
||||
<< android::base::Join(parts, " ");
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<WipeTask>(fp, parts[0]);
|
||||
}
|
||||
|
||||
std::vector<ImageEntry> boot_images_;
|
||||
std::vector<ImageEntry> os_images_;
|
||||
FlashingPlan* fp_;
|
||||
};
|
||||
std::unique_ptr<Task> ParseFastbootInfoLine(const FlashingPlan* fp,
|
||||
const std::vector<std::string>& command) {
|
||||
if (command.size() == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
std::unique_ptr<Task> task;
|
||||
|
||||
if (command[0] == "flash") {
|
||||
task = ParseFlashCommand(fp, std::vector<std::string>{command.begin() + 1, command.end()});
|
||||
} else if (command[0] == "reboot") {
|
||||
task = ParseRebootCommand(fp, std::vector<std::string>{command.begin() + 1, command.end()});
|
||||
} else if (command[0] == "update-super" && command.size() == 1) {
|
||||
task = std::make_unique<UpdateSuperTask>(fp);
|
||||
} else if (command[0] == "erase" && command.size() == 2) {
|
||||
task = ParseWipeCommand(fp, std::vector<std::string>{command.begin() + 1, command.end()});
|
||||
}
|
||||
if (!task) {
|
||||
LOG(ERROR) << "unknown command parsing fastboot-info.txt line: "
|
||||
<< android::base::Join(command, " ");
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
void AddResizeTasks(const FlashingPlan* fp, std::vector<std::unique_ptr<Task>>* tasks) {
|
||||
// expands "resize-partitions" into individual commands : resize {os_partition_1}, resize
|
||||
// {os_partition_2}, etc.
|
||||
std::vector<std::unique_ptr<Task>> resize_tasks;
|
||||
std::optional<size_t> loc;
|
||||
for (size_t i = 0; i < tasks->size(); i++) {
|
||||
if (auto flash_task = tasks->at(i)->AsFlashTask()) {
|
||||
if (should_flash_in_userspace(flash_task->GetPartitionAndSlot())) {
|
||||
if (!loc) {
|
||||
loc = i;
|
||||
}
|
||||
resize_tasks.emplace_back(std::make_unique<ResizeTask>(
|
||||
fp, flash_task->GetPartition(), "0", fp->slot_override));
|
||||
}
|
||||
}
|
||||
}
|
||||
// if no logical partitions (although should never happen since system will always need to be
|
||||
// flashed)
|
||||
if (!loc) {
|
||||
return;
|
||||
}
|
||||
tasks->insert(tasks->begin() + loc.value(), std::make_move_iterator(resize_tasks.begin()),
|
||||
std::make_move_iterator(resize_tasks.end()));
|
||||
return;
|
||||
}
|
||||
|
||||
static bool IsNumber(const std::string& s) {
|
||||
bool period = false;
|
||||
for (size_t i = 0; i < s.length(); i++) {
|
||||
if (!isdigit(s[i])) {
|
||||
if (!period && s[i] == '.' && i != 0 && i != s.length() - 1) {
|
||||
period = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsIgnore(const std::vector<std::string>& command) {
|
||||
if (command[0][0] == '#') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CheckFastbootInfoRequirements(const std::vector<std::string>& command) {
|
||||
if (command.size() != 2) {
|
||||
LOG(ERROR) << "unknown characters in version info in fastboot-info.txt -> "
|
||||
<< android::base::Join(command, " ");
|
||||
return false;
|
||||
}
|
||||
if (command[0] != "version") {
|
||||
LOG(ERROR) << "unknown characters in version info in fastboot-info.txt -> "
|
||||
<< android::base::Join(command, " ");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsNumber(command[1])) {
|
||||
LOG(ERROR) << "version number contains non-numeric values in fastboot-info.txt -> "
|
||||
<< android::base::Join(command, " ");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG(VERBOSE) << "Checking 'fastboot-info.txt version'";
|
||||
if (command[1] < PLATFORM_TOOLS_VERSION) {
|
||||
return true;
|
||||
}
|
||||
LOG(ERROR) << "fasboot-info.txt version: " << command[1]
|
||||
<< " not compatible with host tool version --> " << PLATFORM_TOOLS_VERSION;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Task>> ParseFastbootInfo(const FlashingPlan* fp,
|
||||
const std::vector<std::string>& file) {
|
||||
std::vector<std::unique_ptr<Task>> tasks;
|
||||
// Get os_partitions that need to be resized
|
||||
for (auto& text : file) {
|
||||
std::vector<std::string> command = android::base::Tokenize(text, " ");
|
||||
if (IsIgnore(command)) {
|
||||
continue;
|
||||
}
|
||||
if (command.size() > 1 && command[0] == "version") {
|
||||
if (!CheckFastbootInfoRequirements(command)) {
|
||||
return {};
|
||||
}
|
||||
continue;
|
||||
} else if (command.size() >= 2 && command[0] == "if-wipe") {
|
||||
if (!fp->wants_wipe) {
|
||||
continue;
|
||||
}
|
||||
command.erase(command.begin());
|
||||
}
|
||||
auto task = ParseFastbootInfoLine(fp, command);
|
||||
if (!task) {
|
||||
LOG(ERROR) << "Error when parsing fastboot-info.txt, falling back on Hardcoded list: "
|
||||
<< text;
|
||||
return {};
|
||||
}
|
||||
tasks.emplace_back(std::move(task));
|
||||
}
|
||||
if (auto flash_super_task = FlashSuperLayoutTask::InitializeFromTasks(fp, tasks)) {
|
||||
auto it = tasks.begin();
|
||||
for (size_t i = 0; i < tasks.size(); i++) {
|
||||
if (auto flash_task = tasks[i]->AsFlashTask()) {
|
||||
if (should_flash_in_userspace(flash_task->GetPartitionAndSlot())) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (auto wipe_task = tasks[i]->AsWipeTask()) {
|
||||
break;
|
||||
}
|
||||
it++;
|
||||
}
|
||||
tasks.insert(it, std::move(flash_super_task));
|
||||
} else {
|
||||
AddResizeTasks(fp, &tasks);
|
||||
}
|
||||
return tasks;
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Task>> ParseFastbootInfo(const FlashingPlan* fp, std::ifstream& fs) {
|
||||
if (!fs || fs.eof()) return {};
|
||||
|
||||
std::string text;
|
||||
std::vector<std::string> file;
|
||||
// Get os_partitions that need to be resized
|
||||
while (std::getline(fs, text)) {
|
||||
file.emplace_back(text);
|
||||
}
|
||||
return ParseFastbootInfo(fp, file);
|
||||
}
|
||||
|
||||
FlashAllTool::FlashAllTool(FlashingPlan* fp) : fp_(fp) {}
|
||||
|
||||
|
@ -1611,38 +1790,23 @@ void FlashAllTool::Flash() {
|
|||
|
||||
CancelSnapshotIfNeeded();
|
||||
|
||||
// First flash boot partitions. We allow this to happen either in userspace
|
||||
// or in bootloader fastboot.
|
||||
FlashImages(boot_images_);
|
||||
|
||||
std::vector<std::unique_ptr<Task>> tasks;
|
||||
|
||||
if (auto flash_super_task = FlashSuperLayoutTask::Initialize(fp_, os_images_)) {
|
||||
tasks.emplace_back(std::move(flash_super_task));
|
||||
} else {
|
||||
// Sync the super partition. This will reboot to userspace fastboot if needed.
|
||||
tasks.emplace_back(std::make_unique<UpdateSuperTask>(fp_));
|
||||
// Resize any logical partition to 0, so each partition is reset to 0
|
||||
// extents, and will achieve more optimal allocation.
|
||||
for (const auto& [image, slot] : os_images_) {
|
||||
// Retrofit devices have two super partitions, named super_a and super_b.
|
||||
// On these devices, secondary slots must be flashed as physical
|
||||
// partitions (otherwise they would not mount on first boot). To enforce
|
||||
// this, we delete any logical partitions for the "other" slot.
|
||||
if (is_retrofit_device()) {
|
||||
std::string partition_name = image->part_name + "_"s + slot;
|
||||
if (image->IsSecondary() && is_logical(partition_name)) {
|
||||
fp_->fb->DeletePartition(partition_name);
|
||||
}
|
||||
tasks.emplace_back(std::make_unique<DeleteTask>(fp_, partition_name));
|
||||
}
|
||||
tasks.emplace_back(std::make_unique<ResizeTask>(fp_, image->part_name, "0", slot));
|
||||
}
|
||||
std::string path = find_item_given_name("fastboot-info.txt");
|
||||
std::ifstream stream(path);
|
||||
std::vector<std::unique_ptr<Task>> tasks = ParseFastbootInfo(fp_, stream);
|
||||
if (tasks.empty()) {
|
||||
LOG(VERBOSE) << "Flashing from hardcoded images. fastboot-info.txt is empty or does not "
|
||||
"exist";
|
||||
HardcodedFlash();
|
||||
return;
|
||||
}
|
||||
LOG(VERBOSE) << "Flashing from fastboot-info.txt";
|
||||
for (auto& task : tasks) {
|
||||
task->Run();
|
||||
}
|
||||
FlashImages(os_images_);
|
||||
if (fp_->wants_wipe) {
|
||||
// avoid adding duplicate wipe tasks in fastboot main code.
|
||||
fp_->wants_wipe = false;
|
||||
}
|
||||
}
|
||||
|
||||
void FlashAllTool::CheckRequirements() {
|
||||
|
@ -1693,6 +1857,42 @@ void FlashAllTool::CollectImages() {
|
|||
}
|
||||
}
|
||||
|
||||
void FlashAllTool::HardcodedFlash() {
|
||||
CollectImages();
|
||||
// First flash boot partitions. We allow this to happen either in userspace
|
||||
// or in bootloader fastboot.
|
||||
FlashImages(boot_images_);
|
||||
|
||||
std::vector<std::unique_ptr<Task>> tasks;
|
||||
|
||||
if (auto flash_super_task = FlashSuperLayoutTask::Initialize(fp_, os_images_)) {
|
||||
tasks.emplace_back(std::move(flash_super_task));
|
||||
} else {
|
||||
// Sync the super partition. This will reboot to userspace fastboot if needed.
|
||||
tasks.emplace_back(std::make_unique<UpdateSuperTask>(fp_));
|
||||
// Resize any logical partition to 0, so each partition is reset to 0
|
||||
// extents, and will achieve more optimal allocation.
|
||||
for (const auto& [image, slot] : os_images_) {
|
||||
// Retrofit devices have two super partitions, named super_a and super_b.
|
||||
// On these devices, secondary slots must be flashed as physical
|
||||
// partitions (otherwise they would not mount on first boot). To enforce
|
||||
// this, we delete any logical partitions for the "other" slot.
|
||||
if (is_retrofit_device()) {
|
||||
std::string partition_name = image->part_name + "_"s + slot;
|
||||
if (image->IsSecondary() && should_flash_in_userspace(partition_name)) {
|
||||
fp_->fb->DeletePartition(partition_name);
|
||||
}
|
||||
tasks.emplace_back(std::make_unique<DeleteTask>(fp_, partition_name));
|
||||
}
|
||||
tasks.emplace_back(std::make_unique<ResizeTask>(fp_, image->part_name, "0", slot));
|
||||
}
|
||||
}
|
||||
for (auto& i : tasks) {
|
||||
i->Run();
|
||||
}
|
||||
FlashImages(os_images_);
|
||||
}
|
||||
|
||||
void FlashAllTool::FlashImages(const std::vector<std::pair<const Image*, std::string>>& images) {
|
||||
for (const auto& [image, slot] : images) {
|
||||
fastboot_buffer buf;
|
||||
|
|
|
@ -29,7 +29,10 @@
|
|||
|
||||
#include <string>
|
||||
#include "fastboot_driver.h"
|
||||
#include "fastboot_driver_interface.h"
|
||||
#include "filesystem.h"
|
||||
#include "super_flash_helper.h"
|
||||
#include "task.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <bootimg.h>
|
||||
|
@ -47,6 +50,19 @@ class FastBootTool {
|
|||
unsigned ParseFsOption(const char*);
|
||||
};
|
||||
|
||||
enum fb_buffer_type {
|
||||
FB_BUFFER_FD,
|
||||
FB_BUFFER_SPARSE,
|
||||
};
|
||||
|
||||
struct fastboot_buffer {
|
||||
enum fb_buffer_type type;
|
||||
std::vector<SparsePtr> files;
|
||||
int64_t sz;
|
||||
unique_fd fd;
|
||||
int64_t image_size;
|
||||
};
|
||||
|
||||
enum class ImageType {
|
||||
// Must be flashed for device to boot into the kernel.
|
||||
BootCritical,
|
||||
|
@ -83,7 +99,27 @@ struct FlashingPlan {
|
|||
std::string slot_override;
|
||||
std::string current_slot;
|
||||
std::string secondary_slot;
|
||||
fastboot::FastBootDriver* fb;
|
||||
|
||||
fastboot::IFastBootDriver* fb;
|
||||
};
|
||||
|
||||
class FlashAllTool {
|
||||
public:
|
||||
FlashAllTool(FlashingPlan* fp);
|
||||
|
||||
void Flash();
|
||||
|
||||
private:
|
||||
void CheckRequirements();
|
||||
void DetermineSlot();
|
||||
void CollectImages();
|
||||
void FlashImages(const std::vector<std::pair<const Image*, std::string>>& images);
|
||||
void FlashImage(const Image& image, const std::string& slot, fastboot_buffer* buf);
|
||||
void HardcodedFlash();
|
||||
|
||||
std::vector<ImageEntry> boot_images_;
|
||||
std::vector<ImageEntry> os_images_;
|
||||
FlashingPlan* fp_;
|
||||
};
|
||||
|
||||
bool should_flash_in_userspace(const std::string& partition_name);
|
||||
|
@ -94,6 +130,21 @@ void do_for_partitions(const std::string& part, const std::string& slot,
|
|||
std::string find_item(const std::string& item);
|
||||
void reboot_to_userspace_fastboot();
|
||||
void syntax_error(const char* fmt, ...);
|
||||
std::string get_current_slot();
|
||||
|
||||
// Code for Parsing fastboot-info.txt
|
||||
bool CheckFastbootInfoRequirements(const std::vector<std::string>& command);
|
||||
std::unique_ptr<FlashTask> ParseFlashCommand(const FlashingPlan* fp,
|
||||
const std::vector<std::string>& parts);
|
||||
std::unique_ptr<RebootTask> ParseRebootCommand(const FlashingPlan* fp,
|
||||
const std::vector<std::string>& parts);
|
||||
std::unique_ptr<WipeTask> ParseWipeCommand(const FlashingPlan* fp,
|
||||
const std::vector<std::string>& parts);
|
||||
std::unique_ptr<Task> ParseFastbootInfoLine(const FlashingPlan* fp,
|
||||
const std::vector<std::string>& command);
|
||||
void AddResizeTasks(const FlashingPlan* fp, std::vector<std::unique_ptr<Task>>& tasks);
|
||||
std::vector<std::unique_ptr<Task>> ParseFastbootInfo(const FlashingPlan* fp,
|
||||
const std::vector<std::string>& file);
|
||||
|
||||
struct NetworkSerial {
|
||||
Socket::Protocol protocol;
|
||||
|
@ -103,7 +154,7 @@ struct NetworkSerial {
|
|||
|
||||
Result<NetworkSerial, FastbootError> ParseNetworkSerial(const std::string& serial);
|
||||
bool supports_AB();
|
||||
std::string GetPartitionName(const ImageEntry& entry, std::string& current_slot_);
|
||||
std::string GetPartitionName(const ImageEntry& entry, const std::string& current_slot_);
|
||||
void flash_partition_files(const std::string& partition, const std::vector<SparsePtr>& files);
|
||||
int64_t get_sparse_limit(int64_t size);
|
||||
std::vector<SparsePtr> resparse_file(sparse_file* s, int64_t max_size);
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
#include <vector>
|
||||
|
||||
#include <android-base/endian.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <bootimg.h>
|
||||
|
@ -41,21 +40,13 @@
|
|||
#include <sparse/sparse.h>
|
||||
|
||||
#include "constants.h"
|
||||
#include "fastboot_driver_interface.h"
|
||||
#include "transport.h"
|
||||
|
||||
class Transport;
|
||||
|
||||
namespace fastboot {
|
||||
|
||||
enum RetCode : int {
|
||||
SUCCESS = 0,
|
||||
BAD_ARG,
|
||||
IO_ERROR,
|
||||
BAD_DEV_RESP,
|
||||
DEVICE_FAIL,
|
||||
TIMEOUT,
|
||||
};
|
||||
|
||||
struct DriverCallbacks {
|
||||
std::function<void(const std::string&)> prolog = [](const std::string&) {};
|
||||
std::function<void(int)> epilog = [](int) {};
|
||||
|
@ -63,7 +54,7 @@ struct DriverCallbacks {
|
|||
std::function<void(const std::string&)> text = [](const std::string&) {};
|
||||
};
|
||||
|
||||
class FastBootDriver {
|
||||
class FastBootDriver : public IFastBootDriver {
|
||||
friend class FastBootTest;
|
||||
|
||||
public:
|
||||
|
@ -78,9 +69,10 @@ class FastBootDriver {
|
|||
RetCode Boot(std::string* response = nullptr, std::vector<std::string>* info = nullptr);
|
||||
RetCode Continue(std::string* response = nullptr, std::vector<std::string>* info = nullptr);
|
||||
RetCode CreatePartition(const std::string& partition, const std::string& size);
|
||||
RetCode DeletePartition(const std::string& partition);
|
||||
RetCode DeletePartition(const std::string& partition) override;
|
||||
RetCode Download(const std::string& name, android::base::borrowed_fd fd, size_t size,
|
||||
std::string* response = nullptr, std::vector<std::string>* info = nullptr);
|
||||
std::string* response = nullptr,
|
||||
std::vector<std::string>* info = nullptr) override;
|
||||
RetCode Download(android::base::borrowed_fd fd, size_t size, std::string* response = nullptr,
|
||||
std::vector<std::string>* info = nullptr);
|
||||
RetCode Download(const std::string& name, const std::vector<char>& buf,
|
||||
|
@ -93,16 +85,17 @@ class FastBootDriver {
|
|||
RetCode Download(sparse_file* s, bool use_crc = false, std::string* response = nullptr,
|
||||
std::vector<std::string>* info = nullptr);
|
||||
RetCode Erase(const std::string& partition, std::string* response = nullptr,
|
||||
std::vector<std::string>* info = nullptr);
|
||||
std::vector<std::string>* info = nullptr) override;
|
||||
RetCode Flash(const std::string& partition, std::string* response = nullptr,
|
||||
std::vector<std::string>* info = nullptr);
|
||||
RetCode GetVar(const std::string& key, std::string* val,
|
||||
std::vector<std::string>* info = nullptr);
|
||||
std::vector<std::string>* info = nullptr) override;
|
||||
RetCode GetVarAll(std::vector<std::string>* response);
|
||||
RetCode Reboot(std::string* response = nullptr, std::vector<std::string>* info = nullptr);
|
||||
RetCode Reboot(std::string* response = nullptr,
|
||||
std::vector<std::string>* info = nullptr) override;
|
||||
RetCode RebootTo(std::string target, std::string* response = nullptr,
|
||||
std::vector<std::string>* info = nullptr);
|
||||
RetCode ResizePartition(const std::string& partition, const std::string& size);
|
||||
std::vector<std::string>* info = nullptr) override;
|
||||
RetCode ResizePartition(const std::string& partition, const std::string& size) override;
|
||||
RetCode SetActive(const std::string& slot, std::string* response = nullptr,
|
||||
std::vector<std::string>* info = nullptr);
|
||||
RetCode Upload(const std::string& outfile, std::string* response = nullptr,
|
||||
|
@ -116,7 +109,7 @@ class FastBootDriver {
|
|||
/* HIGHER LEVEL COMMANDS -- Composed of the commands above */
|
||||
RetCode FlashPartition(const std::string& partition, const std::vector<char>& data);
|
||||
RetCode FlashPartition(const std::string& partition, android::base::borrowed_fd fd,
|
||||
uint32_t sz);
|
||||
uint32_t sz) override;
|
||||
RetCode FlashPartition(const std::string& partition, sparse_file* s, uint32_t sz,
|
||||
size_t current, size_t total);
|
||||
|
||||
|
@ -128,7 +121,7 @@ class FastBootDriver {
|
|||
void SetInfoCallback(std::function<void(const std::string&)> info);
|
||||
static const std::string RCString(RetCode rc);
|
||||
std::string Error();
|
||||
RetCode WaitForDisconnect();
|
||||
RetCode WaitForDisconnect() override;
|
||||
|
||||
// Note: set_transport will return the previous transport.
|
||||
Transport* set_transport(Transport* transport);
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// Copyright (C) 2023 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.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "android-base/unique_fd.h"
|
||||
|
||||
class Transport;
|
||||
|
||||
namespace fastboot {
|
||||
|
||||
enum RetCode : int {
|
||||
SUCCESS = 0,
|
||||
BAD_ARG,
|
||||
IO_ERROR,
|
||||
BAD_DEV_RESP,
|
||||
DEVICE_FAIL,
|
||||
TIMEOUT,
|
||||
};
|
||||
|
||||
class IFastBootDriver {
|
||||
public:
|
||||
RetCode virtual FlashPartition(const std::string& partition, android::base::borrowed_fd fd,
|
||||
uint32_t sz) = 0;
|
||||
RetCode virtual DeletePartition(const std::string& partition) = 0;
|
||||
RetCode virtual WaitForDisconnect() = 0;
|
||||
RetCode virtual Reboot(std::string* response = nullptr,
|
||||
std::vector<std::string>* info = nullptr) = 0;
|
||||
|
||||
RetCode virtual RebootTo(std::string target, std::string* response = nullptr,
|
||||
std::vector<std::string>* info = nullptr) = 0;
|
||||
RetCode virtual GetVar(const std::string& key, std::string* val,
|
||||
std::vector<std::string>* info = nullptr) = 0;
|
||||
RetCode virtual Download(const std::string& name, android::base::borrowed_fd fd, size_t size,
|
||||
std::string* response = nullptr,
|
||||
std::vector<std::string>* info = nullptr) = 0;
|
||||
RetCode virtual RawCommand(const std::string& cmd, const std::string& message,
|
||||
std::string* response = nullptr,
|
||||
std::vector<std::string>* info = nullptr, int* dsize = nullptr) = 0;
|
||||
RetCode virtual ResizePartition(const std::string& partition, const std::string& size) = 0;
|
||||
RetCode virtual Erase(const std::string& partition, std::string* response = nullptr,
|
||||
std::vector<std::string>* info = nullptr) = 0;
|
||||
virtual ~IFastBootDriver() = default;
|
||||
};
|
||||
} // namespace fastboot
|
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// Copyright (C) 2023 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.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "fastboot_driver_interface.h"
|
||||
|
||||
namespace fastboot {
|
||||
|
||||
class MockFastbootDriver : public IFastBootDriver {
|
||||
public:
|
||||
MOCK_METHOD(RetCode, FlashPartition,
|
||||
(const std::string& partition, android::base::borrowed_fd fd, uint32_t sz),
|
||||
(override));
|
||||
MOCK_METHOD(RetCode, DeletePartition, (const std::string&), (override));
|
||||
MOCK_METHOD(RetCode, Reboot, (std::string*, std::vector<std::string>*), (override));
|
||||
MOCK_METHOD(RetCode, RebootTo, (std::string, std::string*, std::vector<std::string>*),
|
||||
(override));
|
||||
|
||||
MOCK_METHOD(RetCode, GetVar, (const std::string&, std::string*, std::vector<std::string>*),
|
||||
(override));
|
||||
|
||||
MOCK_METHOD(RetCode, Download,
|
||||
(const std::string&, android::base::borrowed_fd, size_t, std::string*,
|
||||
std::vector<std::string>*),
|
||||
(override));
|
||||
|
||||
MOCK_METHOD(RetCode, RawCommand,
|
||||
(const std::string&, const std::string&, std::string*, std::vector<std::string>*,
|
||||
int*),
|
||||
(override));
|
||||
MOCK_METHOD(RetCode, ResizePartition, (const std::string&, const std::string&), (override));
|
||||
MOCK_METHOD(RetCode, Erase, (const std::string&, std::string*, std::vector<std::string>*),
|
||||
(override));
|
||||
MOCK_METHOD(RetCode, WaitForDisconnect, (), (override));
|
||||
};
|
||||
|
||||
} // namespace fastboot
|
|
@ -14,12 +14,16 @@
|
|||
// limitations under the License.
|
||||
//
|
||||
#include "task.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/parseint.h>
|
||||
|
||||
#include "fastboot.h"
|
||||
#include "filesystem.h"
|
||||
#include "super_flash_helper.h"
|
||||
|
||||
#include <android-base/parseint.h>
|
||||
#include "util.h"
|
||||
|
||||
using namespace std::string_literals;
|
||||
FlashTask::FlashTask(const std::string& _slot, const std::string& _pname, const std::string& _fname,
|
||||
|
@ -42,8 +46,22 @@ void FlashTask::Run() {
|
|||
do_for_partitions(pname_, slot_, flash, true);
|
||||
}
|
||||
|
||||
RebootTask::RebootTask(FlashingPlan* fp) : fp_(fp){};
|
||||
RebootTask::RebootTask(FlashingPlan* fp, const std::string& reboot_target)
|
||||
std::string FlashTask::GetPartitionAndSlot() {
|
||||
auto slot = slot_;
|
||||
if (slot.empty()) {
|
||||
slot = get_current_slot();
|
||||
}
|
||||
if (slot.empty()) {
|
||||
return pname_;
|
||||
}
|
||||
if (slot == "all") {
|
||||
LOG(FATAL) << "Cannot retrieve a singular name when using all slots";
|
||||
}
|
||||
return pname_ + "_" + slot;
|
||||
}
|
||||
|
||||
RebootTask::RebootTask(const FlashingPlan* fp) : fp_(fp){};
|
||||
RebootTask::RebootTask(const FlashingPlan* fp, const std::string& reboot_target)
|
||||
: reboot_target_(reboot_target), fp_(fp){};
|
||||
|
||||
void RebootTask::Run() {
|
||||
|
@ -90,7 +108,7 @@ void FlashSuperLayoutTask::Run() {
|
|||
}
|
||||
|
||||
std::unique_ptr<FlashSuperLayoutTask> FlashSuperLayoutTask::Initialize(
|
||||
FlashingPlan* fp, std::vector<ImageEntry>& os_images) {
|
||||
const FlashingPlan* fp, std::vector<ImageEntry>& os_images) {
|
||||
if (!supports_AB()) {
|
||||
LOG(VERBOSE) << "Cannot optimize flashing super on non-AB device";
|
||||
return nullptr;
|
||||
|
@ -153,7 +171,78 @@ std::unique_ptr<FlashSuperLayoutTask> FlashSuperLayoutTask::Initialize(
|
|||
partition_size);
|
||||
}
|
||||
|
||||
UpdateSuperTask::UpdateSuperTask(FlashingPlan* fp) : fp_(fp) {}
|
||||
std::unique_ptr<FlashSuperLayoutTask> FlashSuperLayoutTask::InitializeFromTasks(
|
||||
const FlashingPlan* fp, std::vector<std::unique_ptr<Task>>& tasks) {
|
||||
if (!supports_AB()) {
|
||||
LOG(VERBOSE) << "Cannot optimize flashing super on non-AB device";
|
||||
return nullptr;
|
||||
}
|
||||
if (fp->slot_override == "all") {
|
||||
LOG(VERBOSE) << "Cannot optimize flashing super for all slots";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Does this device use dynamic partitions at all?
|
||||
unique_fd fd = fp->source->OpenFile("super_empty.img");
|
||||
|
||||
if (fd < 0) {
|
||||
LOG(VERBOSE) << "could not open super_empty.img";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string super_name;
|
||||
// Try to find whether there is a super partition.
|
||||
if (fp->fb->GetVar("super-partition-name", &super_name) != fastboot::SUCCESS) {
|
||||
super_name = "super";
|
||||
}
|
||||
uint64_t partition_size;
|
||||
std::string partition_size_str;
|
||||
if (fp->fb->GetVar("partition-size:" + super_name, &partition_size_str) != fastboot::SUCCESS) {
|
||||
LOG(VERBOSE) << "Cannot optimize super flashing: could not determine super partition";
|
||||
return nullptr;
|
||||
}
|
||||
partition_size_str = fb_fix_numeric_var(partition_size_str);
|
||||
if (!android::base::ParseUint(partition_size_str, &partition_size)) {
|
||||
LOG(VERBOSE) << "Could not parse " << super_name << " size: " << partition_size_str;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<SuperFlashHelper> helper = std::make_unique<SuperFlashHelper>(*fp->source);
|
||||
if (!helper->Open(fd)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const auto& task : tasks) {
|
||||
if (auto flash_task = task->AsFlashTask()) {
|
||||
if (should_flash_in_userspace(flash_task->GetPartitionAndSlot())) {
|
||||
auto partition = flash_task->GetPartitionAndSlot();
|
||||
if (!helper->AddPartition(partition, flash_task->GetImageName(), false)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto s = helper->GetSparseLayout();
|
||||
if (!s) return nullptr;
|
||||
// Remove images that we already flashed, just in case we have non-dynamic OS images.
|
||||
auto remove_if_callback = [&](const auto& task) -> bool {
|
||||
if (auto flash_task = task->AsFlashTask()) {
|
||||
return helper->WillFlash(flash_task->GetPartitionAndSlot());
|
||||
} else if (auto update_super_task = task->AsUpdateSuperTask()) {
|
||||
return true;
|
||||
} else if (auto reboot_task = task->AsRebootTask()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
tasks.erase(std::remove_if(tasks.begin(), tasks.end(), remove_if_callback), tasks.end());
|
||||
|
||||
return std::make_unique<FlashSuperLayoutTask>(super_name, std::move(helper), std::move(s),
|
||||
partition_size);
|
||||
}
|
||||
|
||||
UpdateSuperTask::UpdateSuperTask(const FlashingPlan* fp) : fp_(fp) {}
|
||||
|
||||
void UpdateSuperTask::Run() {
|
||||
unique_fd fd = fp_->source->OpenFile("super_empty.img");
|
||||
|
@ -177,7 +266,7 @@ void UpdateSuperTask::Run() {
|
|||
fp_->fb->RawCommand(command, "Updating super partition");
|
||||
}
|
||||
|
||||
ResizeTask::ResizeTask(FlashingPlan* fp, const std::string& pname, const std::string& size,
|
||||
ResizeTask::ResizeTask(const FlashingPlan* fp, const std::string& pname, const std::string& size,
|
||||
const std::string& slot)
|
||||
: fp_(fp), pname_(pname), size_(size), slot_(slot) {}
|
||||
|
||||
|
@ -196,7 +285,7 @@ void DeleteTask::Run() {
|
|||
fp_->fb->DeletePartition(pname_);
|
||||
}
|
||||
|
||||
WipeTask::WipeTask(FlashingPlan* fp, const std::string& pname) : fp_(fp), pname_(pname){};
|
||||
WipeTask::WipeTask(const FlashingPlan* fp, const std::string& pname) : fp_(fp), pname_(pname){};
|
||||
|
||||
void WipeTask::Run() {
|
||||
std::string partition_type;
|
||||
|
|
|
@ -18,15 +18,28 @@
|
|||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "fastboot.h"
|
||||
#include "fastboot_driver.h"
|
||||
#include "super_flash_helper.h"
|
||||
#include "util.h"
|
||||
|
||||
struct FlashingPlan;
|
||||
struct Image;
|
||||
using ImageEntry = std::pair<const Image*, std::string>;
|
||||
|
||||
class FlashTask;
|
||||
class RebootTask;
|
||||
class UpdateSuperTask;
|
||||
class WipeTask;
|
||||
|
||||
class Task {
|
||||
public:
|
||||
Task() = default;
|
||||
virtual void Run() = 0;
|
||||
virtual FlashTask* AsFlashTask() { return nullptr; }
|
||||
virtual RebootTask* AsRebootTask() { return nullptr; }
|
||||
virtual UpdateSuperTask* AsUpdateSuperTask() { return nullptr; }
|
||||
virtual WipeTask* AsWipeTask() { return nullptr; }
|
||||
|
||||
virtual ~Task() = default;
|
||||
};
|
||||
|
||||
|
@ -34,7 +47,12 @@ class FlashTask : public Task {
|
|||
public:
|
||||
FlashTask(const std::string& slot, const std::string& pname, const std::string& fname,
|
||||
const bool apply_vbmeta);
|
||||
virtual FlashTask* AsFlashTask() override { return this; }
|
||||
|
||||
std::string GetPartition() { return pname_; }
|
||||
std::string GetImageName() { return fname_; }
|
||||
std::string GetPartitionAndSlot();
|
||||
std::string GetSlot() { return slot_; }
|
||||
void Run() override;
|
||||
|
||||
private:
|
||||
|
@ -46,8 +64,9 @@ class FlashTask : public Task {
|
|||
|
||||
class RebootTask : public Task {
|
||||
public:
|
||||
RebootTask(FlashingPlan* fp);
|
||||
RebootTask(FlashingPlan* fp, const std::string& reboot_target);
|
||||
RebootTask(const FlashingPlan* fp);
|
||||
RebootTask(const FlashingPlan* fp, const std::string& reboot_target);
|
||||
virtual RebootTask* AsRebootTask() override { return this; }
|
||||
void Run() override;
|
||||
|
||||
private:
|
||||
|
@ -59,8 +78,10 @@ class FlashSuperLayoutTask : public Task {
|
|||
public:
|
||||
FlashSuperLayoutTask(const std::string& super_name, std::unique_ptr<SuperFlashHelper> helper,
|
||||
SparsePtr sparse_layout, uint64_t super_size);
|
||||
static std::unique_ptr<FlashSuperLayoutTask> Initialize(FlashingPlan* fp,
|
||||
static std::unique_ptr<FlashSuperLayoutTask> Initialize(const FlashingPlan* fp,
|
||||
std::vector<ImageEntry>& os_images);
|
||||
static std::unique_ptr<FlashSuperLayoutTask> InitializeFromTasks(
|
||||
const FlashingPlan* fp, std::vector<std::unique_ptr<Task>>& tasks);
|
||||
using ImageEntry = std::pair<const Image*, std::string>;
|
||||
void Run() override;
|
||||
|
||||
|
@ -73,7 +94,8 @@ class FlashSuperLayoutTask : public Task {
|
|||
|
||||
class UpdateSuperTask : public Task {
|
||||
public:
|
||||
UpdateSuperTask(FlashingPlan* fp);
|
||||
UpdateSuperTask(const FlashingPlan* fp);
|
||||
virtual UpdateSuperTask* AsUpdateSuperTask() override { return this; }
|
||||
void Run() override;
|
||||
|
||||
private:
|
||||
|
@ -82,7 +104,7 @@ class UpdateSuperTask : public Task {
|
|||
|
||||
class ResizeTask : public Task {
|
||||
public:
|
||||
ResizeTask(FlashingPlan* fp, const std::string& pname, const std::string& size,
|
||||
ResizeTask(const FlashingPlan* fp, const std::string& pname, const std::string& size,
|
||||
const std::string& slot);
|
||||
void Run() override;
|
||||
|
||||
|
@ -105,7 +127,8 @@ class DeleteTask : public Task {
|
|||
|
||||
class WipeTask : public Task {
|
||||
public:
|
||||
WipeTask(FlashingPlan* fp, const std::string& pname);
|
||||
WipeTask(const FlashingPlan* fp, const std::string& pname);
|
||||
virtual WipeTask* AsWipeTask() override { return this; }
|
||||
void Run() override;
|
||||
|
||||
private:
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
//
|
||||
// Copyright (C) 2023 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.
|
||||
//
|
||||
|
||||
#include "task.h"
|
||||
#include "fastboot.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include "android-base/strings.h"
|
||||
using android::base::Split;
|
||||
|
||||
class ParseTest : public ::testing ::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
fp = std::make_unique<FlashingPlan>();
|
||||
fp->slot_override = "b";
|
||||
fp->secondary_slot = "a";
|
||||
fp->wants_wipe = false;
|
||||
}
|
||||
void TearDown() override {}
|
||||
|
||||
std::unique_ptr<FlashingPlan> fp;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
static std::vector<std::unique_ptr<Task>> collectTasks(FlashingPlan* fp,
|
||||
const std::vector<std::string>& commands) {
|
||||
std::vector<std::vector<std::string>> vec_commands;
|
||||
for (auto& command : commands) {
|
||||
vec_commands.emplace_back(android::base::Split(command, " "));
|
||||
}
|
||||
std::vector<std::unique_ptr<Task>> tasks;
|
||||
for (auto& command : vec_commands) {
|
||||
tasks.emplace_back(ParseFastbootInfoLine(fp, command));
|
||||
}
|
||||
return tasks;
|
||||
}
|
||||
|
||||
std::unique_ptr<Task> ParseCommand(FlashingPlan* fp, std::string command) {
|
||||
std::vector<std::string> vec_command = android::base::Split(command, " ");
|
||||
return ParseFastbootInfoLine(fp, vec_command);
|
||||
}
|
||||
|
||||
TEST_F(ParseTest, CORRECT_FlASH_TASK_FORMED) {
|
||||
std::vector<std::string> commands = {"flash dtbo", "flash --slot-other system system_other.img",
|
||||
"flash system", "flash --apply-vbmeta vbmeta"};
|
||||
|
||||
std::vector<std::unique_ptr<Task>> tasks = collectTasks(fp.get(), commands);
|
||||
|
||||
std::vector<std::vector<std::string>> expected_values{
|
||||
{"dtbo", "dtbo_b", "b", "dtbo.img"},
|
||||
{"system", "system_a", "a", "system_other.img"},
|
||||
{"system", "system_b", "b", "system.img"},
|
||||
{"vbmeta", "vbmeta_b", "b", "vbmeta.img"}
|
||||
|
||||
};
|
||||
|
||||
for (auto& task : tasks) {
|
||||
ASSERT_TRUE(task != nullptr);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < tasks.size(); i++) {
|
||||
auto task = tasks[i]->AsFlashTask();
|
||||
ASSERT_TRUE(task != nullptr);
|
||||
ASSERT_EQ(task->GetPartition(), expected_values[i][0]);
|
||||
ASSERT_EQ(task->GetPartitionAndSlot(), expected_values[i][1]);
|
||||
ASSERT_EQ(task->GetSlot(), expected_values[i][2]);
|
||||
ASSERT_EQ(task->GetImageName(), expected_values[i][3]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ParseTest, VERSION_CHECK_CORRRECT) {
|
||||
std::vector<std::string> correct_versions = {
|
||||
"version 1.0",
|
||||
"version 22.00",
|
||||
};
|
||||
|
||||
std::vector<std::string> bad_versions = {"version", "version .01", "version x1",
|
||||
"version 1.0.1", "version 1.", "s 1.0",
|
||||
"version 1.0 2.0"};
|
||||
|
||||
for (auto& version : correct_versions) {
|
||||
ASSERT_TRUE(CheckFastbootInfoRequirements(android::base::Split(version, " "))) << version;
|
||||
}
|
||||
for (auto& version : bad_versions) {
|
||||
ASSERT_FALSE(CheckFastbootInfoRequirements(android::base::Split(version, " "))) << version;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ParseTest, BAD_FASTBOOT_INFO_INPUT) {
|
||||
ASSERT_EQ(ParseCommand(fp.get(), "flash"), nullptr);
|
||||
ASSERT_EQ(ParseCommand(fp.get(), "flash --slot-other --apply-vbmeta"), nullptr);
|
||||
ASSERT_EQ(ParseCommand(fp.get(), "flash --apply-vbmeta"), nullptr);
|
||||
ASSERT_EQ(ParseCommand(fp.get(), "if-wipe"), nullptr);
|
||||
ASSERT_EQ(ParseCommand(fp.get(), "if-wipe flash"), nullptr);
|
||||
ASSERT_EQ(ParseCommand(fp.get(), "wipe dtbo"), nullptr);
|
||||
ASSERT_EQ(ParseCommand(fp.get(), "update-super dtbo"), nullptr);
|
||||
ASSERT_EQ(ParseCommand(fp.get(), "flash system system.img system"), nullptr);
|
||||
ASSERT_EQ(ParseCommand(fp.get(), "reboot bootloader fastboot"), nullptr);
|
||||
ASSERT_EQ(ParseCommand(fp.get(),
|
||||
"flash --slot-other --apply-vbmeta system system_other.img system"),
|
||||
nullptr);
|
||||
ASSERT_EQ(ParseCommand(fp.get(), "erase"), nullptr);
|
||||
ASSERT_EQ(ParseCommand(fp.get(), "erase dtbo dtbo"), nullptr);
|
||||
ASSERT_EQ(ParseCommand(fp.get(), "wipe this"), nullptr);
|
||||
}
|
|
@ -90,6 +90,8 @@ static const struct fs_path_config android_dirs[] = {
|
|||
{ 00755, AID_ROOT, AID_SHELL, 0, "system/vendor" },
|
||||
{ 00750, AID_ROOT, AID_SHELL, 0, "system/xbin" },
|
||||
{ 00751, AID_ROOT, AID_SHELL, 0, "system/apex/*/bin" },
|
||||
{ 00750, AID_ROOT, AID_SYSTEM, 0, "system_ext/apex/com.android.tethering/bin/for-system" },
|
||||
{ 00750, AID_ROOT, AID_SYSTEM, 0, "system_ext/apex/com.android.tethering.inprocess/bin/for-system" },
|
||||
{ 00751, AID_ROOT, AID_SHELL, 0, "system_ext/bin" },
|
||||
{ 00751, AID_ROOT, AID_SHELL, 0, "system_ext/apex/*/bin" },
|
||||
{ 00751, AID_ROOT, AID_SHELL, 0, "vendor/bin" },
|
||||
|
@ -198,6 +200,8 @@ static const struct fs_path_config android_files[] = {
|
|||
// in user builds.
|
||||
{ 06755, AID_CLAT, AID_CLAT, 0, "system/apex/com.android.tethering/bin/for-system/clatd" },
|
||||
{ 06755, AID_CLAT, AID_CLAT, 0, "system/apex/com.android.tethering.inprocess/bin/for-system/clatd" },
|
||||
{ 06755, AID_CLAT, AID_CLAT, 0, "system_ext/apex/com.android.tethering/bin/for-system/clatd" },
|
||||
{ 06755, AID_CLAT, AID_CLAT, 0, "system_ext/apex/com.android.tethering.inprocess/bin/for-system/clatd" },
|
||||
{ 00700, AID_SYSTEM, AID_SHELL, CAP_MASK_LONG(CAP_BLOCK_SUSPEND),
|
||||
"system/bin/inputflinger" },
|
||||
{ 00750, AID_ROOT, AID_SHELL, CAP_MASK_LONG(CAP_SETUID) |
|
||||
|
|
|
@ -96,6 +96,10 @@ void removeAllEmptyProcessGroups(void);
|
|||
// Returns false in case of error, true in case of success
|
||||
bool getAttributePathForTask(const std::string& attr_name, int tid, std::string* path);
|
||||
|
||||
// Check if a profile can be applied without failing.
|
||||
// Returns true if it can be applied without failing, false otherwise
|
||||
bool isProfileValidForProcess(const std::string& profile_name, int uid, int pid);
|
||||
|
||||
#endif // __ANDROID_VNDK__
|
||||
|
||||
__END_DECLS
|
||||
|
|
|
@ -657,3 +657,13 @@ bool setProcessGroupLimit(uid_t, int pid, int64_t limit_in_bytes) {
|
|||
bool getAttributePathForTask(const std::string& attr_name, int tid, std::string* path) {
|
||||
return CgroupGetAttributePathForTask(attr_name, tid, path);
|
||||
}
|
||||
|
||||
bool isProfileValidForProcess(const std::string& profile_name, int uid, int pid) {
|
||||
const TaskProfile* tp = TaskProfiles::GetInstance().GetProfile(profile_name);
|
||||
|
||||
if (tp == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return tp->IsValidForProcess(uid, pid);
|
||||
}
|
|
@ -259,6 +259,31 @@ bool SetAttributeAction::ExecuteForUID(uid_t uid) const {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool SetAttributeAction::IsValidForProcess(uid_t, pid_t pid) const {
|
||||
return IsValidForTask(pid);
|
||||
}
|
||||
|
||||
bool SetAttributeAction::IsValidForTask(int tid) const {
|
||||
std::string path;
|
||||
|
||||
if (!attribute_->GetPathForTask(tid, &path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!access(path.c_str(), W_OK)) {
|
||||
// operation will succeed
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!access(path.c_str(), F_OK)) {
|
||||
// file exists but not writable
|
||||
return false;
|
||||
}
|
||||
|
||||
// file does not exist, ignore if optional
|
||||
return optional_;
|
||||
}
|
||||
|
||||
SetCgroupAction::SetCgroupAction(const CgroupController& c, const std::string& p)
|
||||
: controller_(c), path_(p) {
|
||||
FdCacheHelper::Init(controller_.GetTasksFilePath(path_), fd_[ProfileAction::RCT_TASK]);
|
||||
|
@ -396,6 +421,39 @@ void SetCgroupAction::DropResourceCaching(ResourceCacheType cache_type) {
|
|||
FdCacheHelper::Drop(fd_[cache_type]);
|
||||
}
|
||||
|
||||
bool SetCgroupAction::IsValidForProcess(uid_t uid, pid_t pid) const {
|
||||
std::lock_guard<std::mutex> lock(fd_mutex_);
|
||||
if (FdCacheHelper::IsCached(fd_[ProfileAction::RCT_PROCESS])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fd_[ProfileAction::RCT_PROCESS] == FdCacheHelper::FDS_INACCESSIBLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string procs_path = controller()->GetProcsFilePath(path_, uid, pid);
|
||||
return access(procs_path.c_str(), W_OK) == 0;
|
||||
}
|
||||
|
||||
bool SetCgroupAction::IsValidForTask(int) const {
|
||||
std::lock_guard<std::mutex> lock(fd_mutex_);
|
||||
if (FdCacheHelper::IsCached(fd_[ProfileAction::RCT_TASK])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fd_[ProfileAction::RCT_TASK] == FdCacheHelper::FDS_INACCESSIBLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fd_[ProfileAction::RCT_TASK] == FdCacheHelper::FDS_APP_DEPENDENT) {
|
||||
// application-dependent path can't be used with tid
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string tasks_path = controller()->GetTasksFilePath(path_);
|
||||
return access(tasks_path.c_str(), W_OK) == 0;
|
||||
}
|
||||
|
||||
WriteFileAction::WriteFileAction(const std::string& task_path, const std::string& proc_path,
|
||||
const std::string& value, bool logfailures)
|
||||
: task_path_(task_path), proc_path_(proc_path), value_(value), logfailures_(logfailures) {
|
||||
|
@ -532,6 +590,37 @@ void WriteFileAction::DropResourceCaching(ResourceCacheType cache_type) {
|
|||
FdCacheHelper::Drop(fd_[cache_type]);
|
||||
}
|
||||
|
||||
bool WriteFileAction::IsValidForProcess(uid_t, pid_t) const {
|
||||
std::lock_guard<std::mutex> lock(fd_mutex_);
|
||||
if (FdCacheHelper::IsCached(fd_[ProfileAction::RCT_PROCESS])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fd_[ProfileAction::RCT_PROCESS] == FdCacheHelper::FDS_INACCESSIBLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return access(proc_path_.empty() ? task_path_.c_str() : proc_path_.c_str(), W_OK) == 0;
|
||||
}
|
||||
|
||||
bool WriteFileAction::IsValidForTask(int) const {
|
||||
std::lock_guard<std::mutex> lock(fd_mutex_);
|
||||
if (FdCacheHelper::IsCached(fd_[ProfileAction::RCT_TASK])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fd_[ProfileAction::RCT_TASK] == FdCacheHelper::FDS_INACCESSIBLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fd_[ProfileAction::RCT_TASK] == FdCacheHelper::FDS_APP_DEPENDENT) {
|
||||
// application-dependent path can't be used with tid
|
||||
return false;
|
||||
}
|
||||
|
||||
return access(task_path_.c_str(), W_OK) == 0;
|
||||
}
|
||||
|
||||
bool ApplyProfileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
|
||||
for (const auto& profile : profiles_) {
|
||||
profile->ExecuteForProcess(uid, pid);
|
||||
|
@ -558,6 +647,24 @@ void ApplyProfileAction::DropResourceCaching(ResourceCacheType cache_type) {
|
|||
}
|
||||
}
|
||||
|
||||
bool ApplyProfileAction::IsValidForProcess(uid_t uid, pid_t pid) const {
|
||||
for (const auto& profile : profiles_) {
|
||||
if (!profile->IsValidForProcess(uid, pid)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ApplyProfileAction::IsValidForTask(int tid) const {
|
||||
for (const auto& profile : profiles_) {
|
||||
if (!profile->IsValidForTask(tid)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TaskProfile::MoveTo(TaskProfile* profile) {
|
||||
profile->elements_ = std::move(elements_);
|
||||
profile->res_cached_ = res_cached_;
|
||||
|
@ -620,6 +727,20 @@ void TaskProfile::DropResourceCaching(ProfileAction::ResourceCacheType cache_typ
|
|||
res_cached_ = false;
|
||||
}
|
||||
|
||||
bool TaskProfile::IsValidForProcess(uid_t uid, pid_t pid) const {
|
||||
for (const auto& element : elements_) {
|
||||
if (!element->IsValidForProcess(uid, pid)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TaskProfile::IsValidForTask(int tid) const {
|
||||
for (const auto& element : elements_) {
|
||||
if (!element->IsValidForTask(tid)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TaskProfiles::DropResourceCaching(ProfileAction::ResourceCacheType cache_type) const {
|
||||
for (auto& iter : profiles_) {
|
||||
iter.second->DropResourceCaching(cache_type);
|
||||
|
|
|
@ -72,12 +72,14 @@ class ProfileAction {
|
|||
virtual const char* Name() const = 0;
|
||||
|
||||
// Default implementations will fail
|
||||
virtual bool ExecuteForProcess(uid_t, pid_t) const { return false; };
|
||||
virtual bool ExecuteForTask(int) const { return false; };
|
||||
virtual bool ExecuteForUID(uid_t) const { return false; };
|
||||
virtual bool ExecuteForProcess(uid_t, pid_t) const { return false; }
|
||||
virtual bool ExecuteForTask(int) const { return false; }
|
||||
virtual bool ExecuteForUID(uid_t) const { return false; }
|
||||
|
||||
virtual void EnableResourceCaching(ResourceCacheType) {}
|
||||
virtual void DropResourceCaching(ResourceCacheType) {}
|
||||
virtual bool IsValidForProcess(uid_t uid, pid_t pid) const { return false; }
|
||||
virtual bool IsValidForTask(int tid) const { return false; }
|
||||
|
||||
protected:
|
||||
enum CacheUseResult { SUCCESS, FAIL, UNUSED };
|
||||
|
@ -103,6 +105,8 @@ class SetTimerSlackAction : public ProfileAction {
|
|||
|
||||
const char* Name() const override { return "SetTimerSlack"; }
|
||||
bool ExecuteForTask(int tid) const override;
|
||||
bool IsValidForProcess(uid_t uid, pid_t pid) const override { return true; }
|
||||
bool IsValidForTask(int tid) const override { return true; }
|
||||
|
||||
private:
|
||||
unsigned long slack_;
|
||||
|
@ -120,6 +124,8 @@ class SetAttributeAction : public ProfileAction {
|
|||
bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
|
||||
bool ExecuteForTask(int tid) const override;
|
||||
bool ExecuteForUID(uid_t uid) const override;
|
||||
bool IsValidForProcess(uid_t uid, pid_t pid) const override;
|
||||
bool IsValidForTask(int tid) const override;
|
||||
|
||||
private:
|
||||
const IProfileAttribute* attribute_;
|
||||
|
@ -137,6 +143,8 @@ class SetCgroupAction : public ProfileAction {
|
|||
bool ExecuteForTask(int tid) const override;
|
||||
void EnableResourceCaching(ResourceCacheType cache_type) override;
|
||||
void DropResourceCaching(ResourceCacheType cache_type) override;
|
||||
bool IsValidForProcess(uid_t uid, pid_t pid) const override;
|
||||
bool IsValidForTask(int tid) const override;
|
||||
|
||||
const CgroupController* controller() const { return &controller_; }
|
||||
|
||||
|
@ -161,6 +169,8 @@ class WriteFileAction : public ProfileAction {
|
|||
bool ExecuteForTask(int tid) const override;
|
||||
void EnableResourceCaching(ResourceCacheType cache_type) override;
|
||||
void DropResourceCaching(ResourceCacheType cache_type) override;
|
||||
bool IsValidForProcess(uid_t uid, pid_t pid) const override;
|
||||
bool IsValidForTask(int tid) const override;
|
||||
|
||||
private:
|
||||
std::string task_path_, proc_path_, value_;
|
||||
|
@ -186,6 +196,8 @@ class TaskProfile {
|
|||
bool ExecuteForUID(uid_t uid) const;
|
||||
void EnableResourceCaching(ProfileAction::ResourceCacheType cache_type);
|
||||
void DropResourceCaching(ProfileAction::ResourceCacheType cache_type);
|
||||
bool IsValidForProcess(uid_t uid, pid_t pid) const;
|
||||
bool IsValidForTask(int tid) const;
|
||||
|
||||
private:
|
||||
const std::string name_;
|
||||
|
@ -204,6 +216,8 @@ class ApplyProfileAction : public ProfileAction {
|
|||
bool ExecuteForTask(int tid) const override;
|
||||
void EnableResourceCaching(ProfileAction::ResourceCacheType cache_type) override;
|
||||
void DropResourceCaching(ProfileAction::ResourceCacheType cache_type) override;
|
||||
bool IsValidForProcess(uid_t uid, pid_t pid) const override;
|
||||
bool IsValidForTask(int tid) const override;
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<TaskProfile>> profiles_;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include "task_profiles.h"
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/strings.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mntent.h>
|
||||
#include <processgroup/processgroup.h>
|
||||
|
@ -29,13 +30,14 @@ using ::android::base::LogFunction;
|
|||
using ::android::base::LogId;
|
||||
using ::android::base::LogSeverity;
|
||||
using ::android::base::SetLogger;
|
||||
using ::android::base::Split;
|
||||
using ::android::base::VERBOSE;
|
||||
using ::testing::TestWithParam;
|
||||
using ::testing::Values;
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsCgroupV2Mounted() {
|
||||
bool IsCgroupV2MountedRw() {
|
||||
std::unique_ptr<FILE, int (*)(FILE*)> mnts(setmntent("/proc/mounts", "re"), endmntent);
|
||||
if (!mnts) {
|
||||
LOG(ERROR) << "Failed to open /proc/mounts";
|
||||
|
@ -43,9 +45,11 @@ bool IsCgroupV2Mounted() {
|
|||
}
|
||||
struct mntent* mnt;
|
||||
while ((mnt = getmntent(mnts.get()))) {
|
||||
if (strcmp(mnt->mnt_type, "cgroup2") == 0) {
|
||||
return true;
|
||||
if (strcmp(mnt->mnt_type, "cgroup2") != 0) {
|
||||
continue;
|
||||
}
|
||||
const std::vector<std::string> options = Split(mnt->mnt_opts, ",");
|
||||
return std::count(options.begin(), options.end(), "ro") == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -145,8 +149,9 @@ class SetAttributeFixture : public TestWithParam<TestParam> {
|
|||
};
|
||||
|
||||
TEST_P(SetAttributeFixture, SetAttribute) {
|
||||
// Treehugger runs host tests inside a container without cgroupv2 support.
|
||||
if (!IsCgroupV2Mounted()) {
|
||||
// Treehugger runs host tests inside a container either without cgroupv2
|
||||
// support or with the cgroup filesystem mounted read-only.
|
||||
if (!IsCgroupV2MountedRw()) {
|
||||
GTEST_SKIP();
|
||||
return;
|
||||
}
|
||||
|
@ -170,6 +175,32 @@ TEST_P(SetAttributeFixture, SetAttribute) {
|
|||
}
|
||||
}
|
||||
|
||||
class TaskProfileFixture : public TestWithParam<TestParam> {
|
||||
public:
|
||||
~TaskProfileFixture() = default;
|
||||
};
|
||||
|
||||
TEST_P(TaskProfileFixture, TaskProfile) {
|
||||
// Treehugger runs host tests inside a container without cgroupv2 support.
|
||||
if (!IsCgroupV2MountedRw()) {
|
||||
GTEST_SKIP();
|
||||
return;
|
||||
}
|
||||
const TestParam params = GetParam();
|
||||
ProfileAttributeMock pa(params.attr_name);
|
||||
// Test simple profile with one action
|
||||
std::shared_ptr<TaskProfile> tp = std::make_shared<TaskProfile>("test_profile");
|
||||
tp->Add(std::make_unique<SetAttributeAction>(&pa, params.attr_value, params.optional_attr));
|
||||
EXPECT_EQ(tp->IsValidForProcess(getuid(), getpid()), params.result);
|
||||
EXPECT_EQ(tp->IsValidForTask(getpid()), params.result);
|
||||
// Test aggregate profile
|
||||
TaskProfile tp2("meta_profile");
|
||||
std::vector<std::shared_ptr<TaskProfile>> profiles = {tp};
|
||||
tp2.Add(std::make_unique<ApplyProfileAction>(profiles));
|
||||
EXPECT_EQ(tp2.IsValidForProcess(getuid(), getpid()), params.result);
|
||||
EXPECT_EQ(tp2.IsValidForTask(getpid()), params.result);
|
||||
}
|
||||
|
||||
// Test the four combinations of optional_attr {false, true} and cgroup attribute { does not exist,
|
||||
// exists }.
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
|
@ -210,4 +241,28 @@ INSTANTIATE_TEST_SUITE_P(
|
|||
.log_prefix = "Failed to write",
|
||||
.log_suffix = geteuid() == 0 ? "Invalid argument" : "Permission denied"}));
|
||||
|
||||
// Test TaskProfile IsValid calls.
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
TaskProfileTestSuite, TaskProfileFixture,
|
||||
Values(
|
||||
// Test operating on non-existing cgroup attribute fails.
|
||||
TestParam{.attr_name = "no-such-attribute",
|
||||
.attr_value = ".",
|
||||
.optional_attr = false,
|
||||
.result = false},
|
||||
// Test operating on optional non-existing cgroup attribute succeeds.
|
||||
TestParam{.attr_name = "no-such-attribute",
|
||||
.attr_value = ".",
|
||||
.optional_attr = true,
|
||||
.result = true},
|
||||
// Test operating on existing cgroup attribute succeeds.
|
||||
TestParam{.attr_name = "cgroup.procs",
|
||||
.attr_value = ".",
|
||||
.optional_attr = false,
|
||||
.result = true},
|
||||
// Test operating on optional existing cgroup attribute succeeds.
|
||||
TestParam{.attr_name = "cgroup.procs",
|
||||
.attr_value = ".",
|
||||
.optional_attr = true,
|
||||
.result = true}));
|
||||
} // namespace
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
dir dev 0755 0 0
|
||||
nod dev/null 0600 0 0 c 1 3
|
||||
nod dev/console 0600 0 0 c 5 1
|
||||
nod dev/urandom 0600 0 0 c 1 9
|
||||
|
|
Loading…
Reference in New Issue