ptrxyz
ptrxyz14mo ago

Getting free disk space without using child_process.

Hello everyone! I would like to create a small CLI tool using Typescript and Deno. I would like to monitor the free disk space on several disks. How can I get the free disk space on a Linux machine without forking a separate process (like exec --> df and the like)? In addition, I would like to deno compile my little program later, so I am not sure if Node's gyp is an option then.
6 Replies
Mrcool 🇵🇸
Mrcool 🇵🇸14mo ago
I don't think deno runtime exposés this I would just strace df to see how it does it, it probably boils down to a Linux syscall since it finishes immediately In that case you'll need ffi
ptrxyz
ptrxyz14mo ago
I found a npm packae doing exactly that but it's based on gyp. can i somehow "convert" that? or is deno gyp compatible?
Esente
Esente14mo ago
No, there is no equivalent to gyp in Deno Maybe there is some WASM implementation out there for this.
ptrxyz
ptrxyz14mo ago
ok, thanks.
Mrcool 🇵🇸
Mrcool 🇵🇸14mo ago
the program needs to issue statfs syscall, you can't do that in a javascript runtime, your options is to either run df or bind to a library with ffi that does this , i think both options are equivalent really If you want to see an implementation example, you can checkout https://github.com/uutils/coreutils/tree/main/src/uu/df (its also crossplatform) Also here is the simplest program in rust (for linux)
use libc::statfs;
use std::ffi::CString;

fn main() {
for path in get_mount_points() {
let mut stat: statfs = unsafe { std::mem::zeroed() };

let result = unsafe {
let path = CString::new(path.clone()).unwrap();
let path = path.as_ptr();
statfs(path, &mut stat as *mut _)
};
if stat.f_blocks == 0 {
continue;
}
dbg!(&path);

if result == 0 {
print_statfs_info(&stat);
} else {
eprintln!("Error getting filesystem information");
}
}
}

fn get_mount_points() -> Vec<String> {
let raw = std::fs::read_to_string("/proc/self/mountinfo").unwrap();
raw.lines()
.map(|line| line.split_whitespace().nth(4).unwrap().to_owned())
.collect()
}

fn print_statfs_info(stat: &statfs) {
let block_size = stat.f_bsize as u64;
let total_blocks = stat.f_blocks as u64;
let free_blocks = stat.f_bfree as u64;
let available_blocks = stat.f_bavail as u64;

let total_space = block_size * total_blocks;
let used_space = total_space - (block_size * free_blocks);
let avail_space = block_size * available_blocks;
let used_percent = (used_space * 100) / total_space;

println!("Size: {}", format_size(total_space));
println!("Used: {}", format_size(used_space));
println!("Available: {}", format_size(avail_space));
println!("Use%: {}%", used_percent);
}

fn format_size(size: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;

if size >= GB {
format!("{:.2} GB", size as f64 / GB as f64)
} else if size >= MB {
format!("{:.2} MB", size as f64 / MB as f64)
} else if size >= KB {
format!("{:.2} KB", size as f64 / KB as f64)
} else {
format!("{} bytes", size)
}
}
use libc::statfs;
use std::ffi::CString;

fn main() {
for path in get_mount_points() {
let mut stat: statfs = unsafe { std::mem::zeroed() };

let result = unsafe {
let path = CString::new(path.clone()).unwrap();
let path = path.as_ptr();
statfs(path, &mut stat as *mut _)
};
if stat.f_blocks == 0 {
continue;
}
dbg!(&path);

if result == 0 {
print_statfs_info(&stat);
} else {
eprintln!("Error getting filesystem information");
}
}
}

fn get_mount_points() -> Vec<String> {
let raw = std::fs::read_to_string("/proc/self/mountinfo").unwrap();
raw.lines()
.map(|line| line.split_whitespace().nth(4).unwrap().to_owned())
.collect()
}

fn print_statfs_info(stat: &statfs) {
let block_size = stat.f_bsize as u64;
let total_blocks = stat.f_blocks as u64;
let free_blocks = stat.f_bfree as u64;
let available_blocks = stat.f_bavail as u64;

let total_space = block_size * total_blocks;
let used_space = total_space - (block_size * free_blocks);
let avail_space = block_size * available_blocks;
let used_percent = (used_space * 100) / total_space;

println!("Size: {}", format_size(total_space));
println!("Used: {}", format_size(used_space));
println!("Available: {}", format_size(avail_space));
println!("Use%: {}%", used_percent);
}

fn format_size(size: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;

if size >= GB {
format!("{:.2} GB", size as f64 / GB as f64)
} else if size >= MB {
format!("{:.2} MB", size as f64 / MB as f64)
} else if size >= KB {
format!("{:.2} KB", size as f64 / KB as f64)
} else {
format!("{} bytes", size)
}
}
ptrxyz
ptrxyz14mo ago
Thanks a bunch! I figured it out basing my code on the FFI examples and the diskusage npm package.