2020-06-17 17:27:05 +03:00

167 lines
5.4 KiB
Rust

use cid::Cid;
use ipfs_unixfs::file::{visit::IdleFileVisit, FileReadFailed};
use std::convert::TryFrom;
use std::fmt;
use std::io::{Error as IoError, Read, Write};
use std::path::PathBuf;
fn main() {
let cid = match std::env::args().nth(1).map(Cid::try_from) {
Some(Ok(cid)) => cid,
Some(Err(e)) => {
eprintln!("Invalid cid given as argument: {}", e);
std::process::exit(1);
}
None => {
eprintln!("USAGE: {} CID\n", std::env::args().next().unwrap());
eprintln!(
"Will walk the unixfs file pointed out by the CID from default go-ipfs 0.5 \
configuration flatfs blockstore and write all content to stdout."
);
std::process::exit(0);
}
};
let ipfs_path = match std::env::var("IPFS_PATH") {
Ok(s) => s,
Err(e) => {
eprintln!("IPFS_PATH is not set or could not be read: {}", e);
std::process::exit(1);
}
};
let mut blocks = PathBuf::from(ipfs_path);
blocks.push("blocks");
let blockstore = ShardedBlockStore { root: blocks };
match walk(blockstore, &cid) {
Ok((read, content)) => {
eprintln!("Content bytes: {}", content);
eprintln!("Total bytes: {}", read);
}
Err(Error::OpeningFailed(e)) => {
eprintln!("{}\n", e);
eprintln!("This is likely caused by either:");
eprintln!(" - ipfs does not have the block");
eprintln!(" - ipfs is configured to use non-flatfs storage");
eprintln!(" - ipfs is configured to use flatfs with different sharding");
std::process::exit(1);
}
Err(e) => {
eprintln!("Failed to walk the merkle tree: {}", e);
std::process::exit(1);
}
}
}
fn walk(blocks: ShardedBlockStore, start: &Cid) -> Result<(u64, u64), Error> {
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
let mut read_bytes = 0;
let mut content_bytes = 0;
// The blockstore specific way of reading the block. Here we assume go-ipfs 0.5 default flatfs
// configuration, which puts the files at sharded directories and names the blocks as base32
// upper and a suffix of "data".
//
// For the ipfs-unixfs it is important that the raw block data lives long enough that the
// possible content gets to be processed, at minimum one step of the walk as shown in this
// example.
let mut buf = Vec::new();
read_bytes += blocks.as_file(&start.to_bytes())?.read_to_end(&mut buf)? as u64;
// First step of the walk can give content or continued visitation but not both.
let (content, _, _metadata, mut step) = IdleFileVisit::default().start(&buf)?;
stdout.write_all(content)?;
content_bytes += content.len() as u64;
// Following steps repeat the same pattern:
while let Some(visit) = step {
// Read the next link. The `pending_links()` gives the next link and an iterator over the
// following links. The iterator lists the known links in the order of traversal, with the
// exception of possible new links appearing before the older.
let (first, _) = visit.pending_links();
buf.clear();
read_bytes += blocks.as_file(&first.to_bytes())?.read_to_end(&mut buf)? as u64;
// Similar to first step, except we no longer get the file metadata. It is still accessible
// from the `visit` via `AsRef<ipfs_unixfs::file::Metadata>` but likely only needed in
// the first step.
let (content, next_step) = visit.continue_walk(&buf, &mut None)?;
stdout.write_all(content)?;
content_bytes += content.len() as u64;
// Using a while loop combined with `let Some(visit) = step` allows for easy walking.
step = next_step;
}
stdout.flush()?;
Ok((read_bytes, content_bytes))
}
enum Error {
OpeningFailed(IoError),
Other(IoError),
Traversal(ipfs_unixfs::file::FileReadFailed),
}
impl From<IoError> for Error {
fn from(e: IoError) -> Error {
Error::Other(e)
}
}
impl From<FileReadFailed> for Error {
fn from(e: FileReadFailed) -> Error {
Error::Traversal(e)
}
}
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
use Error::*;
match self {
OpeningFailed(e) => write!(fmt, "File opening failed: {}", e),
Other(e) => write!(fmt, "Other file related io error: {}", e),
Traversal(e) => write!(fmt, "Traversal failed, please report this as a bug: {}", e),
}
}
}
struct ShardedBlockStore {
root: PathBuf,
}
impl ShardedBlockStore {
fn as_path(&self, key: &[u8]) -> PathBuf {
// assume that we have a block store with second-to-last/2 sharding
// files in Base32Upper
let encoded = multibase::Base::Base32Upper.encode(key);
let len = encoded.len();
// this is safe because base32 is ascii
let dir = &encoded[(len - 3)..(len - 1)];
assert_eq!(dir.len(), 2);
let mut path = self.root.clone();
path.push(dir);
path.push(encoded);
path.set_extension("data");
path
}
fn as_file(&self, key: &[u8]) -> Result<std::fs::File, Error> {
let path = self.as_path(key);
std::fs::OpenOptions::new()
.read(true)
.open(path)
.map_err(Error::OpeningFailed)
}
}