1#![forbid(unsafe_code)]
65#![warn(missing_docs)]
66
67use std::io;
68use std::path::{Path, PathBuf};
69
70#[cfg(target_os = "linux")]
71use std::path::Component;
72
73#[cfg(target_os = "linux")]
75const MAX_SYMLINK_FOLLOWS: u32 = 40;
76
77pub fn canonicalize(path: impl AsRef<Path>) -> io::Result<PathBuf> {
122 canonicalize_impl(path.as_ref())
123}
124
125#[cfg(target_os = "linux")]
126fn canonicalize_impl(path: &Path) -> io::Result<PathBuf> {
127 if let Some((namespace_prefix, remainder)) = find_namespace_boundary(path) {
129 std::fs::metadata(&namespace_prefix)?;
133
134 if remainder.as_os_str().is_empty() {
135 Ok(namespace_prefix)
137 } else {
138 let resolved_prefix = std::fs::canonicalize(&namespace_prefix)?;
144
145 let full_path = namespace_prefix.join(&remainder);
148 let canonicalized = std::fs::canonicalize(full_path)?;
149
150 if let Ok(suffix) = canonicalized.strip_prefix(&resolved_prefix) {
153 Ok(namespace_prefix.join(suffix))
155 } else {
156 Ok(canonicalized)
160 }
161 }
162 } else {
163 if let Some(magic_path) = detect_indirect_proc_magic_link(path)? {
173 return canonicalize_impl(&magic_path);
176 }
177
178 std::fs::canonicalize(path)
180 }
181}
182
183#[cfg(not(target_os = "linux"))]
184fn canonicalize_impl(path: &Path) -> io::Result<PathBuf> {
185 #[cfg(all(feature = "dunce", windows))]
187 {
188 dunce::canonicalize(path)
189 }
190 #[cfg(not(all(feature = "dunce", windows)))]
191 {
192 std::fs::canonicalize(path)
193 }
194}
195
196#[cfg(target_os = "linux")]
204fn find_namespace_boundary(path: &Path) -> Option<(PathBuf, PathBuf)> {
205 let mut components = path.components();
206
207 if components.next() != Some(Component::RootDir) {
209 return None;
210 }
211
212 match components.next() {
214 Some(Component::Normal(s)) if s == "proc" => {}
215 _ => return None,
216 }
217
218 let pid_component = match components.next() {
220 Some(Component::Normal(s)) => s,
221 _ => return None,
222 };
223
224 let pid_str = pid_component.to_string_lossy();
225 let is_valid_pid = pid_str == "self"
226 || pid_str == "thread-self"
227 || (!pid_str.is_empty() && pid_str.chars().all(|c| c.is_ascii_digit()));
228
229 if !is_valid_pid {
230 return None;
231 }
232
233 let next_component = match components.next() {
235 Some(Component::Normal(s)) => s,
236 _ => return None,
237 };
238
239 if next_component == "root" || next_component == "cwd" {
240 let mut prefix = PathBuf::from("/proc");
242 prefix.push(pid_component);
243 prefix.push(next_component);
244
245 let remainder: PathBuf = components.collect();
247 Some((prefix, remainder))
248 } else if next_component == "task" {
249 let tid_component = match components.next() {
253 Some(Component::Normal(s)) => s,
254 _ => return None,
255 };
256
257 let tid_str = tid_component.to_string_lossy();
258 if tid_str.is_empty() || !tid_str.chars().all(|c| c.is_ascii_digit()) {
259 return None;
260 }
261
262 let ns_type = match components.next() {
264 Some(Component::Normal(s)) if s == "root" || s == "cwd" => s,
265 _ => return None,
266 };
267
268 let mut prefix = PathBuf::from("/proc");
269 prefix.push(pid_component);
270 prefix.push("task");
271 prefix.push(tid_component);
272 prefix.push(ns_type);
273
274 let remainder: PathBuf = components.collect();
276 Some((prefix, remainder))
277 } else {
278 None
279 }
280}
281
282#[cfg(target_os = "linux")]
291fn is_proc_magic_path(path: &Path) -> bool {
292 find_namespace_boundary(path).is_some()
293}
294
295#[cfg(target_os = "linux")]
302fn detect_indirect_proc_magic_link(path: &Path) -> io::Result<Option<PathBuf>> {
303 let mut current_path = if path.is_absolute() {
304 path.to_path_buf()
305 } else {
306 std::env::current_dir()?.join(path)
307 };
308
309 let mut iterations = 0;
310
311 'scan: loop {
313 if iterations >= MAX_SYMLINK_FOLLOWS {
314 return Ok(None);
315 }
316
317 if is_proc_magic_path(¤t_path) {
327 return Ok(Some(current_path));
328 }
329
330 let mut accumulated = PathBuf::new();
331 let mut components = current_path.components().peekable();
332
333 if let Some(Component::RootDir) = components.peek() {
334 accumulated.push("/");
335 components.next();
336 }
337
338 while let Some(component) = components.next() {
339 match component {
340 Component::RootDir => {
341 accumulated.push("/");
342 }
343 Component::CurDir => {}
344 Component::ParentDir => {
345 accumulated.pop();
346 if is_proc_magic_path(&accumulated) {
348 let remainder: PathBuf = components.collect();
350 return Ok(Some(accumulated.join(remainder)));
351 }
352 }
353 Component::Normal(name) => {
354 let next_path = accumulated.join(name);
355
356 let metadata = match std::fs::symlink_metadata(&next_path) {
358 Ok(m) => m,
359 Err(_) => {
360 accumulated.push(name);
361 continue;
362 }
363 };
364
365 if metadata.is_symlink() {
366 iterations += 1;
368 let target = std::fs::read_link(&next_path)?;
369
370 let parent = next_path.parent().unwrap_or(Path::new("/"));
372 let remainder: PathBuf = components.collect();
373
374 let resolved = if target.is_relative() {
375 parent.join(target)
376 } else {
377 target
378 };
379
380 current_path = resolved.join(remainder);
381 continue 'scan; }
383
384 accumulated.push(name);
385 }
386 Component::Prefix(_) => unreachable!("Linux paths don't have prefixes"),
387 }
388 }
389
390 if is_proc_magic_path(&accumulated) {
394 return Ok(Some(accumulated));
395 }
396
397 return Ok(None);
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404
405 #[cfg(target_os = "linux")]
406 mod linux {
407 use super::*;
408
409 #[test]
416 fn test_find_namespace_boundary_proc_pid_root() {
417 let (prefix, remainder) =
420 find_namespace_boundary(Path::new("/proc/1234/root/etc/passwd")).unwrap();
421 assert_eq!(prefix, PathBuf::from("/proc/1234/root"));
422 assert_eq!(remainder, PathBuf::from("etc/passwd"));
423 }
424
425 #[test]
426 fn test_find_namespace_boundary_proc_pid_cwd() {
427 let (prefix, remainder) =
430 find_namespace_boundary(Path::new("/proc/5678/cwd/some/file.txt")).unwrap();
431 assert_eq!(prefix, PathBuf::from("/proc/5678/cwd"));
432 assert_eq!(remainder, PathBuf::from("some/file.txt"));
433 }
434
435 #[test]
436 fn test_find_namespace_boundary_proc_self_root() {
437 let (prefix, remainder) =
440 find_namespace_boundary(Path::new("/proc/self/root/etc/passwd")).unwrap();
441 assert_eq!(prefix, PathBuf::from("/proc/self/root"));
442 assert_eq!(remainder, PathBuf::from("etc/passwd"));
443 }
444
445 #[test]
446 fn test_find_namespace_boundary_proc_thread_self_root() {
447 let (prefix, remainder) =
449 find_namespace_boundary(Path::new("/proc/thread-self/root/app/config")).unwrap();
450 assert_eq!(prefix, PathBuf::from("/proc/thread-self/root"));
451 assert_eq!(remainder, PathBuf::from("app/config"));
452 }
453
454 #[test]
455 fn test_find_namespace_boundary_just_prefix_no_remainder() {
456 let (prefix, remainder) =
458 find_namespace_boundary(Path::new("/proc/1234/root")).unwrap();
459 assert_eq!(prefix, PathBuf::from("/proc/1234/root"));
460 assert_eq!(remainder, PathBuf::from(""));
461 }
462
463 #[test]
464 fn test_find_namespace_boundary_normal_path_returns_none() {
465 assert!(find_namespace_boundary(Path::new("/home/user/file.txt")).is_none());
467 }
468
469 #[test]
470 fn test_find_namespace_boundary_proc_other_files_not_namespace() {
471 assert!(find_namespace_boundary(Path::new("/proc/1234/status")).is_none());
474 assert!(find_namespace_boundary(Path::new("/proc/1234/exe")).is_none());
475 assert!(find_namespace_boundary(Path::new("/proc/1234/fd/0")).is_none());
476 }
477
478 #[test]
479 fn test_find_namespace_boundary_relative_path_rejected() {
480 assert!(find_namespace_boundary(Path::new("proc/1234/root")).is_none());
483 }
484
485 #[test]
486 fn test_find_namespace_boundary_invalid_pid_rejected() {
487 assert!(find_namespace_boundary(Path::new("/proc/abc/root")).is_none());
490 assert!(find_namespace_boundary(Path::new("/proc/123abc/root")).is_none());
491 assert!(find_namespace_boundary(Path::new("/proc//root")).is_none());
492 }
493
494 #[test]
499 fn reading_container_file_from_host() {
500 let container_pid = std::process::id(); let container_root = format!("/proc/{}/root", container_pid);
503 let file_inside_container = format!("{}/etc", container_root);
504
505 let canonical_path = canonicalize(file_inside_container).unwrap();
506
507 assert!(canonical_path.starts_with(&container_root));
509 }
510
511 #[test]
512 fn validating_path_stays_in_container() {
513 let container_pid = std::process::id();
515 let container_root = format!("/proc/{}/root", container_pid);
516 let user_requested_file = format!("{}/etc/passwd", container_root);
517
518 let canonical = canonicalize(user_requested_file).unwrap();
519
520 let is_inside_container = canonical.starts_with(&container_root);
522 assert!(is_inside_container);
523 }
524
525 #[test]
526 fn proc_self_root_preserved_not_resolved_to_slash() {
527 let path = "/proc/self/root";
528
529 let our_result = canonicalize(path).unwrap();
530 let std_result = std::fs::canonicalize(path).unwrap();
531
532 assert_eq!(std_result, PathBuf::from("/"));
534
535 assert_eq!(our_result, PathBuf::from("/proc/self/root"));
537 }
538
539 #[test]
540 fn proc_self_cwd_preserved() {
541 let path = "/proc/self/cwd";
542
543 let result = canonicalize(path).unwrap();
544
545 assert_eq!(result, PathBuf::from("/proc/self/cwd"));
546 }
547
548 #[test]
549 fn explicit_pid_root_preserved() {
550 let my_pid = std::process::id();
551 let path = format!("/proc/{}/root", my_pid);
552
553 let our_result = canonicalize(&path).unwrap();
554 let std_result = std::fs::canonicalize(&path).unwrap();
555
556 assert_eq!(std_result, PathBuf::from("/"));
557 assert_eq!(our_result, PathBuf::from(&path));
558 }
559
560 #[test]
561 fn subpath_through_namespace_preserves_prefix() {
562 let path = "/proc/self/root/etc";
563
564 let result = canonicalize(path).unwrap();
565
566 assert!(result.starts_with("/proc/self/root"));
567 assert!(result.ends_with("etc"));
568 }
569
570 #[test]
571 fn normal_paths_behave_like_std() {
572 let path = std::env::temp_dir();
573
574 let our_result = canonicalize(&path).unwrap();
575 let std_result = std::fs::canonicalize(&path).unwrap();
576
577 assert_eq!(our_result, std_result);
578 }
579
580 #[test]
585 fn nonexistent_file_returns_not_found() {
586 let path = "/proc/self/root/this_file_does_not_exist_12345";
587
588 let result = canonicalize(path);
589
590 assert!(result.is_err());
591 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
592 }
593
594 #[test]
595 fn nonexistent_pid_returns_not_found() {
596 let path = "/proc/4294967295/root"; let result = canonicalize(path);
599
600 assert!(result.is_err());
601 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
602 }
603
604 #[test]
605 fn empty_path_returns_error() {
606 let result = canonicalize("");
607
608 assert!(result.is_err());
609 }
610
611 #[test]
616 fn dotdot_stays_inside_root_namespace() {
617 let path = "/proc/self/root/tmp/../etc";
618
619 let result = canonicalize(path);
620
621 if let Ok(canonical) = result {
622 assert!(canonical.starts_with("/proc/self/root"));
623 }
624 }
625
626 #[test]
627 fn dot_is_normalized_out() {
628 let path = "/proc/self/root/./etc";
629
630 let result = canonicalize(path);
631
632 if let Ok(canonical) = result {
633 assert!(canonical.starts_with("/proc/self/root"));
634 assert!(!canonical.to_string_lossy().contains("/./"));
635 }
636 }
637
638 #[test]
639 fn deep_path_preserves_namespace() {
640 let path = "/proc/self/root/usr/share/doc";
641
642 let result = canonicalize(path);
643
644 if let Ok(canonical) = result {
645 assert!(canonical.starts_with("/proc/self/root"));
646 }
647 }
648
649 #[test]
650 fn trailing_slash_works() {
651 let with_slash = canonicalize("/proc/self/root/");
652 let without_slash = canonicalize("/proc/self/root");
653
654 if let (Ok(a), Ok(b)) = (with_slash, without_slash) {
655 assert!(a.starts_with("/proc/self/root"));
656 assert!(b.starts_with("/proc/self/root"));
657 }
658 }
659
660 #[test]
661 fn thread_self_root_preserved() {
662 let path = "/proc/thread-self/root";
663
664 if let Ok(result) = canonicalize(path) {
665 assert_eq!(result, PathBuf::from("/proc/thread-self/root"));
666 }
667 }
668
669 #[test]
674 fn boundary_detection_handles_trailing_slash() {
675 let (prefix, _remainder) =
676 find_namespace_boundary(Path::new("/proc/1234/root/")).unwrap();
677 assert_eq!(prefix, PathBuf::from("/proc/1234/root"));
678 }
679
680 #[test]
681 fn boundary_detection_handles_dot_components() {
682 let (prefix, _remainder) =
683 find_namespace_boundary(Path::new("/proc/1234/root/./etc/../etc")).unwrap();
684 assert_eq!(prefix, PathBuf::from("/proc/1234/root"));
685 }
686
687 #[test]
692 fn pid_1_root_requires_permission_or_preserves_prefix() {
693 let path = "/proc/1/root";
694
695 match canonicalize(path) {
696 Ok(result) => {
697 assert_eq!(result, PathBuf::from("/proc/1/root"));
699 assert_eq!(std::fs::canonicalize(path).unwrap(), PathBuf::from("/"));
701 }
702 Err(e) => {
703 assert!(matches!(
705 e.kind(),
706 io::ErrorKind::PermissionDenied | io::ErrorKind::NotFound
707 ));
708 }
709 }
710 }
711
712 #[test]
713 fn pid_1_subpath_preserves_prefix_when_accessible() {
714 let path = "/proc/1/root/etc/hostname";
715
716 match canonicalize(path) {
717 Ok(result) => {
718 assert!(
719 result.starts_with("/proc/1/root"),
720 "must preserve /proc/1/root prefix, got: {:?}",
721 result
722 );
723 }
724 Err(e) => {
725 assert!(matches!(
726 e.kind(),
727 io::ErrorKind::PermissionDenied | io::ErrorKind::NotFound
728 ));
729 }
730 }
731 }
732
733 #[test]
734 fn pid_1_cwd_preserves_prefix_when_accessible() {
735 let path = "/proc/1/cwd";
736
737 match canonicalize(path) {
738 Ok(result) => assert_eq!(result, PathBuf::from("/proc/1/cwd")),
739 Err(e) => {
740 assert!(matches!(
741 e.kind(),
742 io::ErrorKind::PermissionDenied | io::ErrorKind::NotFound
743 ));
744 }
745 }
746 }
747
748 #[test]
749 fn self_and_explicit_pid_both_work() {
750 let my_pid = std::process::id();
751
752 let self_result = canonicalize("/proc/self/root").unwrap();
753 let pid_result = canonicalize(format!("/proc/{}/root", my_pid)).unwrap();
754
755 assert_eq!(self_result, PathBuf::from("/proc/self/root"));
756 assert_eq!(pid_result, PathBuf::from(format!("/proc/{}/root", my_pid)));
757 }
758
759 mod indirect_symlink_tests {
764 use super::*;
765 use std::os::unix::fs::symlink;
766
767 #[test]
768 fn symlink_to_proc_self_root_preserves_namespace() {
769 let temp = tempfile::tempdir().unwrap();
770 let link = temp.path().join("link");
771
772 symlink("/proc/self/root", &link).unwrap();
773
774 let result = canonicalize(&link).unwrap();
775
776 assert_ne!(result, PathBuf::from("/")); assert_eq!(result, PathBuf::from("/proc/self/root"));
778 }
779
780 #[test]
781 fn symlink_then_subpath_preserves_namespace() {
782 let temp = tempfile::tempdir().unwrap();
783 let link = temp.path().join("container");
784
785 symlink("/proc/self/root", &link).unwrap();
786
787 let result = canonicalize(link.join("etc")).unwrap();
788
789 assert!(result.starts_with("/proc/self/root"));
790 }
791
792 #[test]
793 fn chained_symlinks_all_followed() {
794 let temp = tempfile::tempdir().unwrap();
795 let link1 = temp.path().join("link1");
796 let link2 = temp.path().join("link2");
797
798 symlink("/proc/self/root", &link2).unwrap();
799 symlink(&link2, &link1).unwrap();
800
801 let result = canonicalize(&link1).unwrap();
802
803 assert_eq!(result, PathBuf::from("/proc/self/root"));
804 }
805
806 #[test]
807 fn symlink_to_explicit_pid_root_preserved() {
808 let my_pid = std::process::id();
809 let target = format!("/proc/{}/root", my_pid);
810 let temp = tempfile::tempdir().unwrap();
811 let link = temp.path().join("link");
812
813 symlink(&target, &link).unwrap();
814
815 let result = canonicalize(&link).unwrap();
816
817 assert_ne!(result, PathBuf::from("/"));
818 assert_eq!(result, PathBuf::from(&target));
819 }
820
821 #[test]
822 fn symlink_to_cwd_preserved() {
823 let temp = tempfile::tempdir().unwrap();
824 let link = temp.path().join("link");
825
826 symlink("/proc/self/cwd", &link).unwrap();
827
828 let result = canonicalize(&link).unwrap();
829
830 assert!(result.starts_with("/proc/self/cwd"));
831 }
832
833 #[test]
834 fn normal_symlinks_work_like_std() {
835 let temp = tempfile::tempdir().unwrap();
836 let target = temp.path().join("target");
837 let link = temp.path().join("link");
838
839 std::fs::create_dir(&target).unwrap();
840 symlink(&target, &link).unwrap();
841
842 let our_result = canonicalize(&link).unwrap();
843 let std_result = std::fs::canonicalize(&link).unwrap();
844
845 assert_eq!(our_result, std_result);
846 }
847
848 #[test]
849 fn symlink_loop_returns_error_not_hang() {
850 let temp = tempfile::tempdir().unwrap();
851 let link_a = temp.path().join("a");
852 let link_b = temp.path().join("b");
853
854 symlink(&link_b, &link_a).unwrap();
855 symlink(&link_a, &link_b).unwrap();
856
857 let result = canonicalize(&link_a);
858
859 assert!(result.is_err());
860 }
861
862 #[test]
863 fn symlink_to_thread_self_root_preserved() {
864 let temp = tempfile::tempdir().unwrap();
865 let link = temp.path().join("thread_link");
866
867 symlink("/proc/thread-self/root", &link).unwrap();
868
869 if let Ok(result) = canonicalize(&link) {
871 assert!(result.starts_with("/proc/thread-self/root"));
872 }
873 }
874 }
875
876 mod security_tests {
881 use super::*;
882
883 #[test]
884 fn excessive_dotdot_cannot_escape_root_namespace() {
885 let path = "/proc/self/root/../../../../../../../etc/passwd";
886
887 if let Ok(result) = canonicalize(path) {
888 assert!(result.starts_with("/proc/self/root"));
889 }
890 }
891
892 #[test]
893 fn idempotent_canonicalization() {
894 let paths = ["/proc/self/root", "/proc/self/root/etc", "/proc/self/cwd"];
895
896 for path in &paths {
897 if let Ok(first) = canonicalize(path) {
898 if let Ok(second) = canonicalize(&first) {
899 assert_eq!(first, second);
900 }
901 }
902 }
903 }
904
905 #[test]
906 fn uppercase_proc_not_magic() {
907 let result = canonicalize("/PROC/self/root");
908
909 match result {
910 Ok(path) => assert!(!path.starts_with("/proc/")),
911 Err(e) => assert_eq!(e.kind(), io::ErrorKind::NotFound),
912 }
913 }
914
915 #[test]
916 fn double_slashes_normalized() {
917 if let Ok(normal) = canonicalize("/proc/self/root") {
918 if let Ok(doubled) = canonicalize("//proc//self//root") {
919 assert_eq!(normal, doubled);
920 }
921 }
922 }
923
924 #[test]
925 fn relative_proc_path_not_magic() {
926 let _ = canonicalize("proc/self/root"); }
929
930 #[test]
931 fn missing_pid_not_namespace() {
932 let result = find_namespace_boundary(Path::new("/proc/root"));
933 assert!(result.is_none());
934 }
935
936 #[test]
937 fn invalid_special_names_not_namespace() {
938 for name in &["parent", "init", "current", "me"] {
939 let path = format!("/proc/{}/root", name);
940 assert!(find_namespace_boundary(Path::new(&path)).is_none());
941 }
942 }
943
944 #[test]
945 fn long_numeric_pid_accepted() {
946 let long_pid = "9".repeat(100);
947 let path = format!("/proc/{}/root", long_pid);
948 assert!(find_namespace_boundary(Path::new(&path)).is_some());
949 }
950
951 #[test]
952 fn pid_zero_syntactically_valid() {
953 assert!(find_namespace_boundary(Path::new("/proc/0/root")).is_some());
954 assert!(canonicalize("/proc/0/root").is_err()); }
956
957 #[test]
958 fn negative_pid_not_valid() {
959 assert!(find_namespace_boundary(Path::new("/proc/-1/root")).is_none());
960 }
961
962 #[test]
963 fn leading_zeros_in_pid_accepted() {
964 assert!(find_namespace_boundary(Path::new("/proc/0001234/root")).is_some());
965 }
966
967 #[test]
968 fn symlink_to_deep_proc_path_preserves_prefix() {
969 use std::os::unix::fs::symlink;
970
971 let temp = tempfile::tempdir().unwrap();
972 let link = temp.path().join("link");
973
974 symlink("/proc/self/root/etc", &link).unwrap();
975
976 if let Ok(result) = canonicalize(&link) {
977 assert!(result.starts_with("/proc/self/root"));
978 }
979 }
980
981 #[test]
982 fn relative_symlink_looking_like_proc_not_magic() {
983 use std::os::unix::fs::symlink;
984
985 let temp = tempfile::tempdir().unwrap();
986 let fake_proc = temp.path().join("proc/self/root");
987 std::fs::create_dir_all(fake_proc).unwrap();
988
989 let link = temp.path().join("link");
990 symlink("proc/self/root", &link).unwrap();
991
992 let result = canonicalize(&link).unwrap();
993
994 assert!(!result.starts_with("/proc/self/root"));
995 assert!(result.starts_with(temp.path()));
996 }
997
998 #[test]
999 fn relative_symlink_escape_behaves_like_std() {
1000 use std::os::unix::fs::symlink;
1003
1004 let temp = tempfile::tempdir().unwrap();
1005 let subdir = temp.path().join("subdir");
1006 std::fs::create_dir(&subdir).unwrap();
1007
1008 let escape_link = subdir.join("escape");
1009 symlink("../../../../../../etc", &escape_link).unwrap();
1010
1011 let our_result = canonicalize(&escape_link);
1012 let std_result = std::fs::canonicalize(&escape_link);
1013
1014 match (our_result, std_result) {
1015 (Ok(ours), Ok(stds)) => assert_eq!(ours, stds),
1016 (Err(_), Err(_)) => {} _ => panic!("Behavior should match std"),
1018 }
1019 }
1020 }
1021 }
1022
1023 #[cfg(not(target_os = "linux"))]
1024 mod non_linux {
1025 use super::*;
1026
1027 #[test]
1028 fn test_canonicalize_is_std_on_non_linux() {
1029 let tmp = std::env::temp_dir();
1031 let our_result = canonicalize(&tmp).expect("should succeed");
1032 let std_result = std::fs::canonicalize(&tmp).expect("should succeed");
1033 #[cfg(all(feature = "dunce", windows))]
1035 {
1036 let our_str = our_result.to_string_lossy();
1037 let std_str = std_result.to_string_lossy();
1038 assert!(!our_str.starts_with(r"\\?\"), "dunce should simplify path");
1040 assert!(std_str.starts_with(r"\\?\"), "std returns UNC format");
1041 assert_eq!(our_str.as_ref(), std_str.trim_start_matches(r"\\?\"));
1043 }
1044 #[cfg(not(all(feature = "dunce", windows)))]
1046 {
1047 assert_eq!(our_result, std_result);
1048 }
1049 }
1050 }
1051}