File: cat.rs

package info (click to toggle)
rust-rust-unixfs 0.4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 652 kB
  • sloc: sh: 17; makefile: 2
file content (166 lines) | stat: -rw-r--r-- 5,526 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
use cid::Cid;
use rust_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!("{e}\n");
            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(rust_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)
    }
}