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

Commit 871a8f23 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Logic for atmoic/tombstone behavior; split mode.

Flesh out logic for cache directories that request new atomic and/or
tombstone clearing behaviors.  Atomic directories are considered for
deletion as a single all-or-nothing unit, and tombstone directories
truncate any removed files instead of unlinking them.

Since these behaviors can be mixed together, add local tests that
quickly verify several different permutations.

Reduce memory footprint of CacheItem objects by only storing name
and pointer to parent (instead of full path).  Fix ordering bug by
switching to std::stable_sort.

Add "V2_DEFY_QUOTA" flag so we can split clearing into two distinct
phases: clearing data for apps above their quotas, and then pushing
deeper by clearing data for apps below their quotas.

Test: adb shell /data/nativetest64/installd_cache_test/installd_cache_test
Bug: 34692014, 33811826
Change-Id: I156897de1d1d1c371b2b837128b2e286bf33d40d
parent 42714ad6
Loading
Loading
Loading
Loading
+55 −9
Original line number Diff line number Diff line
@@ -16,8 +16,9 @@

#include "CacheItem.h"

#include <stdint.h>
#include <inttypes.h>
#include <stdint.h>
#include <sys/xattr.h>

#include <android-base/logging.h>
#include <android-base/stringprintf.h>
@@ -29,13 +30,24 @@ using android::base::StringPrintf;
namespace android {
namespace installd {

CacheItem::CacheItem(const std::shared_ptr<CacheItem>& parent, FTSENT* p) : mParent(parent) {
CacheItem::CacheItem(FTSENT* p) {
    level = p->fts_level;
    directory = S_ISDIR(p->fts_statp->st_mode);
    size = p->fts_statp->st_blocks * 512;
    modified = p->fts_statp->st_mtime;

    mParent = static_cast<CacheItem*>(p->fts_parent->fts_pointer);
    if (mParent) {
        atomic = mParent->atomic;
        tombstone = mParent->tombstone;
        mName = p->fts_name;
        mName.insert(0, "/");
    } else {
        atomic = false;
        tombstone = false;
        mName = p->fts_path;
    }
}

CacheItem::~CacheItem() {
}
@@ -46,7 +58,7 @@ std::string CacheItem::toString() {

std::string CacheItem::buildPath() {
    std::string res = mName;
    std::shared_ptr<CacheItem> parent = mParent;
    CacheItem* parent = mParent;
    while (parent) {
        res.insert(0, parent->mName);
        parent = parent->mParent;
@@ -57,13 +69,47 @@ std::string CacheItem::buildPath() {
int CacheItem::purge() {
    auto path = buildPath();
    if (directory) {
        return delete_dir_contents_and_dir(path, true);
        FTS *fts;
        FTSENT *p;
        char *argv[] = { (char*) path.c_str(), nullptr };
        if (!(fts = fts_open(argv, FTS_PHYSICAL | FTS_XDEV, NULL))) {
            PLOG(WARNING) << "Failed to fts_open " << path;
            return -1;
        }
        while ((p = fts_read(fts)) != nullptr) {
            switch (p->fts_info) {
            case FTS_D:
                if (p->fts_level == 0) {
                    p->fts_number = tombstone;
                } else {
        int res = unlink(path.c_str());
        if (res != 0) {
            PLOG(WARNING) << "Failed to unlink " << path;
                    p->fts_number = p->fts_parent->fts_number
                            | (getxattr(p->fts_path, kXattrCacheTombstone, nullptr, 0) >= 0);
                }
                break;
            case FTS_F:
                if (p->fts_parent->fts_number) {
                    truncate(p->fts_path, 0);
                } else {
                    unlink(p->fts_path);
                }
                break;
            case FTS_DEFAULT:
            case FTS_SL:
            case FTS_SLNONE:
                unlink(p->fts_path);
                break;
            case FTS_DP:
                rmdir(p->fts_path);
                break;
            }
        }
        return 0;
    } else {
        if (tombstone) {
            return truncate(path.c_str(), 0);
        } else {
            return unlink(path.c_str());
        }
        return res;
    }
}

+4 −2
Original line number Diff line number Diff line
@@ -36,7 +36,7 @@ namespace installd {
 */
class CacheItem {
public:
    CacheItem(const std::shared_ptr<CacheItem>& parent, FTSENT* p);
    CacheItem(FTSENT* p);
    ~CacheItem();

    std::string toString();
@@ -46,11 +46,13 @@ public:

    short level;
    bool directory;
    bool atomic;
    bool tombstone;
    int64_t size;
    time_t modified;

private:
    std::shared_ptr<CacheItem> mParent;
    CacheItem* mParent;
    std::string mName;

    DISALLOW_COPY_AND_ASSIGN(CacheItem);
+49 −19
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@

#include <fts.h>
#include <sys/quota.h>
#include <sys/xattr.h>
#include <utils/Trace.h>

#include <android-base/logging.h>
@@ -86,30 +87,59 @@ void CacheTracker::loadItemsFrom(const std::string& path) {
        PLOG(WARNING) << "Failed to fts_open " << path;
        return;
    }
    // TODO: add support for "user.atomic" and "user.tombstone" xattrs
    while ((p = fts_read(fts)) != NULL) {
    while ((p = fts_read(fts)) != nullptr) {
        if (p->fts_level == 0) continue;

        // Create tracking nodes for everything we encounter
        switch (p->fts_info) {
        case FTS_D:
            // Track the newest mtime of anything inside so we consider
            // deleting the directory last
            p->fts_number = p->fts_statp->st_mtime;
            break;
        case FTS_DP:
            p->fts_statp->st_mtime = p->fts_number;
        case FTS_DEFAULT:
        case FTS_F:
        case FTS_SL:
        case FTS_SLNONE: {
            auto item = std::shared_ptr<CacheItem>(new CacheItem(p));
            p->fts_pointer = static_cast<void*>(item.get());
            items.push_back(item);
        }
        }

            // Ignore the actual top-level cache directories
            if (p->fts_level == 0) break;
        switch (p->fts_info) {
        case FTS_D: {
            auto item = static_cast<CacheItem*>(p->fts_pointer);
            item->atomic |= (getxattr(p->fts_path, kXattrCacheAtomic, nullptr, 0) >= 0);
            item->tombstone |= (getxattr(p->fts_path, kXattrCacheTombstone, nullptr, 0) >= 0);

            // When atomic, immediately collect all files under tree
            if (item->atomic) {
                while ((p = fts_read(fts)) != nullptr) {
                    if (p->fts_info == FTS_DP && p->fts_level == item->level) break;
                    switch (p->fts_info) {
                    case FTS_D:
                    case FTS_DEFAULT:
                    case FTS_F:
                    case FTS_SL:
                    case FTS_SLNONE:
            // TODO: optimize path memory footprint
            items.push_back(std::shared_ptr<CacheItem>(new CacheItem(nullptr, p)));
                        item->size += p->fts_statp->st_blocks * 512;
                        item->modified = std::max(item->modified, p->fts_statp->st_mtime);
                    }
                }
            }
        }
        }

            // Track the newest modified item under this tree
            p->fts_parent->fts_number =
                    std::max(p->fts_parent->fts_number, p->fts_statp->st_mtime);
            break;
        // Bubble up modified time to parent
        switch (p->fts_info) {
        case FTS_DP:
        case FTS_DEFAULT:
        case FTS_F:
        case FTS_SL:
        case FTS_SLNONE: {
            auto item = static_cast<CacheItem*>(p->fts_pointer);
            auto parent = static_cast<CacheItem*>(p->fts_parent->fts_pointer);
            if (parent) {
                parent->modified = std::max(parent->modified, item->modified);
            }
        }
        }
    }
    fts_close(fts);
@@ -137,7 +167,7 @@ void CacheTracker::loadItems() {
        }
        return left->directory;
    };
    std::sort(items.begin(), items.end(), cmp);
    std::stable_sort(items.begin(), items.end(), cmp);
    ATRACE_END();
}

+10 −1
Original line number Diff line number Diff line
@@ -86,7 +86,8 @@ static constexpr int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
static constexpr int FLAG_CLEAR_CODE_CACHE_ONLY = 1 << 9;
static constexpr int FLAG_USE_QUOTA = 1 << 12;
static constexpr int FLAG_FREE_CACHE_V2 = 1 << 13;
static constexpr int FLAG_FREE_CACHE_NOOP = 1 << 14;
static constexpr int FLAG_FREE_CACHE_V2_DEFY_QUOTA = 1 << 14;
static constexpr int FLAG_FREE_CACHE_NOOP = 1 << 15;

namespace {

@@ -833,6 +834,14 @@ binder::Status InstalldNativeService::freeCache(const std::unique_ptr<std::strin
        ATRACE_BEGIN("bounce");
        std::shared_ptr<CacheTracker> active;
        while (active || !queue.empty()) {
            // Only look at apps under quota when explicitly requested
            if (active && (active->getCacheRatio() < 10000)
                    && !(flags & FLAG_FREE_CACHE_V2_DEFY_QUOTA)) {
                LOG(DEBUG) << "Active ratio " << active->getCacheRatio()
                        << " isn't over quota, and defy not requested";
                break;
            }

            // Find the best tracker to work with; this might involve swapping
            // if the active tracker is no longer the most over quota
            bool nextBetter = active && !queue.empty()
+19 −0
Original line number Diff line number Diff line
@@ -14,3 +14,22 @@ cc_test {
        "libdiskusage",
    ],
}

cc_test {
    name: "installd_cache_test",
    clang: true,
    srcs: ["installd_cache_test.cpp"],
    shared_libs: [
        "libbase",
        "libbinder",
        "libcutils",
        "liblog",
        "liblogwrap",
        "libselinux",
        "libutils",
    ],
    static_libs: [
        "libinstalld",
        "libdiskusage",
    ],
}
Loading