diff options
| -rw-r--r-- | cmds/installd/Android.bp | 2 | ||||
| -rw-r--r-- | cmds/installd/CacheItem.cpp | 71 | ||||
| -rw-r--r-- | cmds/installd/CacheItem.h | 62 | ||||
| -rw-r--r-- | cmds/installd/CacheTracker.cpp | 162 | ||||
| -rw-r--r-- | cmds/installd/CacheTracker.h | 77 | ||||
| -rw-r--r-- | cmds/installd/InstalldNativeService.cpp | 190 | ||||
| -rw-r--r-- | cmds/installd/InstalldNativeService.h | 9 | ||||
| -rw-r--r-- | cmds/installd/binder/android/os/IInstalld.aidl | 4 |
8 files changed, 550 insertions, 27 deletions
diff --git a/cmds/installd/Android.bp b/cmds/installd/Android.bp index 93174bf5f2..33db6db60c 100644 --- a/cmds/installd/Android.bp +++ b/cmds/installd/Android.bp @@ -6,6 +6,8 @@ cc_defaults { "-Werror", ], srcs: [ + "CacheItem.cpp", + "CacheTracker.cpp", "InstalldNativeService.cpp", "dexopt.cpp", "globals.cpp", diff --git a/cmds/installd/CacheItem.cpp b/cmds/installd/CacheItem.cpp new file mode 100644 index 0000000000..d1bdded8b1 --- /dev/null +++ b/cmds/installd/CacheItem.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "CacheItem.h" + +#include <stdint.h> +#include <inttypes.h> + +#include <android-base/logging.h> +#include <android-base/stringprintf.h> + +#include "utils.h" + +using android::base::StringPrintf; + +namespace android { +namespace installd { + +CacheItem::CacheItem(const std::shared_ptr<CacheItem>& parent, FTSENT* p) : mParent(parent) { + 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; + mName = p->fts_path; +} + +CacheItem::~CacheItem() { +} + +std::string CacheItem::toString() { + return StringPrintf("%s size=%" PRId64 " mod=%ld", buildPath().c_str(), size, modified); +} + +std::string CacheItem::buildPath() { + std::string res = mName; + std::shared_ptr<CacheItem> parent = mParent; + while (parent) { + res.insert(0, parent->mName); + parent = parent->mParent; + } + return res; +} + +int CacheItem::purge() { + auto path = buildPath(); + if (directory) { + return delete_dir_contents_and_dir(path, true); + } else { + int res = unlink(path.c_str()); + if (res != 0) { + PLOG(WARNING) << "Failed to unlink " << path; + } + return res; + } +} + +} // namespace installd +} // namespace android diff --git a/cmds/installd/CacheItem.h b/cmds/installd/CacheItem.h new file mode 100644 index 0000000000..bec8bc85b8 --- /dev/null +++ b/cmds/installd/CacheItem.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_INSTALLD_CACHE_ITEM_H +#define ANDROID_INSTALLD_CACHE_ITEM_H + +#include <memory> +#include <string> + +#include <fts.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <android-base/macros.h> + +namespace android { +namespace installd { + +/** + * Single cache item that can be purged to free up space. This may be an + * isolated file, or an entire directory tree that should be atomically + * deleted. + */ +class CacheItem { +public: + CacheItem(const std::shared_ptr<CacheItem>& parent, FTSENT* p); + ~CacheItem(); + + std::string toString(); + std::string buildPath(); + + int purge(); + + short level; + bool directory; + int64_t size; + time_t modified; + +private: + std::shared_ptr<CacheItem> mParent; + std::string mName; + + DISALLOW_COPY_AND_ASSIGN(CacheItem); +}; + +} // namespace installd +} // namespace android + +#endif // ANDROID_INSTALLD_CACHE_ITEM_H diff --git a/cmds/installd/CacheTracker.cpp b/cmds/installd/CacheTracker.cpp new file mode 100644 index 0000000000..23c4330ca2 --- /dev/null +++ b/cmds/installd/CacheTracker.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define ATRACE_TAG ATRACE_TAG_PACKAGE_MANAGER + +#include "CacheTracker.h" + +#include <fts.h> +#include <sys/quota.h> +#include <utils/Trace.h> + +#include <android-base/logging.h> +#include <android-base/stringprintf.h> + +#include "utils.h" + +using android::base::StringPrintf; + +namespace android { +namespace installd { + +CacheTracker::CacheTracker(userid_t userId, appid_t appId, const std::string& quotaDevice) : + cacheUsed(0), cacheQuota(0), mUserId(userId), mAppId(appId), mQuotaDevice(quotaDevice), + mItemsLoaded(false) { +} + +CacheTracker::~CacheTracker() { +} + +std::string CacheTracker::toString() { + return StringPrintf("UID=%d used=%" PRId64 " quota=%" PRId64 " ratio=%d", + multiuser_get_uid(mUserId, mAppId), cacheUsed, cacheQuota, getCacheRatio()); +} + +void CacheTracker::addDataPath(const std::string& dataPath) { + mDataPaths.push_back(dataPath); +} + +void CacheTracker::loadStats() { + int cacheGid = multiuser_get_cache_gid(mUserId, mAppId); + if (cacheGid != -1 && !mQuotaDevice.empty()) { + ATRACE_BEGIN("loadStats quota"); + struct dqblk dq; + if (quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), mQuotaDevice.c_str(), cacheGid, + reinterpret_cast<char*>(&dq)) != 0) { + ATRACE_END(); + if (errno != ESRCH) { + PLOG(ERROR) << "Failed to quotactl " << mQuotaDevice << " for GID " << cacheGid; + } + } else { + cacheUsed = dq.dqb_curspace; + ATRACE_END(); + return; + } + } + + ATRACE_BEGIN("loadStats tree"); + cacheUsed = 0; + for (auto path : mDataPaths) { + auto cachePath = read_path_inode(path, "cache", kXattrInodeCache); + auto codeCachePath = read_path_inode(path, "code_cache", kXattrInodeCodeCache); + calculate_tree_size(cachePath, &cacheUsed); + calculate_tree_size(codeCachePath, &cacheUsed); + } + ATRACE_END(); +} + +void CacheTracker::loadItemsFrom(const std::string& path) { + 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; + } + // TODO: add support for "user.atomic" and "user.tombstone" xattrs + while ((p = fts_read(fts)) != NULL) { + 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; + + // Ignore the actual top-level cache directories + if (p->fts_level == 0) break; + 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))); + + // 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; + } + } + fts_close(fts); +} + +void CacheTracker::loadItems() { + items.clear(); + + ATRACE_BEGIN("loadItems"); + for (auto path : mDataPaths) { + loadItemsFrom(read_path_inode(path, "cache", kXattrInodeCache)); + loadItemsFrom(read_path_inode(path, "code_cache", kXattrInodeCodeCache)); + } + ATRACE_END(); + + ATRACE_BEGIN("sortItems"); + auto cmp = [](std::shared_ptr<CacheItem> left, std::shared_ptr<CacheItem> right) { + // TODO: sort dotfiles last + // TODO: sort code_cache last + if (left->modified != right->modified) { + return (left->modified > right->modified); + } + if (left->level != right->level) { + return (left->level < right->level); + } + return left->directory; + }; + std::sort(items.begin(), items.end(), cmp); + ATRACE_END(); +} + +void CacheTracker::ensureItems() { + if (mItemsLoaded) { + return; + } else { + loadItems(); + mItemsLoaded = true; + } +} + +int CacheTracker::getCacheRatio() { + if (cacheQuota == 0) { + return 0; + } else { + return (cacheUsed * 10000) / cacheQuota; + } +} + +} // namespace installd +} // namespace android diff --git a/cmds/installd/CacheTracker.h b/cmds/installd/CacheTracker.h new file mode 100644 index 0000000000..91692d7469 --- /dev/null +++ b/cmds/installd/CacheTracker.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_INSTALLD_CACHE_TRACKER_H +#define ANDROID_INSTALLD_CACHE_TRACKER_H + +#include <memory> +#include <string> +#include <queue> + +#include <sys/types.h> +#include <sys/stat.h> + +#include <android-base/macros.h> +#include <cutils/multiuser.h> + +#include "CacheItem.h" + +namespace android { +namespace installd { + +/** + * Cache tracker for a single UID. Each tracker is used in two modes: first + * for loading lightweight "stats", and then by loading detailed "items" + * which can then be purged to free up space. + */ +class CacheTracker { +public: + CacheTracker(userid_t userId, appid_t appId, const std::string& quotaDevice); + ~CacheTracker(); + + std::string toString(); + + void addDataPath(const std::string& dataPath); + + void loadStats(); + void loadItems(); + + void ensureItems(); + + int getCacheRatio(); + + int64_t cacheUsed; + int64_t cacheQuota; + + std::vector<std::shared_ptr<CacheItem>> items; + +private: + userid_t mUserId; + appid_t mAppId; + std::string mQuotaDevice; + bool mItemsLoaded; + + std::vector<std::string> mDataPaths; + + void loadItemsFrom(const std::string& path); + + DISALLOW_COPY_AND_ASSIGN(CacheTracker); +}; + +} // namespace installd +} // namespace android + +#endif // ANDROID_INSTALLD_CACHE_TRACKER_H diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp index c1413ff015..9a984b433b 100644 --- a/cmds/installd/InstalldNativeService.cpp +++ b/cmds/installd/InstalldNativeService.cpp @@ -56,6 +56,7 @@ #include "otapreopt_utils.h" #include "utils.h" +#include "CacheTracker.h" #include "MatchExtensionGen.h" #ifndef LOG_TAG @@ -86,6 +87,8 @@ static constexpr int FLAG_STORAGE_CE = 1 << 1; 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; #define MIN_RESTRICTED_HOME_SDK_VERSION 24 // > M namespace { @@ -200,11 +203,18 @@ status_t InstalldNativeService::dump(int fd, const Vector<String16> & /* args */ } std::lock_guard<std::recursive_mutex> lock(mLock); - out << "installd is happy!" << endl << endl; - out << "Devices with quota support:" << endl; + out << "installd is happy!" << endl; + + out << endl << "Devices with quota support:" << endl; for (const auto& n : mQuotaDevices) { out << " " << n.first << " = " << n.second << endl; } + + out << endl << "Per-UID cache quotas:" << endl; + for (const auto& n : mCacheQuotas) { + out << " " << n.first << " = " << n.second << endl; + } + out << endl; out.flush(); @@ -809,46 +819,164 @@ binder::Status InstalldNativeService::destroyUserData(const std::unique_ptr<std: * when just reading from the cache, which is pretty awful. */ binder::Status InstalldNativeService::freeCache(const std::unique_ptr<std::string>& uuid, - int64_t freeStorageSize) { + int64_t freeStorageSize, int32_t flags) { ENFORCE_UID(AID_SYSTEM); CHECK_ARGUMENT_UUID(uuid); std::lock_guard<std::recursive_mutex> lock(mLock); - const char* uuid_ = uuid ? uuid->c_str() : nullptr; - cache_t* cache; - int64_t avail; + // TODO: remove this once framework is more robust + invalidateMounts(); + const char* uuid_ = uuid ? uuid->c_str() : nullptr; auto data_path = create_data_path(uuid_); + auto device = findQuotaDeviceForUuid(uuid); + auto noop = (flags & FLAG_FREE_CACHE_NOOP); - avail = data_disk_free(data_path); - if (avail < 0) { + int64_t free = data_disk_free(data_path); + int64_t needed = freeStorageSize - free; + if (free < 0) { return error("Failed to determine free space for " + data_path); - } - - ALOGI("free_cache(%" PRId64 ") avail %" PRId64 "\n", freeStorageSize, avail); - if (avail >= freeStorageSize) { + } else if (free >= freeStorageSize) { return ok(); } - cache = start_cache_collection(); + LOG(DEBUG) << "Found " << data_path << " with " << free << " free; caller requested " + << freeStorageSize; + + if (flags & FLAG_FREE_CACHE_V2) { + // This new cache strategy fairly removes files from UIDs by deleting + // files from the UIDs which are most over their allocated quota + + // 1. Create trackers for every known UID + ATRACE_BEGIN("create"); + std::unordered_map<uid_t, std::shared_ptr<CacheTracker>> trackers; + for (auto user : get_known_users(uuid_)) { + FTS *fts; + FTSENT *p; + char *argv[] = { + (char*) create_data_user_ce_path(uuid_, user).c_str(), + (char*) create_data_user_de_path(uuid_, user).c_str(), + nullptr + }; + if (!(fts = fts_open(argv, FTS_PHYSICAL | FTS_XDEV, NULL))) { + return error("Failed to fts_open"); + } + while ((p = fts_read(fts)) != NULL) { + if (p->fts_info == FTS_D && p->fts_level == 1) { + uid_t uid = p->fts_statp->st_uid; + auto search = trackers.find(uid); + if (search != trackers.end()) { + search->second->addDataPath(p->fts_path); + } else { + auto tracker = std::shared_ptr<CacheTracker>(new CacheTracker( + multiuser_get_user_id(uid), multiuser_get_app_id(uid), device)); + tracker->addDataPath(p->fts_path); + tracker->cacheQuota = mCacheQuotas[uid]; + if (tracker->cacheQuota == 0) { + LOG(WARNING) << "UID " << uid << " has no cache quota; assuming 64MB"; + tracker->cacheQuota = 67108864; + } + trackers[uid] = tracker; + } + fts_set(fts, p, FTS_SKIP); + } + } + fts_close(fts); + } + ATRACE_END(); - auto users = get_known_users(uuid_); - for (auto user : users) { - add_cache_files(cache, create_data_user_ce_path(uuid_, user)); - add_cache_files(cache, create_data_user_de_path(uuid_, user)); - add_cache_files(cache, - StringPrintf("%s/Android/data", create_data_media_path(uuid_, user).c_str())); - } + // 2. Populate tracker stats and insert into priority queue + ATRACE_BEGIN("populate"); + auto cmp = [](std::shared_ptr<CacheTracker> left, std::shared_ptr<CacheTracker> right) { + return (left->getCacheRatio() < right->getCacheRatio()); + }; + std::priority_queue<std::shared_ptr<CacheTracker>, + std::vector<std::shared_ptr<CacheTracker>>, decltype(cmp)> queue(cmp); + for (const auto& it : trackers) { + it.second->loadStats(); + queue.push(it.second); + } + ATRACE_END(); - clear_cache_files(data_path, cache, freeStorageSize); - finish_cache_collection(cache); + // 3. Bounce across the queue, freeing items from whichever tracker is + // the most over their assigned quota + ATRACE_BEGIN("bounce"); + std::shared_ptr<CacheTracker> active; + while (active || !queue.empty()) { + // 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() + && active->getCacheRatio() < queue.top()->getCacheRatio(); + if (!active || nextBetter) { + if (active) { + // Current tracker still has items, so we'll consider it + // again later once it bubbles up to surface + queue.push(active); + } + active = queue.top(); queue.pop(); + active->ensureItems(); + continue; + } - avail = data_disk_free(data_path); - if (avail >= freeStorageSize) { + // If no items remain, go find another tracker + if (active->items.empty()) { + active = nullptr; + continue; + } else { + auto item = active->items.back(); + active->items.pop_back(); + + LOG(DEBUG) << "Purging " << item->toString() << " from " << active->toString(); + if (!noop) { + item->purge(); + } + active->cacheUsed -= item->size; + needed -= item->size; + } + + // Verify that we're actually done before bailing, since sneaky + // apps might be using hardlinks + if (needed <= 0) { + free = data_disk_free(data_path); + needed = freeStorageSize - free; + if (needed <= 0) { + break; + } else { + LOG(WARNING) << "Expected to be done but still need " << needed; + } + } + } + ATRACE_END(); + + } else { + ATRACE_BEGIN("start"); + cache_t* cache = start_cache_collection(); + ATRACE_END(); + + ATRACE_BEGIN("add"); + for (auto user : get_known_users(uuid_)) { + add_cache_files(cache, create_data_user_ce_path(uuid_, user)); + add_cache_files(cache, create_data_user_de_path(uuid_, user)); + add_cache_files(cache, + StringPrintf("%s/Android/data", create_data_media_path(uuid_, user).c_str())); + } + ATRACE_END(); + + ATRACE_BEGIN("clear"); + clear_cache_files(data_path, cache, freeStorageSize); + ATRACE_END(); + + ATRACE_BEGIN("finish"); + finish_cache_collection(cache); + ATRACE_END(); + } + + free = data_disk_free(data_path); + if (free >= freeStorageSize) { return ok(); } else { return error(StringPrintf("Failed to free up %" PRId64 " on %s; final free space %" PRId64, - freeStorageSize, data_path.c_str(), avail)); + freeStorageSize, data_path.c_str(), free)); } } @@ -1389,6 +1517,18 @@ binder::Status InstalldNativeService::getExternalSize(const std::unique_ptr<std: return ok(); } +binder::Status InstalldNativeService::setAppQuota(const std::unique_ptr<std::string>& uuid, + int32_t userId, int32_t appId, int64_t cacheQuota) { + ENFORCE_UID(AID_SYSTEM); + CHECK_ARGUMENT_UUID(uuid); + std::lock_guard<std::recursive_mutex> lock(mLock); + + int32_t uid = multiuser_get_uid(userId, appId); + mCacheQuotas[uid] = cacheQuota; + + return ok(); +} + // Dumps the contents of a profile file, using pkgname's dex files for pretty // printing the result. binder::Status InstalldNativeService::dumpProfiles(int32_t uid, const std::string& packageName, diff --git a/cmds/installd/InstalldNativeService.h b/cmds/installd/InstalldNativeService.h index 0208fb147c..0a9f12f311 100644 --- a/cmds/installd/InstalldNativeService.h +++ b/cmds/installd/InstalldNativeService.h @@ -24,6 +24,7 @@ #include <vector> #include <unordered_map> +#include <android-base/macros.h> #include <binder/BinderService.h> #include <cutils/multiuser.h> @@ -67,6 +68,9 @@ public: binder::Status getExternalSize(const std::unique_ptr<std::string>& uuid, int32_t userId, int32_t flags, std::vector<int64_t>* _aidl_return); + binder::Status setAppQuota(const std::unique_ptr<std::string>& uuid, + int32_t userId, int32_t appId, int64_t cacheQuota); + binder::Status moveCompleteApp(const std::unique_ptr<std::string>& fromUuid, const std::unique_ptr<std::string>& toUuid, const std::string& packageName, const std::string& dataAppName, int32_t appId, const std::string& seInfo, @@ -90,7 +94,8 @@ public: int32_t uid); binder::Status rmPackageDir(const std::string& packageDir); binder::Status markBootComplete(const std::string& instructionSet); - binder::Status freeCache(const std::unique_ptr<std::string>& uuid, int64_t freeStorageSize); + binder::Status freeCache(const std::unique_ptr<std::string>& uuid, int64_t freeStorageSize, + int32_t flags); binder::Status linkNativeLibraryDirectory(const std::unique_ptr<std::string>& uuid, const std::string& packageName, const std::string& nativeLibPath32, int32_t userId); binder::Status createOatDir(const std::string& oatDir, const std::string& instructionSet); @@ -108,6 +113,8 @@ private: /* Map from mount point to underlying device node */ std::unordered_map<std::string, std::string> mQuotaDevices; + /* Map from UID to cache quota size */ + std::unordered_map<uid_t, int64_t> mCacheQuotas; std::string findQuotaDeviceForUuid(const std::unique_ptr<std::string>& uuid); }; diff --git a/cmds/installd/binder/android/os/IInstalld.aidl b/cmds/installd/binder/android/os/IInstalld.aidl index 2f12ea9f37..aa5e4f23fe 100644 --- a/cmds/installd/binder/android/os/IInstalld.aidl +++ b/cmds/installd/binder/android/os/IInstalld.aidl @@ -38,6 +38,8 @@ interface IInstalld { long[] getUserSize(@nullable @utf8InCpp String uuid, int userId, int flags, in int[] appIds); long[] getExternalSize(@nullable @utf8InCpp String uuid, int userId, int flags); + void setAppQuota(@nullable @utf8InCpp String uuid, int userId, int appId, long cacheQuota); + void moveCompleteApp(@nullable @utf8InCpp String fromUuid, @nullable @utf8InCpp String toUuid, @utf8InCpp String packageName, @utf8InCpp String dataAppName, int appId, @utf8InCpp String seInfo, int targetSdkVersion); @@ -58,7 +60,7 @@ interface IInstalld { void idmap(@utf8InCpp String targetApkPath, @utf8InCpp String overlayApkPath, int uid); void rmPackageDir(@utf8InCpp String packageDir); void markBootComplete(@utf8InCpp String instructionSet); - void freeCache(@nullable @utf8InCpp String uuid, long freeStorageSize); + void freeCache(@nullable @utf8InCpp String uuid, long freeStorageSize, int flags); void linkNativeLibraryDirectory(@nullable @utf8InCpp String uuid, @utf8InCpp String packageName, @utf8InCpp String nativeLibPath32, int userId); void createOatDir(@utf8InCpp String oatDir, @utf8InCpp String instructionSet); |