Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e25b77c1 authored by Eric Biggers's avatar Eric Biggers
Browse files

UsageStatsService: don't create stuff directly in /data/system_de

/data/system_de is only for per-user data; it *must* only contain
per-user encrypted directories.  Only vold should ever create anything
directly in this directory.  In preparation for removing system_server's
write access to this directory (https://r.android.com/2078213), make
UsageStatsService store its globalcomponentusage file at
/data/system/usagestats/globalcomponentusage instead of
/data/system_de/usagestats/globalcomponentusage.

Migration happens lazily, except that the old file and directory aren't
ever deleted since the SELinux policy will no longer allow system_server
to do that; the old file just stops being used.  vold will need to
handle the cleanup instead, or we could just leave the file around.

Note that before Android 11, UsageStatsService stored per-user stats in
/data/system/usagestats/$userId.  These per-user stats are *not* the
same thing as globalcomponentusage, which was added in Android 12.
UsageStatsService contains code to migrate the per-user stats to
/data/system_ce/$userId/usagestats.  This is fine, and is the right
thing to do, since the per-user stats are potentially sensitive per-user
data.  This does mean that UsageStatsService now implements two types of
migrations, and that the source directory for one migration is the
target directory for the other, which is a bit unfortunate.  However,
since different files are involved in each one, it all works out.

Bug: 156305599
Change-Id: I53c16640e8ed8b7eac111990f1cdb3f59579e051
parent 697b47b6
Loading
Loading
Loading
Loading
+40 −32
Original line number Diff line number Diff line
@@ -76,7 +76,6 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -140,13 +139,26 @@ public class UsageStatsService extends SystemService implements
    private static final boolean ENABLE_KERNEL_UPDATES = true;
    private static final File KERNEL_COUNTER_FILE = new File("/proc/uid_procstat/set");

    private static final File USAGE_STATS_LEGACY_DIR = new File(
            Environment.getDataSystemDirectory(), "usagestats");
    // For migration purposes, indicates whether to keep the legacy usage stats directory or not
    private static final boolean KEEP_LEGACY_DIR = false;

    private static final File COMMON_USAGE_STATS_DE_DIR =
    // /data/system/usagestats.  Now only used for globalcomponentusage.  Previously per-user stats
    // were stored here too, but they've been moved to /data/system_ce/$userId/usagestats.
    private static final File COMMON_USAGE_STATS_DIR =
            new File(Environment.getDataSystemDirectory(), "usagestats");
    private static final File LEGACY_USER_USAGE_STATS_DIR = COMMON_USAGE_STATS_DIR;

    // /data/system_de/usagestats.  When the globalcomponentusage file was added, it was incorrectly
    // added here instead of in /data/system/usagestats where it should be.  We lazily migrate this
    // file by reading it from here if needed, and always writing it to the new path.  We don't
    // delete the old directory, as system_server no longer has permission to do so.
    //
    // Note, this migration is *not* related to the migration of the per-user stats from
    // /data/system/usagestats/$userId to /data/system_ce/$userId/usagestats mentioned above.  Both
    // of these just happen to involve /data/system/usagestats.  /data/system is the right place for
    // system data not tied to a user, but the wrong place for per-user data.  So due to two
    // separate mistakes, we've unfortunately ended up with one case where we need to move files out
    // of /data/system, and one case where we need to move a different file *into* /data/system.
    private static final File LEGACY_COMMON_USAGE_STATS_DIR =
            new File(Environment.getDataSystemDeDirectory(), "usagestats");

    private static final String GLOBAL_COMPONENT_USAGE_FILE_NAME = "globalcomponentusage";

    private static final char TOKEN_DELIMITER = '/';
@@ -630,7 +642,7 @@ public class UsageStatsService extends SystemService implements
                final int previousVersion = Integer.parseInt(reader.readLine());
                // UsageStatsDatabase.BACKUP_VERSION was 4 when usage stats were migrated to CE.
                if (previousVersion >= 4) {
                    deleteLegacyDir(userId);
                    deleteLegacyUserDir(userId);
                    return;
                }
                // If migration logic needs to be changed in a future version, do it here.
@@ -651,7 +663,7 @@ public class UsageStatsService extends SystemService implements
        }

        Slog.i(TAG, "Starting migration to system CE for user " + userId);
        final File legacyUserDir = new File(USAGE_STATS_LEGACY_DIR, Integer.toString(userId));
        final File legacyUserDir = new File(LEGACY_USER_USAGE_STATS_DIR, Integer.toString(userId));
        if (legacyUserDir.exists()) {
            copyRecursively(usageStatsDir, legacyUserDir);
        }
@@ -666,8 +678,8 @@ public class UsageStatsService extends SystemService implements
        }
        Slog.i(TAG, "Finished migration to system CE for user " + userId);

        // Migration was successful - delete the legacy directory
        deleteLegacyDir(userId);
        // Migration was successful - delete the legacy user directory
        deleteLegacyUserDir(userId);
    }

    private static void copyRecursively(final File parent, File f) {
@@ -698,21 +710,14 @@ public class UsageStatsService extends SystemService implements
        }
    }

    private void deleteLegacyDir(int userId) {
        final File legacyUserDir = new File(USAGE_STATS_LEGACY_DIR, Integer.toString(userId));
        if (!KEEP_LEGACY_DIR && legacyUserDir.exists()) {
    private void deleteLegacyUserDir(int userId) {
        final File legacyUserDir = new File(LEGACY_USER_USAGE_STATS_DIR, Integer.toString(userId));
        if (legacyUserDir.exists()) {
            deleteRecursively(legacyUserDir);
            if (legacyUserDir.exists()) {
                Slog.w(TAG, "Error occurred while attempting to delete legacy usage stats "
                        + "dir for user " + userId);
            }
            // If all users have been migrated, delete the parent legacy usage stats directory
            if (USAGE_STATS_LEGACY_DIR.list() != null
                    && USAGE_STATS_LEGACY_DIR.list().length == 0) {
                if (!USAGE_STATS_LEGACY_DIR.delete()) {
                    Slog.w(TAG, "Error occurred while attempting to delete legacy usage stats dir");
                }
            }
        }
    }

@@ -807,13 +812,16 @@ public class UsageStatsService extends SystemService implements
    }

    private void loadGlobalComponentUsageLocked() {
        final File[] packageUsageFile = COMMON_USAGE_STATS_DE_DIR.listFiles(
                (dir, name) -> TextUtils.equals(name, GLOBAL_COMPONENT_USAGE_FILE_NAME));
        if (packageUsageFile == null || packageUsageFile.length == 0) {
        AtomicFile af = new AtomicFile(new File(COMMON_USAGE_STATS_DIR,
                    GLOBAL_COMPONENT_USAGE_FILE_NAME));
        if (!af.exists()) {
            af = new AtomicFile(new File(LEGACY_COMMON_USAGE_STATS_DIR,
                        GLOBAL_COMPONENT_USAGE_FILE_NAME));
            if (!af.exists()) {
                return;
            }

        final AtomicFile af = new AtomicFile(packageUsageFile[0]);
            Slog.i(TAG, "Reading " + GLOBAL_COMPONENT_USAGE_FILE_NAME + " file from old location");
        }
        final Map<String, Long> tmpUsage = new ArrayMap<>();
        try {
            try (FileInputStream in = af.openRead()) {
@@ -831,7 +839,7 @@ public class UsageStatsService extends SystemService implements
            }
        } catch (Exception e) {
            // Most likely trying to read a corrupted file - log the failure
            Slog.e(TAG, "Could not read " + packageUsageFile[0]);
            Slog.e(TAG, "Could not read " + af.getBaseFile());
        }
    }

@@ -840,11 +848,11 @@ public class UsageStatsService extends SystemService implements
            return;
        }

        if (!COMMON_USAGE_STATS_DE_DIR.mkdirs() && !COMMON_USAGE_STATS_DE_DIR.exists()) {
            throw new IllegalStateException("Common usage stats DE directory does not exist: "
                    + COMMON_USAGE_STATS_DE_DIR.getAbsolutePath());
        if (!COMMON_USAGE_STATS_DIR.mkdirs() && !COMMON_USAGE_STATS_DIR.exists()) {
            throw new IllegalStateException("Common usage stats directory does not exist: "
                    + COMMON_USAGE_STATS_DIR.getAbsolutePath());
        }
        final File lastTimePackageFile = new File(COMMON_USAGE_STATS_DE_DIR,
        final File lastTimePackageFile = new File(COMMON_USAGE_STATS_DIR,
                GLOBAL_COMPONENT_USAGE_FILE_NAME);
        final AtomicFile af = new AtomicFile(lastTimePackageFile);
        FileOutputStream fos = null;