1use std::borrow::Cow;
11use std::collections::HashSet;
12use std::env;
13use std::ffi::OsStr;
14use std::fs;
15use std::fs::remove_file as fs_remove_file;
16use std::io;
17use std::io::ErrorKind;
18use std::io::Write;
19#[cfg(unix)]
20use std::os::unix::fs::PermissionsExt;
21use std::path::Component;
22use std::path::Path;
23use std::path::PathBuf;
24
25use anyhow::bail;
26use anyhow::Context;
27use fn_error_context::context;
28
29use crate::errors::IOContext;
30
31pub fn atomic_write_symlink(path: &Path, data: &[u8]) -> io::Result<()> {
47 let append_name = |suffix: &str| -> PathBuf {
48 let mut s = path.to_path_buf().into_os_string();
49 s.push(suffix);
50 s.into()
51 };
52 let temp_name = || -> PathBuf { append_name(&format!(".{:x}.atomic", rand::random::<u32>())) };
53
54 let _lock = crate::lock::PathLock::exclusive(append_name(".lock"))?;
56
57 let (real_path, mut file) = loop {
59 let real_path = temp_name();
60 match fs::OpenOptions::new()
61 .create_new(true)
62 .write(true)
63 .open(&real_path)
64 {
65 Ok(file) => break Ok((real_path, file)),
66 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => continue, Err(e) => {
68 break Err(e).path_context("error opening atomic symlink real path", &real_path);
69 }
70 }
71 }?;
72 let real_file_name = real_path
73 .file_name()
74 .expect("real_path should have a file name");
75
76 file.write_all(data)
78 .path_context("error writing atomic symlink data to real path", &real_path)?;
79 drop(file);
80
81 let symlink_tmp_path = loop {
83 let symlink_path = temp_name();
84 match symlink_file(Path::new(real_file_name), &symlink_path) {
85 Ok(()) => break Ok(symlink_path),
86 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => continue, Err(e) => {
88 break Err(e).path_context("error creating temp atomic symlink", &symlink_path);
89 }
90 }
91 }?;
92
93 fs::rename(symlink_tmp_path, path).path_context("error renaming temp atomic symlink", path)?;
95
96 let _ = (|| -> io::Result<()> {
98 let looks_like_atomic = |s: &OsStr, prefix: &OsStr| -> bool {
99 if let (Some(s), Some(prefix)) = (s.to_str(), prefix.to_str()) {
100 s.starts_with(prefix) && s.ends_with(".atomic")
101 } else {
102 false
103 }
104 };
105 if let (Some(dir), Some(prefix)) = (path.parent(), path.file_name()) {
106 for entry in fs::read_dir(dir).path_context("error reading atomic symlink dir", dir)? {
107 let entry = entry.path_context("error reading atomic symlink dir entry", dir)?;
108 let name = entry.file_name();
109 if name != prefix && looks_like_atomic(&name, prefix) && name != real_file_name {
110 let _ = remove_file(&entry.path());
111 }
112 }
113 }
114 Ok(())
115 })();
116
117 Ok(())
118}
119
120pub fn symlink_file(src: &Path, dst: &Path) -> io::Result<()> {
122 #[cfg(windows)]
123 return std::os::windows::fs::symlink_file(src, dst);
124 #[cfg(unix)]
125 return std::os::unix::fs::symlink(src, dst);
126 #[cfg(all(not(unix), not(windows)))]
127 return Err(io::Error::new(
128 ErrorKind::Other,
129 "symlink is not supported by the system",
130 ));
131}
132
133pub fn symlink_dir(src: &Path, dst: &Path) -> io::Result<()> {
135 #[cfg(windows)]
136 return std::os::windows::fs::symlink_dir(src, dst);
137 #[cfg(not(windows))]
138 symlink_file(src, dst)
139}
140
141pub fn strip_unc_prefix(path: &Path) -> &Path {
143 path.strip_prefix(r"\\?\").unwrap_or(path)
144}
145
146pub fn absolute(path: impl AsRef<Path>) -> io::Result<PathBuf> {
165 let path = path.as_ref();
166 let path = if path.is_absolute() {
167 path.to_path_buf()
168 } else {
169 std::env::current_dir()?.join(path)
170 };
171
172 if !path.is_absolute() {
173 return Err(io::Error::new(
174 io::ErrorKind::Other,
175 format!("cannot get absolute path from {:?}", path),
176 ));
177 }
178
179 Ok(normalize(&path))
180}
181
182pub fn normalize(path: &Path) -> PathBuf {
197 let mut result = PathBuf::new();
198 let mut poppable: usize = 0;
199 let mut has_root = false;
200 for component in path.components() {
201 match component {
202 Component::Normal(_) => {
203 poppable += 1;
204 result.push(component);
205 }
206 Component::RootDir => {
207 has_root = true;
208 result.push(component);
209 }
210 Component::Prefix(_) => {
211 result.push(component);
212 }
213 Component::ParentDir => {
214 if poppable > 0 {
215 result.pop();
216 poppable -= 1;
217 } else if !has_root {
218 result.push(component);
219 }
220 }
221 Component::CurDir => {}
222 }
223 }
224
225 if result.as_os_str().is_empty() {
226 return ".".into();
227 }
228
229 result
230}
231
232pub fn root_relative_path(root: &Path, cwd: &Path, path: &Path) -> io::Result<Option<PathBuf>> {
235 let path = normalize(&root.join(cwd).join(path));
238
239 if let Ok(suffix) = path.strip_prefix(root) {
241 return Ok(Some(suffix.to_path_buf()));
242 }
243
244 let root = root.canonicalize().path_context("canonicalizing", root)?;
246
247 let mut test = PathBuf::new();
249 let mut path_parts = path.components();
250 while let Some(part) = path_parts.next() {
251 test.push(part);
252 if test.is_symlink() {
253 test = test.canonicalize().path_context("canonicalizing", &test)?;
255 }
256 if let Ok(suffix) = test.strip_prefix(&root) {
257 return Ok(Some(suffix.join(path_parts)));
258 }
259 }
260
261 Ok(None)
262}
263
264pub fn remove_file<P: AsRef<Path>>(path: P) -> io::Result<()> {
266 let path = path.as_ref();
267 let path: Cow<Path> = if cfg!(windows) {
271 let tmp_path = path.with_extension(format!("tmp.{:x}", rand::random::<u16>()));
272 fs::rename(path, &tmp_path)?;
273 Cow::Owned(tmp_path)
274 } else {
275 Cow::Borrowed(path)
276 };
277 #[allow(clippy::needless_borrows_for_generic_args)]
279 let result = fs_remove_file(&path);
280 #[cfg(windows)]
281 match &result {
282 Err(e) if e.kind() == io::ErrorKind::PermissionDenied => {
283 if let Ok(r) = remove_directory_symlink(&path) {
285 if r {
286 return Ok(());
287 }
288 }
289 if windows_remove_mmap_file(&path).is_ok() {
291 return Ok(());
292 }
293 }
294 _ => {}
295 }
296 result.map_err(Into::into)
297}
298
299#[cfg(windows)]
300fn remove_directory_symlink(path: &Path) -> io::Result<bool> {
302 let metadata = path.symlink_metadata()?;
303 if metadata.is_symlink() {
304 std::fs::remove_dir(path)?;
305 return Ok(true);
306 }
307 Ok(false)
308}
309
310#[cfg(windows)]
313fn windows_remove_mmap_file(path: &Path) -> io::Result<()> {
314 use std::os::windows::ffi::OsStrExt;
315
316 use winapi::shared::minwindef::FALSE;
317 use winapi::um::fileapi::CreateFileW;
318 use winapi::um::fileapi::OPEN_EXISTING;
319 use winapi::um::handleapi::CloseHandle;
320 use winapi::um::handleapi::INVALID_HANDLE_VALUE;
321 use winapi::um::winbase::FILE_FLAG_DELETE_ON_CLOSE;
322 use winapi::um::winnt::DELETE;
323 use winapi::um::winnt::FILE_SHARE_DELETE;
324 use winapi::um::winnt::FILE_SHARE_READ;
325 use winapi::um::winnt::FILE_SHARE_WRITE;
326
327 let wpath: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
328 let handle = unsafe {
329 CreateFileW(
330 wpath.as_ptr(),
331 DELETE,
332 FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
333 std::ptr::null_mut(),
334 OPEN_EXISTING,
335 FILE_FLAG_DELETE_ON_CLOSE,
336 std::ptr::null_mut(),
337 )
338 };
339 if handle == INVALID_HANDLE_VALUE {
340 let err = io::Error::last_os_error();
341 if err.kind() == io::ErrorKind::NotFound {
342 Ok(())
343 } else {
344 Err(err)
345 }
346 } else if unsafe { CloseHandle(handle) } == FALSE {
347 Err(io::Error::last_os_error())
348 } else {
349 Ok(())
350 }
351}
352
353#[cfg(unix)]
354fn add_stat_context<T, E: Into<anyhow::Error>>(
355 res: anyhow::Result<T, E>,
356 path: Option<&Path>,
357) -> anyhow::Result<T, anyhow::Error> {
358 use std::os::unix::fs::MetadataExt;
359
360 let res = res.map_err(Into::into);
361
362 if let Some(path) = path {
363 if let Ok(md) = path.metadata() {
364 return res.context(format!(
365 "stat({:?}) = dev:{} ino:{} mode:0o{:o} uid:{} gid:{} mtime:{}",
366 path,
367 md.dev(),
368 md.ino(),
369 md.mode(),
370 md.uid(),
371 md.gid(),
372 md.mtime()
373 ));
374 }
375 }
376
377 res
378}
379
380#[cfg(unix)]
383fn resolve_symlinks(path: &Path) -> anyhow::Result<PathBuf> {
384 fn inner(path: PathBuf, seen: &mut HashSet<PathBuf>) -> anyhow::Result<PathBuf> {
385 if seen.contains(&path) {
386 bail!("symlink cycle containing {:?}", path);
387 }
388
389 seen.insert(path.clone());
390
391 match path.read_link() {
392 Ok(target) => inner(target, seen),
393
394 Err(err) if err.kind() == io::ErrorKind::InvalidInput => Ok(path),
396 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(path),
397
398 Err(err) => add_stat_context(
400 Err(err).context(format!("statting {:?}", path)),
401 path.parent(),
402 ),
403 }
404 }
405
406 let mut seen = HashSet::new();
407 let mut res = inner(path.to_path_buf(), &mut seen);
408 if seen.len() > 1 {
409 res = res.with_context(|| format!("traversing symlinks from {:?}", path));
410 }
411 res
412}
413
414#[cfg(unix)]
420#[context("creating dir {:?} with mode 0o{:o}", path, mode)]
421fn create_dir_with_mode(path: &Path, mode: u32) -> anyhow::Result<()> {
422 use anyhow::anyhow;
423
424 let path = resolve_symlinks(path)?;
425
426 match path.metadata() {
427 Ok(md) if md.is_file() => {
428 return Err(anyhow!(io::Error::from(ErrorKind::AlreadyExists)))
429 .context(format!("path exists as a file: {:?}", path));
430 }
431 Ok(md) => {
432 if md.permissions().mode() & mode != mode {
434 let _ = fs::set_permissions(path, fs::Permissions::from_mode(mode));
436 }
437 return Ok(());
438 }
439 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
441 Err(err) => {
442 return add_stat_context(
443 Err(err).context(format!("error statting {:?}", path)),
444 path.parent(),
445 );
446 }
447 };
448
449 let parent = path.parent().ok_or_else(|| {
450 io::Error::new(
451 ErrorKind::NotFound,
452 format!("`{:?}` does not have a parent directory", path),
453 )
454 })?;
455
456 let parent = resolve_symlinks(parent)?;
457
458 let temp = add_stat_context(tempfile::TempDir::new_in(&parent), Some(&parent))
459 .with_context(|| format!("creating temp dir in {:?}", parent))?;
460
461 fs::set_permissions(&temp, fs::Permissions::from_mode(mode))
462 .with_context(|| format!("setting permissions on temp dir {:?}", temp))?;
463
464 let temp = temp.into_path();
465 if let Err(e) = fs::rename(&temp, &path) {
466 let _ = fs::remove_dir(&temp);
469
470 match e.raw_os_error() {
477 Some(libc::ENOTEMPTY) | Some(libc::ENOTDIR) => {
478 Err(io::Error::from(ErrorKind::AlreadyExists).into())
479 }
480 _ => Err::<(), anyhow::Error>(e.into())
481 .context(format!("renaming temp dir {:?} to {:?}", temp, &path)),
482 }
483 } else {
484 Ok(())
485 }
486}
487
488#[cfg(not(unix))]
491#[context("creating dir {:?}", path)]
492fn create_dir_with_mode(path: &Path, _mode: u32) -> anyhow::Result<()> {
493 match fs::create_dir(path) {
494 Ok(()) => Ok(()),
495 Err(err) if err.kind() == io::ErrorKind::AlreadyExists && path.is_dir() => Ok(()),
496 Err(err) => Err(err.into()),
497 }
498}
499
500fn is_io_error_kind(err: &anyhow::Error, kind: ErrorKind) -> bool {
501 err.downcast_ref::<io::Error>()
502 .is_some_and(|err| err.kind() == kind)
503}
504
505pub fn create_dir(path: impl AsRef<Path>) -> anyhow::Result<()> {
507 create_dir_with_mode(path.as_ref(), 0o755)
508}
509
510pub fn create_shared_dir(path: impl AsRef<Path>) -> anyhow::Result<()> {
512 create_dir_with_mode(path.as_ref(), 0o2775)
513}
514
515pub fn create_dir_all_with_mode(path: impl AsRef<Path>, mode: u32) -> anyhow::Result<()> {
517 let mut to_create = vec![path.as_ref()];
518 while let Some(dir) = to_create.pop() {
519 match create_dir_with_mode(dir, mode) {
520 Ok(()) => continue,
521 Err(err) if is_io_error_kind(&err, io::ErrorKind::NotFound) => {
522 to_create.push(dir);
523 match dir.parent() {
524 Some(parent) => to_create.push(parent),
525 None => return Err(err),
526 }
527 }
528 Err(err) => return Err(err),
529 }
530 }
531 Ok(())
532}
533
534pub fn create_shared_dir_all(path: impl AsRef<Path>) -> anyhow::Result<()> {
536 create_dir_all_with_mode(path, 0o2775)
537}
538
539pub fn expand_path(path: impl AsRef<str>) -> PathBuf {
555 expand_path_impl(path.as_ref(), |k| env::var(k).ok(), dirs::home_dir)
556}
557
558fn expand_path_impl<E, H>(path: &str, getenv: E, homedir: H) -> PathBuf
561where
562 E: FnMut(&str) -> Option<String>,
563 H: FnOnce() -> Option<PathBuf>,
564{
565 let path = {
578 let mut new_path = String::new();
579 let mut is_starting = true;
580 for ch in path.chars() {
581 if ch == '%' {
582 if is_starting {
583 new_path.push_str("${");
584 } else {
585 new_path.push('}');
586 }
587 is_starting = !is_starting;
588 } else if cfg!(windows) && ch == '/' {
589 new_path.push('\\')
592 } else {
593 new_path.push(ch);
594 }
595 }
596 new_path
597 };
598
599 let path = shellexpand::env_with_context_no_errors(&path, getenv);
600 shellexpand::tilde_with_context(&path, homedir)
601 .as_ref()
602 .into()
603}
604
605pub fn relativize(base: &Path, path: &Path) -> PathBuf {
607 let mut base_iter = base.iter();
608 let mut path_iter = path.iter();
609 let mut rel_path = PathBuf::new();
610 loop {
611 match (base_iter.next(), path_iter.next()) {
612 (Some(ref c), Some(ref p)) if c == p => continue,
613
614 (Some(_c), remaining_path) => {
626 rel_path.push(".."); for _ in base_iter {
631 rel_path.push("..");
632 }
633
634 if let Some(p) = remaining_path {
635 rel_path.push(p);
636 for component in path_iter {
637 rel_path.push(component);
638 }
639 }
640 break;
641 }
642
643 (None, Some(p)) => {
647 rel_path.push(p);
648 for component in path_iter {
649 rel_path.push(component);
650 }
651 break;
652 }
653
654 (None, None) => {
660 break;
661 }
662 }
663 }
664
665 rel_path
666}
667
668#[cfg(windows)]
670pub fn replace_slash_with_backslash(path: &Path) -> PathBuf {
671 use std::os::windows::ffi::OsStrExt;
672 use std::os::windows::ffi::OsStringExt;
673
674 use widestring::U16Str;
675
676 let mut utf16_string: Vec<u16> = path.as_os_str().encode_wide().collect();
678
679 let to_replace = U16Str::from_slice(&mut utf16_string)
680 .char_indices()
681 .filter_map(|(i, c)| {
682 c.ok()
683 .map(|c| if c == '/' { Some(i) } else { None })
684 .flatten()
685 })
686 .collect::<Vec<_>>();
687
688 for i in to_replace {
689 utf16_string[i] = '\\' as u16;
690 }
691
692 PathBuf::from(std::ffi::OsString::from_wide(&utf16_string))
693}
694
695#[cfg(test)]
696mod tests {
697 use std::fs::create_dir_all;
698 use std::fs::File;
699
700 use anyhow::Result;
701 use tempfile::TempDir;
702
703 use super::*;
704
705 #[cfg(windows)]
706 mod windows {
707 use std::os::windows::ffi::OsStrExt;
708 use std::os::windows::ffi::OsStringExt;
709
710 use super::*;
711
712 #[test]
713 fn test_absolute_fullpath() {
714 assert_eq!(absolute("C:/foo").unwrap(), Path::new("C:\\foo"));
715 assert_eq!(
716 absolute("x:\\a/b\\./.\\c").unwrap(),
717 Path::new("x:\\a\\b\\c")
718 );
719 assert_eq!(
720 absolute("y:/a/b\\../..\\c\\../d\\./.").unwrap(),
721 Path::new("y:\\d")
722 );
723 assert_eq!(
724 absolute("z:/a/b\\../..\\../..\\..").unwrap(),
725 Path::new("z:\\")
726 );
727 }
728
729 #[test]
730 fn test_normalize_path() {
731 assert_eq!(normalize(r"a/b\c\..\.".as_ref()), Path::new(r"a\b"));
732 assert_eq!(normalize("z:/a//b/./".as_ref()), Path::new(r"z:\a\b"));
733 }
734
735 #[test]
736 fn test_replace_slash_with_backslash() {
737 let utf16_bytes: &[u16] = &[0xd83c, 0xdf31, 0x002f, 0x0073, 0x0061, 0x0070];
739 let path = PathBuf::from(std::ffi::OsString::from_wide(utf16_bytes));
740 let expected = "🌱\\sap".encode_utf16().collect::<Vec<_>>();
741 assert_eq!(
742 replace_slash_with_backslash(&path)
743 .as_os_str()
744 .encode_wide()
745 .collect::<Vec<_>>(),
746 expected,
747 );
748
749 let utf16_bytes: &[u16] = &[0xd83c, 0x002f, 0x002f, 0x0073, 0x0061, 0x0070];
752 let expected: Vec<u16> = Vec::from([0xd83c, 0x005c, 0x005c, 0x0073, 0x0061, 0x0070]);
754 let path = PathBuf::from(std::ffi::OsString::from_wide(utf16_bytes));
755 assert_eq!(
756 replace_slash_with_backslash(&path)
757 .as_os_str()
758 .encode_wide()
759 .collect::<Vec<_>>(),
760 expected,
761 );
762
763 let utf16_bytes: &[u16] = &[0x002f, 0xdf31, 0x002f, 0x0073, 0x0061, 0x0070];
765 let expected: Vec<u16> = Vec::from([0x005c, 0xdf31, 0x005c, 0x0073, 0x0061, 0x0070]);
766 let path = PathBuf::from(std::ffi::OsString::from_wide(utf16_bytes));
767 assert_eq!(
768 replace_slash_with_backslash(&path)
769 .as_os_str()
770 .encode_wide()
771 .collect::<Vec<_>>(),
772 expected,
773 );
774 }
775 }
776
777 #[cfg(unix)]
778 mod unix {
779 use super::*;
780
781 #[test]
782 fn test_absolute_fullpath() {
783 assert_eq!(absolute("/a/./b\\c/../d/.").unwrap(), Path::new("/a/d"));
784 assert_eq!(absolute("/a/../../../../b").unwrap(), Path::new("/b"));
785 assert_eq!(absolute("/../../..").unwrap(), Path::new("/"));
786 assert_eq!(absolute("/../../../").unwrap(), Path::new("/"));
787 assert_eq!(
788 absolute("//foo///bar//baz").unwrap(),
789 Path::new("/foo/bar/baz")
790 );
791 assert_eq!(absolute("//").unwrap(), Path::new("/"));
792 }
793
794 #[test]
795 fn test_normalize_path() {
796 assert_eq!(normalize("/a/./b/../d/.".as_ref()), Path::new("/a/d"));
797 assert_eq!(normalize("./a/b/c/../../".as_ref()), Path::new("a"));
798 assert_eq!(normalize("".as_ref()), Path::new("."));
799 assert_eq!(normalize(".".as_ref()), Path::new("."));
800 assert_eq!(normalize("..".as_ref()), Path::new(".."));
801 assert_eq!(normalize("/..".as_ref()), Path::new("/"));
802 assert_eq!(normalize("/../..".as_ref()), Path::new("/"));
803 assert_eq!(normalize("./..".as_ref()), Path::new(".."));
804 assert_eq!(normalize("../../..".as_ref()), Path::new("../../.."));
805 assert_eq!(normalize("////".as_ref()), Path::new("/"));
806 }
807
808 #[test]
809 fn test_create_dir_mode() -> Result<()> {
810 let tempdir = TempDir::new()?;
811 let mut path = tempdir.path().to_path_buf();
812 path.push("dir");
813 create_dir(&path)?;
814 assert!(path.is_dir());
815 let metadata = path.metadata()?;
816 assert_eq!(metadata.permissions().mode(), 0o40755);
817 assert_eq!(tempdir.path().read_dir()?.count(), 1);
819 Ok(())
820 }
821
822 #[test]
823 fn test_create_shared_dir() -> Result<()> {
824 let tempdir = TempDir::new()?;
825 let mut path = tempdir.path().to_path_buf();
826 path.push("shared");
827 create_shared_dir(&path)?;
828 assert!(path.is_dir());
829 let metadata = path.metadata()?;
830 assert_eq!(metadata.permissions().mode(), 0o42775);
831 assert_eq!(tempdir.path().read_dir()?.count(), 1);
833 Ok(())
834 }
835
836 #[test]
837 fn test_fixup_perms() -> Result<()> {
838 let tempdir = TempDir::new()?;
839 let mut path = tempdir.path().to_path_buf();
840 path.push("shared");
841
842 create_dir_with_mode(&path, 0o775)?;
844 let metadata = path.metadata()?;
845 assert_eq!(metadata.permissions().mode(), 0o40775);
846
847 create_shared_dir(&path)?;
849 let metadata = path.metadata()?;
850 assert_eq!(metadata.permissions().mode(), 0o42775);
851
852 Ok(())
853 }
854
855 #[test]
856 fn test_create_dir_no_perms() -> Result<()> {
857 let tempdir = TempDir::new()?;
858 let mut path = tempdir.path().to_path_buf();
859 path.push("nope");
860
861 std::fs::create_dir(&path)?;
862 std::fs::set_permissions(&path, fs::Permissions::from_mode(0o0))?;
863
864 let err = create_dir_with_mode(&path.join("dir"), 0o775).unwrap_err();
865 assert!(is_io_error_kind(&err, io::ErrorKind::PermissionDenied));
866
867 assert!(format!("{:?}", err).contains(&format!("stat({:?}) = ", path)));
869
870 Ok(())
871 }
872 }
873
874 fn test_create_dir_all_fn(
875 create_fn: &dyn Fn(&PathBuf) -> anyhow::Result<()>,
876 mode: u32,
877 ) -> Result<()> {
878 let tempdir = TempDir::new()?;
879 let path = tempdir.path().join("foo").join("bar");
880 create_fn(&path)?;
881 assert!(path.is_dir());
882
883 #[cfg(unix)]
884 {
885 let metadata = path.metadata()?;
886 assert_eq!(metadata.permissions().mode(), mode);
887 }
888
889 #[cfg(windows)]
890 let _ = mode;
891
892 create_fn(&path)?;
894
895 #[cfg(unix)]
896 {
897 let broken_symlink = tempdir.path().join("foo").join("bar").join("oops");
898 symlink_file(&tempdir.path().join("doesnt_exist"), &broken_symlink)?;
899
900 assert!(create_fn(&broken_symlink.join("nope")).is_ok());
902 assert!(tempdir.path().join("doesnt_exist").join("nope").is_dir());
903 }
904
905 let regular_file = tempdir.path().join("regular_file");
907 File::create(®ular_file)?;
908 assert!(create_fn(®ular_file).is_err());
909 assert!(create_fn(®ular_file.join("no_can_do")).is_err());
910
911 Ok(())
912 }
913
914 #[test]
915 #[cfg_attr(windows, ignore)] fn test_atomic_write_symlink() -> Result<()> {
917 let dir = tempfile::tempdir().unwrap();
918 let path = dir.path();
919 let j = |name| -> PathBuf { path.join(name) };
920 fs::write(j("a"), b"1")?;
922 atomic_write_symlink(&j("c"), b"2")?;
924 let file = fs::OpenOptions::new().read(true).open(j("c"))?;
926 let mmap = unsafe { memmap2::Mmap::map(&file) }?;
927 fs::write(j("c.aaaa.atomic"), b"0")?;
929 atomic_write_symlink(&j("c"), b"3")?;
931 assert_eq!(mmap.as_ref(), b"2");
933 assert_eq!(fs::read(j("c"))?, b"3");
935 assert!(j("a").exists());
937 let count = || {
939 fs::read_dir(path)
940 .unwrap()
941 .filter(|e| e.as_ref().unwrap().path().exists())
942 .count()
943 };
944 assert_eq!(count(), 4);
945
946 atomic_write_symlink(&j("a"), b"4")?;
949 atomic_write_symlink(&j("a"), b"5")?;
951 atomic_write_symlink(&j("a"), b"6")?;
952 assert_eq!(count(), 6);
954 Ok(())
955 }
956
957 #[test]
958 fn test_mmap_delete() {
959 let dir = tempfile::tempdir().unwrap();
960 let path = dir.path().join("foo");
961 fs::write(&path, b"bar").unwrap();
962 let file = fs::OpenOptions::new().read(true).open(&path).unwrap();
963 let _mmap = unsafe { memmap2::Mmap::map(&file) }.unwrap();
964 remove_file(&path).unwrap();
965 assert!(!path.exists());
966 fs::write(&path, b"baz").unwrap();
968 }
969
970 #[test]
971 fn test_create_dir_non_exist() -> Result<()> {
972 let tempdir = TempDir::new()?;
973 let mut path = tempdir.path().to_path_buf();
974 path.push("dir");
975 create_dir(&path)?;
976 assert!(path.is_dir());
977 Ok(())
978 }
979
980 #[test]
981 fn test_create_dir_exist() -> Result<()> {
982 let tempdir = TempDir::new()?;
983 let mut path = tempdir.path().to_path_buf();
984 path.push("dir");
985 create_dir(&path)?;
986 assert!(&path.is_dir());
987 create_dir(&path)?;
988 assert!(&path.is_dir());
989 Ok(())
990 }
991
992 #[test]
993 fn test_create_dir_file_exist() -> Result<()> {
994 let tempdir = TempDir::new()?;
995 let mut path = tempdir.path().to_path_buf();
996 path.push("dir");
997 File::create(&path)?;
998
999 let err = create_dir(&path).unwrap_err();
1000 assert!(is_io_error_kind(&err, io::ErrorKind::AlreadyExists));
1001
1002 Ok(())
1003 }
1004
1005 #[test]
1006 fn test_create_dir_with_nonexistent_parent() -> Result<()> {
1007 let tempdir = TempDir::new()?;
1008 let mut path = tempdir.path().to_path_buf();
1009 path.push("nonexistentparent");
1010 path.push("dir");
1011 let err = create_dir(&path).unwrap_err();
1012 assert!(is_io_error_kind(&err, ErrorKind::NotFound));
1013 Ok(())
1014 }
1015
1016 #[test]
1017 fn test_create_dir_without_empty_path() {
1018 let empty = Path::new("");
1019 let err = create_dir(empty).unwrap_err();
1020 assert!(is_io_error_kind(&err, ErrorKind::NotFound));
1021 }
1022
1023 #[test]
1024 fn test_path_expansion() {
1025 fn getenv(key: &str) -> Option<String> {
1026 match key {
1027 "foo" => Some("~/a".into()),
1028 "bar" => Some("b".into()),
1029 _ => None,
1030 }
1031 }
1032
1033 fn homedir() -> Option<PathBuf> {
1034 Some(PathBuf::from("/home/user"))
1035 }
1036
1037 let path = "$foo/${bar}/$baz";
1038 let expected = PathBuf::from("/home/user/a/b/$baz");
1039
1040 assert_eq!(expand_path_impl(path, getenv, homedir), expected);
1041 }
1042
1043 #[test]
1044 fn test_create_shared_dir_all() -> Result<()> {
1045 test_create_dir_all_fn(&|path| create_shared_dir_all(path), 0o42775)
1046 }
1047
1048 #[test]
1049 fn test_create_dir_all_with_mode() -> Result<()> {
1050 test_create_dir_all_fn(&|path| create_dir_all_with_mode(path, 0o777), 0o40777)
1051 }
1052
1053 #[test]
1054 fn test_relativize_absolute_paths() {
1055 let check = |base, path, expected| {
1056 assert_eq!(
1057 relativize(Path::new(base), Path::new(path)),
1058 Path::new(expected)
1059 );
1060 };
1061 check("/", "/", "");
1062 check("/foo/bar/baz", "/foo/bar/baz", "");
1063 check("/foo/bar", "/foo/bar/baz", "baz");
1064 check("/foo", "/foo/bar/baz", "bar/baz");
1065 check("/foo/bar/baz", "/foo/bar", "..");
1066 check("/foo/bar/baz", "/foo", "../..");
1067 check("/foo/bar/baz", "/foo/BAR", "../../BAR");
1068 check("/foo/bar/baz", "/foo/BAR/BAZ", "../../BAR/BAZ");
1069 }
1070
1071 #[test]
1072 fn test_relativize_platform_absolute_paths() {
1073 let cwd = Path::new(".").canonicalize().unwrap();
1075 let result = relativize(&cwd, &cwd.join("a").join("b"));
1076 assert_eq!(result, Path::new("a").join("b"));
1077 }
1078
1079 #[test]
1080 fn test_root_relative_path() -> Result<()> {
1081 let tempdir = TempDir::new()?;
1082
1083 let parent = tempdir.path().join("parent");
1084 let root = parent.join("root");
1085 let child = root.join("child");
1086 create_dir_all(&child)?;
1087
1088 assert_eq!(
1089 root_relative_path(&root, &root, ".".as_ref())?,
1090 Some(PathBuf::from(""))
1091 );
1092 assert_eq!(
1093 root_relative_path(&root, &child, ".".as_ref())?,
1094 Some(PathBuf::from("child"))
1095 );
1096 assert_eq!(
1097 root_relative_path(&root, &child, "foo".as_ref())?,
1098 Some(["child", "foo"].iter().collect::<PathBuf>()),
1099 );
1100
1101 let symlink_to_root = parent.join("symlink_to_root");
1102 symlink_dir(&root, &symlink_to_root)?;
1103 assert_eq!(
1104 root_relative_path(&root, &root, &symlink_to_root.join("child"))?,
1105 Some(PathBuf::from("child")),
1106 );
1107
1108 let symlink_to_child = parent.join("symlink_to_child");
1109 symlink_dir(&child, &symlink_to_child)?;
1110 assert_eq!(
1111 root_relative_path(&root, &root, &symlink_to_child.join("foo"))?,
1112 Some(["child", "foo"].iter().collect::<PathBuf>()),
1113 );
1114
1115 assert!(root_relative_path(&root, &parent, "foo".as_ref())?.is_none());
1116
1117 assert!(root_relative_path(&root, &root, &parent.join("rootbeer"))?.is_none());
1119
1120 Ok(())
1121 }
1122}