btrfs_cli/subvolume/
find_new.rs1use crate::{Format, Runnable, util::open_path};
2use anyhow::{Context, Result};
3use btrfs_uapi::{
4 filesystem::sync,
5 inode::ino_paths,
6 raw::{
7 BTRFS_EXTENT_DATA_KEY, BTRFS_FILE_EXTENT_INLINE,
8 BTRFS_FILE_EXTENT_PREALLOC, BTRFS_FILE_EXTENT_REG,
9 btrfs_file_extent_item,
10 },
11 subvolume::subvolume_info,
12 tree_search::{SearchKey, tree_search},
13 util::read_le_u64,
14};
15use clap::Parser;
16use std::{mem, os::unix::io::AsFd, path::PathBuf};
17
18#[derive(Parser, Debug)]
23pub struct SubvolumeFindNewCommand {
24 path: PathBuf,
26
27 last_gen: u64,
29}
30
31impl Runnable for SubvolumeFindNewCommand {
32 fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
33 let file = open_path(&self.path)?;
34
35 sync(file.as_fd()).with_context(|| {
37 format!("failed to sync '{}'", self.path.display())
38 })?;
39
40 let info = subvolume_info(file.as_fd()).with_context(|| {
42 format!(
43 "failed to get subvolume info for '{}'",
44 self.path.display()
45 )
46 })?;
47 let max_gen = info.generation;
48
49 let mut key = SearchKey::for_type(0, BTRFS_EXTENT_DATA_KEY);
53 key.min_transid = self.last_gen;
54
55 let mut cache_ino: u64 = 0;
56 let mut cache_name: Option<String> = None;
57
58 tree_search(file.as_fd(), key, |hdr, data| {
59 let gen_off = mem::offset_of!(btrfs_file_extent_item, generation);
60 let type_off = mem::offset_of!(btrfs_file_extent_item, type_);
61 let compression_off = mem::offset_of!(btrfs_file_extent_item, compression);
62
63 if data.len() < type_off + 1 {
65 return Ok(());
66 }
67
68 let found_gen = read_le_u64(data, gen_off);
69 if found_gen < self.last_gen {
70 return Ok(());
71 }
72
73 let extent_type = data[type_off];
74 let compressed = data.get(compression_off).copied().unwrap_or(0) != 0;
75
76 let (disk_start, disk_offset, len) =
77 if extent_type == BTRFS_FILE_EXTENT_REG as u8
78 || extent_type == BTRFS_FILE_EXTENT_PREALLOC as u8
79 {
80 let disk_bytenr_off = mem::offset_of!(btrfs_file_extent_item, disk_bytenr);
81 let offset_off = mem::offset_of!(btrfs_file_extent_item, offset);
82 let num_bytes_off = mem::offset_of!(btrfs_file_extent_item, num_bytes);
83
84 if data.len() < num_bytes_off + 8 {
85 return Ok(());
86 }
87 (
88 read_le_u64(data, disk_bytenr_off),
89 read_le_u64(data, offset_off),
90 read_le_u64(data, num_bytes_off),
91 )
92 } else if extent_type == BTRFS_FILE_EXTENT_INLINE as u8 {
93 let ram_bytes_off = mem::offset_of!(btrfs_file_extent_item, ram_bytes);
94 if data.len() < ram_bytes_off + 8 {
95 return Ok(());
96 }
97 (0, 0, read_le_u64(data, ram_bytes_off))
98 } else {
99 return Ok(());
100 };
101
102 let name = if hdr.objectid == cache_ino {
105 cache_name.as_deref().unwrap_or("unknown")
106 } else {
107 let resolved = match ino_paths(file.as_fd(), hdr.objectid) {
108 Ok(paths) if !paths.is_empty() => Some(paths.into_iter().next().unwrap()),
109 _ => None,
110 };
111 cache_ino = hdr.objectid;
112 cache_name = resolved;
113 cache_name.as_deref().unwrap_or("unknown")
114 };
115
116 let mut flags = String::new();
118 if compressed {
119 flags.push_str("COMPRESS");
120 }
121 if extent_type == BTRFS_FILE_EXTENT_PREALLOC as u8 {
122 if !flags.is_empty() {
123 flags.push('|');
124 }
125 flags.push_str("PREALLOC");
126 }
127 if extent_type == BTRFS_FILE_EXTENT_INLINE as u8 {
128 if !flags.is_empty() {
129 flags.push('|');
130 }
131 flags.push_str("INLINE");
132 }
133 if flags.is_empty() {
134 flags.push_str("NONE");
135 }
136
137 println!(
138 "inode {} file offset {} len {} disk start {} offset {} gen {} flags {flags} {name}",
139 hdr.objectid, hdr.offset, len, disk_start, disk_offset, found_gen,
140 );
141
142 Ok(())
143 })
144 .with_context(|| format!("tree search failed for '{}'", self.path.display()))?;
145
146 println!("transid marker was {max_gen}");
147
148 Ok(())
149 }
150}