1use crate::fs::errors;
5#[cfg(all(racy_asserts, not(windows)))]
6use crate::fs::symlink_unchecked;
7#[cfg(racy_asserts)]
8use crate::fs::{canonicalize, manually, map_result, stat_unchecked, FollowSymlinks, Metadata};
9#[cfg(all(racy_asserts, windows))]
10use crate::fs::{symlink_dir_unchecked, symlink_file_unchecked};
11use std::path::Path;
12use std::{fs, io};
13
14#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))]
18#[cfg(not(windows))]
19#[inline]
20pub fn symlink(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> {
21 if old_path.has_root() {
27 return Err(errors::escape_attempt());
28 }
29
30 write_symlink_impl(old_path, new_start, new_path)
31}
32
33#[cfg(not(windows))]
34fn write_symlink_impl(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> {
35 use crate::fs::symlink_impl;
36
37 #[cfg(racy_asserts)]
38 let stat_before = stat_unchecked(new_start, new_path, FollowSymlinks::No);
39
40 let result = symlink_impl(old_path, new_start, new_path);
42
43 #[cfg(racy_asserts)]
44 let stat_after = stat_unchecked(new_start, new_path, FollowSymlinks::No);
45
46 #[cfg(racy_asserts)]
47 check_symlink(
48 old_path,
49 new_start,
50 new_path,
51 &stat_before,
52 &result,
53 &stat_after,
54 );
55
56 result
57}
58
59#[cfg(not(windows))]
62pub fn symlink_contents<P: AsRef<Path>, Q: AsRef<Path>>(
63 old_path: P,
64 new_start: &fs::File,
65 new_path: Q,
66) -> io::Result<()> {
67 write_symlink_impl(old_path.as_ref(), new_start, new_path.as_ref())
68}
69
70#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))]
73#[cfg(windows)]
74#[inline]
75pub fn symlink_file(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> {
76 use crate::fs::symlink_file_impl;
77
78 if old_path.has_root() {
80 return Err(errors::escape_attempt());
81 }
82
83 #[cfg(racy_asserts)]
84 let stat_before = stat_unchecked(new_start, new_path, FollowSymlinks::No);
85
86 let result = symlink_file_impl(old_path, new_start, new_path);
88
89 #[cfg(racy_asserts)]
90 let stat_after = stat_unchecked(new_start, new_path, FollowSymlinks::No);
91
92 #[cfg(racy_asserts)]
93 check_symlink_file(
94 old_path,
95 new_start,
96 new_path,
97 &stat_before,
98 &result,
99 &stat_after,
100 );
101
102 result
103}
104
105#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))]
108#[cfg(windows)]
109#[inline]
110pub fn symlink_dir(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> {
111 use crate::fs::symlink_dir_impl;
112
113 if old_path.has_root() {
115 return Err(errors::escape_attempt());
116 }
117
118 #[cfg(racy_asserts)]
119 let stat_before = stat_unchecked(new_start, new_path, FollowSymlinks::No);
120
121 let result = symlink_dir_impl(old_path, new_start, new_path);
123
124 #[cfg(racy_asserts)]
125 let stat_after = stat_unchecked(new_start, new_path, FollowSymlinks::No);
126
127 #[cfg(racy_asserts)]
128 check_symlink_dir(
129 old_path,
130 new_start,
131 new_path,
132 &stat_before,
133 &result,
134 &stat_after,
135 );
136
137 result
138}
139
140#[cfg(all(not(windows), racy_asserts))]
141#[allow(clippy::enum_glob_use)]
142fn check_symlink(
143 old_path: &Path,
144 new_start: &fs::File,
145 new_path: &Path,
146 stat_before: &io::Result<Metadata>,
147 result: &io::Result<()>,
148 stat_after: &io::Result<Metadata>,
149) {
150 use io::ErrorKind::*;
151
152 match (
153 map_result(stat_before),
154 map_result(result),
155 map_result(stat_after),
156 ) {
157 (Err((NotFound, _)), Ok(()), Ok(metadata)) => {
158 assert!(metadata.file_type().is_symlink());
159 let canon =
160 manually::canonicalize_with(new_start, new_path, FollowSymlinks::No).unwrap();
161 assert_same_file_metadata!(
162 &stat_unchecked(new_start, &canon, FollowSymlinks::No).unwrap(),
163 &metadata
164 );
165 }
166
167 (Ok(metadata_before), Err((AlreadyExists, _)), Ok(metadata_after)) => {
168 assert_same_file_metadata!(&metadata_before, &metadata_after);
169 }
170
171 (_, Err((_kind, _message)), _) => match map_result(&canonicalize(new_start, new_path)) {
172 Ok(canon) => match map_result(&symlink_unchecked(old_path, new_start, &canon)) {
173 Err((_unchecked_kind, _unchecked_message)) => {
174 }
189 _ => panic!("unsandboxed symlink success"),
190 },
191 Err((_canon_kind, _canon_message)) => {
192 }
197 },
198
199 _other => {
200 }
209 }
210}
211
212#[cfg(all(windows, racy_asserts))]
213#[allow(clippy::enum_glob_use)]
214fn check_symlink_file(
215 old_path: &Path,
216 new_start: &fs::File,
217 new_path: &Path,
218 stat_before: &io::Result<Metadata>,
219 result: &io::Result<()>,
220 stat_after: &io::Result<Metadata>,
221) {
222 use io::ErrorKind::*;
223
224 match (
225 map_result(stat_before),
226 map_result(result),
227 map_result(stat_after),
228 ) {
229 (Err((NotFound, _)), Ok(()), Ok(metadata)) => {
230 assert!(metadata.file_type().is_symlink());
231 let canon =
232 manually::canonicalize_with(new_start, new_path, FollowSymlinks::No).unwrap();
233 assert_same_file_metadata!(
234 &stat_unchecked(new_start, &canon, FollowSymlinks::No).unwrap(),
235 &metadata
236 );
237 }
238
239 (Ok(metadata_before), Err((AlreadyExists, _)), Ok(metadata_after)) => {
240 assert_same_file_metadata!(&metadata_before, &metadata_after);
241 }
242
243 (_, Err((_kind, _message)), _) => match map_result(&canonicalize(new_start, new_path)) {
244 Ok(canon) => match map_result(&symlink_file_unchecked(old_path, new_start, &canon)) {
245 Err((_unchecked_kind, _unchecked_message)) => {
246 }
261 _ => panic!("unsandboxed symlink success"),
262 },
263 Err((_canon_kind, _canon_message)) => {
264 }
269 },
270
271 _other => {
272 }
281 }
282}
283
284#[cfg(all(windows, racy_asserts))]
285#[allow(clippy::enum_glob_use)]
286fn check_symlink_dir(
287 old_path: &Path,
288 new_start: &fs::File,
289 new_path: &Path,
290 stat_before: &io::Result<Metadata>,
291 result: &io::Result<()>,
292 stat_after: &io::Result<Metadata>,
293) {
294 use io::ErrorKind::*;
295
296 match (
297 map_result(stat_before),
298 map_result(result),
299 map_result(stat_after),
300 ) {
301 (Err((NotFound, _)), Ok(()), Ok(metadata)) => {
302 assert!(metadata.file_type().is_symlink());
303 let canon =
304 manually::canonicalize_with(new_start, new_path, FollowSymlinks::No).unwrap();
305 assert_same_file_metadata!(
306 &stat_unchecked(new_start, &canon, FollowSymlinks::No).unwrap(),
307 &metadata
308 );
309 }
310
311 (Ok(metadata_before), Err((AlreadyExists, _)), Ok(metadata_after)) => {
312 assert_same_file_metadata!(&metadata_before, &metadata_after);
313 }
314
315 (_, Err((_kind, _message)), _) => match map_result(&canonicalize(new_start, new_path)) {
316 Ok(canon) => match map_result(&symlink_dir_unchecked(old_path, new_start, &canon)) {
317 Err((_unchecked_kind, _unchecked_message)) => {
318 }
333 _ => panic!("unsandboxed symlink success"),
334 },
335 Err((_canon_kind, _canon_message)) => {
336 }
341 },
342
343 _other => {
344 }
353 }
354}