Skip to content

Commit c0d72ec

Browse files
committed
[thin_dump] Prompt for repair on input errors
thin_dump should display the repairing hint if there's any error in the input metadata rather than the output process. A context object thus is added to the returned error for callers to identify the error type. Note that a broken pipe error (EPIPE) is returned as an output error since the Rust std library ignores SIGPIPE by default [1][2]. However, due to the dump process leverages NodeVisitor, a low-level output error is stringified into a BTreeError, and thus the caller cannot tell whether it's broken pipe or not. [1] rust-lang/rust#13158 [2] rust-lang/rust#62569
1 parent 69ff4d4 commit c0d72ec

File tree

4 files changed

+64
-38
lines changed

4 files changed

+64
-38
lines changed

src/commands/thin_dump.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::sync::Arc;
88

99
use crate::commands::utils::*;
1010
use crate::commands::Command;
11+
use crate::dump_utils::OutputError;
1112
use crate::report::*;
1213
use crate::thin::dump::{dump, ThinDumpOptions};
1314
use crate::thin::metadata_repair::SuperblockOverrides;
@@ -128,9 +129,17 @@ impl<'a> Command<'a> for ThinDumpCommand {
128129
},
129130
};
130131

131-
dump(opts).map_err(|reason| {
132-
report.fatal(&format!("{}", reason));
133-
std::io::Error::from_raw_os_error(libc::EPERM)
134-
})
132+
if let Err(e) = dump(opts) {
133+
if !e.is::<OutputError>() {
134+
report.fatal(&format!("{:?}", e));
135+
report.fatal(
136+
"metadata contains errors (run thin_check for details).\n\
137+
perhaps you wanted to run with --repair ?",
138+
);
139+
}
140+
return Err(std::io::Error::from_raw_os_error(libc::EPERM));
141+
}
142+
143+
Ok(())
135144
}
136145
}

src/dump_utils.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//------------------------------------------
2+
3+
// A wrapper for callers to identify the error type
4+
#[derive(Debug)]
5+
pub struct OutputError;
6+
7+
impl std::fmt::Display for OutputError {
8+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
9+
write!(f, "output error")
10+
}
11+
}
12+
13+
//------------------------------------------

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub mod cache;
1919
pub mod checksum;
2020
pub mod commands;
2121
pub mod copier;
22+
pub mod dump_utils;
2223
pub mod era;
2324
pub mod file_utils;
2425
pub mod grid_layout;

src/thin/dump.rs

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
use anyhow::{anyhow, Result};
1+
use anyhow::{anyhow, Context, Result};
22
use std::fs::File;
33
use std::io::BufWriter;
44
use std::io::Write;
55
use std::path::Path;
66
use std::sync::{Arc, Mutex};
77

88
use crate::checksum;
9+
use crate::dump_utils::*;
910
use crate::io_engine::{AsyncIoEngine, Block, IoEngine, SyncIoEngine};
1011
use crate::pdata::btree::{self, *};
1112
use crate::pdata::btree_walker::*;
@@ -108,6 +109,9 @@ impl<'a> NodeVisitor<BlockTime> for MappingVisitor<'a> {
108109
let mut inner = self.inner.lock().unwrap();
109110
for (k, v) in keys.iter().zip(values.iter()) {
110111
if let Some(run) = inner.builder.next(*k, v.block, v.time) {
112+
// FIXME: BTreeError should carry more information than a string
113+
// so the caller could identify the actual root cause,
114+
// e.g., a broken pipe error or something.
111115
inner
112116
.md_out
113117
.map(&run)
@@ -153,58 +157,49 @@ pub struct ThinDumpOptions<'a> {
153157
pub overrides: SuperblockOverrides,
154158
}
155159

156-
struct Context {
160+
struct ThinDumpContext {
157161
report: Arc<Report>,
158162
engine: Arc<dyn IoEngine + Send + Sync>,
159163
}
160164

161-
fn mk_context(opts: &ThinDumpOptions) -> Result<Context> {
165+
fn mk_context(opts: &ThinDumpOptions) -> Result<ThinDumpContext> {
162166
let engine: Arc<dyn IoEngine + Send + Sync> = if opts.async_io {
163167
Arc::new(AsyncIoEngine::new(opts.input, MAX_CONCURRENT_IO, false)?)
164168
} else {
165169
let nr_threads = std::cmp::max(8, num_cpus::get() * 2);
166170
Arc::new(SyncIoEngine::new(opts.input, nr_threads, false)?)
167171
};
168172

169-
Ok(Context {
173+
Ok(ThinDumpContext {
170174
report: opts.report.clone(),
171175
engine,
172176
})
173177
}
174178

175179
//------------------------------------------
176180

177-
fn emit_leaf(v: &mut MappingVisitor, b: &Block) -> Result<()> {
181+
fn emit_leaf(v: &MappingVisitor, b: &Block) -> Result<()> {
178182
use Node::*;
179183
let path = Vec::new();
180184
let kr = KeyRange::new();
181185

182186
let bt = checksum::metadata_block_type(b.get_data());
183187
if bt != checksum::BT::NODE {
184-
return Err(anyhow!(format!(
185-
"checksum failed for node {}, {:?}",
186-
b.loc, bt
187-
)));
188+
return Err(anyhow!("checksum failed for node {}, {:?}", b.loc, bt));
188189
}
189190

190191
let node = unpack_node::<BlockTime>(&path, b.get_data(), true, true)?;
191192

192193
match node {
193-
Internal { .. } => {
194-
return Err(anyhow!("not a leaf"));
195-
}
194+
Internal { .. } => Err(anyhow!("block {} is not a leaf", b.loc)),
196195
Leaf {
197196
header,
198197
keys,
199198
values,
200-
} => {
201-
if let Err(_e) = v.visit(&path, &kr, &header, &keys, &values) {
202-
return Err(anyhow!("couldn't emit leaf node"));
203-
}
204-
}
199+
} => v
200+
.visit(&path, &kr, &header, &keys, &values)
201+
.context(OutputError),
205202
}
206-
207-
Ok(())
208203
}
209204

210205
fn read_for<T>(engine: Arc<dyn IoEngine>, blocks: &[u64], mut t: T) -> Result<()>
@@ -216,22 +211,27 @@ where
216211
.read_many(cs)
217212
.map_err(|_e| anyhow!("read_many failed"))?
218213
{
219-
t(b.map_err(|_e| anyhow!("read of individual block failed"))?)?;
214+
let blk = b.map_err(|_e| anyhow!("read of individual block failed"))?;
215+
t(blk)?;
220216
}
221217
}
222218

223219
Ok(())
224220
}
225221

226-
fn emit_leaves(engine: Arc<dyn IoEngine>, out: &mut dyn MetadataVisitor, ls: &[u64]) -> Result<()> {
227-
let mut v = MappingVisitor::new(out);
222+
fn emit_leaves(
223+
engine: Arc<dyn IoEngine>,
224+
out: &mut dyn MetadataVisitor,
225+
leaves: &[u64],
226+
) -> Result<()> {
227+
let v = MappingVisitor::new(out);
228228
let proc = |b| {
229-
emit_leaf(&mut v, &b)?;
229+
emit_leaf(&v, &b)?;
230230
Ok(())
231231
};
232232

233-
read_for(engine, ls, proc)?;
234-
v.end_walk().map_err(|_| anyhow!("failed to emit leaves"))
233+
read_for(engine, leaves, proc)?;
234+
v.end_walk().context(OutputError)
235235
}
236236

237237
fn emit_entries(
@@ -252,7 +252,7 @@ fn emit_entries(
252252
leaves.clear();
253253
}
254254
let str = format!("{}", id);
255-
out.ref_shared(&str)?;
255+
out.ref_shared(&str).context(OutputError)?;
256256
}
257257
}
258258
}
@@ -281,12 +281,13 @@ pub fn dump_metadata(
281281
nr_data_blocks: data_root.nr_blocks,
282282
metadata_snap: None,
283283
};
284-
out.superblock_b(&out_sb)?;
284+
out.superblock_b(&out_sb).context(OutputError)?;
285285

286286
for d in &md.defs {
287-
out.def_shared_b(&format!("{}", d.def_id))?;
287+
out.def_shared_b(&format!("{}", d.def_id))
288+
.context(OutputError)?;
288289
emit_entries(engine.clone(), out, &d.map.entries)?;
289-
out.def_shared_e()?;
290+
out.def_shared_e().context(OutputError)?;
290291
}
291292

292293
for dev in &md.devs {
@@ -297,12 +298,12 @@ pub fn dump_metadata(
297298
creation_time: dev.detail.creation_time,
298299
snap_time: dev.detail.snapshotted_time,
299300
};
300-
out.device_b(&device)?;
301+
out.device_b(&device).context(OutputError)?;
301302
emit_entries(engine.clone(), out, &dev.map.entries)?;
302-
out.device_e()?;
303+
out.device_e().context(OutputError)?;
303304
}
304-
out.superblock_e()?;
305-
out.eof()?;
305+
out.superblock_e().context(OutputError)?;
306+
out.eof().context(OutputError)?;
306307

307308
Ok(())
308309
}
@@ -324,11 +325,13 @@ pub fn dump(opts: ThinDumpOptions) -> Result<()> {
324325
read_superblock(ctx.engine.as_ref(), SUPERBLOCK_LOCATION)
325326
.and_then(|sb| sb.overrides(&opts.overrides))?
326327
};
328+
327329
let md = build_metadata(ctx.engine.clone(), &sb)?;
328330
let md = optimise_metadata(md)?;
329331

330332
let writer: Box<dyn Write> = if opts.output.is_some() {
331-
Box::new(BufWriter::new(File::create(opts.output.unwrap())?))
333+
let f = File::create(opts.output.unwrap()).context(OutputError)?;
334+
Box::new(BufWriter::new(f))
332335
} else {
333336
Box::new(BufWriter::new(std::io::stdout()))
334337
};

0 commit comments

Comments
 (0)