linuxutils_system/
blkdiscard.rs1use linuxutils_common::man::ManContent;
2
3pub const MAN: ManContent = ManContent::empty();
4
5use clap::Parser;
6use std::{fs::File, io, os::unix::io::AsRawFd, process::ExitCode};
7
8const BLKGETSIZE64: libc::c_ulong = 0x80081272;
9const BLKSSZGET: libc::c_ulong = 0x1268;
10const BLKDISCARD: libc::c_ulong = 0x1277;
11const BLKSECDISCARD: libc::c_ulong = 0x127d;
12const BLKZEROOUT: libc::c_ulong = 0x127f;
13
14#[derive(Parser)]
19#[command(name = "blkdiscard", about = "Discard sectors on a block device")]
20pub struct Args {
21 #[arg(short = 'f', long)]
23 force: bool,
24
25 #[arg(short = 'o', long, default_value = "0")]
27 offset: u64,
28
29 #[arg(short = 'l', long)]
31 length: Option<u64>,
32
33 #[arg(short = 'p', long)]
35 step: Option<u64>,
36
37 #[arg(short = 'q', long)]
39 quiet: bool,
40
41 #[arg(short = 's', long)]
43 secure: bool,
44
45 #[arg(short = 'z', long)]
47 zeroout: bool,
48
49 #[arg(short = 'v', long)]
51 verbose: bool,
52
53 device: String,
55}
56
57fn get_device_size(fd: i32) -> io::Result<u64> {
58 let mut size: u64 = 0;
59 if unsafe { libc::ioctl(fd, BLKGETSIZE64, &mut size) } < 0 {
60 Err(io::Error::last_os_error())
61 } else {
62 Ok(size)
63 }
64}
65
66fn get_sector_size(fd: i32) -> io::Result<u32> {
67 let mut ss: libc::c_int = 0;
68 if unsafe { libc::ioctl(fd, BLKSSZGET, &mut ss) } < 0 {
69 Err(io::Error::last_os_error())
70 } else {
71 Ok(ss as u32)
72 }
73}
74
75fn do_discard(
76 fd: i32,
77 ioctl_nr: libc::c_ulong,
78 offset: u64,
79 length: u64,
80) -> io::Result<()> {
81 let range: [u64; 2] = [offset, length];
82 if unsafe { libc::ioctl(fd, ioctl_nr, &range) } < 0 {
83 Err(io::Error::last_os_error())
84 } else {
85 Ok(())
86 }
87}
88
89pub fn run(args: Args) -> ExitCode {
90 if args.secure && args.zeroout {
91 eprintln!("blkdiscard: --secure and --zeroout are mutually exclusive");
92 return ExitCode::FAILURE;
93 }
94
95 let oflags = if args.force {
96 libc::O_RDWR
97 } else {
98 libc::O_RDWR | libc::O_EXCL
99 };
100
101 let dev_cstr = match std::ffi::CString::new(args.device.as_str()) {
102 Ok(c) => c,
103 Err(_) => {
104 eprintln!("blkdiscard: invalid device path");
105 return ExitCode::FAILURE;
106 }
107 };
108
109 let fd = unsafe { libc::open(dev_cstr.as_ptr(), oflags) };
110 if fd < 0 {
111 eprintln!(
112 "blkdiscard: cannot open '{}': {}",
113 args.device,
114 io::Error::last_os_error()
115 );
116 return ExitCode::FAILURE;
117 }
118
119 let file = unsafe { File::from_raw_fd(fd) };
121 let fd = file.as_raw_fd();
122
123 let dev_size = match get_device_size(fd) {
124 Ok(s) => s,
125 Err(e) => {
126 eprintln!("blkdiscard: cannot get device size: {e}");
127 return ExitCode::FAILURE;
128 }
129 };
130
131 let sector_size = get_sector_size(fd).unwrap_or(512) as u64;
132
133 let offset = args.offset;
134 let length = args.length.unwrap_or(dev_size.saturating_sub(offset));
135
136 if !offset.is_multiple_of(sector_size) {
137 eprintln!(
138 "blkdiscard: offset {offset} is not aligned to sector size {sector_size}"
139 );
140 return ExitCode::FAILURE;
141 }
142 if !length.is_multiple_of(sector_size) {
143 eprintln!(
144 "blkdiscard: length {length} is not aligned to sector size {sector_size}"
145 );
146 return ExitCode::FAILURE;
147 }
148
149 let ioctl_nr = if args.secure {
150 BLKSECDISCARD
151 } else if args.zeroout {
152 BLKZEROOUT
153 } else {
154 BLKDISCARD
155 };
156
157 let action = if args.secure {
158 "secure discard"
159 } else if args.zeroout {
160 "zeroout"
161 } else {
162 "discard"
163 };
164
165 if args.verbose {
166 eprintln!(
167 "blkdiscard: {action} offset={offset} length={length} device={}",
168 args.device
169 );
170 }
171
172 if let Some(step) = args.step {
173 let mut current = offset;
174 let end = offset + length;
175 while current < end {
176 let chunk = step.min(end - current);
177 if let Err(e) = do_discard(fd, ioctl_nr, current, chunk) {
178 eprintln!(
179 "blkdiscard: {action} failed at offset {current}: {e}"
180 );
181 return if e.raw_os_error() == Some(libc::EOPNOTSUPP) {
182 ExitCode::from(2)
183 } else {
184 ExitCode::FAILURE
185 };
186 }
187 current += chunk;
188 }
189 } else if let Err(e) = do_discard(fd, ioctl_nr, offset, length) {
190 eprintln!("blkdiscard: {action} failed: {e}");
191 return if e.raw_os_error() == Some(libc::EOPNOTSUPP) {
192 ExitCode::from(2)
193 } else {
194 ExitCode::FAILURE
195 };
196 }
197
198 ExitCode::SUCCESS
199}
200
201use std::os::unix::io::FromRawFd;