rust_unixfs/
symlink.rs

1//! UnixFS symlink support. UnixFS symlinks are UnixFS messages similar to single block files, but
2//! the link name or target path is encoded in the UnixFS::Data field. This means that the target
3//! path could be in any encoding, however it is always treated as an utf8 Unix path. Could be that
4//! this is wrong.
5
6use crate::pb::{FlatUnixFs, UnixFs, UnixFsType};
7use alloc::borrow::Cow;
8use quick_protobuf::{MessageWrite, Writer};
9
10/// Appends a dag-pb block for for a symlink to the given target_path. It is expected that the
11/// `target_path` is valid relative unix path relative to the place in which this is used but
12/// targets validity cannot really be judged.
13pub fn serialize_symlink_block(target_path: &str, block_buffer: &mut Vec<u8>) {
14    // should this fail or not? protobuf encoding cannot fail here, however we might create a too
15    // large block but what's the limit?
16    //
17    // why not return a (Cid, Vec<u8>) like usually with cidv0? well...
18
19    let node = FlatUnixFs {
20        links: Vec::new(),
21        data: UnixFs {
22            Type: UnixFsType::Symlink,
23            Data: Some(Cow::Borrowed(target_path.as_bytes())),
24            ..Default::default()
25        },
26    };
27
28    let mut writer = Writer::new(block_buffer);
29    node.write_message(&mut writer).expect("unexpected failure");
30}
31
32#[cfg(test)]
33mod tests {
34    use super::serialize_symlink_block;
35    use core::convert::TryFrom;
36    use ipld_core::cid::Cid;
37    use multihash::{self, Multihash};
38    use sha2::{Digest, Sha256};
39
40    #[test]
41    fn simple_symlink() {
42        let mut buf = Vec::new();
43
44        // this symlink just points to a "b" at the same level, used in `symlinks_in_trees` to
45        // create the `foo_directory/a` which links to sibling `b` or the directory
46        // `foo_directory/b`.
47        serialize_symlink_block("b", &mut buf);
48
49        let mh = Multihash::wrap(
50            multihash_codetable::Code::Sha2_256.into(),
51            &Sha256::digest(&buf),
52        )
53        .unwrap();
54        let cid = Cid::new_v0(mh).expect("sha2_256 is the correct multihash for cidv0");
55
56        assert_eq!(
57            cid.to_string(),
58            "QmfLJN6HLyREnWr7QQNmgmuNziUhcbwUopkHQ8gD3pMfp6"
59        );
60    }
61
62    #[test]
63    fn symlinks_in_trees_rooted() {
64        use crate::dir::builder::BufferingTreeBuilder;
65
66        let mut tree = BufferingTreeBuilder::default();
67
68        tree.put_link(
69            "foo_directory/b/car",
70            Cid::try_from("QmNYVgoDXh3dqC1jjCuYqQ9w4XfiocehPZjEPiQiCVYv33").unwrap(),
71            12,
72        )
73        .unwrap();
74
75        tree.put_link(
76            "foo_directory/a",
77            Cid::try_from("QmfLJN6HLyREnWr7QQNmgmuNziUhcbwUopkHQ8gD3pMfp6").unwrap(),
78            7,
79        )
80        .unwrap();
81
82        let otn = tree.build().last().unwrap().unwrap();
83
84        assert_eq!(
85            otn.cid.to_string(),
86            "QmZDVQHwjHwA4SyzEDtJLNxmZeJVK1W8BWFAHV61x2Rs19"
87        );
88    }
89
90    #[test]
91    fn symlinks_in_trees_wrapped() {
92        use crate::dir::builder::{BufferingTreeBuilder, TreeOptions};
93
94        // note regarding the root directory; now we can add the paths without the first component
95        // `foo_directory` and still get the same result as in `symlinks_in_trees_rooted`.
96        let mut opts = TreeOptions::default();
97        opts.wrap_with_directory();
98
99        let mut tree = BufferingTreeBuilder::new(opts);
100
101        tree.put_link(
102            "b/car",
103            Cid::try_from("QmNYVgoDXh3dqC1jjCuYqQ9w4XfiocehPZjEPiQiCVYv33").unwrap(),
104            12,
105        )
106        .unwrap();
107
108        tree.put_link(
109            "a",
110            Cid::try_from("QmfLJN6HLyREnWr7QQNmgmuNziUhcbwUopkHQ8gD3pMfp6").unwrap(),
111            7,
112        )
113        .unwrap();
114
115        let otn = tree.build().last().unwrap().unwrap();
116
117        assert_eq!(
118            otn.cid.to_string(),
119            "QmZDVQHwjHwA4SyzEDtJLNxmZeJVK1W8BWFAHV61x2Rs19"
120        );
121    }
122
123    #[test]
124    fn walking_symlink_containing_tree() {
125        use crate::walk::{ContinuedWalk, Walker};
126        use hex_literal::hex;
127        use std::path::PathBuf;
128
129        // while this case or similar should be repeated in the walker tests, the topic of symlinks
130        // and how the target path names are handled (esp. on windows) is curious enough to warrant
131        // duplicating these three cases here.
132
133        let mut fake = crate::test_support::FakeBlockstore::default();
134
135        // if `simple_symlink` and `symlinks_in_trees_*` passed, they would had created these
136        // blocks, which we now take for granted.
137
138        let tree_blocks: &[(&'static str, &'static [u8])] = &[
139            ("QmZDVQHwjHwA4SyzEDtJLNxmZeJVK1W8BWFAHV61x2Rs19", &hex!("12290a221220fc7fac69ddb44e39686ecfd1ecc6c52ab653f4227e533ee74a2e238f8b2143d3120161180712290a221220b924ddb19181d159c29eec7c98ec506976a76d40241ccd203b226849ce6e0b72120162183d0a020801")),
140            ("QmfLJN6HLyREnWr7QQNmgmuNziUhcbwUopkHQ8gD3pMfp6", &hex!("0a050804120162")),
141            ("QmaoNjmCQ9774sR6H4DzgGPafXyuVVTCyBeXLaxueKYRLm", &hex!("122b0a2212200308c49252eb61966f802baf45074e074f3b3b90619766e0589c1445261a1a221203636172180c0a020801")),
142            ("QmNYVgoDXh3dqC1jjCuYqQ9w4XfiocehPZjEPiQiCVYv33", &hex!("0a0a080212046361720a1804")),
143        ];
144
145        for (expected, bytes) in tree_blocks {
146            assert_eq!(*expected, fake.insert_v0(bytes).to_string());
147        }
148
149        let mut walker = Walker::new(
150            // note: this matches the `symlinks_in_trees` root cid (the last cid produced)
151            Cid::try_from(tree_blocks[0].0).unwrap(),
152            String::default(),
153        );
154
155        #[derive(Debug, PartialEq, Eq)]
156        enum Entry {
157            Dir(PathBuf),
158            Symlink(PathBuf, String),
159            File(PathBuf),
160        }
161
162        let mut actual = Vec::new();
163
164        while walker.should_continue() {
165            let (next, _) = walker.pending_links();
166            let next = fake.get_by_cid(next);
167
168            match walker.next(next, &mut None).unwrap() {
169                ContinuedWalk::File(_fs, _cid, path, _metadata, _total_size) => {
170                    actual.push(Entry::File(path.into()));
171                }
172                ContinuedWalk::RootDirectory(_cid, path, _metadata)
173                | ContinuedWalk::Directory(_cid, path, _metadata) => {
174                    actual.push(Entry::Dir(path.into()));
175                }
176                ContinuedWalk::Bucket(..) => { /* ignore */ }
177                ContinuedWalk::Symlink(link_name, _cid, path, _metadata) => {
178                    actual.push(Entry::Symlink(
179                        path.into(),
180                        core::str::from_utf8(link_name).unwrap().to_owned(),
181                    ));
182                }
183            };
184        }
185
186        // possibly noteworthy: compare these paths to the ones used when creating; there was
187        // non-empty root component `foo_directory`, refer to `symlinks_in_trees_*` variants for
188        // more.
189        let expected = &[
190            Entry::Dir(PathBuf::from("")),
191            Entry::Symlink(PathBuf::from("a"), String::from("b")),
192            Entry::Dir(PathBuf::from("b")),
193            Entry::File({
194                let mut p = PathBuf::from("b");
195                p.push("car");
196                p
197            }),
198        ];
199
200        assert_eq!(expected, actual.as_slice());
201    }
202}