unixfs_v1/
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 crate::pb::DAG_PB;
35
36    use super::serialize_symlink_block;
37    use core::convert::TryFrom;
38    use libipld::multihash::{Code, MultihashDigest};
39    use libipld::Cid;
40
41    #[test]
42    fn simple_symlink() {
43        let mut buf = Vec::new();
44
45        // this symlink just points to a "b" at the same level, used in `symlinks_in_trees` to
46        // create the `foo_directory/a` which links to sibling `b` or the directory
47        // `foo_directory/b`.
48        serialize_symlink_block("b", &mut buf);
49
50        let mh = Code::Sha2_256.digest(&buf);
51        let cid = Cid::new_v1(DAG_PB, mh);
52
53        assert_eq!(
54            cid.to_string(),
55            "bafybeih4p6wgtxnujy4wq3wp2hwmnrjkwzj7iit6km7oosroeohywikd2m"
56        );
57    }
58
59    #[test]
60    fn symlinks_in_trees_rooted() {
61        use crate::dir::builder::BufferingTreeBuilder;
62
63        let mut tree = BufferingTreeBuilder::default();
64
65        tree.put_link(
66            "foo_directory/b/car",
67            Cid::try_from("QmNYVgoDXh3dqC1jjCuYqQ9w4XfiocehPZjEPiQiCVYv33").unwrap(),
68            12,
69        )
70        .unwrap();
71
72        tree.put_link(
73            "foo_directory/a",
74            Cid::try_from("QmfLJN6HLyREnWr7QQNmgmuNziUhcbwUopkHQ8gD3pMfp6").unwrap(),
75            7,
76        )
77        .unwrap();
78
79        let otn = tree.build().last().unwrap().unwrap();
80
81        assert_eq!(
82            otn.cid.to_string(),
83            "bafybeibsalmqatls4cakc5ivjn3yawtb66h5ztg33w2us7oomnlt2ugzgu"
84        );
85    }
86
87    #[test]
88    fn symlinks_in_trees_wrapped() {
89        use crate::dir::builder::{BufferingTreeBuilder, TreeOptions};
90
91        // note regarding the root directory; now we can add the paths without the first component
92        // `foo_directory` and still get the same result as in `symlinks_in_trees_rooted`.
93        let mut opts = TreeOptions::default();
94        opts.wrap_with_directory();
95
96        let mut tree = BufferingTreeBuilder::new(opts);
97
98        tree.put_link(
99            "b/car",
100            Cid::try_from("QmNYVgoDXh3dqC1jjCuYqQ9w4XfiocehPZjEPiQiCVYv33").unwrap(),
101            12,
102        )
103        .unwrap();
104
105        tree.put_link(
106            "a",
107            Cid::try_from("QmfLJN6HLyREnWr7QQNmgmuNziUhcbwUopkHQ8gD3pMfp6").unwrap(),
108            7,
109        )
110        .unwrap();
111
112        let otn = tree.build().last().unwrap().unwrap();
113
114        assert_eq!(
115            otn.cid.to_string(),
116            "bafybeibsalmqatls4cakc5ivjn3yawtb66h5ztg33w2us7oomnlt2ugzgu"
117        );
118    }
119
120    #[test]
121    fn walking_symlink_containing_tree() {
122        use crate::walk::{ContinuedWalk, Walker};
123        use hex_literal::hex;
124        use std::path::PathBuf;
125
126        // while this case or similar should be repeated in the walker tests, the topic of symlinks
127        // and how the target path names are handled (esp. on windows) is curious enough to warrant
128        // duplicating these three cases here.
129
130        let mut fake = crate::test_support::FakeBlockstore::default();
131
132        // if `simple_symlink` and `symlinks_in_trees_*` passed, they would had created these
133        // blocks, which we now take for granted.
134
135        let tree_blocks: &[(&'static str, &'static [u8])] = &[
136            ("QmZDVQHwjHwA4SyzEDtJLNxmZeJVK1W8BWFAHV61x2Rs19", &hex!("12290a221220fc7fac69ddb44e39686ecfd1ecc6c52ab653f4227e533ee74a2e238f8b2143d3120161180712290a221220b924ddb19181d159c29eec7c98ec506976a76d40241ccd203b226849ce6e0b72120162183d0a020801")),
137            ("QmfLJN6HLyREnWr7QQNmgmuNziUhcbwUopkHQ8gD3pMfp6", &hex!("0a050804120162")),
138            ("QmaoNjmCQ9774sR6H4DzgGPafXyuVVTCyBeXLaxueKYRLm", &hex!("122b0a2212200308c49252eb61966f802baf45074e074f3b3b90619766e0589c1445261a1a221203636172180c0a020801")),
139            ("QmNYVgoDXh3dqC1jjCuYqQ9w4XfiocehPZjEPiQiCVYv33", &hex!("0a0a080212046361720a1804")),
140        ];
141
142        for (expected, bytes) in tree_blocks {
143            assert_eq!(*expected, fake.insert_v0(bytes).to_string());
144        }
145
146        let mut walker = Walker::new(
147            // note: this matches the `symlinks_in_trees` root cid (the last cid produced)
148            Cid::try_from(tree_blocks[0].0).unwrap(),
149            String::default(),
150        );
151
152        #[derive(Debug, PartialEq, Eq)]
153        enum Entry {
154            Dir(PathBuf),
155            Symlink(PathBuf, String),
156            File(PathBuf),
157        }
158
159        let mut actual = Vec::new();
160
161        while walker.should_continue() {
162            let (next, _) = walker.pending_links();
163            let next = fake.get_by_cid(next);
164
165            match walker.next(next, &mut None).unwrap() {
166                ContinuedWalk::File(_fs, _cid, path, _metadata, _total_size) => {
167                    actual.push(Entry::File(path.into()));
168                }
169                ContinuedWalk::RootDirectory(_cid, path, _metadata)
170                | ContinuedWalk::Directory(_cid, path, _metadata) => {
171                    actual.push(Entry::Dir(path.into()));
172                }
173                ContinuedWalk::Bucket(..) => { /* ignore */ }
174                ContinuedWalk::Symlink(link_name, _cid, path, _metadata) => {
175                    actual.push(Entry::Symlink(
176                        path.into(),
177                        core::str::from_utf8(link_name).unwrap().to_owned(),
178                    ));
179                }
180            };
181        }
182
183        // possibly noteworthy: compare these paths to the ones used when creating; there was
184        // non-empty root component `foo_directory`, refer to `symlinks_in_trees_*` variants for
185        // more.
186        let expected = &[
187            Entry::Dir(PathBuf::from("")),
188            Entry::Symlink(PathBuf::from("a"), String::from("b")),
189            Entry::Dir(PathBuf::from("b")),
190            Entry::File({
191                let mut p = PathBuf::from("b");
192                p.push("car");
193                p
194            }),
195        ];
196
197        assert_eq!(expected, actual.as_slice());
198    }
199}