cryptohome: add android cache directory deletion.

Design: go/arc-disk-full-design

BUG=b:29377354
TEST=P2_TEST_FILTER="FreeDiskSpaceTest.*" \
  cros_workon_make --test cryptohome --board ${BOARD}
TEST=create a 500MB cache file for first user
  log in as second user and fill up disk with
  dd if=/dev/zero \
  of=data/data/org.chromium.arc.applauncher/cache/f \
  bs=1000000 count=5000
  and observe only the cache is deleted and profile remains.

Change-Id: Icc85dc3d01e8d45099865dd667572109e16eccd6
Reviewed-on: https://chromium-review.googlesource.com/355330
Commit-Ready: Gwendal Grignou <gwendal@google.com>
Tested-by: Junichi Uekawa <uekawa@chromium.org>
Tested-by: Gwendal Grignou <gwendal@google.com>
Reviewed-by: Gwendal Grignou <gwendal@google.com>
(cherry picked from commit 147423c632a884081e9155b6024e1f1341b36624)
Reviewed-on: https://chromium-review.googlesource.com/360783
Trybot-Ready: Junichi Uekawa <uekawa@chromium.org>
Reviewed-by: Darren Krahn <dkrahn@chromium.org>
Reviewed-by: Dan Spaid <dspaid@chromium.org>
Commit-Queue: Junichi Uekawa <uekawa@chromium.org>
diff --git a/cryptohome/homedirs.cc b/cryptohome/homedirs.cc
index 5df7658..8173d01 100644
--- a/cryptohome/homedirs.cc
+++ b/cryptohome/homedirs.cc
@@ -36,6 +36,7 @@
 const char *kShadowRoot = "/home/.shadow";
 const char *kEmptyOwner = "";
 const char kGCacheFilesAttribute[] = "user.GCacheFiles";
+const char kAndroidCacheFilesAttribute[] = "user.AndroidCache";
 
 HomeDirs::HomeDirs()
     : default_platform_(new Platform()),
@@ -98,6 +99,14 @@
   if (freeDiskSpace >= kEnoughFreeSpace)
     return true;
 
+  // Clean Cache directories for every user (except current one).
+  DoForEveryUnmountedCryptohome(base::Bind(
+      &HomeDirs::DeleteAndroidCacheCallback,
+      base::Unretained(this)));
+
+  if (platform_->AmountOfFreeDiskSpace(shadow_root_) >= kEnoughFreeSpace)
+    return true;
+
   // Initialize user timestamp cache if it has not been yet. This reads the
   // last-activity time from each homedir's SerializedVaultKeyset.  This value
   // is only updated in the value keyset on unmount and every 24 hrs, so a
@@ -861,6 +870,31 @@
   }
 }
 
+void HomeDirs::DeleteAndroidCacheCallback(const FilePath& vault) {
+  const FilePath root = vault.Append(kRootHomeSuffix);
+  // Find the cache directory by walking under vault/root directory
+  // and looking for AndroidCache xattr set. Data is stored under
+  // root/android-data/data/data/[package name]/cache. It is not
+  // desirable to make all package name directories unencrypted, they
+  // are not marked as tracked directory.
+  // TODO(crbug/625872): Mark root/android/data/data/ as pass through.
+  // TODO(uekawa): Not all boards have android running, we probably
+  // don't need to check for board that do not have an android
+  // configuration.
+  std::unique_ptr<cryptohome::FileEnumerator> file_enumerator(
+      platform_->GetFileEnumerator(root.value(), true,
+                                   base::FileEnumerator::DIRECTORIES));
+  std::string next_path;
+  while (!(next_path = file_enumerator->Next()).empty()) {
+    std::string value;
+    if (platform_->HasExtendedFileAttribute(
+            next_path, kAndroidCacheFilesAttribute)) {
+      LOG(WARNING) << "Deleting Android Cache " << next_path;
+      platform_->DeleteFile(next_path, true);
+    }
+  }
+}
+
 void HomeDirs::AddUserTimestampToCacheCallback(const FilePath& vault) {
   const FilePath user_dir = vault.DirName();
   const std::string obfuscated_username = user_dir.BaseName().value();
diff --git a/cryptohome/homedirs.h b/cryptohome/homedirs.h
index a33ccbf..9ade585 100644
--- a/cryptohome/homedirs.h
+++ b/cryptohome/homedirs.h
@@ -34,6 +34,7 @@
 
 const int64_t kEnoughFreeSpace = 1LL << 30;
 extern const char kGCacheFilesAttribute[];
+extern const char kAndroidCacheFilesAttribute[];
 
 class Credentials;
 class Platform;
@@ -224,6 +225,8 @@
   bool FindGCacheFilesDir(const base::FilePath& vault, std::string* dir);
   // Callback used during FreeDiskSpace().
   void DeleteGCacheTmpCallback(const base::FilePath& vault);
+  // Callback used during FreeDiskSpace().
+  void DeleteAndroidCacheCallback(const base::FilePath& vault);
   // Recursively deletes all contents of a directory while leaving the directory
   // itself intact.
   void DeleteDirectoryContents(const base::FilePath& dir);
diff --git a/cryptohome/homedirs_unittest.cc b/cryptohome/homedirs_unittest.cc
index d2741e9..cfff68e 100644
--- a/cryptohome/homedirs_unittest.cc
+++ b/cryptohome/homedirs_unittest.cc
@@ -268,7 +268,7 @@
           DoAll(SetArgPointee<2>(homedir_paths_),
                 Return(true)));
     EXPECT_CALL(platform_, AmountOfFreeDiskSpace(kTestRoot))
-      .Times(3).WillRepeatedly(Return(0))
+      .Times(4).WillRepeatedly(Return(0))
       .RetiresOnSaturation();
     EXPECT_CALL(platform_, DirectoryExists(_))
       .WillRepeatedly(Return(true));
@@ -276,18 +276,19 @@
     EXPECT_CALL(platform_, GetFileEnumerator(_, false, _))
       .Times(user_count * 2)
       .WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
-    // N users * 1 GCache files dir
+    // N users * (1 GCache files dir + 1 Android cache dir)
     EXPECT_CALL(platform_, GetFileEnumerator(_, true, _))
-      .Times(user_count)
+      .Times(user_count * 2)
       .WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
   }
 };
 
 TEST_F(FreeDiskSpaceTest, InitializeTimeCacheWithNoTime) {
-  // To get to the init logic, we need to fail three kEnoughFreeSpace checks.
+  // To get to the init logic, we need to fail four kEnoughFreeSpace checks.
   EXPECT_CALL(platform_, AmountOfFreeDiskSpace(kTestRoot))
     .WillOnce(Return(0))
     .WillOnce(Return(0))
+    .WillOnce(Return(0))
     .WillOnce(Return(kEnoughFreeSpace - 1));
 
   // Expect cache clean ups.
@@ -315,6 +316,11 @@
               GetFileEnumerator(EndsWith("user/GCache/v1"), true, _))
       .Times(4)
       .WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
+  EXPECT_CALL(platform_,
+              GetFileEnumerator(EndsWith(
+                  std::string(kVaultDir) + "/root"), true, _))
+      .Times(4)
+      .WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
 
   // Now cover the actual initialization piece
   EXPECT_CALL(timestamp_cache_, initialized())
@@ -346,10 +352,11 @@
 }
 
 TEST_F(FreeDiskSpaceTest, InitializeTimeCacheWithOneTime) {
-  // To get to the init logic, we need to fail three kEnoughFreeSpace checks.
+  // To get to the init logic, we need to fail four kEnoughFreeSpace checks.
   EXPECT_CALL(platform_, AmountOfFreeDiskSpace(kTestRoot))
     .WillOnce(Return(0))
     .WillOnce(Return(0))
+    .WillOnce(Return(0))
     .WillOnce(Return(kEnoughFreeSpace - 1));
 
   // Expect cache clean ups.
@@ -375,6 +382,11 @@
                                            true, _))
       .Times(4)
       .WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
+  EXPECT_CALL(platform_, GetFileEnumerator(EndsWith(std::string(kVaultDir) +
+                                                    "/root"),
+                                           true, _))
+      .Times(4)
+      .WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
 
   // Now cover the actual initialization piece
   EXPECT_CALL(timestamp_cache_, initialized())
@@ -560,6 +572,77 @@
   EXPECT_TRUE(homedirs_.FreeDiskSpace());
 }
 
+TEST_F(FreeDiskSpaceTest, CacheAndGCacheAndAndroidCleanup) {
+  EXPECT_CALL(platform_, EnumerateDirectoryEntries(kTestRoot, false, _))
+    .WillRepeatedly(
+        DoAll(SetArgPointee<2>(homedir_paths_),
+              Return(true)));
+  EXPECT_CALL(platform_, AmountOfFreeDiskSpace(kTestRoot))
+    .WillOnce(Return(0))
+    .WillOnce(Return(0))
+    .WillOnce(Return(0))
+    .WillOnce(Return(kEnoughFreeSpace + 1));
+  EXPECT_CALL(platform_, DirectoryExists(_))
+    .WillRepeatedly(Return(true));
+
+  // Skip per-cache and GCache enumerations done per user in order to
+  // test Android cache deletions.
+  EXPECT_CALL(platform_, GetFileEnumerator(EndsWith("/Cache"), false, _))
+      .Times(4)
+      .WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
+
+  // DeleteGCacheTmpCallback enumerate all directories to find GCache files
+  // directory.
+  EXPECT_CALL(
+      platform_,
+      GetFileEnumerator(EndsWith(std::string(kVaultDir) + "/user/GCache/v1"),
+                        true, base::FileEnumerator::DIRECTORIES))
+      .Times(4)
+      .WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
+  EXPECT_CALL(platform_,
+              GetFileEnumerator(EndsWith("/GCache/v1/tmp"), false, _))
+      .Times(4)
+      .WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
+
+  // Now test for the Android user, just test for the first user.
+  NiceMock<MockFileEnumerator>* fe = new NiceMock<MockFileEnumerator>;
+  EXPECT_CALL(platform_, GetFileEnumerator(EndsWith(
+      std::string(kVaultDir) + "/root"), true, _))
+      .WillOnce(Return(fe))
+      .WillOnce(InvokeWithoutArgs(CreateMockFileEnumerator))
+      .WillOnce(InvokeWithoutArgs(CreateMockFileEnumerator))
+      .WillOnce(InvokeWithoutArgs(CreateMockFileEnumerator));
+
+  // Return a cache and non-cache directory.
+  EXPECT_CALL(*fe, Next())
+      .WillOnce(Return(StringPrintf(
+          "%s/%s", homedir_paths_[0].c_str(),
+          "android-data/data/data/com.google.hogehoge/cache")))
+      .WillOnce(Return(StringPrintf(
+          "%s/%s", homedir_paths_[0].c_str(),
+          "android-data/data/data/com.google.hogehoge/data")))
+      .WillRepeatedly(Return(""));
+
+  EXPECT_CALL(platform_, HasExtendedFileAttribute(
+      EndsWith("android-data/data/data/com.google.hogehoge/cache"),
+      kAndroidCacheFilesAttribute))
+      .WillOnce(Return(true));
+  EXPECT_CALL(platform_, HasExtendedFileAttribute(
+      EndsWith("android-data/data/data/com.google.hogehoge/data"),
+      kAndroidCacheFilesAttribute))
+      .WillOnce(Return(false));
+
+  // Confirm android cache dir is removed and data directory is not.
+  EXPECT_CALL(platform_, DeleteFile(
+      EndsWith("android-data/data/data/com.google.hogehoge/cache"), _))
+      .WillOnce(Return(true));
+  EXPECT_CALL(platform_, DeleteFile(
+      EndsWith("android-data/data/data/com.google.hogehoge/data"), _))
+      .Times(0);
+
+  EXPECT_TRUE(homedirs_.FreeDiskSpace());
+}
+
 TEST_F(FreeDiskSpaceTest, CleanUpOneUser) {
   // Ensure that the oldest user directory deleted, but not any
   // others, if the first deletion frees enough space.
@@ -865,18 +948,18 @@
   // 3 users * (1 Cache dir + 1 GCache tmp dir)
   EXPECT_CALL(platform_, GetFileEnumerator(_, false, _))
     .Times(6).WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
-  // 3 users * 1 GCache files dir
+  // 3 users * (1 GCache files dir + 1 Android cache)
   EXPECT_CALL(platform_, GetFileEnumerator(_, true, _))
-    .Times(3).WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
+    .Times(6).WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
 
   EXPECT_CALL(platform_,
       IsDirectoryMountedWith(StartsWith(homedir_paths_[0]), _))
-    .Times(4)  // Cache, GCache, mounted dir count, user removal
+    .Times(5)  // Cache, GCache, android, mounted dir count, user removal
     .WillRepeatedly(Return(true));
   for (size_t i = 1; i < arraysize(kHomedirs); ++i) {
     EXPECT_CALL(platform_,
         IsDirectoryMountedWith(StartsWith(homedir_paths_[i]), _))
-      .Times(3)  // Cache, GCache, mounted dir count
+      .Times(4)  // Cache, GCache, android, mounted dir count
       .WillRepeatedly(Return(false));
   }