Log the file format of cros_install on postinstall action
This patch adds a new function to utils which determines the format
of a file based on magic constants on the header and returns a
human-readable description of it. This currently only supports ELF
files and is used to log the file format of the cros_install
binary on post-install.
BUG=chromium:356187
TEST=Unittests.
Change-Id: Ie6e91c3f5fa398c39894704db9071489560a8ff7
Reviewed-on: https://chromium-review.googlesource.com/191609
Tested-by: Alex Deymo <deymo@chromium.org>
Reviewed-by: Don Garrett <dgarrett@chromium.org>
Commit-Queue: Alex Deymo <deymo@chromium.org>
diff --git a/postinstall_runner_action.cc b/postinstall_runner_action.cc
index 6be13e1..f8bcc36 100644
--- a/postinstall_runner_action.cc
+++ b/postinstall_runner_action.cc
@@ -18,7 +18,13 @@
using std::vector;
namespace {
+// The absolute path to the post install command.
const char kPostinstallScript[] = "/postinst";
+
+// Path to the binary file used by kPostinstallScript. Used to get and log the
+// file format of the binary to debug issues when the ELF format on the update
+// doesn't match the one on the current system. This path is not executed.
+const char kDebugPostinstallBinaryPath[] = "/usr/bin/cros_installer";
}
void PostinstallRunnerAction::PerformAction() {
@@ -66,6 +72,15 @@
}
}
+ // Logs the file format of the postinstall script we are about to run. This
+ // will help debug when the postinstall script doesn't match the architecture
+ // of our build.
+ LOG(INFO) << "Format file for new " << kPostinstallScript << " is: "
+ << utils::GetFileFormat(temp_rootfs_dir_ + kPostinstallScript);
+ LOG(INFO) << "Format file for new " << kDebugPostinstallBinaryPath << " is: "
+ << utils::GetFileFormat(
+ temp_rootfs_dir_ + kDebugPostinstallBinaryPath);
+
// Runs the postinstall script asynchronously to free up the main loop while
// it's running.
vector<string> command;
diff --git a/utils.cc b/utils.cc
index 7558f35..70b9c75 100644
--- a/utils.cc
+++ b/utils.cc
@@ -56,6 +56,11 @@
// one second.
const int kUnmountMaxNumOfRetries = 5;
const int kUnmountRetryIntervalInMicroseconds = 200 * 1000; // 200 ms
+
+// Number of bytes to read from a file to attempt to detect its contents. Used
+// in GetFileFormat.
+const int kGetFileFormatMaxHeaderSize = 32;
+
} // namespace
namespace utils {
@@ -665,6 +670,83 @@
return true;
}
+// Tries to parse the header of an ELF file to obtain a human-readable
+// description of it on the |output| string.
+static bool GetFileFormatELF(const char* buffer, size_t size, string* output) {
+ // 0x00: EI_MAG - ELF magic header, 4 bytes.
+ if (size < 4 || memcmp(buffer, "\x7F""ELF", 4) != 0)
+ return false;
+ *output = "ELF";
+
+ // 0x04: EI_CLASS, 1 byte.
+ if (size < 0x04 + 1)
+ return true;
+ switch (buffer[4]) {
+ case 1:
+ *output += " 32-bit";
+ break;
+ case 2:
+ *output += " 64-bit";
+ break;
+ default:
+ *output += " ?-bit";
+ }
+
+ // 0x05: EI_DATA, endianness, 1 byte.
+ if (size < 0x05 + 1)
+ return true;
+ char ei_data = buffer[5];
+ switch (ei_data) {
+ case 1:
+ *output += " little-endian";
+ break;
+ case 2:
+ *output += " big-endian";
+ break;
+ default:
+ *output += " ?-endian";
+ // Don't parse anything after the 0x10 offset if endianness is unknown.
+ return true;
+ }
+
+ // 0x12: e_machine, 2 byte endianness based on ei_data
+ if (size < 0x12 + 2)
+ return true;
+ uint16 e_machine = *reinterpret_cast<const uint16*>(buffer+0x12);
+ // Fix endianess regardless of the host endianess.
+ if (ei_data == 1)
+ e_machine = le16toh(e_machine);
+ else
+ e_machine = be16toh(e_machine);
+
+ switch (e_machine) {
+ case 0x03:
+ *output += " x86";
+ break;
+ case 0x28:
+ *output += " arm";
+ break;
+ case 0x3E:
+ *output += " x86-64";
+ break;
+ default:
+ *output += " unknown-arch";
+ }
+ return true;
+}
+
+string GetFileFormat(const string& path) {
+ vector<char> buffer;
+ if (!ReadFileChunkAndAppend(path, 0, kGetFileFormatMaxHeaderSize, &buffer))
+ return "File not found.";
+
+ string result;
+ if (GetFileFormatELF(buffer.data(), buffer.size(), &result))
+ return result;
+
+ return "data";
+}
+
bool GetBootloader(BootLoader* out_bootloader) {
// For now, hardcode to syslinux.
*out_bootloader = BootLoader_SYSLINUX;
diff --git a/utils.h b/utils.h
index f404653..e129cbe 100644
--- a/utils.h
+++ b/utils.h
@@ -193,6 +193,11 @@
int* out_block_count,
int* out_block_size);
+
+// Returns a human-readable string with the file format based on magic constants
+// on the header of the file.
+std::string GetFileFormat(const std::string& path);
+
// Returns the string representation of the given UTC time.
// such as "11/14/2011 14:05:30 GMT".
std::string ToString(const base::Time utc_time);
diff --git a/utils_unittest.cc b/utils_unittest.cc
index d75a6b0..ef771b9 100644
--- a/utils_unittest.cc
+++ b/utils_unittest.cc
@@ -327,6 +327,48 @@
}
namespace {
+void GetFileFormatTester(const string& expected,
+ const vector<uint8>& contents) {
+ ScopedTempFile file;
+ ASSERT_TRUE(utils::WriteFile(file.GetPath().c_str(),
+ reinterpret_cast<const char*>(contents.data()),
+ contents.size()));
+ EXPECT_EQ(expected, utils::GetFileFormat(file.GetPath()));
+}
+}
+
+TEST(UtilsTest, GetFileFormatTest) {
+ EXPECT_EQ("File not found.", utils::GetFileFormat("/path/to/nowhere"));
+ GetFileFormatTester("data", vector<uint8>{1, 2, 3, 4, 5, 6, 7, 8});
+ GetFileFormatTester("ELF", vector<uint8>{0x7f, 0x45, 0x4c, 0x46});
+
+ // Real tests from cros_installer on different boards.
+ // ELF 32-bit LSB executable, Intel 80386
+ GetFileFormatTester(
+ "ELF 32-bit little-endian x86",
+ vector<uint8>{0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x90, 0x83, 0x04, 0x08, 0x34, 0x00, 0x00, 0x00});
+
+ // ELF 32-bit LSB executable, ARM
+ GetFileFormatTester(
+ "ELF 32-bit little-endian arm",
+ vector<uint8>{0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x28, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x85, 0x8b, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00});
+
+ // ELF 64-bit LSB executable, x86-64
+ GetFileFormatTester(
+ "ELF 64-bit little-endian x86-64",
+ vector<uint8>{0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xb0, 0x04, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00});
+}
+
+namespace {
gboolean TerminateScheduleCrashReporterUploadTest(void* arg) {
GMainLoop* loop = reinterpret_cast<GMainLoop*>(arg);
g_main_loop_quit(loop);