blob: 5d8b64d08daee9a2dab34523954899273a98ba75 [file] [log] [blame] [edit]
// 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 "swap_management/swap_tool.h"
#include "swap_management/swap_tool_status.h"
#include <cinttypes>
#include <utility>
#include <base/files/dir_reader_posix.h>
#include <base/logging.h>
#include <base/posix/safe_strerror.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <base/threading/platform_thread.h>
#include <base/time/time.h>
#include <chromeos/dbus/swap_management/dbus-constants.h>
namespace swap_management {
namespace {
constexpr char kSwapSizeFile[] = "/var/lib/swap/swap_size";
constexpr char kZramDeviceFile[] = "/dev/zram0";
constexpr char kZramSysfsDir[] = "/sys/block/zram0";
constexpr char kZramWritebackName[] = "zram-writeback";
constexpr char kZramIntegrityName[] = "zram-integrity";
constexpr char kZramWritebackIntegrityMount[] = "/run/zram-integrity";
constexpr char kZramBackingDevice[] = "/sys/block/zram0/backing_dev";
constexpr char kStatefulPartitionDir[] =
"/mnt/stateful_partition/unencrypted/userspace_swap.tmp";
constexpr uint32_t kMiB = 1048576;
constexpr uint32_t kSectorSize = 512;
constexpr base::TimeDelta kMaxIdleAge = base::Days(30);
constexpr uint64_t kMinFilelistDefaultValueKB = 1000000;
// Round up multiple will round the first argument |number| up to the next
// multiple of the second argument |alignment|.
uint64_t RoundupMultiple(uint64_t number, uint64_t alignment) {
return ((number + (alignment - 1)) / alignment) * alignment;
}
} // namespace
absl::StatusOr<std::unique_ptr<LoopDev>> LoopDev::Create(
const std::string& path) {
return Create(path, false, 0);
}
absl::StatusOr<std::unique_ptr<LoopDev>> LoopDev::Create(
const std::string& path, bool direct_io, uint32_t sector_size) {
std::vector<std::string> command({"/sbin/losetup", "--show"});
if (direct_io)
command.push_back("--direct-io=on");
if (sector_size != 0)
command.push_back("--sector-size=" + std::to_string(sector_size));
command.push_back("-f");
command.push_back(path);
std::string loop_dev_path;
absl::Status status =
SwapToolUtil::Get()->RunProcessHelper(command, &loop_dev_path);
if (!status.ok())
return status;
base::TrimWhitespaceASCII(loop_dev_path, base::TRIM_ALL, &loop_dev_path);
return std::unique_ptr<LoopDev>(new LoopDev(loop_dev_path));
}
LoopDev::~LoopDev() {
absl::Status status = absl::OkStatus();
if (!path_.empty()) {
status =
SwapToolUtil::Get()->RunProcessHelper({"/sbin/losetup", "-d", path_});
LOG_IF(ERROR, !status.ok()) << status;
path_.clear();
}
}
std::string LoopDev::GetPath() {
return path_;
}
absl::StatusOr<std::unique_ptr<DmDev>> DmDev::Create(
const std::string& name, const std::string& table_fmt) {
absl::Status status = absl::OkStatus();
status = SwapToolUtil::Get()->RunProcessHelper(
{"/sbin/dmsetup", "create", name, "--table", table_fmt});
if (!status.ok())
return status;
std::unique_ptr<DmDev> dm_dev = std::unique_ptr<DmDev>(new DmDev(name));
status = dm_dev->Wait();
if (!status.ok())
return status;
return std::move(dm_dev);
}
DmDev::~DmDev() {
absl::Status status = absl::OkStatus();
if (!name_.empty()) {
status = SwapToolUtil::Get()->RunProcessHelper(
{"/sbin/dmsetup", "remove", "--deferred", name_});
LOG_IF(ERROR, !status.ok()) << status;
name_.clear();
}
}
// Wait for up to 5 seconds for a dm device to become available,
// if it doesn't then return failed status. This is needed because dm devices
// may take a few seconds to become visible at /dev/mapper after the table is
// switched.
absl::Status DmDev::Wait() {
constexpr base::TimeDelta kMaxWaitTime = base::Seconds(5);
constexpr base::TimeDelta kRetryDelay = base::Milliseconds(100);
std::string path = GetPath();
base::Time startTime = base::Time::Now();
while (true) {
if (base::Time::Now() - startTime > kMaxWaitTime)
return absl::UnavailableError(
path + " is not available after " +
std::to_string(kMaxWaitTime.InMilliseconds()) + " ms.");
if (SwapToolUtil::Get()
->PathExists(base::FilePath("/dev/mapper/").Append(name_))
.ok())
return absl::OkStatus();
base::PlatformThread::Sleep(kRetryDelay);
}
}
std::string DmDev::GetPath() {
return "/dev/mapper/" + name_;
}
// Check if swap is already turned on.
absl::StatusOr<bool> SwapTool::IsZramSwapOn() {
std::string swaps;
absl::Status status = SwapToolUtil::Get()->ReadFileToString(
base::FilePath("/proc/swaps"), &swaps);
if (!status.ok())
return status;
std::vector<std::string> swaps_lines = base::SplitString(
swaps, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
// Skip the first line which is header. Swap is turned on if swaps_lines
// contains entry with zram0 keyword.
for (size_t i = 1; i < swaps_lines.size(); i++) {
if (swaps_lines[i].find("zram0") != std::string::npos)
return true;
}
return false;
}
// Extract second field of MemTotal entry in /proc/meminfo. The unit for
// MemTotal is KiB.
absl::StatusOr<uint64_t> SwapTool::GetMemTotal() {
std::string mem_info;
absl::Status status = SwapToolUtil::Get()->ReadFileToString(
base::FilePath("/proc/meminfo"), &mem_info);
if (!status.ok())
return status;
std::vector<std::string> mem_info_lines = base::SplitString(
mem_info, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
for (auto& line : mem_info_lines) {
if (line.find("MemTotal") != std::string::npos) {
std::string buf = base::SplitString(line, " ", base::KEEP_WHITESPACE,
base::SPLIT_WANT_NONEMPTY)[1];
uint64_t res = 0;
if (!absl::SimpleAtoi(buf, &res))
return absl::OutOfRangeError("Failed to convert " + buf +
" to 64-bit unsigned integer.");
return res;
}
}
return absl::NotFoundError("Could not get MemTotal in /proc/meminfo");
}
// Compute fraction of total RAM used for low-mem margin. The fraction is
// given in bips. A "bip" or "basis point" is 1/100 of 1%.
absl::Status SwapTool::SetDefaultLowMemoryMargin(uint64_t mem_total) {
// Calculate critical margin in MiB, which is 5.2% free. Ignore the decimal.
uint64_t critical_margin = (mem_total / 1024) * 0.052;
// Calculate moderate margin in MiB, which is 40% free. Ignore the decimal.
uint64_t moderate_margin = (mem_total / 1024) * 0.4;
// Write into margin special file.
return SwapToolUtil::Get()->WriteFile(
base::FilePath("/sys/kernel/mm/chromeos-low_mem/margin"),
std::to_string(critical_margin) + " " + std::to_string(moderate_margin));
}
// Initialize MM tunnables.
absl::Status SwapTool::InitializeMMTunables(uint64_t mem_total) {
absl::Status status = SetDefaultLowMemoryMargin(mem_total);
if (!status.ok())
return status;
return SwapToolUtil::Get()->WriteFile(
base::FilePath("/proc/sys/vm/min_filelist_kbytes"),
std::to_string(kMinFilelistDefaultValueKB));
}
// Return zram (compressed ram disk) size in byte for swap.
// kSwapSizeFile contains the zram size in MiB.
// Empty or missing kSwapSizeFile means use default size, which is
// mem_total
// * 2.
// 0 means do not enable zram.
absl::StatusOr<uint64_t> SwapTool::GetZramSize(uint64_t mem_total) {
// For security, only read first few bytes of kSwapSizeFile.
std::string buf;
absl::Status status = SwapToolUtil::Get()->ReadFileToStringWithMaxSize(
base::FilePath(kSwapSizeFile), &buf, 5);
// If the file doesn't exist we use default zram size, other errors we must
// propagate back.
if (!status.ok() && !absl::IsNotFound(status))
return status;
// Trim the potential leading/trailing ASCII whitespaces.
// Note that TrimWhitespaceASCII can safely use the same variable for inputs
// and outputs.
base::TrimWhitespaceASCII(buf, base::TRIM_ALL, &buf);
if (absl::IsNotFound(status) || buf.empty())
return mem_total * 1024 * 2;
uint64_t requested_size_mib = 0;
if (!absl::SimpleAtoi(buf, &requested_size_mib))
return absl::OutOfRangeError("Failed to convert " +
std::to_string(requested_size_mib) +
" to 64-bit unsigned integer.");
if (requested_size_mib == 0)
return absl::InvalidArgumentError("Swap is not turned on since " +
std::string(kSwapSizeFile) +
" contains 0.");
return requested_size_mib * 1024 * 1024;
}
// Run swapon to enable zram swapping.
// swapon may fail because of races with other programs that inspect all
// block devices, so try several times.
absl::Status SwapTool::EnableZramSwapping() {
constexpr uint8_t kMaxEnableTries = 10;
constexpr base::TimeDelta kRetryDelayUs = base::Milliseconds(100);
absl::Status status = absl::OkStatus();
for (size_t i = 0; i < kMaxEnableTries; i++) {
status = SwapToolUtil::Get()->RunProcessHelper(
{"/sbin/swapon", kZramDeviceFile});
if (status.ok())
return status;
LOG(WARNING) << "swapon " << kZramDeviceFile << " failed, try " << i
<< " times, last error:" << status;
base::PlatformThread::Sleep(kRetryDelayUs);
}
return absl::AbortedError("swapon " + std::string(kZramDeviceFile) +
" failed after " + std::to_string(kMaxEnableTries) +
" tries" + " last error: " + status.ToString());
}
// If we're unable to setup writeback just make sure we clean up any
// mounts.
// Devices are cleanup while class instances are released.
// Errors happenes during cleanup will be logged.
void SwapTool::CleanupWriteback() {
absl::Status status = absl::OkStatus();
status = SwapToolUtil::Get()->Umount(kZramWritebackIntegrityMount);
LOG_IF(ERROR, !status.ok()) << status;
status = SwapToolUtil::Get()->DeleteFile(
base::FilePath(kZramWritebackIntegrityMount));
LOG_IF(ERROR, !status.ok()) << status;
}
// Check if zram writeback can be used on the system.
absl::Status SwapTool::ZramWritebackPrerequisiteCheck(uint32_t size) {
absl::Status status = absl::OkStatus();
// Don't allow |size| less than 128MiB or more than 6GiB to be configured.
constexpr uint32_t kZramWritebackMinSize = 128;
constexpr uint32_t kZramWritebackMaxSize = 6144;
if (size < kZramWritebackMinSize || size > kZramWritebackMaxSize)
return absl::InvalidArgumentError("Invalid size specified.");
// kZramBackingDevice must contains none, no writeback is setup before.
std::string backing_dev;
status = SwapToolUtil::Get()->ReadFileToString(
base::FilePath(kZramBackingDevice), &backing_dev);
if (!status.ok())
return status;
base::TrimWhitespaceASCII(backing_dev, base::TRIM_ALL, &backing_dev);
if (backing_dev != "none")
return absl::AlreadyExistsError(
"Zram already has a backing device assigned.");
// kZramWritebackIntegrityMount must not be mounted.
// rmdir(2) will return -EBUSY if the target is mounted.
// DeleteFile returns absl::OkStatus() if the target does not exist.
status = SwapToolUtil::Get()->DeleteFile(
base::FilePath(kZramWritebackIntegrityMount));
return status;
}
absl::Status SwapTool::GetZramWritebackInfo(uint32_t size) {
absl::Status status = absl::OkStatus();
// Read stateful partition file system statistics using statfs.
// f_blocks is total data blocks in file system.
// f_bfree is free blocks in file system.
// f_bsize is the optimal transfer block size.
absl::StatusOr<struct statfs> stateful_statfs =
SwapToolUtil::Get()->GetStatfs(kStatefulPartitionDir);
if (!stateful_statfs.ok())
return stateful_statfs.status();
// Never allow swapping to disk when the overall free diskspace is less
// than 15% of the overall capacity.
constexpr int kMinFreeStatefulPct = 15;
uint64_t stateful_free_pct =
100 * (*stateful_statfs).f_bfree / (*stateful_statfs).f_blocks;
if (stateful_free_pct < kMinFreeStatefulPct)
return absl::ResourceExhaustedError(
"zram writeback cannot be enabled free disk space" +
std::to_string(stateful_free_pct) + "% is less than the minimum 15%");
stateful_block_size_ = (*stateful_statfs).f_bsize;
wb_nr_blocks_ = size * kMiB / stateful_block_size_;
uint64_t wb_pct_of_stateful =
wb_nr_blocks_ * 100 / (*stateful_statfs).f_bfree;
// Only allow 15% of the free diskspace for swap writeback by maximum.
if (wb_pct_of_stateful > kMinFreeStatefulPct) {
uint64_t old_size = size;
wb_nr_blocks_ = kMinFreeStatefulPct * (*stateful_statfs).f_bfree / 100;
size = wb_nr_blocks_ * stateful_block_size_ / kMiB;
LOG(WARNING) << "zram writeback, requested size of " << old_size << " is "
<< wb_pct_of_stateful
<< "% of the free disk space. Size will be reduced to " << size
<< "MiB";
}
wb_size_bytes_ = RoundupMultiple(size * kMiB, stateful_block_size_);
// Because we rounded up writeback_size bytes recalculate the number of blocks
// used.
wb_nr_blocks_ = wb_size_bytes_ / stateful_block_size_;
return absl::OkStatus();
}
absl::Status SwapTool::CreateDmDevicesAndEnableWriteback() {
absl::Status status = absl::OkStatus();
// Create the actual writeback space on the stateful partition.
constexpr char kZramWritebackBackFileName[] = "zram_writeback.swap";
ScopedFilePath scoped_filepath(
base::FilePath(kStatefulPartitionDir).Append(kZramWritebackBackFileName));
status = SwapToolUtil::Get()->WriteFile(scoped_filepath.get(), std::string());
if (!status.ok())
return status;
status =
SwapToolUtil::Get()->Fallocate(scoped_filepath.get(), wb_size_bytes_);
if (!status.ok())
return status;
// Create writeback loop device.
// See drivers/block/loop.c:230
// We support direct I/O only if lo_offset is aligned with the
// logical I/O size of backing device, and the logical block
// size of loop is bigger than the backing device's and the loop
// needn't transform transfer.
auto writeback_loop = LoopDev::Create(scoped_filepath.get().value(), true,
stateful_block_size_);
if (!writeback_loop.ok())
return writeback_loop.status();
std::string writeback_loop_path = (*writeback_loop)->GetPath();
// Create and mount ramfs for integrity loop device back file.
status = SwapToolUtil::Get()->CreateDirectory(
base::FilePath(kZramWritebackIntegrityMount));
if (!status.ok())
return status;
status = SwapToolUtil::Get()->SetPosixFilePermissions(
base::FilePath(kZramWritebackIntegrityMount), 0700);
if (!status.ok())
return status;
status =
SwapToolUtil::Get()->Mount("none", kZramWritebackIntegrityMount, "ramfs",
0, "noexec,nosuid,noatime,mode=0700");
if (!status.ok())
return status;
// Create integrity loop device.
// See drivers/md/dm-integrity.c and
// https://docs.kernel.org/admin-guide/device-mapper/dm-integrity.html
// In direct write mode, The size of dm-integrity is data(tag) area + initial
// segment.
// The size of data(tag) area is (number of blocks in wb device) *
// (tag size), and then roundup with the size of dm-integrity buffer. The
// default number of sector in a dm-integrity buffer is 128 so the size is
// 65536 bytes.
// The size of initial segment is (superblock size == 4KB) + (size of
// journal). dm-integrity requires at least one journal section even with
// direct write mode. As for now, the size of a single journal section is
// 167936 bytes (328 sectors)
// AES-GCM uses a fixed 12 byte IV. The other 12 bytes are auth tag.
constexpr size_t kDmIntegrityTagSize = 24;
constexpr size_t kDmIntegrityBufSize = 65536;
constexpr size_t kJournalSectionSize = kSectorSize * 328;
constexpr size_t kSuperblockSize = 4096;
constexpr size_t kInitialSegmentSize = kSuperblockSize + kJournalSectionSize;
size_t data_area_size =
RoundupMultiple(wb_nr_blocks_ * kDmIntegrityTagSize, kDmIntegrityBufSize);
size_t integrity_size_bytes = data_area_size + kInitialSegmentSize;
// To be safe, in case the size of dm-integrity increases in the future
// development, roundup it with MiB.
integrity_size_bytes = RoundupMultiple(integrity_size_bytes, kMiB);
constexpr char kZramIntegrityBackFileName[] = "zram_integrity.swap";
scoped_filepath = ScopedFilePath(base::FilePath(kZramWritebackIntegrityMount)
.Append(kZramIntegrityBackFileName));
// Truncate the file to the length of |integrity_size_bytes| by filling with
// 0s.
status = SwapToolUtil::Get()->WriteFile(scoped_filepath.get(),
std::string(integrity_size_bytes, 0));
if (!status.ok())
return status;
auto integrity_loop = LoopDev::Create(scoped_filepath.get().value());
if (!integrity_loop.ok())
return integrity_loop.status();
std::string integrity_loop_path = (*integrity_loop)->GetPath();
// Create a dm-integrity device to use with dm-crypt.
// For the table format, refer to
// https://wiki.gentoo.org/wiki/Device-mapper#Integrity
std::string table_fmt = base::StringPrintf(
"0 %" PRId64 " integrity %s 0 %zu D 4 block_size:%" PRId64
" meta_device:%s journal_sectors:1 buffer_sectors:%zu",
wb_size_bytes_ / kSectorSize, writeback_loop_path.c_str(),
kDmIntegrityTagSize, stateful_block_size_, integrity_loop_path.c_str(),
kDmIntegrityBufSize / kSectorSize);
auto integrity_dm = DmDev::Create(kZramIntegrityName, table_fmt);
if (!integrity_dm.ok())
return integrity_dm.status();
// Create a dm-crypt device for writeback.
absl::StatusOr<std::string> rand_hex32 =
SwapToolUtil::Get()->GenerateRandHex(32);
if (!rand_hex32.ok())
return rand_hex32.status();
table_fmt = base::StringPrintf(
"0 %" PRId64
" crypt capi:gcm(aes)-random %s 0 /dev/mapper/%s 0 4 allow_discards "
"submit_from_crypt_cpus sector_size:%" PRId64 " integrity:%zu:aead",
wb_size_bytes_ / kSectorSize, (*rand_hex32).c_str(), kZramIntegrityName,
stateful_block_size_, kDmIntegrityTagSize);
auto writeback_dm = DmDev::Create(kZramWritebackName, table_fmt);
if (!writeback_dm.ok())
return writeback_dm.status();
// Set up dm-crypt device as the zram writeback backing device.
return SwapToolUtil::Get()->WriteFile(base::FilePath(kZramBackingDevice),
(*writeback_dm)->GetPath());
}
absl::Status SwapTool::SwapStart() {
absl::Status status = absl::OkStatus();
// Return true if swap is already on.
absl::StatusOr<bool> on = IsZramSwapOn();
if (!on.ok())
return on.status();
if (*on) {
LOG(WARNING) << "swap is already on.";
return absl::OkStatus();
}
absl::StatusOr<uint64_t> mem_total = GetMemTotal();
if (!mem_total.ok())
return mem_total.status();
status = InitializeMMTunables(*mem_total);
if (!status.ok())
return status;
absl::StatusOr<uint64_t> size_byte = GetZramSize(*mem_total);
if (!size_byte.ok())
return size_byte.status();
// Load zram module. Ignore failure (it could be compiled in the kernel).
if (!SwapToolUtil::Get()->RunProcessHelper({"/sbin/modprobe", "zram"}).ok())
LOG(WARNING) << "modprobe zram failed (compiled?)";
// Set zram disksize.
LOG(INFO) << "setting zram size to " << *size_byte << " bytes";
status = SwapToolUtil::Get()->WriteFile(
base::FilePath("/sys/block/zram0/disksize"), std::to_string(*size_byte));
if (!status.ok())
return status;
// Set swap area.
status =
SwapToolUtil::Get()->RunProcessHelper({"/sbin/mkswap", kZramDeviceFile});
if (!status.ok())
return status;
return EnableZramSwapping();
}
absl::Status SwapTool::SwapStop() {
// Return false if swap is already off.
absl::StatusOr<bool> on = IsZramSwapOn();
if (!on.ok())
return on.status();
if (!*on) {
LOG(WARNING) << "Swap is already off.";
return absl::OkStatus();
}
// It is possible that the Filename of swap file zram0 in /proc/swaps shows
// wrong path "/zram0", since devtmpfs in minijail mount namespace is lazily
// unmounted while swap_management terminates.
// At this point we already know swap is on, with the only swap device
// /dev/zram0 we have, anyway we turn off /dev/zram0, regardless what
// /proc/swaps shows.
absl::Status status = SwapToolUtil::Get()->RunProcessHelper(
{"/sbin/swapoff", "-v", kZramDeviceFile});
if (!status.ok())
return status;
// When we start up, we try to configure zram0, but it doesn't like to
// be reconfigured on the fly. Reset it so we can changes its params.
// If there was a backing device being used, it will be automatically
// removed because after it's created it was removed with deferred remove.
return SwapToolUtil::Get()->WriteFile(
base::FilePath("/sys/block/zram0/reset"), "1");
}
// Set zram disksize in MiB.
// If `size` equals 0, set zram disksize to the default value.
absl::Status SwapTool::SwapSetSize(uint32_t size) {
// Remove kSwapSizeFile so SwapStart will use default size for zram.
if (size == 0)
return SwapToolUtil::Get()->DeleteFile(base::FilePath(kSwapSizeFile));
if (size < 100 || size > 20000)
return absl::InvalidArgumentError("Size is not between 100 and 20000 MiB.");
return SwapToolUtil::Get()->WriteFile(base::FilePath(kSwapSizeFile),
std::to_string(size));
}
std::string SwapTool::SwapStatus() {
std::stringstream output;
std::string tmp;
// Show general swap info first.
if (SwapToolUtil::Get()
->ReadFileToString(base::FilePath("/proc/swaps"), &tmp)
.ok())
output << tmp;
// Show tunables.
if (SwapToolUtil::Get()
->ReadFileToString(
base::FilePath("/sys/kernel/mm/chromeos-low_mem/margin"), &tmp)
.ok())
output << "low-memory margin (MiB): " + tmp;
if (SwapToolUtil::Get()
->ReadFileToString(base::FilePath("/proc/sys/vm/min_filelist_kbytes"),
&tmp)
.ok())
output << "min_filelist_kbytes (KiB): " + tmp;
if (SwapToolUtil::Get()
->ReadFileToString(
base::FilePath(
"/sys/kernel/mm/chromeos-low_mem/ram_vs_swap_weight"),
&tmp)
.ok())
output << "ram_vs_swap_weight: " + tmp;
if (SwapToolUtil::Get()
->ReadFileToString(base::FilePath("/proc/sys/vm/extra_free_kbytes"),
&tmp)
.ok())
output << "extra_free_kbytes (KiB): " + tmp;
// Show top entries in kZramSysfsDir for zram setting.
base::DirReaderPosix dir_reader(kZramSysfsDir);
if (dir_reader.IsValid()) {
output << "\ntop-level entries in " + std::string(kZramSysfsDir) + ":\n";
base::FilePath zram_sysfs(kZramSysfsDir);
while (dir_reader.Next()) {
std::string name = dir_reader.name();
if (SwapToolUtil::Get()
->ReadFileToString(zram_sysfs.Append(name), &tmp)
.ok() &&
!tmp.empty()) {
std::vector<std::string> lines = base::SplitString(
tmp, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
for (auto& line : lines)
output << name + ": " + line + "\n";
}
}
}
return output.str();
}
absl::Status SwapTool::SwapZramEnableWriteback(uint32_t size) {
absl::Status status = absl::OkStatus();
status = ZramWritebackPrerequisiteCheck(size);
if (!status.ok())
return status;
status = GetZramWritebackInfo(size);
if (!status.ok())
return status;
status = CreateDmDevicesAndEnableWriteback();
if (!status.ok()) {
CleanupWriteback();
return status;
}
LOG(INFO) << "Enabled writeback with size " +
std::to_string(wb_size_bytes_ / kMiB) + "MiB";
return absl::OkStatus();
}
absl::Status SwapTool::SwapZramSetWritebackLimit(uint32_t num_pages) {
base::FilePath filepath =
base::FilePath(kZramSysfsDir).Append("writeback_limit_enable");
absl::Status status = SwapToolUtil::Get()->WriteFile(filepath, "1");
if (!status.ok())
return status;
filepath = base::FilePath(kZramSysfsDir).Append("writeback_limit");
return SwapToolUtil::Get()->WriteFile(filepath, std::to_string(num_pages));
}
absl::Status SwapTool::SwapZramMarkIdle(uint32_t age_seconds) {
const auto age = base::Seconds(age_seconds);
// Only allow marking pages as idle between 0 sec and 30 days.
if (age > kMaxIdleAge)
return absl::OutOfRangeError("Invalid age" + std::to_string(age_seconds));
base::FilePath filepath = base::FilePath(kZramSysfsDir).Append("idle");
return SwapToolUtil::Get()->WriteFile(filepath,
std::to_string(age.InSeconds()));
}
absl::Status SwapTool::InitiateSwapZramWriteback(uint32_t mode) {
base::FilePath filepath = base::FilePath(kZramSysfsDir).Append("writeback");
std::string mode_str;
if (mode == WRITEBACK_IDLE) {
mode_str = "idle";
} else if (mode == WRITEBACK_HUGE) {
mode_str = "huge";
} else if (mode == WRITEBACK_HUGE_IDLE) {
mode_str = "huge_idle";
} else {
return absl::InvalidArgumentError("Invalid mode");
}
return SwapToolUtil::Get()->WriteFile(filepath, mode_str);
}
absl::Status SwapTool::MGLRUSetEnable(bool enable) {
base::FilePath filepath = base::FilePath("/sys/kernel/mm/lru_gen/enabled");
if (enable) {
absl::Status status = SwapToolUtil::Get()->WriteFile(filepath, "y");
if (status.ok() || !absl::IsInvalidArgument(status))
return status;
// If writing "y" fails with invalid argument error, fallback to write 1.
return SwapToolUtil::Get()->WriteFile(filepath, "1");
}
return SwapToolUtil::Get()->WriteFile(filepath, "0");
}
} // namespace swap_management