Merge "Add an installd command to prune dex files."
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index 3930224..566ce55 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -73,7 +73,7 @@
         }
     } else {
         if (S_ISDIR(libStat.st_mode)) {
-            if (delete_dir_contents(libsymlink, 1, 0) < 0) {
+            if (delete_dir_contents(libsymlink, 1, NULL) < 0) {
                 ALOGE("couldn't delete lib directory during install for: %s", libsymlink);
                 return -1;
             }
@@ -176,6 +176,10 @@
     return 0;
 }
 
+static int lib_dir_matcher(const char* file_name, const int is_dir) {
+  return is_dir && !strcmp(file_name, "lib");
+}
+
 int delete_user_data(const char *pkgname, userid_t userid)
 {
     char pkgdir[PKG_PATH_MAX];
@@ -184,7 +188,7 @@
         return -1;
 
     /* delete contents, excluding "lib", but not the directory itself */
-    return delete_dir_contents(pkgdir, 0, "lib");
+    return delete_dir_contents(pkgdir, 0, &lib_dir_matcher);
 }
 
 int make_user_data(const char *pkgname, uid_t uid, userid_t userid, const char* seinfo)
@@ -225,7 +229,7 @@
         }
     } else {
         if (S_ISDIR(libStat.st_mode)) {
-            if (delete_dir_contents(libsymlink, 1, 0) < 0) {
+            if (delete_dir_contents(libsymlink, 1, NULL) < 0) {
                 ALOGE("couldn't delete lib directory during install for non-primary: %s",
                         libsymlink);
                 unlink(pkgdir);
@@ -307,7 +311,7 @@
         return -1;
 
         /* delete contents, not the directory, no exceptions */
-    return delete_dir_contents(cachedir, 0, 0);
+    return delete_dir_contents(cachedir, 0, NULL);
 }
 
 /* Try to ensure free_size bytes of storage are available.
@@ -1152,7 +1156,7 @@
         }
     } else {
         if (S_ISDIR(libStat.st_mode)) {
-            if (delete_dir_contents(libsymlink, 1, 0) < 0) {
+            if (delete_dir_contents(libsymlink, 1, NULL) < 0) {
                 rc = -1;
                 goto out;
             }
@@ -1377,3 +1381,30 @@
     free(userdir);
     return ret;
 }
+
+static int prune_dex_exclusion_predicate(const char *file_name, const int is_dir)
+{
+    // Don't exclude any directories, we want to inspect them
+    // recusively for files.
+    if (is_dir) {
+      return 0;
+    }
+
+
+    // Don't exclude regular files that start with the list
+    // of prefixes.
+    static const char data_app_prefix[] = "data@app@";
+    static const char data_priv_app_prefix[] = "data@priv-app@";
+    if (!strncmp(file_name, data_app_prefix, sizeof(data_app_prefix) - 1) ||
+        !strncmp(file_name, data_priv_app_prefix, sizeof(data_priv_app_prefix) - 1)) {
+      return 0;
+    }
+
+    // Exclude all regular files that don't start with the prefix "data@app@" or
+    // "data@priv-app@".
+    return 1;
+}
+
+int prune_dex_cache() {
+  return delete_dir_contents(DALVIK_CACHE_PREFIX, 0, &prune_dex_exclusion_predicate);
+}
diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c
index f714836..4368a9e 100644
--- a/cmds/installd/installd.c
+++ b/cmds/installd/installd.c
@@ -140,6 +140,12 @@
                              /* pkgName, seinfo, uid*/
 }
 
+static int do_prune_dex_cache(char **arg __attribute__((unused)),
+                              char reply[REPLY_MAX] __attribute__((unused)))
+{
+    return prune_dex_cache();
+}
+
 struct cmdinfo {
     const char *name;
     unsigned numargs;
@@ -166,6 +172,7 @@
     { "rmuser",               1, do_rm_user },
     { "idmap",                3, do_idmap },
     { "restorecondata",       3, do_restorecon_data },
+    { "prunedexcache",        0, do_prune_dex_cache },
 };
 
 static int readx(int s, void *_buf, int count)
diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h
index ff26e49..c1e6e0f 100644
--- a/cmds/installd/installd.h
+++ b/cmds/installd/installd.h
@@ -159,7 +159,7 @@
 
 int delete_dir_contents(const char *pathname,
                         int also_delete_dir,
-                        const char *ignore);
+                        int (*exclusion_predicate)(const char *name, const int is_dir));
 
 int delete_dir_contents_fd(int dfd, const char *name);
 
@@ -220,3 +220,4 @@
 int linklib(const char* target, const char* source, int userId);
 int idmap(const char *target_path, const char *overlay_path, uid_t uid);
 int restorecon_data();
+int prune_dex_cache();
diff --git a/cmds/installd/utils.c b/cmds/installd/utils.c
index 120fd62..35172de 100644
--- a/cmds/installd/utils.c
+++ b/cmds/installd/utils.c
@@ -219,7 +219,8 @@
     return 0;
 }
 
-static int _delete_dir_contents(DIR *d, const char *ignore)
+static int _delete_dir_contents(DIR *d,
+                                int (*exclusion_predicate)(const char *name, const int is_dir))
 {
     int result = 0;
     struct dirent *de;
@@ -232,8 +233,10 @@
     while ((de = readdir(d))) {
         const char *name = de->d_name;
 
-            /* skip the ignore name if provided */
-        if (ignore && !strcmp(name, ignore)) continue;
+            /* check using the exclusion predicate, if provided */
+        if (exclusion_predicate && exclusion_predicate(name, (de->d_type == DT_DIR))) {
+            continue;
+        }
 
         if (de->d_type == DT_DIR) {
             int r, subfd;
@@ -258,7 +261,7 @@
                 result = -1;
                 continue;
             }
-            if (_delete_dir_contents(subdir, 0)) {
+            if (_delete_dir_contents(subdir, exclusion_predicate)) {
                 result = -1;
             }
             closedir(subdir);
@@ -279,7 +282,7 @@
 
 int delete_dir_contents(const char *pathname,
                         int also_delete_dir,
-                        const char *ignore)
+                        int (*exclusion_predicate)(const char*, const int))
 {
     int res = 0;
     DIR *d;
@@ -289,7 +292,7 @@
         ALOGE("Couldn't opendir %s: %s\n", pathname, strerror(errno));
         return -errno;
     }
-    res = _delete_dir_contents(d, ignore);
+    res = _delete_dir_contents(d, exclusion_predicate);
     closedir(d);
     if (also_delete_dir) {
         if (rmdir(pathname)) {