1use linuxutils_common::man::ManContent;
2
3pub const MAN: ManContent = ManContent::empty();
4
5use clap::Parser;
6use cols::{Cols, print_table};
7use std::{
8 fs::File,
9 io::{self, BufRead},
10 os::unix::io::AsRawFd,
11 process::ExitCode,
12};
13
14const BLKROSET: libc::c_ulong = 0x125d;
16const BLKROGET: libc::c_ulong = 0x125e;
17const BLKRRPART: libc::c_ulong = 0x125f;
18const BLKGETSIZE: libc::c_ulong = 0x1260;
19const BLKFLSBUF: libc::c_ulong = 0x1261;
20const BLKRASET: libc::c_ulong = 0x1262;
21const BLKRAGET: libc::c_ulong = 0x1263;
22const BLKFRASET: libc::c_ulong = 0x1264;
23const BLKFRAGET: libc::c_ulong = 0x1265;
24const BLKSECTGET: libc::c_ulong = 0x1267;
25const BLKSSZGET: libc::c_ulong = 0x1268;
26const BLKBSZGET: libc::c_ulong = 0x80081270;
27const BLKBSZSET: libc::c_ulong = 0x40081271;
28const BLKGETSIZE64: libc::c_ulong = 0x80081272;
29const BLKIOMIN: libc::c_ulong = 0x1278;
30const BLKIOOPT: libc::c_ulong = 0x1279;
31const BLKALIGNOFF: libc::c_ulong = 0x127a;
32const BLKPBSZGET: libc::c_ulong = 0x127b;
33const BLKDISCARDZEROES: libc::c_ulong = 0x127c;
34
35#[derive(Parser)]
41#[command(
42 name = "blockdev",
43 about = "Call block device ioctls from the command line",
44 override_usage = "blockdev [-q] [-v] command [command...] device [device...]\n \
45 blockdev --report [device...]"
46)]
47pub struct Args {
48 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
50 args: Vec<String>,
51}
52
53enum Cmd {
54 GetInt(libc::c_ulong, &'static str),
55 GetUint(libc::c_ulong, &'static str),
56 GetU64(libc::c_ulong, &'static str),
57 GetUlong(libc::c_ulong, &'static str),
58 GetSz,
59 SetInt(libc::c_ulong, libc::c_int, &'static str),
60 SetUlong(libc::c_ulong, libc::c_ulong, &'static str),
61 Action(libc::c_ulong, &'static str),
62}
63
64fn ioctl_get_int(fd: i32, nr: libc::c_ulong) -> io::Result<i64> {
65 let mut val: libc::c_int = 0;
66 if unsafe { libc::ioctl(fd, nr, &mut val) } < 0 {
67 Err(io::Error::last_os_error())
68 } else {
69 Ok(val as i64)
70 }
71}
72
73fn ioctl_get_uint(fd: i32, nr: libc::c_ulong) -> io::Result<i64> {
74 let mut val: libc::c_uint = 0;
75 if unsafe { libc::ioctl(fd, nr, &mut val) } < 0 {
76 Err(io::Error::last_os_error())
77 } else {
78 Ok(val as i64)
79 }
80}
81
82fn ioctl_get_u64(fd: i32, nr: libc::c_ulong) -> io::Result<u64> {
83 let mut val: u64 = 0;
84 if unsafe { libc::ioctl(fd, nr, &mut val) } < 0 {
85 Err(io::Error::last_os_error())
86 } else {
87 Ok(val)
88 }
89}
90
91fn ioctl_get_ulong(fd: i32, nr: libc::c_ulong) -> io::Result<i64> {
92 let mut val: libc::c_ulong = 0;
93 if unsafe { libc::ioctl(fd, nr, &mut val) } < 0 {
94 Err(io::Error::last_os_error())
95 } else {
96 Ok(val as i64)
97 }
98}
99
100fn ioctl_set_int(
101 fd: i32,
102 nr: libc::c_ulong,
103 val: libc::c_int,
104) -> io::Result<()> {
105 if unsafe { libc::ioctl(fd, nr, &val) } < 0 {
106 Err(io::Error::last_os_error())
107 } else {
108 Ok(())
109 }
110}
111
112fn ioctl_set_ulong(
113 fd: i32,
114 nr: libc::c_ulong,
115 val: libc::c_ulong,
116) -> io::Result<()> {
117 if unsafe { libc::ioctl(fd, nr, val) } < 0 {
118 Err(io::Error::last_os_error())
119 } else {
120 Ok(())
121 }
122}
123
124fn ioctl_action(fd: i32, nr: libc::c_ulong) -> io::Result<()> {
125 if unsafe { libc::ioctl(fd, nr, 0) } < 0 {
126 Err(io::Error::last_os_error())
127 } else {
128 Ok(())
129 }
130}
131
132fn execute_cmd(
133 fd: i32,
134 cmd: &Cmd,
135 verbose: bool,
136 quiet: bool,
137) -> io::Result<()> {
138 match cmd {
139 Cmd::GetInt(nr, name) => {
140 let val = ioctl_get_int(fd, *nr)?;
141 if verbose {
142 print!("{name}: ");
143 }
144 println!("{val}");
145 }
146 Cmd::GetUint(nr, name) => {
147 let val = ioctl_get_uint(fd, *nr)?;
148 if verbose {
149 print!("{name}: ");
150 }
151 println!("{val}");
152 }
153 Cmd::GetU64(nr, name) => {
154 let val = ioctl_get_u64(fd, *nr)?;
155 if verbose {
156 print!("{name}: ");
157 }
158 println!("{val}");
159 }
160 Cmd::GetUlong(nr, name) => {
161 let val = ioctl_get_ulong(fd, *nr)?;
162 if verbose {
163 print!("{name}: ");
164 }
165 println!("{val}");
166 }
167 Cmd::GetSz => {
168 let bytes = ioctl_get_u64(fd, BLKGETSIZE64)?;
169 if verbose {
170 print!("getsz: ");
171 }
172 println!("{}", bytes / 512);
173 }
174 Cmd::SetInt(nr, val, name) => {
175 ioctl_set_int(fd, *nr, *val)?;
176 if !quiet {
177 eprintln!("{name} succeeded.");
178 }
179 }
180 Cmd::SetUlong(nr, val, name) => {
181 ioctl_set_ulong(fd, *nr, *val)?;
182 if !quiet {
183 eprintln!("{name} succeeded.");
184 }
185 }
186 Cmd::Action(nr, name) => {
187 ioctl_action(fd, *nr)?;
188 if !quiet {
189 eprintln!("{name} succeeded.");
190 }
191 }
192 }
193 Ok(())
194}
195
196fn get_start_sector(device: &str) -> u64 {
197 let Ok(f) = File::open(device) else {
198 return 0;
199 };
200 let mut stat: libc::stat = unsafe { std::mem::zeroed() };
201 if unsafe { libc::fstat(f.as_raw_fd(), &mut stat) } < 0 {
202 return 0;
203 }
204 let major = libc::major(stat.st_rdev as libc::dev_t);
205 let minor = libc::minor(stat.st_rdev as libc::dev_t);
206 let path = format!("/sys/dev/block/{major}:{minor}/start");
207 std::fs::read_to_string(path)
208 .ok()
209 .and_then(|s| s.trim().parse().ok())
210 .unwrap_or(0)
211}
212
213fn devices_from_proc_partitions() -> Vec<String> {
214 let mut devices = Vec::new();
215 let Ok(file) = File::open("/proc/partitions") else {
216 return devices;
217 };
218 for line in io::BufReader::new(file)
219 .lines()
220 .map_while(Result::ok)
221 .skip(2)
222 {
223 let fields: Vec<&str> = line.split_whitespace().collect();
224 if fields.len() >= 4 {
225 devices.push(format!("/dev/{}", fields[3]));
226 }
227 }
228 devices
229}
230
231#[derive(Cols)]
232struct ReportRow {
233 #[column(right, header = "RO")]
234 ro: i64,
235
236 #[column(right, header = "RA")]
237 ra: i64,
238
239 #[column(right, header = "SSZ")]
240 ssz: i64,
241
242 #[column(right, header = "BSZ")]
243 bsz: i64,
244
245 #[column(right, header = "StartSec")]
246 start_sec: u64,
247
248 #[column(right, header = "Size")]
249 size: u64,
250
251 #[column(header = "Device")]
252 device: String,
253}
254
255fn print_report(devices: &[String]) {
256 let mut rows = Vec::new();
257 for device in devices {
258 let Ok(f) = File::options().read(true).open(device) else {
259 continue;
260 };
261 let fd = f.as_raw_fd();
262 rows.push(ReportRow {
263 ro: ioctl_get_int(fd, BLKROGET).unwrap_or(-1),
264 ra: ioctl_get_ulong(fd, BLKRAGET).unwrap_or(-1),
265 ssz: ioctl_get_int(fd, BLKSSZGET).unwrap_or(-1),
266 bsz: ioctl_get_int(fd, BLKBSZGET).unwrap_or(-1),
267 start_sec: get_start_sector(device),
268 size: ioctl_get_u64(fd, BLKGETSIZE64).unwrap_or(0),
269 device: device.clone(),
270 });
271 }
272 let table = ReportRow::to_table(&rows);
273 let _ = print_table(&table, &mut io::stdout().lock());
274}
275
276struct ParsedArgs {
277 commands: Vec<Cmd>,
278 devices: Vec<String>,
279 verbose: bool,
280 quiet: bool,
281 report: bool,
282}
283
284fn parse_commands(raw_args: &[String]) -> Result<ParsedArgs, String> {
285 let mut commands: Vec<Cmd> = Vec::new();
286 let mut devices: Vec<String> = Vec::new();
287 let mut verbose = false;
288 let mut quiet = false;
289 let mut report = false;
290 let mut i = 0;
291
292 while i < raw_args.len() {
293 let arg = &raw_args[i];
294 match arg.as_str() {
295 "-v" | "--verbose" => verbose = true,
296 "-q" => quiet = true,
297 "--report" => report = true,
298
299 "--getro" => commands.push(Cmd::GetInt(BLKROGET, "getro")),
300 "--getbsz" => commands.push(Cmd::GetInt(BLKBSZGET, "getbsz")),
301 "--getss" => commands.push(Cmd::GetInt(BLKSSZGET, "getss")),
302 "--getpbsz" => commands.push(Cmd::GetUint(BLKPBSZGET, "getpbsz")),
303 "--getsize" => commands.push(Cmd::GetUlong(BLKGETSIZE, "getsize")),
304 "--getsize64" => {
305 commands.push(Cmd::GetU64(BLKGETSIZE64, "getsize64"))
306 }
307 "--getsz" => commands.push(Cmd::GetSz),
308 "--getra" => commands.push(Cmd::GetUlong(BLKRAGET, "getra")),
309 "--getfra" => commands.push(Cmd::GetUlong(BLKFRAGET, "getfra")),
310 "--getiomin" => commands.push(Cmd::GetUint(BLKIOMIN, "getiomin")),
311 "--getioopt" => commands.push(Cmd::GetUint(BLKIOOPT, "getioopt")),
312 "--getalignoff" => {
313 commands.push(Cmd::GetUint(BLKALIGNOFF, "getalignoff"))
314 }
315 "--getmaxsect" => {
316 commands.push(Cmd::GetUint(BLKSECTGET, "getmaxsect"))
317 }
318 "--getdiscardzeroes" => {
319 commands
320 .push(Cmd::GetUint(BLKDISCARDZEROES, "getdiscardzeroes"));
321 }
322
323 "--setro" => commands.push(Cmd::SetInt(BLKROSET, 1, "setro")),
324 "--setrw" => commands.push(Cmd::SetInt(BLKROSET, 0, "setrw")),
325 "--setbsz" => {
326 i += 1;
327 let val: libc::c_int = raw_args
328 .get(i)
329 .ok_or("--setbsz requires an argument")?
330 .parse()
331 .map_err(|_| "--setbsz: invalid number")?;
332 commands.push(Cmd::SetInt(BLKBSZSET, val, "setbsz"));
333 }
334 "--setra" => {
335 i += 1;
336 let val: libc::c_ulong = raw_args
337 .get(i)
338 .ok_or("--setra requires an argument")?
339 .parse()
340 .map_err(|_| "--setra: invalid number")?;
341 commands.push(Cmd::SetUlong(BLKRASET, val, "setra"));
342 }
343 "--setfra" => {
344 i += 1;
345 let val: libc::c_ulong = raw_args
346 .get(i)
347 .ok_or("--setfra requires an argument")?
348 .parse()
349 .map_err(|_| "--setfra: invalid number")?;
350 commands.push(Cmd::SetUlong(BLKFRASET, val, "setfra"));
351 }
352 "--flushbufs" => commands.push(Cmd::Action(BLKFLSBUF, "flushbufs")),
353 "--rereadpt" => commands.push(Cmd::Action(BLKRRPART, "rereadpt")),
354
355 other if other.starts_with('-') => {
356 return Err(format!("unknown command: {other}"));
357 }
358 _ => devices.push(arg.clone()),
359 }
360 i += 1;
361 }
362
363 Ok(ParsedArgs {
364 commands,
365 devices,
366 verbose,
367 quiet,
368 report,
369 })
370}
371
372pub fn run(args: Args) -> ExitCode {
373 let ParsedArgs {
374 commands,
375 devices,
376 verbose,
377 quiet,
378 report,
379 } = match parse_commands(&args.args) {
380 Ok(parsed) => parsed,
381 Err(e) => {
382 eprintln!("blockdev: {e}");
383 return ExitCode::FAILURE;
384 }
385 };
386
387 if report {
388 let devs = if devices.is_empty() {
389 devices_from_proc_partitions()
390 } else {
391 devices
392 };
393 print_report(&devs);
394 return ExitCode::SUCCESS;
395 }
396
397 if commands.is_empty() {
398 eprintln!("blockdev: no command given, try 'blockdev --help'");
399 return ExitCode::FAILURE;
400 }
401
402 if devices.is_empty() {
403 eprintln!("blockdev: no device specified");
404 return ExitCode::FAILURE;
405 }
406
407 let mut failed = false;
408 for device in &devices {
409 let f = match File::options().read(true).write(true).open(device) {
410 Ok(f) => f,
411 Err(e) => {
412 eprintln!("blockdev: cannot open {device}: {e}");
413 failed = true;
414 continue;
415 }
416 };
417 let fd = f.as_raw_fd();
418 for cmd in &commands {
419 if let Err(e) = execute_cmd(fd, cmd, verbose, quiet) {
420 eprintln!("blockdev: {device}: {e}");
421 failed = true;
422 }
423 }
424 }
425
426 if failed {
427 ExitCode::FAILURE
428 } else {
429 ExitCode::SUCCESS
430 }
431}