• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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