1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #define ATRACE_TAG ATRACE_TAG_PACKAGE_MANAGER 18 19 #include "CacheTracker.h" 20 21 #include <fts.h> 22 #include <sys/quota.h> 23 #include <sys/xattr.h> 24 #include <utils/Trace.h> 25 26 #include <android-base/logging.h> 27 #include <android-base/stringprintf.h> 28 29 #include "utils.h" 30 31 using android::base::StringPrintf; 32 33 namespace android { 34 namespace installd { 35 36 CacheTracker::CacheTracker(userid_t userId, appid_t appId, const std::string& quotaDevice) : 37 cacheUsed(0), cacheQuota(0), mUserId(userId), mAppId(appId), mQuotaDevice(quotaDevice), 38 mItemsLoaded(false) { 39 } 40 41 CacheTracker::~CacheTracker() { 42 } 43 44 std::string CacheTracker::toString() { 45 return StringPrintf("UID=%d used=%" PRId64 " quota=%" PRId64 " ratio=%d", 46 multiuser_get_uid(mUserId, mAppId), cacheUsed, cacheQuota, getCacheRatio()); 47 } 48 49 void CacheTracker::addDataPath(const std::string& dataPath) { 50 mDataPaths.push_back(dataPath); 51 } 52 53 void CacheTracker::loadStats() { 54 ATRACE_BEGIN("loadStats quota"); 55 cacheUsed = 0; 56 if (loadQuotaStats()) { 57 return; 58 } 59 ATRACE_END(); 60 61 ATRACE_BEGIN("loadStats tree"); 62 cacheUsed = 0; 63 for (auto path : mDataPaths) { 64 auto cachePath = read_path_inode(path, "cache", kXattrInodeCache); 65 auto codeCachePath = read_path_inode(path, "code_cache", kXattrInodeCodeCache); 66 calculate_tree_size(cachePath, &cacheUsed); 67 calculate_tree_size(codeCachePath, &cacheUsed); 68 } 69 ATRACE_END(); 70 } 71 72 bool CacheTracker::loadQuotaStats() { 73 int cacheGid = multiuser_get_cache_gid(mUserId, mAppId); 74 int extCacheGid = multiuser_get_ext_cache_gid(mUserId, mAppId); 75 if (!mQuotaDevice.empty() && cacheGid != -1 && extCacheGid != -1) { 76 struct dqblk dq; 77 if (quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), mQuotaDevice.c_str(), cacheGid, 78 reinterpret_cast<char*>(&dq)) != 0) { 79 if (errno != ESRCH) { 80 PLOG(ERROR) << "Failed to quotactl " << mQuotaDevice << " for GID " << cacheGid; 81 } 82 return false; 83 } else { 84 cacheUsed += dq.dqb_curspace; 85 } 86 87 if (quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), mQuotaDevice.c_str(), extCacheGid, 88 reinterpret_cast<char*>(&dq)) != 0) { 89 if (errno != ESRCH) { 90 PLOG(ERROR) << "Failed to quotactl " << mQuotaDevice << " for GID " << cacheGid; 91 } 92 return false; 93 } else { 94 cacheUsed += dq.dqb_curspace; 95 } 96 return true; 97 } else { 98 return false; 99 } 100 } 101 102 void CacheTracker::loadItemsFrom(const std::string& path) { 103 FTS *fts; 104 FTSENT *p; 105 char *argv[] = { (char*) path.c_str(), nullptr }; 106 if (!(fts = fts_open(argv, FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV, NULL))) { 107 PLOG(WARNING) << "Failed to fts_open " << path; 108 return; 109 } 110 while ((p = fts_read(fts)) != nullptr) { 111 if (p->fts_level == 0) continue; 112 113 // Create tracking nodes for everything we encounter 114 switch (p->fts_info) { 115 case FTS_D: 116 case FTS_DEFAULT: 117 case FTS_F: 118 case FTS_SL: 119 case FTS_SLNONE: { 120 auto item = std::shared_ptr<CacheItem>(new CacheItem(p)); 121 p->fts_pointer = static_cast<void*>(item.get()); 122 items.push_back(item); 123 } 124 } 125 126 switch (p->fts_info) { 127 case FTS_D: { 128 auto item = static_cast<CacheItem*>(p->fts_pointer); 129 item->group |= (getxattr(p->fts_path, kXattrCacheGroup, nullptr, 0) >= 0); 130 item->tombstone |= (getxattr(p->fts_path, kXattrCacheTombstone, nullptr, 0) >= 0); 131 132 // When group, immediately collect all files under tree 133 if (item->group) { 134 while ((p = fts_read(fts)) != nullptr) { 135 if (p->fts_info == FTS_DP && p->fts_level == item->level) break; 136 switch (p->fts_info) { 137 case FTS_D: 138 case FTS_DEFAULT: 139 case FTS_F: 140 case FTS_SL: 141 case FTS_SLNONE: 142 item->size += p->fts_statp->st_blocks * 512; 143 item->modified = std::max(item->modified, p->fts_statp->st_mtime); 144 } 145 } 146 } 147 } 148 } 149 150 // Bubble up modified time to parent 151 switch (p->fts_info) { 152 case FTS_DP: 153 case FTS_DEFAULT: 154 case FTS_F: 155 case FTS_SL: 156 case FTS_SLNONE: { 157 auto item = static_cast<CacheItem*>(p->fts_pointer); 158 auto parent = static_cast<CacheItem*>(p->fts_parent->fts_pointer); 159 if (parent) { 160 parent->modified = std::max(parent->modified, item->modified); 161 } 162 } 163 } 164 } 165 fts_close(fts); 166 } 167 168 void CacheTracker::loadItems() { 169 items.clear(); 170 171 ATRACE_BEGIN("loadItems"); 172 for (auto path : mDataPaths) { 173 loadItemsFrom(read_path_inode(path, "cache", kXattrInodeCache)); 174 loadItemsFrom(read_path_inode(path, "code_cache", kXattrInodeCodeCache)); 175 } 176 ATRACE_END(); 177 178 ATRACE_BEGIN("sortItems"); 179 auto cmp = [](std::shared_ptr<CacheItem> left, std::shared_ptr<CacheItem> right) { 180 // TODO: sort dotfiles last 181 // TODO: sort code_cache last 182 if (left->modified != right->modified) { 183 return (left->modified > right->modified); 184 } 185 if (left->level != right->level) { 186 return (left->level < right->level); 187 } 188 return left->directory; 189 }; 190 std::stable_sort(items.begin(), items.end(), cmp); 191 ATRACE_END(); 192 } 193 194 void CacheTracker::ensureItems() { 195 if (mItemsLoaded) { 196 return; 197 } else { 198 loadItems(); 199 mItemsLoaded = true; 200 } 201 } 202 203 int CacheTracker::getCacheRatio() { 204 if (cacheQuota == 0) { 205 return 0; 206 } else { 207 return (cacheUsed * 10000) / cacheQuota; 208 } 209 } 210 211 } // namespace installd 212 } // namespace android 213