AU: Update Downloader to support our image formats.
The downloader used to be dumb in the sense that it would pipe output
to either a DirectFileWriter or a DirectFileWriter via a
GzipDecompressingFileWriter, depending on if we were downloading an
update that was compressed or not. Sadly, things have gotten more
complex: we need to download to two partitions (kernel + rootfs), and
we may stream data via a DeltaPerformer (a type of FileWriter) to the
disk. Thus, the Downloader streams to either
1. gzip decompress->split_writer->direct to disk OR
2. delta performer
Other misc changes: Change FilesystemCopierAction to support
optionally copying the kernel partition rather than root partition.
InstallPlan struct: add an entry for destiation kernel partition.
Test Utils: a new ScopedTempFile class
Utils: support for getting the booted kernel partition device.
BUG=None
TEST=attached unittests
Review URL: http://codereview.chromium.org/1694025
diff --git a/download_action.cc b/download_action.cc
index 6da6719..5387821 100644
--- a/download_action.cc
+++ b/download_action.cc
@@ -13,44 +13,60 @@
namespace chromeos_update_engine {
DownloadAction::DownloadAction(HttpFetcher* http_fetcher)
- : size_(0),
- should_decompress_(false),
- writer_(NULL),
+ : writer_(NULL),
http_fetcher_(http_fetcher) {}
DownloadAction::~DownloadAction() {}
void DownloadAction::PerformAction() {
http_fetcher_->set_delegate(this);
- CHECK(!writer_);
- direct_file_writer_.reset(new DirectFileWriter);
// Get the InstallPlan and read it
CHECK(HasInputObject());
- InstallPlan install_plan(GetInputObject());
+ install_plan_ = GetInputObject();
- should_decompress_ = install_plan.is_full_update;
- url_ = install_plan.download_url;
- output_path_ = install_plan.install_path;
- hash_ = install_plan.download_hash;
- install_plan.Dump();
+ install_plan_.Dump();
- if (should_decompress_) {
- decompressing_file_writer_.reset(
- new GzipDecompressingFileWriter(direct_file_writer_.get()));
- writer_ = decompressing_file_writer_.get();
+ if (writer_) {
+ LOG(INFO) << "Using writer for test.";
} else {
- writer_ = direct_file_writer_.get();
+ if (install_plan_.is_full_update) {
+ kernel_file_writer_.reset(new DirectFileWriter);
+ rootfs_file_writer_.reset(new DirectFileWriter);
+ split_file_writer_.reset(new SplitFileWriter(kernel_file_writer_.get(),
+ rootfs_file_writer_.get()));
+ split_file_writer_->SetFirstOpenArgs(
+ install_plan_.kernel_install_path.c_str(),
+ O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE,
+ 0644);
+ decompressing_file_writer_.reset(
+ new GzipDecompressingFileWriter(split_file_writer_.get()));
+ writer_ = decompressing_file_writer_.get();
+ } else {
+ delta_performer_.reset(new DeltaPerformer);
+ writer_ = delta_performer_.get();
+ }
}
- int rc = writer_->Open(output_path_.c_str(),
- O_TRUNC | O_WRONLY | O_CREAT | O_LARGEFILE, 0644);
+ int rc = writer_->Open(install_plan_.install_path.c_str(),
+ O_TRUNC | O_WRONLY | O_CREAT | O_LARGEFILE,
+ 0644);
if (rc < 0) {
- LOG(ERROR) << "Unable to open output file " << output_path_;
+ LOG(ERROR) << "Unable to open output file " << install_plan_.install_path;
// report error to processor
processor_->ActionComplete(this, false);
return;
}
- http_fetcher_->BeginTransfer(url_);
+ if (!install_plan_.is_full_update) {
+ if (!delta_performer_->OpenKernel(
+ install_plan_.kernel_install_path.c_str())) {
+ LOG(ERROR) << "Unable to open kernel file "
+ << install_plan_.kernel_install_path.c_str();
+ writer_->Close();
+ processor_->ActionComplete(this, false);
+ return;
+ }
+ }
+ http_fetcher_->BeginTransfer(install_plan_.download_url);
}
void DownloadAction::TerminateProcessing() {
@@ -76,9 +92,10 @@
if (successful) {
// Make sure hash is correct
omaha_hash_calculator_.Finalize();
- if (omaha_hash_calculator_.hash() != hash_) {
- LOG(ERROR) << "Download of " << url_ << " failed. Expect hash "
- << hash_ << " but got hash " << omaha_hash_calculator_.hash();
+ if (omaha_hash_calculator_.hash() != install_plan_.download_hash) {
+ LOG(ERROR) << "Download of " << install_plan_.download_url
+ << " failed. Expect hash " << install_plan_.download_hash
+ << " but got hash " << omaha_hash_calculator_.hash();
successful = false;
}
}
diff --git a/download_action.h b/download_action.h
index d5ec026..0f375fa 100644
--- a/download_action.h
+++ b/download_action.h
@@ -16,13 +16,20 @@
#include "base/scoped_ptr.h"
#include "update_engine/action.h"
#include "update_engine/decompressing_file_writer.h"
+#include "update_engine/delta_performer.h"
#include "update_engine/file_writer.h"
#include "update_engine/http_fetcher.h"
#include "update_engine/install_plan.h"
#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/split_file_writer.h"
-// The Download Action downloads a requested url to a specified path on disk.
-// The url and output path are determined by the InstallPlan passed in.
+// The Download Action downloads a specified url to disk. The url should
+// point to either a full or delta update. If a full update, the file will
+// be piped into a SplitFileWriter, which will direct it to the kernel
+// and rootfs partitions. If it's a delta update, the destination kernel
+// and rootfs should already contain the source-version that this delta
+// update goes from. In this case, the update will be piped into a
+// DeltaPerformer that will apply the delta to the disk.
namespace chromeos_update_engine {
@@ -50,6 +57,11 @@
void PerformAction();
void TerminateProcessing();
+ // Testing
+ void SetTestFileWriter(FileWriter* writer) {
+ writer_ = writer;
+ }
+
// Debugging/logging
static std::string StaticType() { return "DownloadAction"; }
std::string Type() const { return StaticType(); }
@@ -60,33 +72,23 @@
virtual void TransferComplete(HttpFetcher *fetcher, bool successful);
private:
- // Expected size of the file (will be used for progress info)
- const size_t size_;
-
- // URL to download
- std::string url_;
-
- // Path to save URL to
- std::string output_path_;
-
- // Expected hash of the file. The hash must match for this action to
- // succeed.
- std::string hash_;
-
- // Whether the caller requested that we decompress the downloaded data.
- bool should_decompress_;
+ // The InstallPlan passed in
+ InstallPlan install_plan_;
// The FileWriter that downloaded data should be written to. It will
- // either point to *decompressing_file_writer_ or *direct_file_writer_.
+ // either point to *decompressing_file_writer_ or *delta_performer_.
FileWriter* writer_;
- // If non-null, a FileWriter used for gzip decompressing downloaded data
+ // These are used for full updates:
scoped_ptr<GzipDecompressingFileWriter> decompressing_file_writer_;
+ scoped_ptr<SplitFileWriter> split_file_writer_;
+ scoped_ptr<DirectFileWriter> kernel_file_writer_;
+ scoped_ptr<DirectFileWriter> rootfs_file_writer_;
- // Used to write out the downloaded file
- scoped_ptr<DirectFileWriter> direct_file_writer_;
+ // Used to apply a delta update:
+ scoped_ptr<DeltaPerformer> delta_performer_;
- // pointer to the HttpFetcher that does the http work
+ // Pointer to the HttpFetcher that does the http work.
scoped_ptr<HttpFetcher> http_fetcher_;
// Used to find the hash of the bytes downloaded
diff --git a/download_action_unittest.cc b/download_action_unittest.cc
index d2c5cb1..6353584 100644
--- a/download_action_unittest.cc
+++ b/download_action_unittest.cc
@@ -65,32 +65,30 @@
return FALSE;
}
-void TestWithData(const vector<char>& data, bool compress) {
- vector<char> use_data;
- if (compress) {
- use_data = GzipCompressData(data);
- } else {
- use_data = data;
- }
-
+void TestWithData(const vector<char>& data) {
GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
// TODO(adlr): see if we need a different file for build bots
- const string path("/tmp/DownloadActionTest");
+ ScopedTempFile output_temp_file;
+ DirectFileWriter writer;
+
// takes ownership of passed in HttpFetcher
- InstallPlan install_plan(compress, "",
- OmahaHashCalculator::OmahaHashOfData(use_data),
- path);
+ InstallPlan install_plan(true,
+ "",
+ OmahaHashCalculator::OmahaHashOfData(data),
+ output_temp_file.GetPath(),
+ "");
ObjectFeederAction<InstallPlan> feeder_action;
feeder_action.set_obj(install_plan);
- DownloadAction download_action(new MockHttpFetcher(&use_data[0],
- use_data.size()));
+ DownloadAction download_action(new MockHttpFetcher(&data[0],
+ data.size()));
+ download_action.SetTestFileWriter(&writer);
BondActions(&feeder_action, &download_action);
DownloadActionTestProcessorDelegate delegate;
delegate.loop_ = loop;
delegate.expected_data_ = data;
- delegate.path_ = path;
+ delegate.path_ = output_temp_file.GetPath();
ActionProcessor processor;
processor.set_delegate(&delegate);
processor.EnqueueAction(&feeder_action);
@@ -99,9 +97,6 @@
g_timeout_add(0, &StartProcessorInRunLoop, &processor);
g_main_loop_run(loop);
g_main_loop_unref(loop);
-
- // remove temp file; don't care if there are errors here
- unlink(path.c_str());
}
} // namespace {}
@@ -109,8 +104,7 @@
vector<char> small;
const char* foo = "foo";
small.insert(small.end(), foo, foo + strlen(foo));
- TestWithData(small, false);
- TestWithData(small, true);
+ TestWithData(small);
}
TEST(DownloadActionTest, LargeTest) {
@@ -123,8 +117,7 @@
else
c++;
}
- TestWithData(big, false);
- TestWithData(big, true);
+ TestWithData(big);
}
namespace {
@@ -153,13 +146,16 @@
vector<char> data(kMockHttpFetcherChunkSize + kMockHttpFetcherChunkSize / 2);
memset(&data[0], 0, data.size());
- const string path("/tmp/DownloadActionTest");
+ ScopedTempFile temp_file;
{
+ DirectFileWriter writer;
+
// takes ownership of passed in HttpFetcher
ObjectFeederAction<InstallPlan> feeder_action;
- InstallPlan install_plan(false, "", "", path);
+ InstallPlan install_plan(true, "", "", temp_file.GetPath(), "");
feeder_action.set_obj(install_plan);
DownloadAction download_action(new MockHttpFetcher(&data[0], data.size()));
+ download_action.SetTestFileWriter(&writer);
TerminateEarlyTestProcessorDelegate delegate;
delegate.loop_ = loop;
ActionProcessor processor;
@@ -174,7 +170,8 @@
}
// 1 or 0 chunks should have come through
- const off_t resulting_file_size(utils::FileSize(path));
+ const off_t resulting_file_size(utils::FileSize(temp_file.GetPath()));
+ EXPECT_GE(resulting_file_size, 0);
if (resulting_file_size != 0)
EXPECT_EQ(kMockHttpFetcherChunkSize, resulting_file_size);
}
@@ -231,13 +228,18 @@
TEST(DownloadActionTest, PassObjectOutTest) {
GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+ DirectFileWriter writer;
+
// takes ownership of passed in HttpFetcher
- InstallPlan install_plan(false, "",
+ InstallPlan install_plan(true,
+ "",
OmahaHashCalculator::OmahaHashOfString("x"),
+ "/dev/null",
"/dev/null");
ObjectFeederAction<InstallPlan> feeder_action;
feeder_action.set_obj(install_plan);
DownloadAction download_action(new MockHttpFetcher("x", 1));
+ download_action.SetTestFileWriter(&writer);
DownloadActionTestAction test_action;
test_action.expected_input_object_ = install_plan;
@@ -263,12 +265,15 @@
GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
const string path("/fake/path/that/cant/be/created/because/of/missing/dirs");
+ DirectFileWriter writer;
// takes ownership of passed in HttpFetcher
- InstallPlan install_plan(false, "", "", path);
+ InstallPlan install_plan(true, "", "", path, "");
ObjectFeederAction<InstallPlan> feeder_action;
feeder_action.set_obj(install_plan);
DownloadAction download_action(new MockHttpFetcher("x", 1));
+ download_action.SetTestFileWriter(&writer);
+
BondActions(&feeder_action, &download_action);
ActionProcessor processor;
diff --git a/filesystem_copier_action.cc b/filesystem_copier_action.cc
index 18bab44..bd44867 100755
--- a/filesystem_copier_action.cc
+++ b/filesystem_copier_action.cc
@@ -47,17 +47,23 @@
return;
}
- const string source =
- copy_source_.empty() ? utils::BootDevice() : copy_source_;
- LOG(INFO) << "Copying from " << source << " to "
- << install_plan_.install_path;
+ string source = copy_source_;
+ if (source.empty()) {
+ source = copying_kernel_install_path_ ?
+ utils::BootKernelDevice(utils::BootDevice()) :
+ utils::BootDevice();
+ }
+
+ const string destination = copying_kernel_install_path_ ?
+ install_plan_.kernel_install_path :
+ install_plan_.install_path;
int src_fd = open(source.c_str(), O_RDONLY);
if (src_fd < 0) {
PLOG(ERROR) << "Unable to open " << source << " for reading:";
return;
}
- int dst_fd = open(install_plan_.install_path.c_str(),
+ int dst_fd = open(destination.c_str(),
O_WRONLY | O_TRUNC | O_CREAT,
0644);
if (dst_fd < 0) {
diff --git a/filesystem_copier_action.h b/filesystem_copier_action.h
index 9e7f060..786c2ab 100644
--- a/filesystem_copier_action.h
+++ b/filesystem_copier_action.h
@@ -32,8 +32,9 @@
class FilesystemCopierAction : public Action<FilesystemCopierAction> {
public:
- FilesystemCopierAction()
- : src_stream_(NULL),
+ explicit FilesystemCopierAction(bool copying_kernel_install_path)
+ : copying_kernel_install_path_(copying_kernel_install_path),
+ src_stream_(NULL),
dst_stream_(NULL),
canceller_(NULL),
read_in_flight_(false),
@@ -69,6 +70,10 @@
// was_cancelled should be true if TerminateProcessing() was called.
void Cleanup(bool success, bool was_cancelled);
+ // If true, this action is copying to the kernel_install_path from
+ // the install plan, otherwise it's copying just to the install_path.
+ const bool copying_kernel_install_path_;
+
// The path to copy from. If empty (the default), the source is from the
// passed in InstallPlan.
std::string copy_source_;
diff --git a/filesystem_copier_action_unittest.cc b/filesystem_copier_action_unittest.cc
index 3ccea2b..6b43817 100644
--- a/filesystem_copier_action_unittest.cc
+++ b/filesystem_copier_action_unittest.cc
@@ -22,7 +22,9 @@
class FilesystemCopierActionTest : public ::testing::Test {
protected:
- void DoTest(bool run_out_of_space, bool terminate_early);
+ void DoTest(bool run_out_of_space,
+ bool terminate_early,
+ bool use_kernel_partition);
void SetUp() {
}
void TearDown() {
@@ -83,10 +85,13 @@
TEST_F(FilesystemCopierActionTest, RunAsRootSimpleTest) {
ASSERT_EQ(0, getuid());
- DoTest(false, false);
+ DoTest(false, false, false);
+
+ DoTest(false, false, true);
}
void FilesystemCopierActionTest::DoTest(bool run_out_of_space,
- bool terminate_early) {
+ bool terminate_early,
+ bool use_kernel_partition) {
GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
string a_loop_file;
@@ -132,7 +137,10 @@
// Set up the action objects
InstallPlan install_plan;
install_plan.is_full_update = false;
- install_plan.install_path = b_dev;
+ if (use_kernel_partition)
+ install_plan.kernel_install_path = b_dev;
+ else
+ install_plan.install_path = b_dev;
ActionProcessor processor;
FilesystemCopierActionTestDelegate delegate;
@@ -140,7 +148,7 @@
processor.set_delegate(&delegate);
ObjectFeederAction<InstallPlan> feeder_action;
- FilesystemCopierAction copier_action;
+ FilesystemCopierAction copier_action(use_kernel_partition);
ObjectCollectorAction<InstallPlan> collector_action;
BondActions(&feeder_action, &copier_action);
@@ -202,7 +210,7 @@
processor.set_delegate(&delegate);
- FilesystemCopierAction copier_action;
+ FilesystemCopierAction copier_action(false);
ObjectCollectorAction<InstallPlan> collector_action;
BondActions(&copier_action, &collector_action);
@@ -222,9 +230,9 @@
processor.set_delegate(&delegate);
ObjectFeederAction<InstallPlan> feeder_action;
- InstallPlan install_plan(true, "", "", "");
+ InstallPlan install_plan(true, "", "", "", "");
feeder_action.set_obj(install_plan);
- FilesystemCopierAction copier_action;
+ FilesystemCopierAction copier_action(false);
ObjectCollectorAction<InstallPlan> collector_action;
BondActions(&feeder_action, &copier_action);
@@ -246,9 +254,9 @@
processor.set_delegate(&delegate);
ObjectFeederAction<InstallPlan> feeder_action;
- InstallPlan install_plan(false, "", "", "/some/missing/file/path");
+ InstallPlan install_plan(false, "", "", "/no/such/file", "/no/such/file");
feeder_action.set_obj(install_plan);
- FilesystemCopierAction copier_action;
+ FilesystemCopierAction copier_action(false);
ObjectCollectorAction<InstallPlan> collector_action;
BondActions(&copier_action, &collector_action);
@@ -264,12 +272,12 @@
TEST_F(FilesystemCopierActionTest, RunAsRootNoSpaceTest) {
ASSERT_EQ(0, getuid());
- DoTest(true, false);
+ DoTest(true, false, false);
}
TEST_F(FilesystemCopierActionTest, RunAsRootTerminateEarlyTest) {
ASSERT_EQ(0, getuid());
- DoTest(false, true);
+ DoTest(false, true, false);
}
} // namespace chromeos_update_engine
diff --git a/install_plan.h b/install_plan.h
index 9151e1b..fba89fe 100644
--- a/install_plan.h
+++ b/install_plan.h
@@ -17,23 +17,27 @@
InstallPlan(bool is_full,
const std::string& url,
const std::string& hash,
- const std::string& install_path)
+ const std::string& install_path,
+ const std::string& kernel_install_path)
: is_full_update(is_full),
download_url(url),
download_hash(hash),
- install_path(install_path) {}
+ install_path(install_path),
+ kernel_install_path(kernel_install_path) {}
InstallPlan() : is_full_update(false) {}
bool is_full_update;
std::string download_url; // url to download from
std::string download_hash; // hash of the data at the url
std::string install_path; // path to install device
+ std::string kernel_install_path; // path to kernel install device
bool operator==(const InstallPlan& that) const {
return (is_full_update == that.is_full_update) &&
(download_url == that.download_url) &&
(download_hash == that.download_hash) &&
- (install_path == that.install_path);
+ (install_path == that.install_path) &&
+ (kernel_install_path == that.kernel_install_path);
}
bool operator!=(const InstallPlan& that) const {
return !((*this) == that);
@@ -42,7 +46,8 @@
LOG(INFO) << "InstallPlan: "
<< (is_full_update ? "full_update" : "delta_update")
<< ", url: " << download_url << ", hash: " << download_hash
- << ", install_path: " << install_path;
+ << ", install_path: " << install_path
+ << ", kernel_install_path: " << kernel_install_path;
}
};
diff --git a/split_file_writer.cc b/split_file_writer.cc
index e868947..690d4e3 100644
--- a/split_file_writer.cc
+++ b/split_file_writer.cc
@@ -49,8 +49,9 @@
// to the first FileWriter.
if (bytes_received_ < static_cast<off_t>(sizeof(uint64_t))) {
// Write more to the initial buffer
- size_t bytes_to_copy = min(count,
- sizeof(first_length_buf_) - bytes_received_);
+ size_t bytes_to_copy = min(static_cast<off_t>(count),
+ static_cast<off_t>(sizeof(first_length_buf_)) -
+ bytes_received_);
memcpy(&first_length_buf_[bytes_received_], bytes, bytes_to_copy);
bytes_received_ += bytes_to_copy;
count -= bytes_to_copy;
diff --git a/test_utils.h b/test_utils.h
index 4bc107e..ca797d9 100644
--- a/test_utils.h
+++ b/test_utils.h
@@ -9,8 +9,10 @@
#include <string>
#include <vector>
#include <gtest/gtest.h>
+#include "base/scoped_ptr.h"
#include "update_engine/action.h"
#include "update_engine/subprocess.h"
+#include "update_engine/utils.h"
// These are some handy functions for unittests.
@@ -119,6 +121,20 @@
DISALLOW_COPY_AND_ASSIGN(ScopedLoopbackDeviceReleaser);
};
+class ScopedTempFile {
+ public:
+ ScopedTempFile() {
+ EXPECT_TRUE(utils::MakeTempFile("/tmp/update_engine_test_temp_file.XXXXXX",
+ &path_,
+ NULL));
+ unlinker_.reset(new ScopedPathUnlinker(path_));
+ }
+ const std::string& GetPath() { return path_; }
+ private:
+ std::string path_;
+ scoped_ptr<ScopedPathUnlinker> unlinker_;
+};
+
// Useful actions for test
class NoneType;
@@ -191,4 +207,4 @@
} // namespace chromeos_update_engine
-#endif // CHROMEOS_PLATFORM_UPDATE_ENGINE_TEST_UTILS_H__
\ No newline at end of file
+#endif // CHROMEOS_PLATFORM_UPDATE_ENGINE_TEST_UTILS_H__
diff --git a/update_attempter.cc b/update_attempter.cc
index 6b8c361..84410ad 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -35,7 +35,9 @@
shared_ptr<OmahaResponseHandlerAction> response_handler_action(
new OmahaResponseHandlerAction);
shared_ptr<FilesystemCopierAction> filesystem_copier_action(
- new FilesystemCopierAction);
+ new FilesystemCopierAction(false));
+ shared_ptr<FilesystemCopierAction> filesystem_copier_action_kernel(
+ new FilesystemCopierAction(true));
shared_ptr<DownloadAction> download_action(
new DownloadAction(new LibcurlHttpFetcher));
shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
@@ -49,6 +51,8 @@
actions_.push_back(shared_ptr<AbstractAction>(update_check_action));
actions_.push_back(shared_ptr<AbstractAction>(response_handler_action));
actions_.push_back(shared_ptr<AbstractAction>(filesystem_copier_action));
+ actions_.push_back(shared_ptr<AbstractAction>(
+ filesystem_copier_action_kernel));
actions_.push_back(shared_ptr<AbstractAction>(download_action));
actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action));
actions_.push_back(shared_ptr<AbstractAction>(set_bootable_flag_action));
@@ -64,7 +68,10 @@
BondActions(request_prep_action.get(), update_check_action.get());
BondActions(update_check_action.get(), response_handler_action.get());
BondActions(response_handler_action.get(), filesystem_copier_action.get());
- BondActions(filesystem_copier_action.get(), download_action.get());
+ BondActions(response_handler_action.get(),
+ filesystem_copier_action_kernel.get());
+ BondActions(filesystem_copier_action_kernel.get(),
+ download_action.get());
// TODO(adlr): Bond these actions together properly
// BondActions(download_action.get(), install_action.get());
// BondActions(install_action.get(), postinstall_runner_action.get());
diff --git a/utils.cc b/utils.cc
index 85d9e7d..83096d2 100644
--- a/utils.cc
+++ b/utils.cc
@@ -297,7 +297,7 @@
return 0 == str.compare(0, prefix.size(), prefix);
}
-const std::string BootDevice() {
+const string BootDevice() {
string proc_cmdline;
if (!ReadFileToString("/proc/cmdline", &proc_cmdline))
return "";
@@ -324,6 +324,21 @@
// TODO(adlr): use findfs to figure out UUID= or LABEL= filesystems
}
+const string BootKernelDevice(const std::string& boot_device) {
+ // Currntly this assumes the last digit of the boot device is
+ // 3, 5, or 7, and changes it to 2, 4, or 6, respectively, to
+ // get the kernel device.
+ string ret = boot_device;
+ if (ret.empty())
+ return ret;
+ char last_char = ret[ret.size() - 1];
+ if (last_char == '3' || last_char == '5' || last_char == '7') {
+ ret[ret.size() - 1] = last_char - 1;
+ return ret;
+ }
+ return "";
+}
+
bool MountFilesystem(const string& device,
const string& mountpoint,
unsigned long mountflags) {
diff --git a/utils.h b/utils.h
index 7983eec..e5d099f 100644
--- a/utils.h
+++ b/utils.h
@@ -135,11 +135,19 @@
}
}
-// Returns the currently booted device. "/dev/sda1", for example.
+// Returns the currently booted device. "/dev/sda3", for example.
// This will not interpret LABEL= or UUID=. You'll need to use findfs
// or something with equivalent funcionality to interpret those.
const std::string BootDevice();
+// Returns the currently booted kernel device, "dev/sda2", for example.
+// Client must pass in the boot device. The suggested calling convention
+// is: BootKernelDevice(BootDevice()).
+// This function works by doing string modification on boot_device.
+// Returns empty string on failure.
+const std::string BootKernelDevice(const std::string& boot_device);
+
+
} // namespace utils
// Class to unmount FS when object goes out of scope
diff --git a/utils_unittest.cc b/utils_unittest.cc
index 74f349d..5ab78cd 100644
--- a/utils_unittest.cc
+++ b/utils_unittest.cc
@@ -88,6 +88,20 @@
EXPECT_FALSE(utils::BootDevice().empty());
}
+TEST(UtilsTest, BootKernelDeviceTest) {
+ EXPECT_EQ("", utils::BootKernelDevice("foo"));
+ EXPECT_EQ("", utils::BootKernelDevice("/dev/sda0"));
+ EXPECT_EQ("", utils::BootKernelDevice("/dev/sda1"));
+ EXPECT_EQ("", utils::BootKernelDevice("/dev/sda2"));
+ EXPECT_EQ("/dev/sda2", utils::BootKernelDevice("/dev/sda3"));
+ EXPECT_EQ("", utils::BootKernelDevice("/dev/sda4"));
+ EXPECT_EQ("/dev/sda4", utils::BootKernelDevice("/dev/sda5"));
+ EXPECT_EQ("", utils::BootKernelDevice("/dev/sda6"));
+ EXPECT_EQ("/dev/sda6", utils::BootKernelDevice("/dev/sda7"));
+ EXPECT_EQ("", utils::BootKernelDevice("/dev/sda8"));
+ EXPECT_EQ("", utils::BootKernelDevice("/dev/sda9"));
+}
+
TEST(UtilsTest, RecursiveUnlinkDirTest) {
EXPECT_EQ(0, mkdir("RecursiveUnlinkDirTest-a", 0755));
EXPECT_EQ(0, mkdir("RecursiveUnlinkDirTest-b", 0755));