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 cid::Cid;
36 use core::convert::TryFrom;
37 use sha2::{Digest, Sha256};
38
39 #[test]
40 fn simple_symlink() {
41 let mut buf = Vec::new();
42
43 serialize_symlink_block("b", &mut buf);
47
48 let mh = multihash::wrap(multihash::Code::Sha2_256, &Sha256::digest(&buf));
49 let cid = Cid::new_v0(mh).expect("sha2_256 is the correct multihash for cidv0");
50
51 assert_eq!(
52 cid.to_string(),
53 "QmfLJN6HLyREnWr7QQNmgmuNziUhcbwUopkHQ8gD3pMfp6"
54 );
55 }
56
57 #[test]
58 fn symlinks_in_trees_rooted() {
59 use crate::dir::builder::BufferingTreeBuilder;
60
61 let mut tree = BufferingTreeBuilder::default();
62
63 tree.put_link(
64 "foo_directory/b/car",
65 Cid::try_from("QmNYVgoDXh3dqC1jjCuYqQ9w4XfiocehPZjEPiQiCVYv33").unwrap(),
66 12,
67 )
68 .unwrap();
69
70 tree.put_link(
71 "foo_directory/a",
72 Cid::try_from("QmfLJN6HLyREnWr7QQNmgmuNziUhcbwUopkHQ8gD3pMfp6").unwrap(),
73 7,
74 )
75 .unwrap();
76
77 let otn = tree.build().last().unwrap().unwrap();
78
79 assert_eq!(
80 otn.cid.to_string(),
81 "QmZDVQHwjHwA4SyzEDtJLNxmZeJVK1W8BWFAHV61x2Rs19"
82 );
83 }
84
85 #[test]
86 fn symlinks_in_trees_wrapped() {
87 use crate::dir::builder::{BufferingTreeBuilder, TreeOptions};
88
89 let mut opts = TreeOptions::default();
92 opts.wrap_with_directory();
93
94 let mut tree = BufferingTreeBuilder::new(opts);
95
96 tree.put_link(
97 "b/car",
98 Cid::try_from("QmNYVgoDXh3dqC1jjCuYqQ9w4XfiocehPZjEPiQiCVYv33").unwrap(),
99 12,
100 )
101 .unwrap();
102
103 tree.put_link(
104 "a",
105 Cid::try_from("QmfLJN6HLyREnWr7QQNmgmuNziUhcbwUopkHQ8gD3pMfp6").unwrap(),
106 7,
107 )
108 .unwrap();
109
110 let otn = tree.build().last().unwrap().unwrap();
111
112 assert_eq!(
113 otn.cid.to_string(),
114 "QmZDVQHwjHwA4SyzEDtJLNxmZeJVK1W8BWFAHV61x2Rs19"
115 );
116 }
117
118 #[test]
119 fn walking_symlink_containing_tree() {
120 use crate::walk::{ContinuedWalk, Walker};
121 use hex_literal::hex;
122 use std::path::PathBuf;
123
124 let mut fake = crate::test_support::FakeBlockstore::default();
129
130 let tree_blocks: &[(&'static str, &'static [u8])] = &[
134 ("QmZDVQHwjHwA4SyzEDtJLNxmZeJVK1W8BWFAHV61x2Rs19", &hex!("12290a221220fc7fac69ddb44e39686ecfd1ecc6c52ab653f4227e533ee74a2e238f8b2143d3120161180712290a221220b924ddb19181d159c29eec7c98ec506976a76d40241ccd203b226849ce6e0b72120162183d0a020801")),
135 ("QmfLJN6HLyREnWr7QQNmgmuNziUhcbwUopkHQ8gD3pMfp6", &hex!("0a050804120162")),
136 ("QmaoNjmCQ9774sR6H4DzgGPafXyuVVTCyBeXLaxueKYRLm", &hex!("122b0a2212200308c49252eb61966f802baf45074e074f3b3b90619766e0589c1445261a1a221203636172180c0a020801")),
137 ("QmNYVgoDXh3dqC1jjCuYqQ9w4XfiocehPZjEPiQiCVYv33", &hex!("0a0a080212046361720a1804")),
138 ];
139
140 for (expected, bytes) in tree_blocks {
141 assert_eq!(*expected, fake.insert_v0(bytes).to_string());
142 }
143
144 let mut walker = Walker::new(
145 Cid::try_from(tree_blocks[0].0).unwrap(),
147 String::default(),
148 );
149
150 #[derive(Debug, PartialEq, Eq)]
151 enum Entry {
152 Dir(PathBuf),
153 Symlink(PathBuf, String),
154 File(PathBuf),
155 }
156
157 let mut actual = Vec::new();
158
159 while walker.should_continue() {
160 let (next, _) = walker.pending_links();
161 let next = fake.get_by_cid(next);
162
163 match walker.next(next, &mut None).unwrap() {
164 ContinuedWalk::File(_fs, _cid, path, _metadata, _total_size) => {
165 actual.push(Entry::File(path.into()));
166 }
167 ContinuedWalk::RootDirectory(_cid, path, _metadata)
168 | ContinuedWalk::Directory(_cid, path, _metadata) => {
169 actual.push(Entry::Dir(path.into()));
170 }
171 ContinuedWalk::Bucket(..) => { }
172 ContinuedWalk::Symlink(link_name, _cid, path, _metadata) => {
173 actual.push(Entry::Symlink(
174 path.into(),
175 core::str::from_utf8(link_name).unwrap().to_owned(),
176 ));
177 }
178 };
179 }
180
181 let expected = &[
185 Entry::Dir(PathBuf::from("")),
186 Entry::Symlink(PathBuf::from("a"), String::from("b")),
187 Entry::Dir(PathBuf::from("b")),
188 Entry::File({
189 let mut p = PathBuf::from("b");
190 p.push("car");
191 p
192 }),
193 ];
194
195 assert_eq!(expected, actual.as_slice());
196 }
197}