1use crate::constants::*;
4
5pub const MAX_SYMLINK_LEN: usize = BLOCK_SIZE - SYMLINK_OFFSET - FILE_LOCATION;
11
12pub fn read_symlink_target(buf: &[u8; BLOCK_SIZE], out: &mut [u8]) -> usize {
29 read_symlink_target_with_block_size(buf, BLOCK_SIZE, out)
30}
31
32pub fn read_symlink_target_with_block_size(buf: &[u8], block_size: usize, out: &mut [u8]) -> usize {
42 let symlink_start = SYMLINK_OFFSET;
44 let symlink_end = block_size.saturating_sub(FILE_LOCATION);
45
46 if symlink_start >= symlink_end || symlink_start >= buf.len() {
47 return 0;
48 }
49
50 let symlink_end = symlink_end.min(buf.len());
51 let latin1 = &buf[symlink_start..symlink_end];
52
53 let len = memchr::memchr(0, latin1).unwrap_or(latin1.len());
54 let latin1 = &latin1[..len];
55
56 latin1_to_utf8_symlink(latin1, out)
58}
59
60fn latin1_to_utf8_symlink(latin1: &[u8], out: &mut [u8]) -> usize {
72 let mut out_pos = 0;
73
74 for (i, &byte) in latin1.iter().enumerate() {
75 let byte = if i == 0 && byte == b':' { b'/' } else { byte };
77
78 if byte < 0x80 {
79 if out_pos >= out.len() {
81 break;
82 }
83 out[out_pos] = byte;
84 out_pos += 1;
85 } else {
86 if out_pos + 1 >= out.len() {
89 break;
90 }
91 out[out_pos] = 0xC0 | (byte >> 6);
92 out[out_pos + 1] = 0x80 | (byte & 0x3F);
93 out_pos += 2;
94 }
95 }
96
97 out_pos
98}
99
100#[inline]
104pub const fn max_utf8_len(latin1_len: usize) -> usize {
105 latin1_len * 2
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn test_latin1_to_utf8_ascii() {
114 let input = b"hello";
115 let mut out = [0u8; 32];
116 let len = latin1_to_utf8_symlink(input, &mut out);
117 assert_eq!(len, 5);
118 assert_eq!(&out[..len], b"hello");
119 }
120
121 #[test]
122 fn test_latin1_to_utf8_high_bytes() {
123 let input = [0xE9];
126 let mut out = [0u8; 32];
127 let len = latin1_to_utf8_symlink(&input, &mut out);
128 assert_eq!(len, 2);
129 assert_eq!(&out[..len], &[0xC3, 0xA9]);
130 }
131
132 #[test]
133 fn test_colon_replacement() {
134 let input = b":path/to/file";
135 let mut out = [0u8; 32];
136 let len = latin1_to_utf8_symlink(input, &mut out);
137 assert_eq!(len, 13);
138 assert_eq!(&out[..len], b"/path/to/file");
139 }
140
141 #[test]
142 fn test_colon_not_at_start() {
143 let input = b"path:to/file";
144 let mut out = [0u8; 32];
145 let len = latin1_to_utf8_symlink(input, &mut out);
146 assert_eq!(len, 12);
147 assert_eq!(&out[..len], b"path:to/file");
148 }
149
150 #[test]
151 fn test_read_symlink_target() {
152 let mut buf = [0u8; BLOCK_SIZE];
153 buf[SYMLINK_OFFSET..SYMLINK_OFFSET + 5].copy_from_slice(b"test\0");
155
156 let mut out = [0u8; 32];
157 let len = read_symlink_target(&buf, &mut out);
158 assert_eq!(len, 4);
159 assert_eq!(&out[..len], b"test");
160 }
161
162 #[test]
163 fn test_read_symlink_with_colon() {
164 let mut buf = [0u8; BLOCK_SIZE];
165 buf[SYMLINK_OFFSET..SYMLINK_OFFSET + 6].copy_from_slice(b":boot\0");
166
167 let mut out = [0u8; 32];
168 let len = read_symlink_target(&buf, &mut out);
169 assert_eq!(len, 5);
170 assert_eq!(&out[..len], b"/boot");
171 }
172}