blob: 84449cf5fe78a44907833d75c2cf3185c1cb812b [file] [log] [blame]
// Copyright 2015 The Android Open Source Project
//
// This software is licensed under the terms of the GNU General Public
// License version 2, as published by the Free Software Foundation, and
// may be copied, distributed, and modified under those terms.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
#include "android/filesystems/partition_config.h"
#include "android/base/system/System.h"
#include "android/filesystems/internal/PartitionConfigBackend.h"
#include "android/utils/debug.h"
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
using android::internal::PartitionConfigBackend;
using android::base::System;
// State structure shared by several functions.
typedef struct {
char** error_message;
AndroidPartitionSetupFunction setup_func;
void* setup_opaque;
PartitionConfigBackend* backend;
} PartitionConfigState;
// Helper function used to record an error message into the |state|,
// then return false. |format| is a typical printf()-like formatting
// string followed by optional arguments. Always return false to
// indicate an error.
static bool partition_config_error(PartitionConfigState* state,
const char* format,
...) {
va_list args;
va_start(args, format);
vasprintf(state->error_message, format, args);
va_end(args);
return false;
}
// Extract the partition type/format of a given partition image
// from the content of fstab.goldfish.
// |fstab| is the address of the fstab.goldfish data in memory.
// |fstabSize| is its size in bytes.
// |partitionName| is the name of the partition for debugging
// purposes (e.g. 'userdata').
// |partitionPath| is the partition path as it appears in the
// fstab file (e.g. '/data').
// On success, sets |*partitionType| to an appropriate value,
// on failure (i.e. |partitionPath| does not appear in the fstab
// file), leave the value untouched.
static void extractPartitionFormat(PartitionConfigState* state,
const std::string& fstab,
const char* partitionName,
const char* partitionPath,
AndroidPartitionType* partitionType) {
std::string partFormat;
if (!state->backend->parsePartitionFormat(fstab, partitionPath,
&partFormat)) {
VERBOSE_PRINT(init, "Could not extract format of %s partition!",
partitionName);
return;
}
VERBOSE_PRINT(init, "Found format of %s partition: '%s'", partitionName,
partFormat.c_str());
*partitionType = androidPartitionType_fromString(partFormat.c_str());
}
// List of value describing how to handle partition images in
// addNandImage() below, when no initial partition image
// file is provided.
//
// MUST_EXIST means that the partition image must exist, otherwise
// dump an error message and exit.
//
// CREATE_IF_NEEDED means that if the partition image doesn't exist, an
// empty partition file should be created on demand.
//
// MUST_WIPE means that the partition image should be wiped cleaned,
// even if it exists. This is useful to implement the -wipe-data option.
typedef enum {
ANDROID_PARTITION_OPEN_MODE_MUST_EXIST,
ANDROID_PARTITION_OPEN_MODE_CREATE_IF_NEEDED,
ANDROID_PARTITION_OPEN_MODE_MUST_WIPE,
} AndroidPartitionOpenMode;
// Add a NAND partition image to the hardware configuration.
//
// |part_name| is a string indicating the type of partition, i.e. "system",
// "userdata" or "cache".
// |part_type| is an enum describing the type of partition. If it is
// DISK_PARTITION_TYPE_PROBE, then try to auto-detect the type directly
// from the content of |part_file| or |part_init_file|.
// |part_mode| is an enum describing how to handle the partition image,
// see AndroidPartitionOpenMode for details.
// |part_size| is the partition size in bytes.
// |part_file| is the partition file path, can be NULL if |path_init_file|
// is not NULL.
// |part_init_file| is an optional path to the initialization partition file.
//
// The NAND partition will be backed by |part_file|, except in the following
// cases:
// - |part_file| is NULL, or its value is "<temp>", indicating that a
// new temporary image file must be used instead.
//
// - |part_file| is not NULL, but the function fails to lock the file,
// indicating it's already used by another instance. A warning should
// be printed to warn the user, and a new temporary image should be
// used.
//
// If |part_file| is not NULL and can be locked, if the partition image does
// not exit, then the file must be created as an empty partition.
//
// If |part_init_file| is not NULL, its content will be used to erase
// the content of the main partition image. This is automatically handled
// by the NAND code though.
//
// If |readonly| is true, then either the |part_file| or |part_init_file| will
// be mounted as read-only devices. This also prevents locking the partition
// image and creation of temporary copies.
//
static bool addNandImage(PartitionConfigState* state,
const char* part_name,
AndroidPartitionType part_type,
AndroidPartitionOpenMode part_mode,
uint64_t part_size,
const char* part_file,
const char* part_init_file,
bool readonly) {
// Sanitize parameters, an empty string must be the same as NULL.
if (part_file && !*part_file) {
part_file = NULL;
}
if (part_init_file && !*part_init_file) {
part_init_file = NULL;
}
// Sanity checks.
if (part_size == 0) {
return partition_config_error(
state, "Invalid %s partition size 0x%" PRIx64,
part_name, part_size);
}
if (part_init_file && !state->backend->pathExists(part_init_file)) {
return partition_config_error(state, "Missing initial %s image: %s",
part_name, part_init_file);
}
// In read-only mode, the initial partition image can be treated as
// the main one.
if (readonly && !part_file && part_init_file) {
part_file = part_init_file;
part_init_file = nullptr;
}
// As a special case, a |part_file| of '<temp>' means a temporary
// partition is needed.
if (part_file && !strcmp(part_file, "<temp>")) {
part_file = NULL;
}
// Verify partition type, or probe it if needed.
{
// First determine which image file to probe.
const char* image_file = NULL;
if (part_file && state->backend->pathExists(part_file)) {
image_file = part_file;
} else if (part_init_file) {
image_file = part_init_file;
} else if (part_type == ANDROID_PARTITION_TYPE_UNKNOWN) {
return partition_config_error(
state,
"Cannot determine type of %s partition: no image files!",
part_name);
}
if (part_type == ANDROID_PARTITION_TYPE_UNKNOWN) {
VERBOSE_PRINT(init, "Probing %s image file for partition type: %s",
part_name, image_file);
part_type = state->backend->probePartitionFileType(image_file);
} else if (image_file) {
// Probe the current image file to check that it is of the
// right partition format.
AndroidPartitionType image_type =
state->backend->probePartitionFileType(image_file);
if (image_type == ANDROID_PARTITION_TYPE_UNKNOWN) {
return partition_config_error(
state, "Cannot determine %s partition type of: %s",
part_name, image_file);
}
if (image_type != part_type) {
// The image file exists, but is not in the proper format!
// This can happen in certain cases, e.g. a KitKat/x86 AVD
// created with SDK 23.0.2 and started with the
// corresponding emulator will create a cache.img in 'yaffs2'
// format, while the system really expect it to be 'ext4',
// as listed in the ramdisk.img.
//
// To work-around the problem, simply re-create the file
// by wiping it when allowed.
if (part_mode == ANDROID_PARTITION_OPEN_MODE_MUST_EXIST) {
return partition_config_error(
state,
"Invalid %s partition image type: %s (expected %s)",
part_name,
androidPartitionType_toString(image_type),
androidPartitionType_toString(part_type));
}
VERBOSE_PRINT(init,
"Image type mismatch for %s partition: "
"%s (expected %s)",
part_name,
androidPartitionType_toString(image_type),
androidPartitionType_toString(part_type));
part_mode = ANDROID_PARTITION_OPEN_MODE_MUST_WIPE;
}
}
}
VERBOSE_PRINT(init, "%s partition format: %s", part_name,
androidPartitionType_toString(part_type));
bool need_make_empty = (part_mode == ANDROID_PARTITION_OPEN_MODE_MUST_WIPE);
// Must be here to avoid freeing the string too early.
std::string tempFile;
if (readonly) {
if (!state->backend->pathExists(part_file)) {
return partition_config_error(
state, "Missing read-only %s partition image: %s",
part_name, part_file);
}
need_make_empty = false;
} else {
bool need_temp_partition = true;
if (part_init_file) {
need_make_empty = true;
}
if (part_file) {
if (!state->backend->pathLockFile(part_file)) {
dwarning("%s image already in use, changes will not persist!\n",
part_name);
} else {
need_temp_partition = false;
// If the partition image is missing, create it.
if (!state->backend->pathExists(part_file)) {
if (part_mode == ANDROID_PARTITION_OPEN_MODE_MUST_EXIST) {
return partition_config_error(
state, "Missing %s partition image: %s",
part_name, part_file);
}
if (!state->backend->pathEmptyFile(part_file) < 0) {
return partition_config_error(
state, "Cannot create %s image file at %s: %s",
part_name, part_file, strerror(errno));
}
need_make_empty = true;
}
}
}
// Do we need a temporary partition image ?
if (need_temp_partition) {
if (!state->backend->pathCreateTempFile(&tempFile)) {
return partition_config_error(state,
"Could not create temp file for "
"%s partition image: %s\n",
part_name, strerror(errno));
}
part_file = tempFile.c_str();
VERBOSE_PRINT(init, "Mapping '%s' partition image to %s", part_name,
part_file);
need_make_empty = true;
}
// Do we need to copy the initial partition file into the real one?
if (part_init_file) {
if (!state->backend->pathCopyFile(part_file, part_init_file)) {
return partition_config_error(
state,
"Could not copy initial %s partition "
"image to real one: %s\n",
part_name, strerror(errno));
}
need_make_empty = false;
}
}
// Do we need to make the partition image empty?
if (need_make_empty) {
VERBOSE_PRINT(init, "Creating empty %s partition image at: %s",
part_name, part_file);
int ret = state->backend->makeEmptyPartition(part_type, part_size,
part_file);
if (ret < 0) {
return partition_config_error(
state, "Could not create %s image file at %s: %s",
part_name, part_file, strerror(-ret));
}
}
(*state->setup_func)(state->setup_opaque, part_name, part_size, part_file,
part_type, readonly);
return true;
}
bool android_partition_configuration_setup(
const AndroidPartitionConfiguration* config,
AndroidPartitionSetupFunction setup_func,
void* setup_opaque,
char** error_message) {
// Setup shared state.
PartitionConfigState state[1] = {{
.error_message = error_message,
.setup_func = setup_func,
.setup_opaque = setup_opaque,
.backend = PartitionConfigBackend::get(),
}};
// Determine format of all partition images, if possible.
// Note that _UNKNOWN means the file, if it exists, will be probed.
AndroidPartitionType system_partition_type = ANDROID_PARTITION_TYPE_UNKNOWN;
AndroidPartitionType userdata_partition_type =
ANDROID_PARTITION_TYPE_UNKNOWN;
AndroidPartitionType cache_partition_type = ANDROID_PARTITION_TYPE_UNKNOWN;
{
// Starting with Android 4.4.x, the ramdisk.img contains
// an fstab.goldfish file that lists the format of each partition.
// If the file exists, parse it to get the appropriate values.
std::string fstab;
if (state->backend->extractRamdiskFile(config->ramdisk_path,
config->fstab_name, &fstab)) {
VERBOSE_PRINT(init, "Ramdisk image contains %s file",
config->fstab_name);
extractPartitionFormat(state, fstab, "system", "/system",
&system_partition_type);
extractPartitionFormat(state, fstab, "userdata", "/data",
&userdata_partition_type);
extractPartitionFormat(state, fstab, "cache", "/cache",
&cache_partition_type);
} else {
VERBOSE_PRINT(init, "No %s file in ramdisk image",
config->fstab_name);
}
}
// Initialize system partition image.
if (!addNandImage(
state, "system", system_partition_type,
ANDROID_PARTITION_OPEN_MODE_MUST_EXIST,
config->system_partition.size, config->system_partition.path,
config->system_partition.init_path, !config->writable_system)) {
return false;
}
// Initialize data partition image.
AndroidPartitionOpenMode userdata_partition_mode = config->wipe_data ?
ANDROID_PARTITION_OPEN_MODE_MUST_WIPE :
ANDROID_PARTITION_OPEN_MODE_CREATE_IF_NEEDED;
if (!addNandImage(state, "userdata", userdata_partition_type,
userdata_partition_mode, config->data_partition.size,
config->data_partition.path,
config->data_partition.init_path, false)) {
return false;
}
// Extend the userdata-qemu.img to the desired size - resize2fs can only
// extend partitions to fill available space.
// Resize userdata-qemu.img if the size is smaller than what config.ini
// says.
// This can happen as user wants a larger data partition without wiping it.
// b.android.com/196926
System::FileSize current_data_size(config->data_partition.size);
System::get()->pathFileSize(config->data_partition.path,
&current_data_size);
if ((config->wipe_data ||
current_data_size < config->data_partition.size) &&
userdata_partition_type == ANDROID_PARTITION_TYPE_EXT4) {
if (current_data_size < config->data_partition.size) {
VERBOSE_PRINT(init,
"userdata partition is resized from %d M to %d M\n",
current_data_size / (1024 * 1024),
config->data_partition.size / (1024 * 1024));
}
state->backend->resizeExt4Partition(config->data_partition.path,
config->data_partition.size);
}
// Initialize cache partition image, if any. Its type depends on the
// kernel version. For anything >= 3.10, it must be EXT4, or
// YAFFS2 otherwise.
if (config->cache_partition.path) {
if (cache_partition_type == ANDROID_PARTITION_TYPE_UNKNOWN) {
cache_partition_type = config->kernel_supports_yaffs2
? ANDROID_PARTITION_TYPE_YAFFS2
: ANDROID_PARTITION_TYPE_EXT4;
}
AndroidPartitionOpenMode cache_partition_mode =
(config->wipe_data
? ANDROID_PARTITION_OPEN_MODE_MUST_WIPE
: ANDROID_PARTITION_OPEN_MODE_CREATE_IF_NEEDED);
if (!addNandImage(state, "cache", cache_partition_type,
cache_partition_mode, config->cache_partition.size,
config->cache_partition.path, NULL, false)) {
return false;
}
}
return true;
}