Skip to main content

idb/cli/
tsid.rs

1use std::collections::BTreeMap;
2use std::io::Write;
3use std::path::Path;
4
5use byteorder::{BigEndian, ByteOrder};
6use serde::Serialize;
7
8use crate::cli::wprintln;
9use crate::innodb::constants::FIL_PAGE_DATA;
10use crate::innodb::tablespace::Tablespace;
11use crate::IdbError;
12
13pub struct TsidOptions {
14    pub datadir: String,
15    pub list: bool,
16    pub tablespace_id: Option<u32>,
17    pub json: bool,
18    pub page_size: Option<u32>,
19}
20
21#[derive(Serialize)]
22struct TsidResultJson {
23    datadir: String,
24    tablespaces: Vec<TsidEntryJson>,
25}
26
27#[derive(Serialize)]
28struct TsidEntryJson {
29    file: String,
30    space_id: u32,
31}
32
33pub fn execute(opts: &TsidOptions, writer: &mut dyn Write) -> Result<(), IdbError> {
34    let datadir = Path::new(&opts.datadir);
35    if !datadir.is_dir() {
36        return Err(IdbError::Argument(format!(
37            "Data directory does not exist: {}",
38            opts.datadir
39        )));
40    }
41
42    let ibd_files = find_ibd_and_ibu_files(datadir)?;
43
44    if ibd_files.is_empty() {
45        if opts.json {
46            let result = TsidResultJson {
47                datadir: opts.datadir.clone(),
48                tablespaces: Vec::new(),
49            };
50            let json = serde_json::to_string_pretty(&result)
51                .map_err(|e| IdbError::Parse(format!("JSON serialization error: {}", e)))?;
52            wprintln!(writer, "{}", json)?;
53        } else {
54            wprintln!(writer, "No .ibd/.ibu files found in {}", opts.datadir)?;
55        }
56        return Ok(());
57    }
58
59    // Collect tablespace IDs
60    let mut results: BTreeMap<String, u32> = BTreeMap::new();
61
62    for ibd_path in &ibd_files {
63        let mut ts = match match opts.page_size {
64            Some(ps) => Tablespace::open_with_page_size(ibd_path, ps),
65            None => Tablespace::open(ibd_path),
66        } {
67            Ok(t) => t,
68            Err(_) => continue,
69        };
70
71        let space_id = match ts.fsp_header() {
72            Some(fsp) => fsp.space_id,
73            None => {
74                // Try reading space_id directly from FSP header position
75                match ts.read_page(0) {
76                    Ok(page0) => {
77                        if page0.len() >= FIL_PAGE_DATA + 4 {
78                            BigEndian::read_u32(&page0[FIL_PAGE_DATA..])
79                        } else {
80                            continue;
81                        }
82                    }
83                    Err(_) => continue,
84                }
85            }
86        };
87
88        let display_path = ibd_path
89            .strip_prefix(datadir)
90            .unwrap_or(ibd_path)
91            .to_string_lossy()
92            .to_string();
93
94        // Filter by tablespace ID if specified
95        if let Some(target_id) = opts.tablespace_id {
96            if space_id != target_id {
97                continue;
98            }
99        }
100
101        results.insert(display_path, space_id);
102    }
103
104    if opts.json {
105        let tablespaces: Vec<TsidEntryJson> = results
106            .iter()
107            .map(|(path, &space_id)| TsidEntryJson {
108                file: path.clone(),
109                space_id,
110            })
111            .collect();
112
113        let result = TsidResultJson {
114            datadir: opts.datadir.clone(),
115            tablespaces,
116        };
117
118        let json = serde_json::to_string_pretty(&result)
119            .map_err(|e| IdbError::Parse(format!("JSON serialization error: {}", e)))?;
120        wprintln!(writer, "{}", json)?;
121    } else {
122        // Print results
123        for (path, space_id) in &results {
124            wprintln!(writer, "{} - Space ID: {}", path, space_id)?;
125        }
126
127        if results.is_empty() {
128            if let Some(target_id) = opts.tablespace_id {
129                wprintln!(writer, "Tablespace ID {} not found.", target_id)?;
130            }
131        }
132    }
133
134    Ok(())
135}
136
137/// Find all .ibd and .ibu files in the data directory.
138fn find_ibd_and_ibu_files(datadir: &Path) -> Result<Vec<std::path::PathBuf>, IdbError> {
139    let mut files = Vec::new();
140
141    let entries = std::fs::read_dir(datadir)
142        .map_err(|e| IdbError::Io(format!("Cannot read directory {}: {}", datadir.display(), e)))?;
143
144    for entry in entries {
145        let entry =
146            entry.map_err(|e| IdbError::Io(format!("Cannot read directory entry: {}", e)))?;
147        let path = entry.path();
148
149        if path.is_dir() {
150            let sub_entries = std::fs::read_dir(&path)
151                .map_err(|e| IdbError::Io(format!("Cannot read {}: {}", path.display(), e)))?;
152
153            for sub_entry in sub_entries {
154                let sub_entry = sub_entry
155                    .map_err(|e| IdbError::Io(format!("Cannot read directory entry: {}", e)))?;
156                let sub_path = sub_entry.path();
157                if let Some(ext) = sub_path.extension() {
158                    if ext == "ibd" || ext == "ibu" {
159                        files.push(sub_path);
160                    }
161                }
162            }
163        } else if let Some(ext) = path.extension() {
164            if ext == "ibd" || ext == "ibu" {
165                files.push(path);
166            }
167        }
168    }
169
170    files.sort();
171    Ok(files)
172}