DoAutomaticFreeDiskSpaceControl() introduced

Change-Id: Ie97ae11b14a3bc00dd90e3dbda1444823c32d121

BUG=chromium-os:12713
TEST=FEATURES="test" emerge-x86-mario chromeos-cryptohome

Review URL: http://codereview.chromium.org/6598074
diff --git a/cryptohome.xml b/cryptohome.xml
index f05d52e..82d498b 100644
--- a/cryptohome.xml
+++ b/cryptohome.xml
@@ -76,6 +76,12 @@
     <method name="AsyncRemoveTrackedSubdirectories">
       <arg type="i" name="async_id" direction="out" />
     </method>
+    <method name="DoAutomaticFreeDiskSpaceControl">
+      <arg type="b" name="done" direction="out" />
+    </method>
+    <method name="AsyncDoAutomaticFreeDiskSpaceControl">
+      <arg type="i" name="async_id" direction="out" />
+    </method>
     <method name="TpmIsReady">
       <arg type="b" name="ready" direction="out" />
     </method>
diff --git a/etc/Cryptohome.conf b/etc/Cryptohome.conf
index e687ce0..98615b1 100644
--- a/etc/Cryptohome.conf
+++ b/etc/Cryptohome.conf
@@ -60,6 +60,12 @@
            send_member="AsyncRemoveTrackedSubdirectories"/>
     <allow send_destination="org.chromium.Cryptohome"
            send_interface="org.chromium.CryptohomeInterface"
+           send_member="DoAutomaticFreeDiskSpaceControl"/>
+    <allow send_destination="org.chromium.Cryptohome"
+           send_interface="org.chromium.CryptohomeInterface"
+           send_member="AsyncDoAutomaticFreeDiskSpaceControl"/>
+    <allow send_destination="org.chromium.Cryptohome"
+           send_interface="org.chromium.CryptohomeInterface"
            send_member="TpmIsReady"/>
     <allow send_destination="org.chromium.Cryptohome"
            send_interface="org.chromium.CryptohomeInterface"
diff --git a/interface.cc b/interface.cc
index 7add748..ad115ba 100644
--- a/interface.cc
+++ b/interface.cc
@@ -157,6 +157,17 @@
                                  GError **error) {
   CRYPTOHOME_WRAP_METHOD(AsyncRemoveTrackedSubdirectories, OUT_async_id);
 }
+gboolean cryptohome_do_automatic_free_disk_space_control(Cryptohome *self,
+                                                         gboolean *OUT_result,
+                                                         GError **error) {
+  CRYPTOHOME_WRAP_METHOD(DoAutomaticFreeDiskSpaceControl, OUT_result);
+}
+gboolean cryptohome_async_do_automatic_free_disk_space_control(
+    Cryptohome *self,
+    gint *OUT_async_id,
+    GError **error) {
+  CRYPTOHOME_WRAP_METHOD(AsyncDoAutomaticFreeDiskSpaceControl, OUT_async_id);
+}
 gboolean cryptohome_tpm_is_ready(Cryptohome *self,
                                  gboolean *OUT_ready,
                                  GError **error) {
diff --git a/interface.h b/interface.h
index 3219978..6504416 100644
--- a/interface.h
+++ b/interface.h
@@ -100,6 +100,13 @@
 gboolean cryptohome_async_remove_tracked_subdirectories(Cryptohome *self,
                                                         gint *OUT_async_id,
                                                         GError **error);
+gboolean cryptohome_do_automatic_free_disk_space_control(Cryptohome *self,
+                                                         gboolean *OUT_result,
+                                                         GError **error);
+gboolean cryptohome_async_do_automatic_free_disk_space_control(
+    Cryptohome *self,
+    gint *OUT_async_id,
+    GError **error);
 gboolean cryptohome_tpm_is_ready(Cryptohome *self,
                                  gboolean *OUT_ready,
                                  GError **error);
diff --git a/make_tests.cc b/make_tests.cc
index a94f6b8..c29f55c 100644
--- a/make_tests.cc
+++ b/make_tests.cc
@@ -45,15 +45,15 @@
   {"testuser11@invalid.domain", "eleven", true, false},
   {"testuser12@invalid.domain", "twelve", false, false},
 };
-const size_t kDefaultUserCount =
-    sizeof(kDefaultUsers) / sizeof(kDefaultUsers[0]);
+const size_t kDefaultUserCount = arraysize(kDefaultUsers);
 
+// Used for tracking directories clean-up tests.
 const TestUserInfo kAlternateUsers[] = {
   {"altuser0@invalid.domain", "zero", true, false},
   {"altuser1@invalid.domain", "odin", true, false},
+  {"altuser2@invalid.domain", "dwaa", true, false},
 };
-const size_t kAlternateUserCount =
-    sizeof(kAlternateUsers) / sizeof(kAlternateUsers[0]);
+const size_t kAlternateUserCount = arraysize(kAlternateUsers);
 
 MakeTests::MakeTests() {
 }
diff --git a/mock_platform.h b/mock_platform.h
index 6dbfdcc..01ab2a9 100644
--- a/mock_platform.h
+++ b/mock_platform.h
@@ -37,6 +37,7 @@
   MOCK_METHOD2(TerminatePidsForUser, bool(const uid_t, bool));
   MOCK_METHOD3(SetOwnership, bool(const std::string&, uid_t, gid_t));
   MOCK_METHOD3(GetUserId, bool(const std::string&, uid_t*, gid_t*));
+  MOCK_METHOD1(AmountOfFreeDiskSpace, int64(const std::string&));
 
  private:
   bool MockGetUserId(const std::string& user, uid_t* user_id, gid_t* group_id) {
diff --git a/mount.cc b/mount.cc
index 9f69d25..b39bac8 100644
--- a/mount.cc
+++ b/mount.cc
@@ -478,7 +478,7 @@
   return result;
 }
 
-void Mount::CleanUnmountedTrackedSubdirectories() const {
+void Mount::DoForEveryUnmountedCryptohome(CryptohomeCallback callback) const {
   FilePath shadow_root(shadow_root_);
   file_util::FileEnumerator dir_enumerator(shadow_root, false,
       file_util::FileEnumerator::DIRECTORIES);
@@ -507,25 +507,48 @@
     if (platform_->IsDirectoryMountedWith(home_dir_, vault_path.value())) {
       continue;
     }
-    file_util::FileEnumerator subdir_enumerator(
-        vault_path,
-        false,
-        file_util::FileEnumerator::DIRECTORIES);
-    for (FilePath subdir_path = subdir_enumerator.Next(); !subdir_path.empty();
-         subdir_path = subdir_enumerator.Next()) {
-      FilePath subdir_name = subdir_path.BaseName();
-      if (subdir_name.value().find(kEncryptedFilePrefix) == 0) {
-        continue;
-      }
-      if (subdir_name.value().compare(".") == 0 ||
-          subdir_name.value().compare("..") == 0) {
-        continue;
-      }
-      file_util::Delete(subdir_path, true);
-    }
+    callback(vault_path);
   }
 }
 
+// Deletes all tracking subdirectories of the given vault.
+static void DeleteTrackedDirsCallback(const FilePath& vault) {
+  file_util::FileEnumerator subdir_enumerator(
+      vault, false, file_util::FileEnumerator::DIRECTORIES);
+  for (FilePath subdir_path = subdir_enumerator.Next(); !subdir_path.empty();
+       subdir_path = subdir_enumerator.Next()) {
+    FilePath subdir_name = subdir_path.BaseName();
+    if (subdir_name.value().find(kEncryptedFilePrefix) == 0) {
+      continue;
+    }
+    if (subdir_name.value().compare(".") == 0 ||
+        subdir_name.value().compare("..") == 0) {
+      continue;
+    }
+    file_util::Delete(subdir_path, true);
+  }
+}
+
+void Mount::CleanUnmountedTrackedSubdirectories() const {
+  DoForEveryUnmountedCryptohome(&DeleteTrackedDirsCallback);
+}
+
+// Deletes Cache tracking directory of the given vault.
+static void DeleteCacheCallback(const FilePath& vault) {
+  LOG(WARNING) << "Deleting Cache for user " << vault.value();
+  file_util::Delete(vault.Append(kCacheDir), true);
+}
+
+void Mount::DoAutomaticFreeDiskSpaceControl() const {
+  if (platform_->AmountOfFreeDiskSpace(home_dir_) > kMinFreeSpace)
+    return;
+
+  // Clean Cache directories for every user (except current one).
+  DoForEveryUnmountedCryptohome(&DeleteCacheCallback);
+
+  // TODO(glotov): do further cleanup.
+}
+
 bool Mount::TestCredentials(const Credentials& credentials) const {
   // If the current logged in user matches, use the UserSession to verify the
   // credentials.  This is less costly than a trip to the TPM, and only verifies
diff --git a/mount.h b/mount.h
index 2948340..4c5b887 100644
--- a/mount.h
+++ b/mount.h
@@ -40,6 +40,9 @@
 extern const char* kCacheDir;
 extern const char* kDownloadsDir;
 
+// Minimum free disk space on stateful_partition not to begin the cleanup
+const int64 kMinFreeSpace = 500 * 1LL << 20;  // 500M bytes
+
 
 // The Mount class handles mounting/unmounting of the user's cryptohome
 // directory as well as offline verification of the user's credentials against
@@ -144,6 +147,10 @@
   // Cleans (removes) content from unmounted tracked subdirectories
   virtual void CleanUnmountedTrackedSubdirectories() const;
 
+  // Checks free disk space and if it falls below minimum
+  // (kMinFreeSpace), performs cleanup
+  virtual void DoAutomaticFreeDiskSpaceControl() const;
+
   // Tests if the given credentials would decrypt the user's cryptohome key
   //
   // Parameters
@@ -372,6 +379,13 @@
   std::string GetUserVaultPath(const Credentials& credentials) const;
 
  private:
+  // Invokes given callback for every unmounted cryptohome
+  //
+  // Parameters
+  //   callback - routine to invoke.
+  typedef void (*CryptohomeCallback)(const FilePath&);
+  void DoForEveryUnmountedCryptohome(CryptohomeCallback callback) const;
+
   // Same as MountCryptohome but specifies if the cryptohome directory should be
   // recreated on a fatal error
   //
diff --git a/mount_task.cc b/mount_task.cc
index 474d2c9..168405f 100644
--- a/mount_task.cc
+++ b/mount_task.cc
@@ -120,4 +120,12 @@
   MountTask::Notify();
 }
 
+void MountTaskAutomaticFreeDiskSpace::Run() {
+  result()->set_return_status(true);
+  if (mount_) {
+    mount_->DoAutomaticFreeDiskSpaceControl();
+  }
+  MountTask::Notify();
+}
+
 }  // namespace cryptohome
diff --git a/mount_task.h b/mount_task.h
index 914da94..a75ae04 100644
--- a/mount_task.h
+++ b/mount_task.h
@@ -331,6 +331,21 @@
   DISALLOW_COPY_AND_ASSIGN(MountTaskRemoveTrackedSubdirectories);
 };
 
+// Implements asychronous removal of tracked subdirectories
+class MountTaskAutomaticFreeDiskSpace : public MountTask {
+ public:
+  MountTaskAutomaticFreeDiskSpace(MountTaskObserver* observer,
+                                       Mount* mount)
+      : MountTask(observer, mount, UsernamePasskey()) {
+  }
+  virtual ~MountTaskAutomaticFreeDiskSpace() { }
+
+  virtual void Run();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MountTaskAutomaticFreeDiskSpace);
+};
+
 }  // namespace cryptohome
 
 #endif // CRYPTOHOME_MOUNT_TASK_H_
diff --git a/mount_task_unittest.cc b/mount_task_unittest.cc
index 2fa11cd..9b4dea5 100644
--- a/mount_task_unittest.cc
+++ b/mount_task_unittest.cc
@@ -236,4 +236,14 @@
   ASSERT_TRUE(event_.IsSignaled());
 }
 
+TEST_F(MountTaskTest, AutomaticFreeDiskSpace) {
+  ASSERT_FALSE(event_.IsSignaled());
+  MountTask* mount_task = new MountTaskAutomaticFreeDiskSpace(NULL, &mount_);
+  mount_task->set_complete_event(&event_);
+  mount_task->set_result(&result_);
+  runner_.message_loop()->PostTask(FROM_HERE, mount_task);
+  event_.TimedWait(wait_time_);
+  ASSERT_TRUE(event_.IsSignaled());
+}
+
 } // namespace cryptohome
diff --git a/mount_unittest.cc b/mount_unittest.cc
index ebfe543..e4b8c72 100644
--- a/mount_unittest.cc
+++ b/mount_unittest.cc
@@ -36,8 +36,8 @@
 
 const char kImageDir[] = "test_image_dir";
 const char kSkelDir[] = "test_image_dir/skel";
+const char kHomeDir[] = "alt_test_home_dir";
 const char kAltImageDir[] = "alt_test_image_dir";
-const char kAltHomeDir[] = "alt_test_home_dir";
 
 class MountTest : public ::testing::Test {
  public:
@@ -544,12 +544,11 @@
 TEST_F(MountTest, MigrationOfTrackedDirs) {
   // Checks that old cryptohomes (without pass-through tracked
   // directories) migrate when Mount()ed.
-  LoadSystemSalt(kAltImageDir);
+  LoadSystemSalt(kImageDir);
   Mount mount;
   NiceMock<MockTpm> tpm;
   mount.get_crypto()->set_tpm(&tpm);
-  mount.set_shadow_root(kAltImageDir);
-  mount.set_skel_source(kSkelDir);
+  mount.set_shadow_root(kImageDir);
   mount.set_use_tpm(false);
 
   NiceMock<MockPlatform> platform;
@@ -558,13 +557,13 @@
   EXPECT_TRUE(mount.Init());
 
   cryptohome::SecureBlob passkey;
-  cryptohome::Crypto::PasswordToPasskey(kAlternateUsers[1].password,
+  cryptohome::Crypto::PasswordToPasskey(kDefaultUsers[8].password,
                                         system_salt_, &passkey);
-  UsernamePasskey up(kAlternateUsers[1].username, passkey);
+  UsernamePasskey up(kDefaultUsers[8].username, passkey);
 
   // As we don't have real mount in the test, immagine its output (home)
   // directory.
-  FilePath home_dir(kAltHomeDir);
+  FilePath home_dir(kHomeDir);
   file_util::CreateDirectory(home_dir);
   mount.set_home_dir(home_dir.value());
 
@@ -595,13 +594,11 @@
   // Now Mount().
   EXPECT_CALL(platform, Mount(_, _, _, _))
       .WillRepeatedly(Return(true));
-  EXPECT_CALL(platform, Unmount(_, _, _))
-      .WillRepeatedly(Return(true));
   Mount::MountError error;
   EXPECT_TRUE(mount.MountCryptohome(up, Mount::MountArgs(), &error));
 
   // Check that vault path now have pass-through version of tracked dirs.
-  FilePath image_dir(kAltImageDir);
+  FilePath image_dir(kImageDir);
   FilePath user_path = image_dir.Append(up.GetObfuscatedUsername(system_salt_));
   FilePath vault_path = user_path.Append("vault");
   ASSERT_TRUE(file_util::PathExists(vault_path.Append(kCacheDir)));
@@ -634,4 +631,83 @@
   EXPECT_TRUE(file_util::IsDirectoryEmpty(home_dir));
 }
 
+TEST_F(MountTest, DoAutomaticFreeDiskSpaceControl) {
+  // Checks that DoAutomaticFreeDiskSpaceControl() does the clean-up
+  // if free disk space is low.
+  LoadSystemSalt(kAltImageDir);
+  Mount mount;
+  NiceMock<MockTpm> tpm;
+  mount.get_crypto()->set_tpm(&tpm);
+  mount.set_shadow_root(kAltImageDir);
+  mount.set_use_tpm(false);
+
+  NiceMock<MockPlatform> platform;
+  mount.set_platform(&platform);
+
+  EXPECT_TRUE(mount.Init());
+
+  // For every user, prepare cryptohome contents.
+  const string contents = "some crypted contets";
+  FilePath image_dir(kAltImageDir);
+  FilePath vault_path[kAlternateUserCount];
+  FilePath cache_dir[kAlternateUserCount];
+  FilePath cache_subdir[kAlternateUserCount];
+  for (size_t user = 0; user < kAlternateUserCount; user ++) {
+    cryptohome::SecureBlob passkey;
+    cryptohome::Crypto::PasswordToPasskey(kAlternateUsers[user].password,
+                                          system_salt_, &passkey);
+    UsernamePasskey up(kAlternateUsers[user].username, passkey);
+    vault_path[user] = image_dir
+        .Append(up.GetObfuscatedUsername(system_salt_))
+        .Append("vault");
+
+    // Let their Cache dirs be filled with some data.
+    cache_dir[user] = vault_path[user].Append(kCacheDir);
+    file_util::CreateDirectory(cache_dir[user]);
+    file_util::WriteFile(cache_dir[user].Append("cached_file"),
+                         contents.c_str(), contents.length());
+    cache_subdir[user] = cache_dir[user].Append("cache_subdir");
+    file_util::CreateDirectory(cache_subdir[user]);
+    file_util::WriteFile(cache_subdir[user].Append("cached_file"),
+                         contents.c_str(), contents.length());
+  }
+
+  // Firstly, pretend we have lots of free space.
+  EXPECT_CALL(platform, AmountOfFreeDiskSpace(_))
+      .WillRepeatedly(Return(kMinFreeSpace + 1));
+
+  // DoAutomaticFreeDiskSpaceControl() must do nothing.
+  mount.DoAutomaticFreeDiskSpaceControl();
+
+  // Check that Cache is not changed.
+  for (size_t user = 0; user < kAlternateUserCount; user ++) {
+    string tested;
+    EXPECT_TRUE(file_util::PathExists(cache_dir[user]));
+    EXPECT_TRUE(file_util::ReadFileToString(
+        cache_dir[user].Append("cached_file"), &tested));
+    EXPECT_EQ(contents, tested);
+    EXPECT_TRUE(file_util::PathExists(cache_subdir[user]));
+    tested.clear();
+    EXPECT_TRUE(file_util::ReadFileToString(
+        cache_subdir[user].Append("cached_file"), &tested));
+    EXPECT_EQ(contents, tested);
+  }
+
+  // Now pretend we have lack of free space.
+  EXPECT_CALL(platform, AmountOfFreeDiskSpace(_))
+      .WillRepeatedly(Return(kMinFreeSpace - 1));
+
+  // DoAutomaticFreeDiskSpaceControl() must do the clean-up..
+  mount.DoAutomaticFreeDiskSpaceControl();
+
+  // Cache must be empty (and may even be deleted).
+  for (size_t user = 0; user < kAlternateUserCount; user ++) {
+    EXPECT_TRUE(file_util::IsDirectoryEmpty(cache_dir[user]));
+
+    // Check that we did not leave any litter.
+    file_util::Delete(cache_dir[user], true);
+    EXPECT_TRUE(file_util::IsDirectoryEmpty(vault_path[user]));
+  }
+}
+
 } // namespace cryptohome
diff --git a/platform.cc b/platform.cc
index 35d0cdd..071e06a 100644
--- a/platform.cc
+++ b/platform.cc
@@ -12,6 +12,7 @@
 #include <signal.h>
 #include <sys/mount.h>
 #include <sys/stat.h>
+#include <sys/statvfs.h>
 #include <sys/types.h>
 
 #include <base/file_util.h>
@@ -366,6 +367,14 @@
   return true;
 }
 
+int64 Platform::AmountOfFreeDiskSpace(const string& path) {
+  struct statvfs stats;
+  if (statvfs(path.c_str(), &stats) != 0) {
+    return -1;
+  }
+  return static_cast<int64>(stats.f_bavail) * stats.f_frsize;
+}
+
 void Platform::ClearUserKeyring() {
   keyctl(KEYCTL_CLEAR, KEY_SPEC_USER_KEYRING);
 }
diff --git a/platform.h b/platform.h
index fc03f9f..200972c 100644
--- a/platform.h
+++ b/platform.h
@@ -154,6 +154,14 @@
   virtual bool GetUserId(const std::string& user, uid_t* user_id,
                          gid_t* group_id);
 
+  // Return the available disk space in bytes on the volume containing |path|,
+  // or -1 on failure.
+  // Code duplicated from Chrome's base::SysInfo::AmountOfFreeDiskSpace().
+  //
+  // Parameters
+  //   path - the pathname of any file within the mounted file system
+  virtual int64 AmountOfFreeDiskSpace(const std::string& path);
+
   // Clears the user keyring
   static void ClearUserKeyring();
 
diff --git a/service.cc b/service.cc
index a7b17ed..dd66f12 100644
--- a/service.cc
+++ b/service.cc
@@ -493,6 +493,29 @@
   return TRUE;
 }
 
+gboolean Service::DoAutomaticFreeDiskSpaceControl(gboolean *OUT_result,
+                                                  GError **error) {
+  MountTaskResult result;
+  base::WaitableEvent event(true, false);
+  MountTaskAutomaticFreeDiskSpace* mount_task =
+      new MountTaskAutomaticFreeDiskSpace(this, mount_);
+  mount_task->set_result(&result);
+  mount_task->set_complete_event(&event);
+  mount_thread_.message_loop()->PostTask(FROM_HERE, mount_task);
+  event.Wait();
+  *OUT_result = result.return_status();
+  return TRUE;
+}
+
+gboolean Service::AsyncDoAutomaticFreeDiskSpaceControl(gint *OUT_async_id,
+                                                       GError **error) {
+  MountTaskAutomaticFreeDiskSpace* mount_task =
+      new MountTaskAutomaticFreeDiskSpace(this, mount_);
+  *OUT_async_id = mount_task->sequence_id();
+  mount_thread_.message_loop()->PostTask(FROM_HERE, mount_task);
+  return TRUE;
+}
+
 gboolean Service::TpmIsReady(gboolean* OUT_ready, GError** error) {
   *OUT_ready = tpm_init_->IsTpmReady();
   return TRUE;
diff --git a/service.h b/service.h
index 3b10291..d66f235 100644
--- a/service.h
+++ b/service.h
@@ -126,6 +126,10 @@
                                                GError **error);
   virtual gboolean AsyncRemoveTrackedSubdirectories(gint *OUT_async_id,
                                                     GError **error);
+  virtual gboolean DoAutomaticFreeDiskSpaceControl(gboolean *OUT_result,
+                                                   GError **error);
+  virtual gboolean AsyncDoAutomaticFreeDiskSpaceControl(gint *OUT_async_id,
+                                                        GError **error);
 
   virtual gboolean TpmIsReady(gboolean* OUT_ready, GError** error);
   virtual gboolean TpmIsEnabled(gboolean* OUT_enabled, GError** error);