| // Copyright 2022 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "brillo/storage_balloon.h" | |
| #include <algorithm> | |
| #include <fcntl.h> | |
| #include <rootdev/rootdev.h> | |
| #include <sys/statvfs.h> | |
| #include <sys/vfs.h> | |
| #include <sys/xattr.h> | |
| #include <cstddef> | |
| #include <memory> | |
| #include <string> | |
| #include "base/files/scoped_file.h" | |
| #include <base/files/file_path.h> | |
| #include <base/files/file_util.h> | |
| #include <base/logging.h> | |
| #include <base/strings/string_number_conversions.h> | |
| #include <base/strings/string_util.h> | |
| #include <brillo/blkdev_utils/get_backing_block_device.h> | |
| namespace brillo { | |
| namespace { | |
| constexpr char kSysFsPath[] = "/sys/fs/ext4"; | |
| constexpr char kReservedClustersPath[] = "reserved_clusters"; | |
| constexpr uint64_t kDefaultClusterCount = 4096; | |
| } // namespace | |
| // static | |
| std::unique_ptr<StorageBalloon> StorageBalloon::GenerateStorageBalloon( | |
| const base::FilePath& path) { | |
| base::FilePath fixed_path = path.StripTrailingSeparators(); | |
| // TODO(sarthakkukreti@): Move to rootdev, create a separate helper to get | |
| // the device. Cannot use GetBackingLogicalDeviceForFile, it requires a | |
| // dependency on udev. | |
| struct stat fs_stat; | |
| if (stat(fixed_path.value().c_str(), &fs_stat)) { | |
| LOG(WARNING) << "Failed to stat filesystem path" << path; | |
| return nullptr; | |
| } | |
| char fs_device[PATH_MAX]; | |
| dev_t dev = fs_stat.st_dev; | |
| int ret = rootdev_wrapper(fs_device, sizeof(fs_device), | |
| false, // Do full resolution. | |
| false, // Remove partition number. | |
| &dev, // Device. | |
| // Path within mountpoint. | |
| fixed_path.value().c_str(), | |
| nullptr, // Use default search path. | |
| nullptr); // Use default /dev path. | |
| if (ret != 0) { | |
| LOG(WARNING) << "Failed to find backing device, error code: " << ret; | |
| return nullptr; | |
| } | |
| return std::unique_ptr<StorageBalloon>(new StorageBalloon( | |
| path, base::FilePath(kSysFsPath) | |
| .AppendASCII(base::FilePath(fs_device).BaseName().value()) | |
| .AppendASCII(kReservedClustersPath))); | |
| } | |
| StorageBalloon::StorageBalloon(const base::FilePath& path, | |
| const base::FilePath& reserved_clusters_path) | |
| : filesystem_path_(path), | |
| sysfs_reserved_clusters_path_(reserved_clusters_path) {} | |
| bool StorageBalloon::IsValid() { | |
| return base::PathExists(filesystem_path_) && | |
| base::PathExists(sysfs_reserved_clusters_path_); | |
| } | |
| StorageBalloon::~StorageBalloon() { | |
| SetBalloonSize(0); | |
| } | |
| bool StorageBalloon::Adjust(int64_t target_space) { | |
| if (!IsValid()) { | |
| LOG(ERROR) << "Invalid balloon"; | |
| return false; | |
| } | |
| if (target_space < 0) { | |
| LOG(ERROR) << "Invalid target space"; | |
| return false; | |
| } | |
| int64_t inflation_size = 0; | |
| if (!CalculateBalloonInflationSize(target_space, &inflation_size)) { | |
| LOG(ERROR) << "Failed to calculate balloon inflation size."; | |
| return false; | |
| } | |
| if (inflation_size == 0) | |
| return true; | |
| int64_t existing_size = GetCurrentBalloonSize(); | |
| if (existing_size < 0) { | |
| LOG(ERROR) << "Failed to get balloon file size"; | |
| return false; | |
| } | |
| return SetBalloonSize(existing_size + inflation_size); | |
| } | |
| bool StorageBalloon::Deflate() { | |
| if (!IsValid()) { | |
| LOG(ERROR) << "Invalid balloon"; | |
| return false; | |
| } | |
| return SetBalloonSize(0); | |
| } | |
| bool StorageBalloon::SetBalloonSize(int64_t size) { | |
| if (!IsValid()) { | |
| LOG(ERROR) << "Invalid balloon"; | |
| return false; | |
| } | |
| if (size < 0) { | |
| size = 0; | |
| } | |
| base::ScopedFD fd(open(sysfs_reserved_clusters_path_.value().c_str(), | |
| O_WRONLY | O_NOFOLLOW | O_CLOEXEC)); | |
| if (!fd.is_valid()) { | |
| PLOG(ERROR) << "Failed to open " << sysfs_reserved_clusters_path_.value(); | |
| return false; | |
| } | |
| return base::WriteFileDescriptor( | |
| fd.get(), | |
| base::NumberToString(kDefaultClusterCount + size / GetClusterSize())); | |
| } | |
| int64_t StorageBalloon::GetCurrentBalloonSize() { | |
| if (!IsValid()) { | |
| LOG(ERROR) << "Invalid balloon"; | |
| return -1; | |
| } | |
| std::string balloon_size_str; | |
| int64_t balloon_size; | |
| if (!base::ReadFileToString(sysfs_reserved_clusters_path_, &balloon_size_str)) | |
| return -1; | |
| base::TrimWhitespaceASCII(balloon_size_str, base::TRIM_ALL, | |
| &balloon_size_str); | |
| if (!base::StringToInt64(balloon_size_str, &balloon_size)) | |
| return -1; | |
| return (balloon_size - kDefaultClusterCount) * GetClusterSize(); | |
| } | |
| bool StorageBalloon::StatVfs(struct statvfs* buf) { | |
| if (!IsValid()) { | |
| LOG(ERROR) << "Invalid balloon"; | |
| return false; | |
| } | |
| return statvfs(filesystem_path_.value().c_str(), buf) == 0; | |
| } | |
| bool StorageBalloon::CalculateBalloonInflationSize(int64_t target_space, | |
| int64_t* inflation_size) { | |
| struct statvfs buf; | |
| if (target_space < 0) { | |
| LOG(ERROR) << "Invalid target space"; | |
| return false; | |
| } | |
| if (!StatVfs(&buf)) { | |
| LOG(ERROR) << "Failed to statvfs() balloon fd"; | |
| return false; | |
| } | |
| int64_t available_space = static_cast<int64_t>(buf.f_bavail) * buf.f_bsize; | |
| *inflation_size = available_space - target_space; | |
| return true; | |
| } | |
| int64_t StorageBalloon::GetClusterSize() { | |
| struct statvfs buf; | |
| if (!StatVfs(&buf)) { | |
| LOG(ERROR) << "Failed to statvfs() balloon fd"; | |
| return -1; | |
| } | |
| return buf.f_bsize; | |
| } | |
| } // namespace brillo |