1 // Copyright 2025 The Android Open Source Project
2 //
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 //! gRPC daemon for the storage ballooning feature.
16
17 use anyhow::anyhow;
18 use anyhow::Context;
19 use anyhow::Result;
20 use api::debian_service_client::DebianServiceClient;
21 use api::StorageBalloonQueueOpeningRequest;
22 use api::StorageBalloonRequestItem;
23 use clap::Parser;
24 use log::debug;
25 use log::error;
26 use log::info;
27 use nix::sys::statvfs::statvfs;
28 pub mod api {
29 tonic::include_proto!("com.android.virtualization.terminal.proto");
30 }
31
32 #[derive(Parser)]
33 /// Flags for running command
34 pub struct Args {
35 /// IP address
36 #[arg(long)]
37 addr: Option<String>,
38
39 /// path to a file where grpc port number is written
40 #[arg(long)]
41 #[arg(alias = "grpc_port_file")]
42 grpc_port_file: String,
43 }
44
45 // Calculates how many blocks to be reserved.
calculate_clusters_count(guest_available_bytes: u64) -> Result<u64>46 fn calculate_clusters_count(guest_available_bytes: u64) -> Result<u64> {
47 let stat = statvfs("/").context("failed to get statvfs")?;
48 let fr_size = stat.fragment_size() as u64;
49
50 if fr_size == 0 {
51 return Err(anyhow::anyhow!("fragment size is zero, fr_size: {}", fr_size));
52 }
53
54 let total = fr_size.checked_mul(stat.blocks() as u64).context(format!(
55 "overflow in total size calculation, fr_size: {}, blocks: {}",
56 fr_size,
57 stat.blocks()
58 ))?;
59
60 let free = fr_size.checked_mul(stat.blocks_available() as u64).context(format!(
61 "overflow in free size calculation, fr_size: {}, blocks_available: {}",
62 fr_size,
63 stat.blocks_available()
64 ))?;
65
66 let used = total
67 .checked_sub(free)
68 .context(format!("underflow in used size calculation (free > total), which should not happen, total: {}, free: {}", total, free))?;
69
70 let avail = std::cmp::min(free, guest_available_bytes);
71 let balloon_size_bytes = free - avail;
72
73 let reserved_clusters_count = balloon_size_bytes.div_ceil(fr_size);
74
75 debug!("total: {total}, free: {free}, used: {used}, avail: {avail}, balloon: {balloon_size_bytes}, clusters_count: {reserved_clusters_count}");
76
77 Ok(reserved_clusters_count)
78 }
79
set_reserved_clusters(clusters_count: u64) -> anyhow::Result<()>80 fn set_reserved_clusters(clusters_count: u64) -> anyhow::Result<()> {
81 const ROOTFS_DEVICE_NAME: &str = "vda1";
82 std::fs::write(
83 format!("/sys/fs/ext4/{ROOTFS_DEVICE_NAME}/reserved_clusters"),
84 clusters_count.to_string(),
85 )
86 .context("failed to write reserved_clusters")?;
87 Ok(())
88 }
89
90 #[tokio::main]
main() -> Result<(), Box<dyn std::error::Error>>91 async fn main() -> Result<(), Box<dyn std::error::Error>> {
92 env_logger::builder().filter_level(log::LevelFilter::Debug).init();
93
94 let args = Args::parse();
95 let gateway_ip_addr = netdev::get_default_gateway()?.ipv4[0];
96 let addr = args.addr.unwrap_or_else(|| gateway_ip_addr.to_string());
97
98 // Wait for `grpc_port_file` becomes available.
99 const GRPC_PORT_MAX_RETRY_COUNT: u32 = 10;
100 for _ in 0..GRPC_PORT_MAX_RETRY_COUNT {
101 if std::path::Path::new(&args.grpc_port_file).exists() {
102 break;
103 }
104 debug!("{} does not exist. Wait 1 second", args.grpc_port_file);
105 tokio::time::sleep(std::time::Duration::from_secs(1)).await;
106 }
107 let grpc_port = std::fs::read_to_string(&args.grpc_port_file)?.trim().to_string();
108 let server_addr = format!("http://{}:{}", addr, grpc_port);
109
110 info!("connect to grpc server {}", server_addr);
111 let mut client = DebianServiceClient::connect(server_addr)
112 .await
113 .map_err(|e| anyhow!("failed to connect to grpc server: {:#}", e))?;
114 info!("connection established");
115
116 let mut res_stream = client
117 .open_storage_balloon_request_queue(tonic::Request::new(
118 StorageBalloonQueueOpeningRequest {},
119 ))
120 .await
121 .map_err(|e| anyhow!("failed to open storage balloon queue: {:#}", e))?
122 .into_inner();
123
124 while let Some(StorageBalloonRequestItem { available_bytes }) =
125 res_stream.message().await.map_err(|e| anyhow!("failed to receive message: {:#}", e))?
126 {
127 let clusters_count = match calculate_clusters_count(available_bytes) {
128 Ok(c) => c,
129 Err(e) => {
130 error!("failed to calculate cluster size to be reserved: {:#}", e);
131 continue;
132 }
133 };
134
135 if let Err(e) = set_reserved_clusters(clusters_count) {
136 error!("failed to set storage balloon size: {}", e);
137 }
138 }
139
140 Ok(())
141 }
142