1use crate::pb::{FlatUnixFs, UnixFs, UnixFsType};
7use alloc::borrow::Cow;
8use quick_protobuf::{MessageWrite, Writer};
9
10pub fn serialize_symlink_block(target_path: &str, block_buffer: &mut Vec<u8>) {
14 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 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 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 let mut fake = crate::test_support::FakeBlockstore::default();
134
135 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 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(..) => { }
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 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}