1 /*
2 * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "quota/quota_manager.h"
17
18 #include <charconv>
19 #include <dirent.h>
20 #include <fstream>
21 #include <linux/fs.h>
22 #include <linux/quota.h>
23 #include <stack>
24 #include <sys/quota.h>
25 #include <sys/stat.h>
26 #include <sys/statvfs.h>
27 #include <thread>
28 #include <unistd.h>
29
30 #include "file_uri.h"
31 #include "sandbox_helper.h"
32 #include "storage_service_errno.h"
33 #include "storage_service_log.h"
34 #include "storage_service_constant.h"
35 #include "utils/file_utils.h"
36 #include "utils/storage_radar.h"
37
38 namespace OHOS {
39 namespace StorageDaemon {
40 constexpr const char *QUOTA_DEVICE_DATA_PATH = "/data";
41 constexpr const char *PROC_MOUNTS_PATH = "/proc/mounts";
42 constexpr const char *DEV_BLOCK_PATH = "/dev/block/";
43 constexpr const char *CONFIG_FILE_PATH = "/etc/passwd";
44 constexpr const char *DATA_DEV_PATH = "/dev/block/by-name/userdata";
45 constexpr uint64_t ONE_KB = 1;
46 constexpr uint64_t ONE_MB = 1024 * ONE_KB;
47 constexpr int32_t ONE_HUNDRED_M_BIT = 1024 * 1024 * 100;
48 constexpr uint64_t PATH_MAX_LEN = 4096;
49 constexpr double DIVISOR = 1024.0 * 1024.0;
50 constexpr double BASE_NUMBER = 10.0;
51 constexpr int32_t ONE_MS = 1000;
52 constexpr int32_t ACCURACY_NUM = 2;
53 constexpr int32_t MAX_UID_COUNT = 100000;
54 static std::map<std::string, std::string> mQuotaReverseMounts;
55 #define Q_GETNEXTQUOTA_LOCAL 0x800009
56 std::recursive_mutex mMountsLock;
57
58 struct NextDqBlk {
59 uint64_t dqbHardLimit;
60 uint64_t dqbBSoftLimit;
61 uint64_t dqbCurSpace;
62 uint64_t dqbIHardLimit;
63 uint64_t dqbISoftLimit;
64 uint64_t dqbCurInodes;
65 uint64_t dqbBTime;
66 uint64_t dqbITime;
67 uint32_t dqbValid;
68 uint32_t dqbId;
69 };
70
GetInstance()71 QuotaManager &QuotaManager::GetInstance()
72 {
73 static QuotaManager instance_;
74 return instance_;
75 }
76
InitialiseQuotaMounts()77 static bool InitialiseQuotaMounts()
78 {
79 std::lock_guard<std::recursive_mutex> lock(mMountsLock);
80 mQuotaReverseMounts.clear();
81 std::ifstream in(PROC_MOUNTS_PATH);
82
83 if (!in.is_open()) {
84 LOGE("Failed to open mounts file");
85 return false;
86 }
87 std::string source;
88 std::string target;
89 std::string ignored;
90
91 while (in.peek() != EOF) {
92 std::getline(in, source, ' ');
93 std::getline(in, target, ' ');
94 std::getline(in, ignored);
95 if (source.compare(0, strlen(DEV_BLOCK_PATH), DEV_BLOCK_PATH) == 0) {
96 struct dqblk dq;
97 if (quotactl(QCMD(Q_GETQUOTA, USRQUOTA), source.c_str(), 0, reinterpret_cast<char*>(&dq)) == 0) {
98 mQuotaReverseMounts[target] = source;
99 }
100 }
101 }
102
103 return true;
104 }
105
GetQuotaSrcMountPath(const std::string & target)106 static std::string GetQuotaSrcMountPath(const std::string &target)
107 {
108 std::lock_guard<std::recursive_mutex> lock(mMountsLock);
109 if (mQuotaReverseMounts.find(target) != mQuotaReverseMounts.end()) {
110 return mQuotaReverseMounts[target];
111 } else {
112 return "";
113 }
114 }
115
GetOccupiedSpaceForUid(int32_t uid,int64_t & size)116 static int64_t GetOccupiedSpaceForUid(int32_t uid, int64_t &size)
117 {
118 LOGE("GetOccupiedSpaceForUid uid:%{public}d", uid);
119 struct dqblk dq;
120 if (quotactl(QCMD(Q_GETQUOTA, USRQUOTA), DATA_DEV_PATH, uid, reinterpret_cast<char*>(&dq)) != 0) {
121 LOGE("Failed to get quotactl, errno : %{public}d", errno);
122 return E_QUOTA_CTL_KERNEL_ERR;
123 }
124 size = static_cast<int64_t>(dq.dqb_curspace);
125 LOGE("GetOccupiedSpaceForUid size:%{public}s", std::to_string(size).c_str());
126 return E_OK;
127 }
128
GetUidStorageStats(const std::string & storageStatus)129 void QuotaManager::GetUidStorageStats(const std::string &storageStatus)
130 {
131 LOGI("GetUidStorageStats begin!");
132 std::vector<UidSaInfo> vec;
133 auto ret = ParseConfigFile(CONFIG_FILE_PATH, vec);
134 if (ret != E_OK) {
135 LOGE("parsePasswd File failed.");
136 return;
137 }
138
139 ret = GetOccupiedSpaceForUidList(vec);
140 if (ret != E_OK) {
141 return;
142 }
143
144 std::sort(vec.begin(), vec.end(), [](const UidSaInfo& a, const UidSaInfo& b) {
145 return a.size > b.size;
146 });
147 std::ostringstream extraData;
148 extraData << storageStatus <<std::endl;
149 for (const auto& info : vec) {
150 if (info.size < ONE_HUNDRED_M_BIT) {
151 continue;
152 }
153 extraData << "{uid:" << info.uid
154 << ",saName:" << info.saName
155 << ",size:" << ConvertBytesToMB(info.size, ACCURACY_NUM)
156 << "MB}"<<std::endl;
157 }
158 LOGI("extraData is %{public}s", extraData.str().c_str());
159 StorageService::StorageRadar::ReportSaSizeResult("QuotaManager::GetUidStorageStats", E_STORAGE_STATUS,
160 extraData.str());
161 LOGI("GetUidStorageStats end!");
162 }
163
ConvertBytesToMB(int64_t bytes,int32_t decimalPlaces)164 double QuotaManager::ConvertBytesToMB(int64_t bytes, int32_t decimalPlaces)
165 {
166 if (bytes < 0) {
167 return 0.0;
168 }
169 double mb = static_cast<double>(bytes) / DIVISOR;
170
171 if (decimalPlaces < 0) {
172 decimalPlaces = 0;
173 }
174 double factor = std::pow(BASE_NUMBER, decimalPlaces);
175 if (factor == 0) {
176 return 0.0;
177 }
178 return std::round(mb * factor) / factor;
179 }
180
StringToInt32(const std::string & strUid,int32_t & outUid32)181 bool QuotaManager::StringToInt32(const std::string &strUid, int32_t &outUid32)
182 {
183 if (strUid.empty()) {
184 return false;
185 }
186 for (char ch : strUid) {
187 if (!std::isdigit(static_cast<unsigned char>(ch))) {
188 return false;
189 }
190 }
191
192 uint64_t uid;
193 auto res = std::from_chars(strUid.data(), strUid.data() + strUid.size(), uid);
194 if (res.ec != std::errc()) {
195 return false;
196 }
197 if (uid > static_cast<uint64_t>(INT32_MAX)) {
198 return false;
199 }
200 outUid32 = static_cast<int32_t>(uid);
201 return true;
202 }
203
GetUid32FromEntry(const std::string & entry,int32_t & outUid32,std::string & saName)204 bool QuotaManager::GetUid32FromEntry(const std::string &entry, int32_t &outUid32, std::string &saName)
205 {
206 size_t firstColon = entry.find(':');
207 if (firstColon == std::string::npos) {
208 return false;
209 }
210 saName = entry.substr(0, firstColon);
211 size_t secondColon = entry.find(':', firstColon + 1);
212 if (secondColon == std::string::npos) {
213 return false;
214 }
215 size_t thirdColon = entry.find(':', secondColon + 1);
216 if (thirdColon == std::string::npos) {
217 return false;
218 }
219 std::string uidStr = entry.substr(secondColon + 1, thirdColon - (secondColon + 1));
220 return StringToInt32(uidStr, outUid32);
221 }
222
ParseConfigFile(const std::string & path,std::vector<struct UidSaInfo> & vec)223 int32_t QuotaManager::ParseConfigFile(const std::string &path, std::vector<struct UidSaInfo> &vec)
224 {
225 LOGI("pasePasswdFile begin!");
226 char realPath[PATH_MAX] = {0x00};
227 if (realpath(path.c_str(), realPath) == nullptr) {
228 LOGE("path not valid, path = %{private}s", path.c_str());
229 return E_JSON_PARSE_ERROR;
230 }
231
232 std::ifstream infile(std::string(realPath), std::ios::in);
233 if (!infile.is_open()) {
234 LOGE("Open file failed, errno = %{public}d", errno);
235 return E_OPEN_JSON_FILE_ERROR;
236 }
237
238 std::string line;
239 while (getline(infile, line)) {
240 if (line == "") {
241 continue;
242 }
243 struct UidSaInfo info;
244 if (GetUid32FromEntry(line, info.uid, info.saName)) {
245 vec.push_back(info);
246 }
247 }
248 infile.close();
249 LOGI("pasePasswdFile end!");
250 return E_OK;
251 }
252
GetOccupiedSpaceForUidList(std::vector<struct UidSaInfo> & vec)253 int64_t QuotaManager::GetOccupiedSpaceForUidList(std::vector<struct UidSaInfo> &vec)
254 {
255 LOGI("GetOccupiedSpaceForUidList begin!");
256 int32_t curUid = 0;
257 int32_t count = 0;
258 std::map<int32_t, int64_t> userAppSizeMap;
259 while (count < MAX_UID_COUNT) {
260 struct NextDqBlk dq;
261 if (quotactl(QCMD(Q_GETNEXTQUOTA_LOCAL, USRQUOTA), DATA_DEV_PATH, curUid, reinterpret_cast<char*>(&dq)) != 0) {
262 LOGE("failed to get next quota, uid is %{public}d, errno is %{public}d,", curUid, errno);
263 break;
264 }
265 int32_t dqUid = static_cast<int32_t>(dq.dqbId);
266 for (struct UidSaInfo &info : vec) {
267 if (info.uid == dqUid) {
268 info.size = static_cast<int64_t>(dq.dqbCurSpace);
269 break;
270 }
271 }
272 if (dqUid >= StorageService::APP_UID) {
273 int32_t userId = dqUid / StorageService::USER_ID_BASE;
274 if (userAppSizeMap.find(userId) != userAppSizeMap.end()) {
275 userAppSizeMap[userId] += static_cast<int64_t>(dq.dqbCurSpace);
276 } else {
277 userAppSizeMap[userId] = static_cast<int64_t>(dq.dqbCurSpace);
278 }
279 }
280 if (dqUid >= StorageService::ZERO_USER_MIN_UID && dqUid <= StorageService::ZERO_USER_MAX_UID) {
281 int32_t userId = StorageService::ZERO_USER;
282 if (userAppSizeMap.find(userId) != userAppSizeMap.end()) {
283 userAppSizeMap[userId] += static_cast<int64_t>(dq.dqbCurSpace);
284 } else {
285 userAppSizeMap[userId] = static_cast<int64_t>(dq.dqbCurSpace);
286 }
287 }
288 count++;
289 curUid = dqUid + 1;
290 if (curUid == 0) {
291 break;
292 }
293 usleep(ONE_MS);
294 }
295 for (const auto &pair : userAppSizeMap) {
296 UidSaInfo info = {pair.first, "", pair.second};
297 vec.push_back(info);
298 }
299 LOGI("GetOccupiedSpaceForUidList end!");
300 return E_OK;
301 }
302
GetOccupiedSpaceForGid(int32_t gid,int64_t & size)303 static int64_t GetOccupiedSpaceForGid(int32_t gid, int64_t &size)
304 {
305 LOGE("GetOccupiedSpaceForGid gid:%{public}d", gid);
306 if (InitialiseQuotaMounts() != true) {
307 LOGE("Failed to initialise quota mounts");
308 return E_INIT_QUOTA_MOUNTS_FAILED;
309 }
310
311 std::string device = "";
312 device = GetQuotaSrcMountPath(QUOTA_DEVICE_DATA_PATH);
313 if (device.empty()) {
314 LOGE("skip when device no quotas present");
315 return E_OK;
316 }
317
318 struct dqblk dq;
319 if (quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), device.c_str(), gid, reinterpret_cast<char*>(&dq)) != 0) {
320 LOGE("Failed to get quotactl, errno : %{public}d", errno);
321 return E_QUOTA_CTL_KERNEL_ERR;
322 }
323
324 size = static_cast<int64_t>(dq.dqb_curspace);
325 LOGE("GetOccupiedSpaceForGid size:%{public}s", std::to_string(size).c_str());
326 return E_OK;
327 }
328
329
GetOccupiedSpaceForPrjId(int32_t prjId,int64_t & size)330 static int64_t GetOccupiedSpaceForPrjId(int32_t prjId, int64_t &size)
331 {
332 LOGE("GetOccupiedSpaceForPrjId prjId:%{public}d", prjId);
333 if (InitialiseQuotaMounts() != true) {
334 LOGE("Failed to initialise quota mounts");
335 return E_INIT_QUOTA_MOUNTS_FAILED;
336 }
337
338 std::string device = "";
339 device = GetQuotaSrcMountPath(QUOTA_DEVICE_DATA_PATH);
340 if (device.empty()) {
341 LOGE("skip when device no quotas present");
342 return E_OK;
343 }
344
345 struct dqblk dq;
346 if (quotactl(QCMD(Q_GETQUOTA, PRJQUOTA), device.c_str(), prjId, reinterpret_cast<char*>(&dq)) != 0) {
347 LOGE("Failed to get quotactl, errno : %{public}d", errno);
348 return E_QUOTA_CTL_KERNEL_ERR;
349 }
350
351 size = static_cast<int64_t>(dq.dqb_curspace);
352 LOGE("GetOccupiedSpaceForPrjId size:%{public}s", std::to_string(size).c_str());
353 return E_OK;
354 }
355
GetOccupiedSpace(int32_t idType,int32_t id,int64_t & size)356 int32_t QuotaManager::GetOccupiedSpace(int32_t idType, int32_t id, int64_t &size)
357 {
358 switch (idType) {
359 case USRID:
360 return GetOccupiedSpaceForUid(id, size);
361 break;
362 case GRPID:
363 return GetOccupiedSpaceForGid(id, size);
364 break;
365 case PRJID:
366 return GetOccupiedSpaceForPrjId(id, size);
367 break;
368 default:
369 return E_NON_EXIST;
370 }
371 return E_OK;
372 }
373
SetBundleQuota(const std::string & bundleName,int32_t uid,const std::string & bundleDataDirPath,int32_t limitSizeMb)374 int32_t QuotaManager::SetBundleQuota(const std::string &bundleName, int32_t uid,
375 const std::string &bundleDataDirPath, int32_t limitSizeMb)
376 {
377 if (bundleName.empty() || bundleDataDirPath.empty() || uid < 0 || limitSizeMb < 0) {
378 LOGE("Calling the function SetBundleQuota with invalid param");
379 return E_PARAMS_INVALID;
380 }
381
382 LOGE("SetBundleQuota Start, bundleName is %{public}s, uid is %{public}d, bundleDataDirPath is %{public}s, "
383 "limit is %{public}d.", bundleName.c_str(), uid, bundleDataDirPath.c_str(), limitSizeMb);
384 if (InitialiseQuotaMounts() != true) {
385 LOGE("Failed to initialise quota mounts");
386 return E_INIT_QUOTA_MOUNTS_FAILED;
387 }
388
389 std::string device = "";
390 if (bundleDataDirPath.find(QUOTA_DEVICE_DATA_PATH) == 0) {
391 device = GetQuotaSrcMountPath(QUOTA_DEVICE_DATA_PATH);
392 }
393 if (device.empty()) {
394 LOGE("skip when device no quotas present");
395 return E_OK;
396 }
397
398 struct dqblk dq;
399 if (quotactl(QCMD(Q_GETQUOTA, USRQUOTA), device.c_str(), uid, reinterpret_cast<char*>(&dq)) != 0) {
400 LOGE("Failed to get hard quota, errno : %{public}d", errno);
401 return E_QUOTA_CTL_KERNEL_ERR;
402 }
403
404 // dqb_bhardlimit is count of 1kB blocks, dqb_curspace is bytes
405 struct statvfs stat;
406 if (statvfs(bundleDataDirPath.c_str(), &stat) != 0) {
407 LOGE("Failed to statvfs, errno : %{public}d", errno);
408 return E_STAT_VFS_KERNEL_ERR;
409 }
410
411 dq.dqb_valid = QIF_LIMITS;
412 dq.dqb_bhardlimit = (uint32_t)limitSizeMb * ONE_MB;
413 if (quotactl(QCMD(Q_SETQUOTA, USRQUOTA), device.c_str(), uid, reinterpret_cast<char*>(&dq)) != 0) {
414 LOGE("Failed to set hard quota, errno : %{public}d", errno);
415 return E_QUOTA_CTL_KERNEL_ERR;
416 } else {
417 LOGD("Applied hard quotas ok");
418 return E_OK;
419 }
420 }
421
SetQuotaPrjId(const std::string & path,int32_t prjId,bool inherit)422 int32_t QuotaManager::SetQuotaPrjId(const std::string &path, int32_t prjId, bool inherit)
423 {
424 struct fsxattr fsx;
425 char *realPath = realpath(path.c_str(), nullptr);
426 if (realPath == nullptr) {
427 LOGE("realpath failed");
428 return E_PARAMS_NULLPTR_ERR;
429 }
430 FILE *f = fopen(realPath, "r");
431 free(realPath);
432 if (f == nullptr) {
433 LOGE("Failed to open %{public}s, errno: %{public}d", path.c_str(), errno);
434 return E_SYS_KERNEL_ERR;
435 }
436 int fd = fileno(f);
437 if (fd < 0) {
438 (void)fclose(f);
439 return E_SYS_KERNEL_ERR;
440 }
441 if (ioctl(fd, FS_IOC_FSGETXATTR, &fsx) == -1) {
442 LOGE("Failed to get extended attributes of %{public}s, errno: %{public}d", path.c_str(), errno);
443 (void)fclose(f);
444 return E_SYS_KERNEL_ERR;
445 }
446 if (fsx.fsx_projid == static_cast<uint32_t>(prjId)) {
447 (void)fclose(f);
448 return E_OK;
449 }
450 fsx.fsx_projid = static_cast<uint32_t>(prjId);
451 if (ioctl(fd, FS_IOC_FSSETXATTR, &fsx) == -1) {
452 LOGE("Failed to set project id for %{public}s, errno: %{public}d", path.c_str(), errno);
453 (void)fclose(f);
454 return E_SYS_KERNEL_ERR;
455 }
456
457 if (inherit) {
458 uint32_t flags;
459 if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) {
460 LOGE("Failed to get flags for %{public}s, errno:%{public}d", path.c_str(), errno);
461 (void)fclose(f);
462 return E_SYS_KERNEL_ERR;
463 }
464 flags |= FS_PROJINHERIT_FL;
465 if (ioctl(fd, FS_IOC_SETFLAGS, &flags) == -1) {
466 LOGE("Failed to set flags for %{public}s, errno:%{public}d", path.c_str(), errno);
467 (void)fclose(f);
468 return E_SYS_KERNEL_ERR;
469 }
470 }
471 (void)fclose(f);
472 return E_OK;
473 }
474
CheckOverLongPath(const std::string & path)475 uint32_t CheckOverLongPath(const std::string &path)
476 {
477 uint32_t len = path.length();
478 if (len >= PATH_MAX_LEN) {
479 size_t found = path.find_last_of('/');
480 std::string sub = path.substr(found + 1);
481 LOGE("Path over long, length:%{public}d, fileName:%{public}s.", len, sub.c_str());
482 }
483 return len;
484 }
485 } // namespace STORAGE_DAEMON
486 } // namespace OHOS
487