1use std::cmp::Ordering;
3use std::env;
4
5use crate::error::AptErrors;
6use crate::{Cache, DepFlags, Package, config};
7
8fn env_usize_nonzero(key: &str) -> Option<usize> {
9 env::var(key).ok()?.parse().ok().filter(|v: &usize| *v > 0)
10}
11
12fn ioctl_terminal_size() -> Option<(usize, usize)> {
13 use std::ffi::{c_int, c_ulong};
14 use std::io::{stderr, stdin, stdout};
15 use std::mem::MaybeUninit;
16 use std::os::unix::io::AsRawFd;
17
18 #[repr(C)]
19 struct Winsize {
20 row: u16,
21 col: u16,
22 x_pixel: u16,
23 y_pixel: u16,
24 }
25
26 extern "C" {
27 fn ioctl(fd: c_int, request: c_ulong, ...) -> c_int;
28 }
29
30 const TIOCGWINSZ: c_ulong = 0x5413;
31
32 fn query(fd: c_int) -> Option<(usize, usize)> {
33 let mut win_size = MaybeUninit::<Winsize>::uninit();
34 let result = unsafe { ioctl(fd, TIOCGWINSZ, win_size.as_mut_ptr()) };
35 if result != 0 {
36 return None;
37 }
38 let win_size = unsafe { win_size.assume_init() };
39 let cols = usize::from(win_size.col);
40 let rows = usize::from(win_size.row);
41 (cols != 0 && rows != 0).then_some((cols, rows))
42 }
43
44 query(stdout().as_raw_fd())
45 .or_else(|| query(stderr().as_raw_fd()))
46 .or_else(|| query(stdin().as_raw_fd()))
47}
48
49pub fn terminal_height() -> usize {
54 ioctl_terminal_size()
55 .map(|(_, rows)| rows)
56 .or_else(|| env_usize_nonzero("LINES"))
57 .unwrap_or(24)
58}
59
60pub fn terminal_width() -> usize {
65 ioctl_terminal_size()
66 .map(|(cols, _)| cols)
67 .or_else(|| env_usize_nonzero("COLUMNS"))
68 .unwrap_or(80)
69}
70
71pub fn cmp_versions(ver1: &str, ver2: &str) -> Ordering {
86 let result = raw::cmp_versions(ver1, ver2);
87 match result {
88 _ if result < 0 => Ordering::Less,
89 _ if result == 0 => Ordering::Equal,
90 _ => Ordering::Greater,
91 }
92}
93
94pub enum DiskSpace {
96 Require(u64),
98 Free(u64),
100}
101
102pub enum NumSys {
104 Binary,
106 Decimal,
108}
109
110pub fn unit_str(val: u64, base: NumSys) -> String {
122 let val = val as f64;
123 let (num, tera, giga, mega, kilo) = match base {
124 NumSys::Binary => (1024.0_f64, "TiB", "GiB", "MiB", "KiB"),
125 NumSys::Decimal => (1000.0_f64, "TB", "GB", "MB", "KB"),
126 };
127
128 let powers = [
129 (num.powi(4), tera),
130 (num.powi(3), giga),
131 (num.powi(2), mega),
132 (num, kilo),
133 ];
134
135 for (divisor, unit) in powers {
136 if val > divisor {
137 return format!("{:.2} {unit}", val / divisor);
138 }
139 }
140 format!("{val} B")
141}
142
143pub fn time_str(seconds: u64) -> String {
145 if seconds > 60 * 60 * 24 {
146 return format!(
147 "{}d {}h {}min {}s",
148 seconds / 60 / 60 / 24,
149 (seconds / 60 / 60) % 24,
150 (seconds / 60) % 60,
151 seconds % 60,
152 );
153 }
154 if seconds > 60 * 60 {
155 return format!(
156 "{}h {}min {}s",
157 (seconds / 60 / 60) % 24,
158 (seconds / 60) % 60,
159 seconds % 60,
160 );
161 }
162 if seconds > 60 {
163 return format!("{}min {}s", (seconds / 60) % 60, seconds % 60,);
164 }
165 format!("{seconds}s")
166}
167
168pub fn get_apt_progress_string(percent: f32, output_width: u32) -> String {
180 raw::get_apt_progress_string(percent, output_width)
181}
182
183pub fn apt_lock() -> Result<(), AptErrors> {
199 config::init_config_system();
200 Ok(raw::apt_lock()?)
201}
202
203pub fn apt_unlock() {
205 config::init_config_system();
206 raw::apt_unlock()
207}
208
209pub fn apt_lock_inner() -> Result<(), AptErrors> {
216 config::init_config_system();
217 Ok(raw::apt_lock_inner()?)
218}
219
220pub fn apt_unlock_inner() {
222 config::init_config_system();
223 raw::apt_unlock_inner()
224}
225
226pub fn apt_is_locked() -> bool {
231 config::init_config_system();
232 raw::apt_is_locked()
233}
234
235pub fn show_broken_pkg(cache: &Cache, pkg: &Package, now: bool) -> Option<String> {
243 if (now && !pkg.is_now_broken()) || (!now && !pkg.is_inst_broken()) {
245 return None;
246 };
247
248 let mut broken_string = String::new();
249
250 broken_string += &format!(" {pkg} :");
251
252 let Some(ver) = (match now {
255 true => pkg.installed(),
256 false => pkg.install_version(),
257 }) else {
258 broken_string += "\n";
259 return Some(broken_string);
260 };
261
262 let indent = pkg.name().len() + 3;
263 let mut first = true;
264
265 for dep in ver.depends_map().values().flatten() {
267 for (i, base_dep) in dep.iter().enumerate() {
268 if !cache.depcache().is_important_dep(base_dep) {
269 continue;
270 }
271
272 let dep_flag = if now { DepFlags::DepGNow } else { DepFlags::DepInstall };
273
274 if cache.depcache().dep_state(base_dep) & dep_flag == dep_flag {
275 continue;
276 }
277
278 if !first {
279 broken_string += &" ".repeat(indent);
280 }
281 first = false;
282
283 if i > 0 {
285 broken_string += &" ".repeat(base_dep.dep_type().as_ref().len() + 3);
286 } else {
287 broken_string += &format!(" {}: ", base_dep.dep_type())
288 }
289
290 broken_string += base_dep.target_package().name();
291
292 if let (Ok(ver_str), Some(comp)) = (base_dep.target_ver(), base_dep.comp_type()) {
293 broken_string += &format!(" ({comp} {ver_str})");
294 }
295
296 let target = base_dep.target_package();
297 if !target.has_provides() {
298 if let Some(target_ver) = target.install_version() {
299 broken_string += &format!(" but {target_ver} is to be installed")
300 } else if target.candidate().is_some() {
301 broken_string += " but it is not going to be installed";
302 } else if target.has_provides() {
303 broken_string += " but it is a virtual package";
304 } else {
305 broken_string += " but it is not installable";
306 }
307 }
308
309 if i + 1 != dep.len() {
310 broken_string += " or"
311 }
312 broken_string += "\n";
313 }
314 }
315 Some(broken_string)
316}
317
318#[cxx::bridge]
319pub(crate) mod raw {
320 unsafe extern "C++" {
321 include!("rust-apt/apt-pkg-c/util.h");
322
323 pub fn cmp_versions(ver1: &str, ver2: &str) -> i32;
332
333 pub fn quote_string(string: &str, bad: String) -> String;
334
335 pub fn get_apt_progress_string(percent: f32, output_width: u32) -> String;
337
338 pub fn apt_lock() -> Result<()>;
340
341 pub fn apt_unlock();
343
344 pub fn apt_lock_inner() -> Result<()>;
346
347 pub fn apt_unlock_inner();
349
350 pub fn apt_is_locked() -> bool;
352 }
353}