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 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 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 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 let mut fake = crate::test_support::FakeBlockstore::default();
131
132 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 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(..) => { }
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 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}