• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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