blob: f2a14c39e3cd7209bcbd4af8670653e7593bc70b [file] [log] [blame]
// 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.
//
// spaced_cli provides a command line interface disk usage queries.
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <locale>
#include <string>
#include <base/command_line.h>
#include <base/task/single_thread_task_executor.h>
#include <base/files/file_path.h>
#include <base/files/file_descriptor_watcher_posix.h>
#include <base/run_loop.h>
#include <base/strings/stringprintf.h>
#include <base/threading/thread.h>
#include <base/time/time.h>
#include <brillo/flag_helper.h>
#include <brillo/syslog_logging.h>
#include "spaced/disk_usage_proxy.h"
namespace {
bool human_readable_sizes = false;
enum Size : int64_t;
std::ostream& operator<<(std::ostream& out, const Size size) {
const int64_t i = static_cast<int64_t>(size);
if (i < 0)
return out << "error (" << i << ")";
out << i;
if (!human_readable_sizes)
return out;
out << " bytes";
if (i < 1024)
return out;
double d = static_cast<double>(i) / 1024;
const char* unit = "KMGT";
while (d >= 1024 && *unit != '\0') {
d /= 1024;
unit++;
}
const int precision = d < 10 ? 2 : d < 100 ? 1 : 0;
return out << base::StringPrintf(" (%.*f %c)", precision, d, *unit);
}
class NumPunct : public std::numpunct<char> {
private:
char do_thousands_sep() const override { return ','; }
std::string do_grouping() const override { return "\3"; }
};
std::string UpdateStateToString(const spaced::StatefulDiskSpaceState& state) {
switch (state) {
case spaced::StatefulDiskSpaceState::NONE:
return "None";
case spaced::StatefulDiskSpaceState::NORMAL:
return "Normal";
case spaced::StatefulDiskSpaceState::LOW:
return "Low";
case spaced::StatefulDiskSpaceState::CRITICAL:
return "Critical";
default:
return "Invalid state";
}
}
// Simply echoes the update received by spaced.
class EchoSpacedObserver : public spaced::SpacedObserverInterface {
public:
EchoSpacedObserver() = default;
~EchoSpacedObserver() override = default;
void OnStatefulDiskSpaceUpdate(
const spaced::StatefulDiskSpaceUpdate& update) override {
std::cout << "Time: " << base::Time::Now()
<< ", State: " << UpdateStateToString(update.state())
<< ", Available space: " << Size(update.free_space_bytes())
<< std::endl;
}
};
} // namespace
int main(int argc, char** argv) {
DEFINE_string(get_free_disk_space, "",
"Gets free disk space available on the given path");
DEFINE_string(get_total_disk_space, "",
"Gets total disk space available on the given path");
DEFINE_bool(get_root_device_size, false, "Gets the size of the root device");
DEFINE_bool(monitor_stateful, false,
"Monitors the space available on the stateful partition");
DEFINE_bool(human, false, "Print human-readable numbers");
brillo::FlagHelper::Init(argc, argv,
"ChromiumOS Space Daemon CLI\n\n"
"Usage: space_cli [options] [path]\n");
std::string nl;
if (FLAGS_human) {
// Ensure that outputted numbers have thousands separators. It makes big
// numbers much easier to read for a human (eg sizes expressed in bytes).
std::cout.imbue(std::locale(std::locale::classic(), new NumPunct));
nl = "\n";
human_readable_sizes = true;
}
base::SingleThreadTaskExecutor task_executor(base::MessagePumpType::IO);
base::FileDescriptorWatcher watcher{task_executor.task_runner()};
const std::unique_ptr<spaced::DiskUsageProxy> disk_usage_proxy =
spaced::DiskUsageProxy::Generate();
if (!disk_usage_proxy) {
LOG(ERROR) << "Failed to get disk usage proxy";
return EXIT_FAILURE;
}
if (!FLAGS_get_free_disk_space.empty()) {
std::cout << Size(disk_usage_proxy->GetFreeDiskSpace(
base::FilePath(FLAGS_get_free_disk_space)))
<< nl;
return EXIT_SUCCESS;
}
if (!FLAGS_get_total_disk_space.empty()) {
std::cout << Size(disk_usage_proxy->GetTotalDiskSpace(
base::FilePath(FLAGS_get_total_disk_space)))
<< nl;
return EXIT_SUCCESS;
}
if (FLAGS_get_root_device_size) {
std::cout << Size(disk_usage_proxy->GetRootDeviceSize()) << nl;
return EXIT_SUCCESS;
}
if (FLAGS_monitor_stateful) {
EchoSpacedObserver observer;
disk_usage_proxy->AddObserver(&observer);
disk_usage_proxy->StartMonitoring();
// Infinite loop; let the user interrupt monitoring with Ctrl+C.
base::RunLoop().Run();
return EXIT_SUCCESS;
}
base::FilePath path(".");
const base::CommandLine::StringVector args =
base::CommandLine::ForCurrentProcess()->GetArgs();
if (!args.empty()) {
if (args.size() > 1) {
LOG(ERROR) << "Too many command line arguments";
return EXIT_FAILURE;
}
path = base::FilePath(args[0]);
}
// Determine full canonical path.
{
char* const rp = realpath(path.value().c_str(), nullptr);
if (!rp) {
PLOG(ERROR) << "Cannot get real path of " << std::quoted(path.value());
return EXIT_FAILURE;
}
path = base::FilePath(rp);
free(rp);
}
std::cout << "path: " << std::quoted(path.value()) << '\n';
std::cout << "free_disk_space: "
<< Size(disk_usage_proxy->GetFreeDiskSpace(path)) << '\n';
std::cout << "total_disk_space: "
<< Size(disk_usage_proxy->GetTotalDiskSpace(path)) << '\n';
std::cout << "root_device_size: "
<< Size(disk_usage_proxy->GetRootDeviceSize()) << '\n';
std::cout.flush();
return EXIT_SUCCESS;
}