| // Copyright 2021 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "spaced/disk_usage_impl.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| |
| #include <fcntl.h> |
| #include <sys/ioctl.h> |
| #include <sys/mount.h> |
| #include <sys/quota.h> |
| #include <sys/stat.h> |
| #include <sys/statvfs.h> |
| |
| #include <base/check.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/posix/eintr_wrapper.h> |
| #include <base/strings/string_util.h> |
| #include <brillo/blkdev_utils/get_backing_block_device.h> |
| #include <rootdev/rootdev.h> |
| |
| namespace spaced { |
| |
| DiskUsageUtilImpl::DiskUsageUtilImpl(const base::FilePath& rootdev, |
| std::optional<brillo::Thinpool> thinpool) |
| : rootdev_(rootdev), thinpool_(thinpool) {} |
| |
| int DiskUsageUtilImpl::StatVFS(const base::FilePath& path, struct statvfs* st) { |
| return HANDLE_EINTR(statvfs(path.value().c_str(), st)); |
| } |
| |
| int DiskUsageUtilImpl::QuotaCtl(int cmd, |
| const base::FilePath& device, |
| int id, |
| struct dqblk* dq) { |
| return quotactl(cmd, device.value().c_str(), id, reinterpret_cast<char*>(dq)); |
| } |
| |
| base::FilePath DiskUsageUtilImpl::GetDevice(const base::FilePath& path) { |
| return brillo::GetBackingLogicalDeviceForFile(path); |
| } |
| |
| int64_t DiskUsageUtilImpl::GetFreeDiskSpace(const base::FilePath& path) { |
| // Use statvfs() to get the free space for the given path. |
| struct statvfs stat; |
| |
| if (StatVFS(path, &stat) != 0) { |
| PLOG(ERROR) << "Failed to run statvfs() on " << path; |
| return -1; |
| } |
| |
| int64_t free_disk_space = static_cast<int64_t>(stat.f_bavail) * stat.f_frsize; |
| |
| return free_disk_space; |
| } |
| |
| int64_t DiskUsageUtilImpl::GetTotalDiskSpace(const base::FilePath& path) { |
| // Use statvfs() to get the total space for the given path. |
| struct statvfs stat; |
| |
| if (StatVFS(path, &stat) != 0) { |
| PLOG(ERROR) << "Failed to run statvfs() on " << path; |
| return -1; |
| } |
| |
| int64_t total_disk_space = |
| static_cast<int64_t>(stat.f_blocks) * stat.f_frsize; |
| |
| int64_t thinpool_total_space; |
| if (thinpool_ && thinpool_->IsValid() && |
| thinpool_->GetTotalSpace(&thinpool_total_space)) { |
| total_disk_space = std::min(total_disk_space, thinpool_total_space); |
| } |
| |
| return total_disk_space; |
| } |
| |
| int64_t DiskUsageUtilImpl::GetBlockDeviceSize(const base::FilePath& device) { |
| base::ScopedFD fd(HANDLE_EINTR( |
| open(device.value().c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC))); |
| if (!fd.is_valid()) { |
| PLOG(ERROR) << "open " << device.value(); |
| return -1; |
| } |
| |
| int64_t size; |
| if (ioctl(fd.get(), BLKGETSIZE64, &size)) { |
| PLOG(ERROR) << "ioctl(BLKGETSIZE): " << device.value(); |
| return -1; |
| } |
| return size; |
| } |
| |
| int64_t DiskUsageUtilImpl::GetRootDeviceSize() { |
| if (rootdev_.empty()) { |
| LOG(WARNING) << "Failed to get root device"; |
| return -1; |
| } |
| |
| return GetBlockDeviceSize(rootdev_); |
| } |
| |
| bool DiskUsageUtilImpl::IsQuotaSupported(const base::FilePath& path) { |
| return GetQuotaCurrentSpaceForUid(path, 0) >= 0; |
| } |
| |
| int64_t DiskUsageUtilImpl::GetQuotaCurrentSpaceForUid( |
| const base::FilePath& path, uint32_t uid) { |
| return GetQuotaCurrentSpaceForId(path, uid, USRQUOTA); |
| } |
| |
| int64_t DiskUsageUtilImpl::GetQuotaCurrentSpaceForGid( |
| const base::FilePath& path, uint32_t gid) { |
| return GetQuotaCurrentSpaceForId(path, gid, GRPQUOTA); |
| } |
| |
| int64_t DiskUsageUtilImpl::GetQuotaCurrentSpaceForProjectId( |
| const base::FilePath& path, uint32_t project_id) { |
| return GetQuotaCurrentSpaceForId(path, project_id, PRJQUOTA); |
| } |
| |
| int64_t DiskUsageUtilImpl::GetQuotaCurrentSpaceForId(const base::FilePath& path, |
| uint32_t id, |
| int quota_type) { |
| DCHECK(0 <= quota_type && quota_type < MAXQUOTAS) |
| << "Invalid quota_type: " << quota_type; |
| |
| const base::FilePath device = GetDevice(path); |
| if (device.empty()) { |
| LOG(ERROR) << "Failed to find logical device for home directory"; |
| return -1; |
| } |
| |
| struct dqblk dq = {}; |
| if (QuotaCtl(QCMD(Q_GETQUOTA, quota_type), device, id, &dq) != 0) { |
| PLOG(ERROR) << "quotactl failed: quota_type=" << quota_type << ", id=" << id |
| << ", device=" << device.value(); |
| return -1; |
| } |
| return dq.dqb_curspace; |
| } |
| |
| } // namespace spaced |