| // Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include <sys/stat.h> | 
 | #include <sys/types.h> | 
 | #include <unistd.h> | 
 | #include <string> | 
 | #include <vector> | 
 | #include <gtest/gtest.h> | 
 | #include "update_engine/postinstall_runner_action.h" | 
 | #include "update_engine/test_utils.h" | 
 | #include "update_engine/utils.h" | 
 |  | 
 | using std::string; | 
 | using std::vector; | 
 |  | 
 | namespace chromeos_update_engine { | 
 |  | 
 | namespace { | 
 | gboolean StartProcessorInRunLoop(gpointer data) { | 
 |   ActionProcessor *processor = reinterpret_cast<ActionProcessor*>(data); | 
 |   processor->StartProcessing(); | 
 |   return FALSE; | 
 | } | 
 | }  // namespace | 
 |  | 
 | class PostinstallRunnerActionTest : public ::testing::Test { | 
 |  public: | 
 |   void DoTest(bool do_losetup, bool do_err_script); | 
 | }; | 
 |  | 
 | class PostinstActionProcessorDelegate : public ActionProcessorDelegate { | 
 |  public: | 
 |   PostinstActionProcessorDelegate() | 
 |       : loop_(NULL), | 
 |         code_(kActionCodeError), | 
 |         code_set_(false) {} | 
 |   void ProcessingDone(const ActionProcessor* processor, | 
 |                       ActionExitCode code) { | 
 |     ASSERT_TRUE(loop_); | 
 |     g_main_loop_quit(loop_); | 
 |   } | 
 |   void ActionCompleted(ActionProcessor* processor, | 
 |                        AbstractAction* action, | 
 |                        ActionExitCode code) { | 
 |     if (action->Type() == PostinstallRunnerAction::StaticType()) { | 
 |       code_ = code; | 
 |       code_set_ = true; | 
 |     } | 
 |   } | 
 |   GMainLoop* loop_; | 
 |   ActionExitCode code_; | 
 |   bool code_set_; | 
 | }; | 
 |  | 
 | TEST_F(PostinstallRunnerActionTest, RunAsRootSimpleTest) { | 
 |   ASSERT_EQ(0, getuid()); | 
 |   DoTest(true, false); | 
 | } | 
 |  | 
 | TEST_F(PostinstallRunnerActionTest, RunAsRootCantMountTest) { | 
 |   ASSERT_EQ(0, getuid()); | 
 |   DoTest(false, false); | 
 | } | 
 |  | 
 | TEST_F(PostinstallRunnerActionTest, RunAsRootErrScriptTest) { | 
 |   ASSERT_EQ(0, getuid()); | 
 |   DoTest(true, true); | 
 | } | 
 |  | 
 | void PostinstallRunnerActionTest::DoTest(bool do_losetup, bool do_err_script) { | 
 |   ASSERT_EQ(0, getuid()) << "Run me as root. Ideally don't run other tests " | 
 |                          << "as root, tho."; | 
 |  | 
 |   const string mountpoint(string(utils::kStatefulPartition) + | 
 |                           "/au_destination"); | 
 |  | 
 |   string cwd; | 
 |   { | 
 |     vector<char> buf(1000); | 
 |     ASSERT_EQ(&buf[0], getcwd(&buf[0], buf.size())); | 
 |     cwd = string(&buf[0], strlen(&buf[0])); | 
 |   } | 
 |  | 
 |   // create the au destination, if it doesn't exist | 
 |   ASSERT_EQ(0, System(string("mkdir -p ") + mountpoint)); | 
 |  | 
 |   // create 10MiB sparse file | 
 |   ASSERT_EQ(0, system("dd if=/dev/zero of=image.dat seek=10485759 bs=1 " | 
 |                       "count=1")); | 
 |  | 
 |   // format it as ext3 | 
 |   ASSERT_EQ(0, system("mkfs.ext3 -F image.dat")); | 
 |  | 
 |   // mount it | 
 |   ASSERT_EQ(0, System(string("mount -o loop image.dat ") + mountpoint)); | 
 |  | 
 |   // put a postinst script in | 
 |   string script = string("#!/bin/bash\ntouch ") + cwd + "/postinst_called\n"; | 
 |   if (do_err_script) { | 
 |     script = "#!/bin/bash\nexit 1"; | 
 |   } | 
 |   ASSERT_TRUE(WriteFileString(mountpoint + "/postinst", script)); | 
 |   ASSERT_EQ(0, System(string("chmod a+x ") + mountpoint + "/postinst")); | 
 |  | 
 |   ASSERT_EQ(0, System(string("umount -d ") + mountpoint)); | 
 |  | 
 |   ASSERT_EQ(0, System(string("rm -f ") + cwd + "/postinst_called")); | 
 |  | 
 |   // get a loop device we can use for the install device | 
 |   FILE* find_dev_cmd = popen("losetup -f", "r"); | 
 |   ASSERT_TRUE(find_dev_cmd); | 
 |  | 
 |   char dev[100] = {0}; | 
 |   size_t r = fread(dev, 1, sizeof(dev), find_dev_cmd); | 
 |   ASSERT_GT(r, 0); | 
 |   ASSERT_LT(r, sizeof(dev)); | 
 |   ASSERT_TRUE(feof(find_dev_cmd)); | 
 |   fclose(find_dev_cmd); | 
 |  | 
 |   // strip trailing newline on dev | 
 |   if (dev[strlen(dev) - 1] == '\n') | 
 |     dev[strlen(dev) - 1] = '\0'; | 
 |  | 
 |   if (do_losetup) | 
 |     ASSERT_EQ(0, System(string("losetup ") + dev + " " + cwd + "/image.dat")); | 
 |  | 
 |   ActionProcessor processor; | 
 |   ObjectFeederAction<InstallPlan> feeder_action; | 
 |   InstallPlan install_plan; | 
 |   install_plan.install_path = dev; | 
 |   feeder_action.set_obj(install_plan); | 
 |   PostinstallRunnerAction runner_action; | 
 |   BondActions(&feeder_action, &runner_action); | 
 |   ObjectCollectorAction<InstallPlan> collector_action; | 
 |   BondActions(&runner_action, &collector_action); | 
 |   PostinstActionProcessorDelegate delegate; | 
 |   processor.EnqueueAction(&feeder_action); | 
 |   processor.EnqueueAction(&runner_action); | 
 |   processor.EnqueueAction(&collector_action); | 
 |   processor.set_delegate(&delegate); | 
 |  | 
 |   GMainLoop* loop = g_main_loop_new(g_main_context_default(), FALSE); | 
 |   delegate.loop_ = loop; | 
 |   g_timeout_add(0, &StartProcessorInRunLoop, &processor); | 
 |   g_main_loop_run(loop); | 
 |   g_main_loop_unref(loop); | 
 |   ASSERT_FALSE(processor.IsRunning()); | 
 |  | 
 |   EXPECT_TRUE(delegate.code_set_); | 
 |   EXPECT_EQ(do_losetup && !do_err_script, delegate.code_ == kActionCodeSuccess); | 
 |   EXPECT_EQ(do_losetup && !do_err_script, | 
 |             !collector_action.object().install_path.empty()); | 
 |   if (do_losetup && !do_err_script) { | 
 |     EXPECT_TRUE(install_plan == collector_action.object()); | 
 |   } | 
 |  | 
 |   struct stat stbuf; | 
 |   int rc = lstat((string(cwd) + "/postinst_called").c_str(), &stbuf); | 
 |   if (do_losetup && !do_err_script) | 
 |     ASSERT_EQ(0, rc); | 
 |   else | 
 |     ASSERT_LT(rc, 0); | 
 |  | 
 |   if (do_losetup) | 
 |     ASSERT_EQ(0, System(string("losetup -d ") + dev)); | 
 |   ASSERT_EQ(0, System(string("rm -f ") + cwd + "/postinst_called")); | 
 |   ASSERT_EQ(0, System(string("rm -f ") + cwd + "/image.dat")); | 
 | } | 
 |  | 
 | // Death tests don't seem to be working on Hardy | 
 | TEST_F(PostinstallRunnerActionTest, DISABLED_RunAsRootDeathTest) { | 
 |   ASSERT_EQ(0, getuid()); | 
 |   PostinstallRunnerAction runner_action; | 
 |   ASSERT_DEATH({ runner_action.TerminateProcessing(); }, | 
 |                "postinstall_runner_action.h:.*] Check failed"); | 
 | } | 
 |  | 
 | }  // namespace chromeos_update_engine |